@team-monolith/cds 1.52.0 → 1.52.2

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 (35) hide show
  1. package/dist/components/InputBase.d.ts +2 -0
  2. package/dist/components/InputBase.js +4 -1
  3. package/dist/patterns/LexicalEditor/nodes/ProblemSelectNode/SelectComponent.js +3 -1
  4. package/dist/patterns/LexicalEditor/nodes/ProblemSelectNode/SettingForm/FormSelection.d.ts +4 -9
  5. package/dist/patterns/LexicalEditor/nodes/ProblemSelectNode/SettingForm/FormSelection.js +32 -27
  6. package/dist/patterns/LexicalEditor/nodes/ProblemSelectNode/SettingForm/SettingForm.d.ts +3 -0
  7. package/dist/patterns/LexicalEditor/nodes/ProblemSelectNode/SettingForm/SettingForm.js +22 -11
  8. package/dist/patterns/LexicalEditor/nodes/SheetInputNode/InputComponent.js +7 -8
  9. package/dist/patterns/LexicalEditor/nodes/SheetInputNode/index.d.ts +0 -1
  10. package/dist/patterns/LexicalEditor/nodes/SheetInputNode/index.js +0 -1
  11. package/dist/patterns/LexicalEditor/nodes/SheetSelectNode/{SelectBox → SelectComponent/SelectBox}/SelectBoxComponent.d.ts +1 -1
  12. package/dist/patterns/LexicalEditor/nodes/SheetSelectNode/{SelectBox → SelectComponent/SelectBox}/SelectBoxEdit.d.ts +1 -1
  13. package/dist/patterns/LexicalEditor/nodes/SheetSelectNode/{SelectBox → SelectComponent/SelectBox}/SelectBoxView.d.ts +1 -1
  14. package/dist/patterns/LexicalEditor/nodes/SheetSelectNode/{SelectBox → SelectComponent/SelectBox}/SelectBoxView.js +1 -1
  15. package/dist/patterns/LexicalEditor/nodes/SheetSelectNode/{SelectComponent.d.ts → SelectComponent/SelectComponent.d.ts} +1 -1
  16. package/dist/patterns/LexicalEditor/nodes/SheetSelectNode/{SelectComponent.js → SelectComponent/SelectComponent.js} +8 -6
  17. package/dist/patterns/LexicalEditor/nodes/SheetSelectNode/{SettingForm → SelectComponent/SettingForm}/FormAllowMultipleAnswers.js +1 -1
  18. package/dist/patterns/LexicalEditor/nodes/SheetSelectNode/{SettingForm → SelectComponent/SettingForm}/FormSelection.d.ts +2 -3
  19. package/dist/patterns/LexicalEditor/nodes/SheetSelectNode/SelectComponent/SettingForm/FormSelection.js +77 -0
  20. package/dist/patterns/LexicalEditor/nodes/SheetSelectNode/{SettingForm → SelectComponent/SettingForm}/SettingForm.d.ts +1 -1
  21. package/dist/patterns/LexicalEditor/nodes/SheetSelectNode/{SettingForm → SelectComponent/SettingForm}/SettingForm.js +23 -13
  22. package/dist/patterns/LexicalEditor/nodes/SheetSelectNode/SelectComponent/index.d.ts +1 -0
  23. package/dist/patterns/LexicalEditor/nodes/SheetSelectNode/SelectComponent/index.js +1 -0
  24. package/dist/patterns/LexicalEditor/nodes/SheetSelectNode/index.d.ts +0 -1
  25. package/dist/patterns/LexicalEditor/nodes/SheetSelectNode/index.js +0 -1
  26. package/dist/patterns/LexicalEditor/plugins/ComponentAdderPlugin/useContextMenuOptions.js +30 -0
  27. package/package.json +1 -1
  28. package/dist/patterns/LexicalEditor/nodes/SheetSelectNode/SettingForm/FormSelection.js +0 -86
  29. /package/dist/patterns/LexicalEditor/nodes/SheetSelectNode/{SelectBox → SelectComponent/SelectBox}/SelectBoxComponent.js +0 -0
  30. /package/dist/patterns/LexicalEditor/nodes/SheetSelectNode/{SelectBox → SelectComponent/SelectBox}/SelectBoxEdit.js +0 -0
  31. /package/dist/patterns/LexicalEditor/nodes/SheetSelectNode/{SelectBox → SelectComponent/SelectBox}/index.d.ts +0 -0
  32. /package/dist/patterns/LexicalEditor/nodes/SheetSelectNode/{SelectBox → SelectComponent/SelectBox}/index.js +0 -0
  33. /package/dist/patterns/LexicalEditor/nodes/SheetSelectNode/{SettingForm → SelectComponent/SettingForm}/FormAllowMultipleAnswers.d.ts +0 -0
  34. /package/dist/patterns/LexicalEditor/nodes/SheetSelectNode/{SettingForm → SelectComponent/SettingForm}/index.d.ts +0 -0
  35. /package/dist/patterns/LexicalEditor/nodes/SheetSelectNode/{SettingForm → SelectComponent/SettingForm}/index.js +0 -0
@@ -45,6 +45,8 @@ interface InputBaseInputProps {
45
45
  }
46
46
  interface InputBaseTextAreaProps {
47
47
  multiline: true;
48
+ /** MUI TextareaAutosize 사용 여부. 전달하지 않으면 사용합니다. */
49
+ disableAutosize?: boolean;
48
50
  /** HTML input 태그에 전달될 ref */
49
51
  inputRef?: ForwardedRef<HTMLTextAreaElement>;
50
52
  /** HTML input 태그에 전달될 props */
@@ -62,7 +62,7 @@ const SIZE_TO_STYLES = (size, fullWidth) => ({
62
62
  padding-bottom: 12px;
63
63
  ${!fullWidth && "max-width: 375px"};
64
64
  `,
65
- }[size]);
65
+ })[size];
66
66
  function InputComponent(props) {
67
67
  const { placeholder, disabled = false, defaultValue, value } = props;
68
68
  const theme = useTheme();
@@ -86,6 +86,9 @@ function InputComponent(props) {
86
86
  `;
87
87
  // Type Safety 때문에 코드의 중복을 허용합니다.
88
88
  if (props.multiline) {
89
+ if (props.disableAutosize) {
90
+ return (_jsx("textarea", Object.assign({ css: style }, props.inputProps, { ref: props.inputRef, onChange: props.onChange, placeholder: placeholder, disabled: disabled, defaultValue: defaultValue, value: value })));
91
+ }
89
92
  return (_jsx(TextareaAutosize, Object.assign({ css: style }, props.inputProps, { ref: props.inputRef, onChange: props.onChange, placeholder: placeholder, disabled: disabled, defaultValue: defaultValue, value: value })));
90
93
  }
91
94
  else {
@@ -65,7 +65,9 @@ export function SelectComponent(props) {
65
65
  display: flex;
66
66
  flex-direction: column;
67
67
  gap: 4px;
68
- ` }, { children: selections.map((selection, index) => (_jsx(SelectBoxEdit, { index: index + 1, isAnswer: "isAnswer" in selection && selection.isAnswer, image: selection.show.image, text: selection.show.text || `${index + 1}번 선택지`, onClick: () => setSettingOpen(true) }, index))) })), _jsx(SquareButton, { size: "small", color: "icon", icon: _jsx(Settings3FillIcon, {}), onClick: () => {
68
+ ` }, { children: selections.map((selection, index) => (_jsx(SelectBoxEdit, { index: index + 1, isAnswer: "isAnswer" in selection && selection.isAnswer, image: selection.show.image, text: selection.show.text === "" && selection.show.image === null
69
+ ? `${index + 1}번 선택지`
70
+ : selection.show.text, onClick: () => setSettingOpen(true) }, index))) })), _jsx(SquareButton, { size: "small", color: "icon", icon: _jsx(Settings3FillIcon, {}), onClick: () => {
69
71
  setSettingOpen(true);
70
72
  } })] })), settingOpen && (_jsx(SettingForm, { selections: selections, nodeKey: nodeKey, onClose: () => setSettingOpen(false) }))] }));
71
73
  }
@@ -1,14 +1,9 @@
1
- import { Selection } from "../ProblemSelectNode";
2
- import { Control, UseFormWatch } from "react-hook-form";
1
+ import { Control, RegisterOptions } from "react-hook-form";
2
+ import { SettingFormData } from "./SettingForm";
3
3
  export interface FormSelectionProps {
4
4
  index: number;
5
- control: Control<{
6
- selections: Selection[];
7
- }, any>;
8
- watch: UseFormWatch<{
9
- selections: Selection[];
10
- }>;
11
- rules?: any;
5
+ control: Control<SettingFormData, any>;
6
+ rules?: Omit<RegisterOptions<SettingFormData, `selections.${number}`>, "disabled" | "valueAsNumber" | "valueAsDate" | "setValueAs">;
12
7
  onDelete?: () => void;
13
8
  }
14
9
  export declare function FormSelection(props: FormSelectionProps): import("@emotion/react/jsx-runtime").JSX.Element;
@@ -1,7 +1,7 @@
1
- import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "@emotion/react/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs } from "@emotion/react/jsx-runtime";
2
2
  /** @jsxImportSource @emotion/react */
3
3
  import styled from "@emotion/styled";
4
- import { Controller } from "react-hook-form";
4
+ import { useController } from "react-hook-form";
5
5
  import { css } from "@emotion/react";
6
6
  import Input from "../../../../../components/Input";
7
7
  import { DeleteBinLineIcon, ErrorWarningFillIcon, ImageAddFillIcon, ImageEditFillIcon, } from "../../../../../icons";
@@ -10,43 +10,48 @@ import Switch from "../../../../../components/Switch";
10
10
  import { useState } from "react";
11
11
  import { InsertImageDialog } from "../../insertImageDialog";
12
12
  export function FormSelection(props) {
13
- const { index, control, watch, rules, onDelete } = props;
13
+ const { index, control, rules, onDelete } = props;
14
14
  const [imageOpen, setImageOpen] = useState(false);
15
15
  const [inputFocused, setInputFocused] = useState(false);
16
+ const { field: { value, onChange }, fieldState: { invalid, error }, } = useController({
17
+ control,
18
+ name: `selections.${index}`,
19
+ rules,
20
+ });
16
21
  return (_jsxs(Container, { children: [_jsx(Index, { children: index + 1 }), _jsxs("div", Object.assign({ css: css `
17
22
  display: flex;
18
23
  flex: 1;
19
24
  flex-direction: column;
20
25
  gap: 4px;
21
- ` }, { children: [_jsx(Controller, { name: `selections.${index}.show.image`, control: control, render: ({ field: { value, onChange } }) => (_jsxs(_Fragment, { children: [_jsx(InsertImageDialog, { title: value ? "이미지 바꾸기" : "이미지 삽입하기", open: imageOpen, onClose: () => setImageOpen(false), updateImg: (props) => onChange(props), deleteButton: Boolean(value) }), value && value.src && (_jsx("img", { src: value.src, alt: value.altText, css: css `
22
- height: auto;
23
- // 이미지로 인해 좌우로 스크롤이 생기는 것을 방지
24
- max-width: min(400px, 100%);
25
- width: fit-content;
26
- border-radius: 6px;
27
- `, draggable: "false" }))] })) }), _jsx(Controller, { name: `selections.${index}.show.text`, control: control, rules: rules, render: ({ field: { value, onChange }, fieldState: { invalid, error }, }) => (_jsx(Input, { size: "small", color: invalid
28
- ? "activeDanger"
29
- : inputFocused
30
- ? "activePrimary"
31
- : "default", value: value, onChange: onChange, inputProps: {
32
- onFocus: (_e) => {
33
- setInputFocused(true);
34
- },
35
- onBlur: (_e) => {
36
- setInputFocused(false);
37
- },
38
- }, placeholder: `${index + 1}번 선택지`, hintIcon: invalid ? _jsx(ErrorWarningFillIcon, {}) : undefined, hintText: error === null || error === void 0 ? void 0 : error.message, multiline: true, fullWidth: true, css: css `
39
- flex: 1;
40
- ` })) })] })), _jsxs("div", Object.assign({ css: css `
26
+ ` }, { children: [_jsx(InsertImageDialog, { title: value.show.image ? "이미지 바꾸기" : "이미지 삽입하기", open: imageOpen, onClose: () => setImageOpen(false), updateImg: (props) => onChange(Object.assign(Object.assign({}, value), { show: Object.assign(Object.assign({}, value.show), { image: props }) })), deleteButton: Boolean(value.show.image) }), value.show.image && (_jsx("img", { src: value.show.image.src, alt: value.show.image.altText, css: css `
27
+ height: auto;
28
+ // 이미지로 인해 좌우로 스크롤이 생기는 것을 방지
29
+ max-width: min(400px, 100%);
30
+ width: fit-content;
31
+ border-radius: 6px;
32
+ `, draggable: "false" })), _jsx(Input, { size: "small", color: invalid
33
+ ? "activeDanger"
34
+ : inputFocused
35
+ ? "activePrimary"
36
+ : "default", value: value.show.text, onChange: (e) => onChange(Object.assign(Object.assign({}, value), { show: Object.assign(Object.assign({}, value.show), { text: e.target.value }) })), inputProps: {
37
+ onFocus: (_e) => {
38
+ setInputFocused(true);
39
+ },
40
+ onBlur: (_e) => {
41
+ setInputFocused(false);
42
+ },
43
+ }, placeholder: `${index + 1}번 선택지`, hintIcon: invalid ? _jsx(ErrorWarningFillIcon, {}) : undefined, hintText: error === null || error === void 0 ? void 0 : error.message, multiline: true, fullWidth: true, css: css `
44
+ flex: 1;
45
+ ` })] })), _jsxs("div", Object.assign({ css: css `
41
46
  display: flex;
42
47
  height: 36px;
43
48
  gap: 8px;
44
49
  align-items: center;
45
- ` }, { children: [_jsx(SquareButton, { color: "icon", size: "xsmall", icon: watch(`selections.${index}.show.image`) ? (_jsx(ImageEditFillIcon, {})) : (_jsx(ImageAddFillIcon, {})), onClick: () => {
50
+ ` }, { children: [_jsx(SquareButton, { color: "icon", size: "xsmall", icon: value.show.image ? _jsx(ImageEditFillIcon, {}) : _jsx(ImageAddFillIcon, {}), onClick: () => {
46
51
  setImageOpen(true);
47
- } }), _jsx(Controller, { name: `selections.${index}.isAnswer`, control: control, render: ({ field: { value, onChange } }) => (_jsxs(Answer, Object.assign({ onClick: () => {
48
- onChange(!value);
49
- } }, { children: ["\uC815\uB2F5", _jsx(Switch, { checked: Boolean(value), size: "small" })] }))) }), onDelete && (_jsx(SquareButton, { color: "white", size: "xsmall", icon: _jsx(DeleteBinLineIcon, {}), onClick: onDelete }))] }))] }));
52
+ } }), _jsxs(Answer, Object.assign({ onClick: () => {
53
+ onChange(Object.assign(Object.assign({}, value), { isAnswer: !value.isAnswer }));
54
+ } }, { children: ["\uC815\uB2F5", _jsx(Switch, { checked: Boolean(value.isAnswer), size: "small" })] })), onDelete && (_jsx(SquareButton, { color: "white", size: "xsmall", icon: _jsx(DeleteBinLineIcon, {}), onClick: onDelete }))] }))] }));
50
55
  }
51
56
  const Container = styled.div(({ theme }) => css `
52
57
  display: flex;
@@ -5,4 +5,7 @@ export interface SettingFormProps {
5
5
  nodeKey: NodeKey;
6
6
  onClose: () => void;
7
7
  }
8
+ export interface SettingFormData {
9
+ selections: Selection[];
10
+ }
8
11
  export default function SettingForm(props: SettingFormProps): import("@emotion/react/jsx-runtime").JSX.Element;
@@ -38,16 +38,25 @@ export default function SettingForm(props) {
38
38
  });
39
39
  onClose();
40
40
  };
41
- function validateDuplicatedSelection(index) {
42
- const selections = watch("selections");
43
- if (index === 0)
41
+ function validateRequired(value) {
42
+ if (value.show.image || value.show.text)
44
43
  return true;
45
- const duplicatedIndex = selections
46
- .slice(0, index)
47
- .findIndex((selection) => selection.show.text === selections[index].show.text);
48
- if (duplicatedIndex < 0)
44
+ return "필수 입력 항목입니다.";
45
+ }
46
+ function validateDuplicated(value, formValues) {
47
+ const shows = formValues.selections.map((selection) => selection.show);
48
+ const duplicatedIndexes = shows.flatMap((show, i) => {
49
+ var _a, _b;
50
+ if (show.text === value.show.text &&
51
+ ((_a = show.image) === null || _a === void 0 ? void 0 : _a.src) === ((_b = value.show.image) === null || _b === void 0 ? void 0 : _b.src))
52
+ return [i];
53
+ return [];
54
+ });
55
+ if (duplicatedIndexes.length < 2)
49
56
  return true;
50
- return `${duplicatedIndex + 1}번 선택지와 같은 내용입니다.`;
57
+ return `${duplicatedIndexes
58
+ .map((i) => i + 1)
59
+ .join(",")}번 선택지가 같은 내용입니다.`;
51
60
  }
52
61
  const answersCount = watch("selections").filter((selection) => selection.isAnswer).length;
53
62
  const hasMultipleAnswers = answersCount > 1;
@@ -55,9 +64,11 @@ export default function SettingForm(props) {
55
64
  return (_jsxs(Form, Object.assign({ onSubmit: handleSubmit(onSettingSubmit) }, { children: [_jsxs(Title, { children: [_jsx(ListRadioIcon, { css: css `
56
65
  width: 12px;
57
66
  height: 12px;
58
- ` }), "\uAC1D\uAD00\uC2DD \uC785\uB825 \uCE78"] }), _jsxs(Content, { children: [_jsxs(FormArea, { children: [_jsx(Label, { children: "\uB2F5\uC548" }), fields.map((field, index) => (_jsx(FormSelection, { index: index, control: control, watch: watch, rules: {
59
- required: "필수 입력 항목입니다.",
60
- validate: () => validateDuplicatedSelection(index),
67
+ ` }), "\uAC1D\uAD00\uC2DD \uC785\uB825 \uCE78"] }), _jsxs(Content, { children: [_jsxs(FormArea, { children: [_jsx(Label, { children: "\uB2F5\uC548" }), fields.map((field, index) => (_jsx(FormSelection, { index: index, control: control, rules: {
68
+ validate: {
69
+ validateRequired,
70
+ validateDuplicated,
71
+ },
61
72
  }, onDelete: index !== 0
62
73
  ? () => {
63
74
  remove(index);
@@ -10,6 +10,9 @@ import { css } from "@emotion/react";
10
10
  import { Input, Settings3FillIcon, SquareButton } from "../../../..";
11
11
  import { SettingForm } from "./SettingForm";
12
12
  import { $isSheetInputNode } from "./SheetInputNode";
13
+ const TEXTAREA_HEIGHT = 84;
14
+ /** VirtualInput을 Input(size: small)처럼 만들기 위한 padding 값입니다. */
15
+ const INPUT_VERTICAL_PADDING = 8;
13
16
  export function InputComponent(props) {
14
17
  const { multiline, value, placeholder, nodeKey } = props;
15
18
  const [editor] = useLexicalComposerContext();
@@ -34,13 +37,9 @@ export function InputComponent(props) {
34
37
  };
35
38
  // view
36
39
  if (!isEditable) {
37
- return (_jsx(Input, { multiline: multiline, css: css `
40
+ return (_jsx(Input, { multiline: multiline, disableAutosize: true, css: css `
38
41
  textarea {
39
- // 100-8*2
40
- max-height: 84px;
41
- min-height: 84px;
42
- // mui TextareaAutosize의 overflow: hidden을 무력화시킵니다.
43
- overflow: auto !important;
42
+ height: ${TEXTAREA_HEIGHT}px;
44
43
  }
45
44
  `, inputProps: freezeProblemNode
46
45
  ? { readOnly: true }
@@ -58,14 +57,14 @@ export function InputComponent(props) {
58
57
  gap: 4px;
59
58
  ` }, { children: [_jsx(VirtualInput, Object.assign({ onClick: () => setSettingOpen((open) => !open), css: multiline &&
60
59
  css `
61
- height: 100px;
60
+ height: ${TEXTAREA_HEIGHT + 2 * INPUT_VERTICAL_PADDING}px;
62
61
  ` }, { children: multiline ? "서술형 입력 칸" : "단답형 입력 칸" })), _jsx(SquareButton, { size: "small", color: "icon", icon: _jsx(Settings3FillIcon, {}), onClick: () => setSettingOpen((open) => !open) })] })), settingOpen && (_jsx(SettingForm, { multiline: multiline, placeholder: placeholder, nodeKey: nodeKey, onClose: () => setSettingOpen(false) }))] }));
63
62
  }
64
63
  const VirtualInput = styled.div(({ theme }) => css `
65
64
  box-sizing: border-box;
66
65
  height: 36px;
67
66
  width: 400px;
68
- padding: 8px 16px;
67
+ padding: ${INPUT_VERTICAL_PADDING}px 16px;
69
68
  align-items: center;
70
69
  border-radius: 8px;
71
70
  background: ${theme.color.background.neutralAlt};
@@ -1,2 +1 @@
1
1
  export * from "./SheetInputNode";
2
- export * from "./InputComponent";
@@ -1,2 +1 @@
1
1
  export * from "./SheetInputNode";
2
- export * from "./InputComponent";
@@ -1,5 +1,5 @@
1
1
  /// <reference types="react" />
2
- import { ImageProps } from "../../insertImageDialog";
2
+ import { ImageProps } from "../../../insertImageDialog";
3
3
  export declare const SelectBoxClasses: {
4
4
  readonly container: "SheetSelectNode-SelectBox-container";
5
5
  readonly index: "SheetSelectNode-SelectBox-index";
@@ -1,4 +1,4 @@
1
- import { ImageProps } from "../../insertImageDialog";
1
+ import { ImageProps } from "../../../insertImageDialog";
2
2
  /** SheetSelectNode의 하나의 선택지 edit모드 컴포넌트입니다. */
3
3
  export declare function SelectBoxEdit(props: {
4
4
  index: number;
@@ -1,4 +1,4 @@
1
- import { ImageProps } from "../../insertImageDialog";
1
+ import { ImageProps } from "../../../insertImageDialog";
2
2
  /** SheetSelectNode의 하나의 선택지 view모드 컴포넌트입니다. */
3
3
  export declare function SelectBoxView(props: {
4
4
  index: number;
@@ -2,7 +2,7 @@ import { jsx as _jsx } from "@emotion/react/jsx-runtime";
2
2
  /** @jsxImportSource @emotion/react */
3
3
  import { css } from "@emotion/react";
4
4
  import { SelectBoxComponent } from "./SelectBoxComponent";
5
- import { CheckFillIcon } from "../../../../../icons";
5
+ import { CheckFillIcon } from "../../../../../../icons";
6
6
  function getIndexIcon(type, index) {
7
7
  return {
8
8
  primary: (_jsx(CheckFillIcon, { css: css `
@@ -1,6 +1,6 @@
1
1
  /** @jsxImportSource @emotion/react */
2
2
  import { NodeKey } from "lexical";
3
- import { Selection } from "./SheetSelectNode";
3
+ import { Selection } from "../SheetSelectNode";
4
4
  export declare function SelectComponent(props: {
5
5
  selections: Selection[];
6
6
  selected: string[];
@@ -1,17 +1,17 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "@emotion/react/jsx-runtime";
2
2
  /** @jsxImportSource @emotion/react */
3
3
  import { $getNodeByKey } from "lexical";
4
- import { $isSheetSelectNode } from "./SheetSelectNode";
4
+ import { $isSheetSelectNode } from "../SheetSelectNode";
5
5
  import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext";
6
6
  import { useContext, useState } from "react";
7
7
  import useLexicalEditable from "@lexical/react/useLexicalEditable";
8
- import { LexicalCustomConfigContext } from "../../LexicalCustomConfigContext";
8
+ import { LexicalCustomConfigContext } from "../../../LexicalCustomConfigContext";
9
9
  import styled from "@emotion/styled";
10
10
  import { css } from "@emotion/react";
11
- import { AlarmWarningFillIcon, Settings3FillIcon } from "../../../../icons";
12
- import { SelectBoxEdit, SelectBoxView } from "./SelectBox";
13
- import SquareButton from "../../../../components/SquareButton";
11
+ import { AlarmWarningFillIcon, Settings3FillIcon } from "../../../../../icons";
12
+ import SquareButton from "../../../../../components/SquareButton";
14
13
  import { SettingForm } from "./SettingForm";
14
+ import { SelectBoxEdit, SelectBoxView } from "./SelectBox";
15
15
  export function SelectComponent(props) {
16
16
  const { selections, selected, allowMultipleAnswers, nodeKey } = props;
17
17
  const [editor] = useLexicalComposerContext();
@@ -60,7 +60,9 @@ export function SelectComponent(props) {
60
60
  display: flex;
61
61
  flex-direction: column;
62
62
  gap: 4px;
63
- ` }, { children: selections.map((selection, index) => (_jsx(SelectBoxEdit, { index: index + 1, image: selection.show.image, text: selection.show.text || `${index + 1}번 선택지`, onClick: () => setSettingOpen((open) => !open) }, index))) })), _jsx(SquareButton, { size: "small", color: "icon", icon: _jsx(Settings3FillIcon, {}), onClick: () => {
63
+ ` }, { children: selections.map((selection, index) => (_jsx(SelectBoxEdit, { index: index + 1, image: selection.show.image, text: selection.show.text === "" && selection.show.image === null
64
+ ? `${index + 1}번 선택지`
65
+ : selection.show.text, onClick: () => setSettingOpen((open) => !open) }, index))) })), _jsx(SquareButton, { size: "small", color: "icon", icon: _jsx(Settings3FillIcon, {}), onClick: () => {
64
66
  setSettingOpen((open) => !open);
65
67
  } })] })), settingOpen && (_jsx(SettingForm, { data: {
66
68
  selections,
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Controller } from "react-hook-form";
3
- import { SegmentedControlButton, SegmentedControlGroup, } from "../../../../SegmentedControl";
3
+ import { SegmentedControlButton, SegmentedControlGroup, } from "../../../../../SegmentedControl";
4
4
  /** SheetSelectNode SettingForm의 다중 선택 허용 여부 폼입니다. */
5
5
  export function FormAllowMultipleAnswers(props) {
6
6
  const { control } = props;
@@ -1,11 +1,10 @@
1
1
  /** @jsxImportSource @emotion/react */
2
- import { Control, UseFormWatch } from "react-hook-form";
2
+ import { Control, RegisterOptions } from "react-hook-form";
3
3
  import { SettingFormData } from "./SettingForm";
4
4
  /** SheetSelectNode SettingForm의 단일 선택지 폼입니다. */
5
5
  export default function FormSelection(props: {
6
6
  index: number;
7
7
  control: Control<SettingFormData, any>;
8
- watch: UseFormWatch<SettingFormData>;
9
- rules?: any;
8
+ rules?: Omit<RegisterOptions<SettingFormData, `selections.${number}.show`>, "disabled" | "valueAsNumber" | "valueAsDate" | "setValueAs">;
10
9
  onDelete?: () => void;
11
10
  }): import("@emotion/react/jsx-runtime").JSX.Element;
@@ -0,0 +1,77 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "@emotion/react/jsx-runtime";
2
+ /** @jsxImportSource @emotion/react */
3
+ import { useController } from "react-hook-form";
4
+ import styled from "@emotion/styled";
5
+ import { css } from "@emotion/react";
6
+ import { useState } from "react";
7
+ import { InsertImageDialog } from "../../../insertImageDialog";
8
+ import { DeleteBinLineIcon, ErrorWarningFillIcon, ImageAddFillIcon, ImageEditFillIcon, Input, SquareButton, } from "../../../../../..";
9
+ /** SheetSelectNode SettingForm의 단일 선택지 폼입니다. */
10
+ export default function FormSelection(props) {
11
+ const { index, control, rules, onDelete } = props;
12
+ const [imageOpen, setImageOpen] = useState(false);
13
+ const [inputFocused, setInputFocused] = useState(false);
14
+ const { field: { value, onChange }, fieldState: { invalid, error }, } = useController({
15
+ control,
16
+ name: `selections.${index}.show`,
17
+ rules,
18
+ });
19
+ return (_jsxs(Container, { children: [_jsx(Index, { children: index + 1 }), _jsxs("div", Object.assign({ css: css `
20
+ display: flex;
21
+ flex: 1;
22
+ flex-direction: column;
23
+ gap: 4px;
24
+ ` }, { children: [_jsx(InsertImageDialog, { title: value.image ? "이미지 바꾸기" : "이미지 삽입하기", open: imageOpen, onClose: () => setImageOpen(false), updateImg: (props) => onChange(Object.assign(Object.assign({}, value), { image: props })), deleteButton: Boolean(value.image) }), value.image && (_jsx("img", { src: value.image.src, alt: value.image.altText, css: css `
25
+ height: auto;
26
+ // 이미지로 인해 좌우로 스크롤이 생기는 것을 방지
27
+ max-width: min(400px, 100%);
28
+ width: fit-content;
29
+ border-radius: 6px;
30
+ `, draggable: "false" })), _jsx(Input, { size: "small", color: invalid
31
+ ? "activeDanger"
32
+ : inputFocused
33
+ ? "activePrimary"
34
+ : "default", value: value.text, onChange: (e) => onChange(Object.assign(Object.assign({}, value), { text: e.target.value })), inputProps: {
35
+ onFocus: (_e) => {
36
+ setInputFocused(true);
37
+ },
38
+ onBlur: (_e) => {
39
+ setInputFocused(false);
40
+ },
41
+ }, placeholder: `${index + 1}번 선택지`, hintIcon: invalid ? _jsx(ErrorWarningFillIcon, {}) : undefined, hintText: error === null || error === void 0 ? void 0 : error.message, multiline: true, fullWidth: true, css: css `
42
+ flex: 1;
43
+ ` })] })), _jsxs("div", Object.assign({ css: css `
44
+ display: flex;
45
+ // 이미지가 들어가서 container height가 커져도 높이가 유지되도록 설정
46
+ height: 36px;
47
+ gap: 8px;
48
+ align-items: center;
49
+ ` }, { children: [_jsx(SquareButton, { color: "icon", size: "xsmall", icon: value.image ? _jsx(ImageEditFillIcon, {}) : _jsx(ImageAddFillIcon, {}), onClick: () => {
50
+ setImageOpen(true);
51
+ } }), onDelete && (_jsx(SquareButton, { color: "white", size: "xsmall", icon: _jsx(DeleteBinLineIcon, {}), onClick: onDelete }))] }))] }));
52
+ }
53
+ const Container = styled.div(({ theme }) => css `
54
+ display: flex;
55
+ padding: 4px 12px;
56
+ gap: 8px;
57
+ border-radius: 8px;
58
+ background: ${theme.color.background.neutralAlt};
59
+ `);
60
+ const Index = styled.div(({ theme }) => css `
61
+ display: flex;
62
+ box-sizing: border-box;
63
+ width: 20px;
64
+ height: 20px;
65
+ padding: 4px;
66
+ margin-top: 8px;
67
+ justify-content: center;
68
+ align-items: center;
69
+ border-radius: 4px;
70
+ border: 1px solid ${theme.color.background.neutralAltActive};
71
+ background: ${theme.color.background.neutralBase};
72
+ color: ${theme.color.foreground.neutralBaseDisabled};
73
+ font-family: ${theme.fontFamily.ui};
74
+ font-size: 14px;
75
+ font-weight: 800;
76
+ line-height: 16px;
77
+ `);
@@ -1,6 +1,6 @@
1
1
  /** @jsxImportSource @emotion/react */
2
2
  import { NodeKey } from "lexical";
3
- import { Selection } from "../SheetSelectNode";
3
+ import { Selection } from "../../SheetSelectNode";
4
4
  export interface SettingFormData {
5
5
  selections: Selection[];
6
6
  allowMultipleAnswers: boolean;
@@ -1,12 +1,12 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "@emotion/react/jsx-runtime";
2
2
  /** @jsxImportSource @emotion/react */
3
3
  import { $getNodeByKey } from "lexical";
4
- import { $isSheetSelectNode } from "../SheetSelectNode";
4
+ import { $isSheetSelectNode } from "../../SheetSelectNode";
5
5
  import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext";
6
6
  import { useFieldArray, useForm } from "react-hook-form";
7
7
  import styled from "@emotion/styled";
8
8
  import { css } from "@emotion/react";
9
- import { AddFillIcon, AlarmWarningFillIcon, Button, ListRadioIcon, shadows, } from "../../../../..";
9
+ import { AddFillIcon, AlarmWarningFillIcon, Button, ListRadioIcon, shadows, } from "../../../../../..";
10
10
  import { uid } from "uid";
11
11
  import FormSelection from "./FormSelection";
12
12
  import { FormAllowMultipleAnswers } from "./FormAllowMultipleAnswers";
@@ -38,23 +38,33 @@ export function SettingForm(props) {
38
38
  });
39
39
  onClose();
40
40
  };
41
- function validateDuplicatedSelection(index) {
42
- const selections = watch("selections");
43
- if (index === 0)
41
+ function validateRequired(value) {
42
+ if (value.image || value.text)
44
43
  return true;
45
- const duplicatedIndex = selections
46
- .slice(0, index)
47
- .findIndex((selection) => selection.show.text === selections[index].show.text);
48
- if (duplicatedIndex < 0)
44
+ return "필수 입력 항목입니다.";
45
+ }
46
+ function validateDuplicated(value, formValues) {
47
+ const shows = formValues.selections.map((selection) => selection.show);
48
+ const duplicatedIndexes = shows.flatMap((show, i) => {
49
+ var _a, _b;
50
+ if (show.text === value.text && ((_a = show.image) === null || _a === void 0 ? void 0 : _a.src) === ((_b = value.image) === null || _b === void 0 ? void 0 : _b.src))
51
+ return [i];
52
+ return [];
53
+ });
54
+ if (duplicatedIndexes.length < 2)
49
55
  return true;
50
- return `${duplicatedIndex + 1}번 선택지와 같은 내용입니다.`;
56
+ return `${duplicatedIndexes
57
+ .map((i) => i + 1)
58
+ .join(",")}번 선택지가 같은 내용입니다.`;
51
59
  }
52
60
  return (_jsxs(Form, Object.assign({ onSubmit: handleSubmit(onSettingSubmit) }, { children: [_jsxs(Title, { children: [_jsx(ListRadioIcon, { css: css `
53
61
  width: 12px;
54
62
  height: 12px;
55
- ` }), "\uC120\uD0DD\uD615 \uC785\uB825 \uCE78"] }), _jsxs(Content, { children: [_jsxs(Left, { children: [_jsxs(FormArea, { children: [_jsx(Label, { children: "\uC120\uD0DD\uC9C0" }), fields.map((field, index) => (_jsx(FormSelection, { index: index, control: control, watch: watch, rules: {
56
- required: "필수 입력 항목입니다.",
57
- validate: () => validateDuplicatedSelection(index),
63
+ ` }), "\uC120\uD0DD\uD615 \uC785\uB825 \uCE78"] }), _jsxs(Content, { children: [_jsxs(Left, { children: [_jsxs(FormArea, { children: [_jsx(Label, { children: "\uC120\uD0DD\uC9C0" }), fields.map((field, index) => (_jsx(FormSelection, { index: index, control: control, rules: {
64
+ validate: {
65
+ validateRequired,
66
+ validateDuplicated,
67
+ },
58
68
  }, onDelete: index !== 0
59
69
  ? () => {
60
70
  remove(index);
@@ -0,0 +1 @@
1
+ export * from "./SelectComponent";
@@ -0,0 +1 @@
1
+ export * from "./SelectComponent";
@@ -1,2 +1 @@
1
- export * from "./SelectComponent";
2
1
  export * from "./SheetSelectNode";
@@ -1,2 +1 @@
1
- export * from "./SelectComponent";
2
1
  export * from "./SheetSelectNode";
@@ -15,6 +15,8 @@ import { useTheme } from "@emotion/react";
15
15
  import { css } from "@emotion/css";
16
16
  import { $isProblemInputNode } from "../../nodes";
17
17
  import { $isProblemSelectNode, } from "../../nodes/ProblemSelectNode";
18
+ import { $isSheetInputNode } from "../../nodes/SheetInputNode";
19
+ import { $isSheetSelectNode, } from "../../nodes/SheetSelectNode";
18
20
  function getParagraphContextMenuOptions(editor, node, setOpen) {
19
21
  return [
20
22
  new ComponentPickerOption("본문", {
@@ -197,6 +199,28 @@ function getProblemSelectContextMenuOptions(node) {
197
199
  }),
198
200
  ];
199
201
  }
202
+ function getSheetInputContextMenuOptions(node) {
203
+ return [
204
+ new ComponentPickerOption("블록 삭제", {
205
+ icon: _jsx(CloseFillIcon, {}),
206
+ keywords: [],
207
+ onSelect: () => {
208
+ node.remove();
209
+ },
210
+ }),
211
+ ];
212
+ }
213
+ function getSheetSelectContextMenuOptions(node) {
214
+ return [
215
+ new ComponentPickerOption("블록 삭제", {
216
+ icon: _jsx(CloseFillIcon, {}),
217
+ keywords: [],
218
+ onSelect: () => {
219
+ node.remove();
220
+ },
221
+ }),
222
+ ];
223
+ }
200
224
  function getColoredQuoteContextMenuOptions(editor, theme, node) {
201
225
  return [
202
226
  new ComponentPickerOption("회색", {
@@ -280,6 +304,12 @@ export function useContextMenuOptions(props) {
280
304
  else if ($isProblemSelectNode(node)) {
281
305
  return getProblemSelectContextMenuOptions(node);
282
306
  }
307
+ else if ($isSheetInputNode(node)) {
308
+ return getSheetInputContextMenuOptions(node);
309
+ }
310
+ else if ($isSheetSelectNode(node)) {
311
+ return getSheetSelectContextMenuOptions(node);
312
+ }
283
313
  else if (node instanceof ParagraphNode) {
284
314
  return getParagraphContextMenuOptions(editor, node, setImageOpen);
285
315
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@team-monolith/cds",
3
- "version": "1.52.0",
3
+ "version": "1.52.2",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "sideEffects": false,
@@ -1,86 +0,0 @@
1
- import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "@emotion/react/jsx-runtime";
2
- /** @jsxImportSource @emotion/react */
3
- import { Controller } from "react-hook-form";
4
- import styled from "@emotion/styled";
5
- import { css } from "@emotion/react";
6
- import { useState } from "react";
7
- import { InsertImageDialog } from "../../insertImageDialog";
8
- import { DeleteBinLineIcon, ErrorWarningFillIcon, ImageAddFillIcon, ImageEditFillIcon, Input, SquareButton, } from "../../../../..";
9
- /** SheetSelectNode SettingForm의 단일 선택지 폼입니다. */
10
- export default function FormSelection(props) {
11
- const { index, control, watch, rules, onDelete } = props;
12
- const [imageOpen, setImageOpen] = useState(false);
13
- const [inputFocused, setInputFocused] = useState(false);
14
- return (_jsxs(Container, { children: [_jsx(Index, { children: index + 1 }), _jsxs("div", Object.assign({ css: css `
15
- display: flex;
16
- flex: 1;
17
- flex-direction: column;
18
- gap: 4px;
19
- ` }, { children: [_jsx(Controller, { name: `selections.${index}.show.image`, control: control, render: ({ field: { value, onChange } }) => (_jsxs(_Fragment, { children: [_jsx(InsertImageDialog, { title: value ? "이미지 바꾸기" : "이미지 삽입하기", open: imageOpen, onClose: () => setImageOpen(false), updateImg: (props) => onChange(props), deleteButton: Boolean(value) }), value && value.src && (_jsx("img", { src: value.src, alt: value.altText, css: css `
20
- height: auto;
21
- // 이미지로 인해 좌우로 스크롤이 생기는 것을 방지
22
- max-width: min(400px, 100%);
23
- width: fit-content;
24
- border-radius: 6px;
25
- `, draggable: "false" }))] })) }), _jsx(Controller, { name: `selections.${index}.show.text`, control: control, rules: rules, render: ({ field: { value, onChange }, fieldState: { invalid, error }, }) => (_jsx(Input, { size: "small", color: invalid
26
- ? "activeDanger"
27
- : inputFocused
28
- ? "activePrimary"
29
- : "default", value: value, onChange: onChange, inputProps: {
30
- onFocus: (_e) => {
31
- setInputFocused(true);
32
- },
33
- onBlur: (_e) => {
34
- setInputFocused(false);
35
- },
36
- }, placeholder: `${index + 1}번 선택지`, hintIcon: invalid ? _jsx(ErrorWarningFillIcon, {}) : undefined, hintText: error === null || error === void 0 ? void 0 : error.message, multiline: true, fullWidth: true, css: css `
37
- flex: 1;
38
- ` })) })] })), _jsxs("div", Object.assign({ css: css `
39
- display: flex;
40
- // 이미지가 들어가서 container height가 커져도 높이가 유지되도록 설정
41
- height: 36px;
42
- gap: 8px;
43
- align-items: center;
44
- ` }, { children: [_jsx(SquareButton, { color: "icon", size: "xsmall", icon: watch(`selections.${index}.show.image`) ? (_jsx(ImageEditFillIcon, {})) : (_jsx(ImageAddFillIcon, {})), onClick: () => {
45
- setImageOpen(true);
46
- } }), onDelete && (_jsx(SquareButton, { color: "white", size: "xsmall", icon: _jsx(DeleteBinLineIcon, {}), onClick: onDelete }))] }))] }));
47
- }
48
- const Container = styled.div(({ theme }) => css `
49
- display: flex;
50
- padding: 4px 12px;
51
- gap: 8px;
52
- border-radius: 8px;
53
- background: ${theme.color.background.neutralAlt};
54
- `);
55
- const Index = styled.div(({ theme }) => css `
56
- display: flex;
57
- box-sizing: border-box;
58
- width: 20px;
59
- height: 20px;
60
- padding: 4px;
61
- margin-top: 8px;
62
- justify-content: center;
63
- align-items: center;
64
- border-radius: 4px;
65
- border: 1px solid ${theme.color.background.neutralAltActive};
66
- background: ${theme.color.background.neutralBase};
67
- color: ${theme.color.foreground.neutralBaseDisabled};
68
- font-family: ${theme.fontFamily.ui};
69
- font-size: 14px;
70
- font-weight: 800;
71
- line-height: 16px;
72
- `);
73
- const Answer = styled.div(({ theme }) => css `
74
- display: flex;
75
- align-items: center;
76
- padding-right: 4px;
77
- gap: 8px;
78
- color: ${theme.color.foreground.neutralBase};
79
- cursor: pointer;
80
- /* Default/Label/14px-Md */
81
- font-family: ${theme.fontFamily.ui};
82
- font-size: 14px;
83
- font-style: normal;
84
- font-weight: 500;
85
- line-height: 16px; /* 114.286% */
86
- `);