@team-monolith/cds 1.8.8 → 1.9.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 (22) hide show
  1. package/dist/patterns/LexicalEditor/LexicalEditor.js +2 -0
  2. package/dist/patterns/LexicalEditor/Plugins.js +2 -1
  3. package/dist/patterns/LexicalEditor/nodes/ProblemSelectNode/ProblemSelectNode.d.ts +39 -0
  4. package/dist/patterns/LexicalEditor/nodes/ProblemSelectNode/ProblemSelectNode.js +72 -0
  5. package/dist/patterns/LexicalEditor/nodes/ProblemSelectNode/SelectBox.d.ts +12 -0
  6. package/dist/patterns/LexicalEditor/nodes/ProblemSelectNode/SelectBox.js +66 -0
  7. package/dist/patterns/LexicalEditor/nodes/ProblemSelectNode/SelectComponent.d.ts +8 -0
  8. package/dist/patterns/LexicalEditor/nodes/ProblemSelectNode/SelectComponent.js +73 -0
  9. package/dist/patterns/LexicalEditor/nodes/ProblemSelectNode/SettingForm/FormSelection.d.ts +11 -0
  10. package/dist/patterns/LexicalEditor/nodes/ProblemSelectNode/SettingForm/FormSelection.js +64 -0
  11. package/dist/patterns/LexicalEditor/nodes/ProblemSelectNode/SettingForm/SettingForm.d.ts +15 -0
  12. package/dist/patterns/LexicalEditor/nodes/ProblemSelectNode/SettingForm/SettingForm.js +104 -0
  13. package/dist/patterns/LexicalEditor/nodes/ProblemSelectNode/SettingForm/index.d.ts +1 -0
  14. package/dist/patterns/LexicalEditor/nodes/ProblemSelectNode/SettingForm/index.js +1 -0
  15. package/dist/patterns/LexicalEditor/nodes/ProblemSelectNode/index.d.ts +2 -0
  16. package/dist/patterns/LexicalEditor/nodes/ProblemSelectNode/index.js +2 -0
  17. package/dist/patterns/LexicalEditor/plugins/ComponentPickerMenuPlugin/ComponentPickerMenuPlugin.js +13 -1
  18. package/dist/patterns/LexicalEditor/plugins/ProblemSelectPlugin/index.d.ts +5 -0
  19. package/dist/patterns/LexicalEditor/plugins/ProblemSelectPlugin/index.js +20 -0
  20. package/dist/patterns/LexicalEditor/theme.d.ts +1 -0
  21. package/dist/patterns/LexicalEditor/theme.js +7 -0
  22. package/package.json +1 -1
@@ -11,6 +11,7 @@ import { getTheme } from "./theme";
11
11
  import { useTheme } from "@emotion/react";
12
12
  import Plugins from "./Plugins";
13
13
  import { ColoredQuoteNode, ProblemInputNode } from "./nodes";
14
+ import { ProblemSelectNode } from "./nodes/ProblemSelectNode";
14
15
  function validateValue(value) {
15
16
  var _a, _b;
16
17
  if (value && typeof value !== "object") {
@@ -35,6 +36,7 @@ export function LexicalEditor(props) {
35
36
  onError: (error) => console.error(error),
36
37
  nodes: [
37
38
  ProblemInputNode,
39
+ ProblemSelectNode,
38
40
  HeadingNode,
39
41
  ListNode,
40
42
  ListItemNode,
@@ -28,6 +28,7 @@ import useLexicalEditable from "@lexical/react/useLexicalEditable";
28
28
  import ListMaxIndentLevelPlugin from "./plugins/ListMaxIndentLevelPlugin";
29
29
  import styled from "@emotion/styled";
30
30
  import ProblemInputPlugin from "./plugins/ProblemInputPlugin";
31
+ import ProblemSelectPlugin from "./plugins/ProblemSelectPlugin";
31
32
  export default function Plugins(props) {
32
33
  const { className, onChange } = props;
33
34
  const isEditable = useLexicalEditable();
@@ -42,7 +43,7 @@ export default function Plugins(props) {
42
43
  onChange === null || onChange === void 0 ? void 0 : onChange(editorState.toJSON());
43
44
  },
44
45
  // ignore 하지 않으면 Form에서 수정하지 않았는데 Dirty로 처리됨.
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, {})] }));
46
+ 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, {}), _jsx(ProblemSelectPlugin, {})] }));
46
47
  }
47
48
  const ScrollArea = styled.div `
48
49
  min-height: 150px;
@@ -0,0 +1,39 @@
1
+ import { DecoratorNode, EditorConfig, LexicalNode, NodeKey, SerializedLexicalNode, Spread } from "lexical";
2
+ import { ReactNode } from "react";
3
+ export interface Selection {
4
+ isAnswer: boolean;
5
+ show: {
6
+ image?: any;
7
+ text: string;
8
+ };
9
+ value: string;
10
+ }
11
+ export interface ProblemSelectPayload {
12
+ selections: Selection[];
13
+ selected: string[];
14
+ key?: NodeKey;
15
+ }
16
+ export type SerializedProblemSelectNode = Spread<ProblemSelectPayload, SerializedLexicalNode>;
17
+ /**
18
+ * selections는 Selection타입의 배열로서 객관식 정보를 담고 있습니다. (교사용)
19
+ * selected는 학생이 선택한 답의 value를 담고 있습니다.(학생용)
20
+ */
21
+ export declare class ProblemSelectNode extends DecoratorNode<ReactNode> {
22
+ __selections: Selection[];
23
+ __selected: string[];
24
+ isInline(): boolean;
25
+ static getType(): string;
26
+ getSelections(): Selection[];
27
+ getSelected(): string[];
28
+ setSolutions(selections: Selection[]): void;
29
+ setSelected(selected: string[]): void;
30
+ static clone(node: ProblemSelectNode): ProblemSelectNode;
31
+ constructor(selections: Selection[], selected: string[], key?: NodeKey);
32
+ createDOM(config: EditorConfig): HTMLElement;
33
+ updateDOM(): boolean;
34
+ static importJSON(serializedNode: SerializedProblemSelectNode): ProblemSelectNode;
35
+ exportJSON(): SerializedProblemSelectNode;
36
+ decorate(): ReactNode;
37
+ }
38
+ export declare function $createProblemSelectNode({ selections, selected, key, }: ProblemSelectPayload): ProblemSelectNode;
39
+ export declare function $isProblemSelectNode(node: LexicalNode | null | undefined): node is ProblemSelectNode;
@@ -0,0 +1,72 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { $applyNodeReplacement, DecoratorNode, } from "lexical";
3
+ import { addClassNamesToElement } from "@lexical/utils";
4
+ import { SelectComponent } from "./SelectComponent";
5
+ /**
6
+ * selections는 Selection타입의 배열로서 객관식 정보를 담고 있습니다. (교사용)
7
+ * selected는 학생이 선택한 답의 value를 담고 있습니다.(학생용)
8
+ */
9
+ export class ProblemSelectNode extends DecoratorNode {
10
+ isInline() {
11
+ return false;
12
+ }
13
+ static getType() {
14
+ return "problem-select";
15
+ }
16
+ getSelections() {
17
+ return this.__selections;
18
+ }
19
+ getSelected() {
20
+ return this.__selected;
21
+ }
22
+ setSolutions(selections) {
23
+ const self = this.getWritable();
24
+ self.__selections = selections;
25
+ }
26
+ setSelected(selected) {
27
+ const self = this.getWritable();
28
+ self.__selected = selected;
29
+ }
30
+ static clone(node) {
31
+ return new ProblemSelectNode(node.__selections, node.__selected, node.__key);
32
+ }
33
+ constructor(selections, selected, key) {
34
+ super(key);
35
+ this.__selections = selections;
36
+ this.__selected = selected;
37
+ }
38
+ createDOM(config) {
39
+ // Define the DOM element here
40
+ const root = document.createElement("div");
41
+ addClassNamesToElement(root, config.theme.problemSelect);
42
+ return root;
43
+ }
44
+ updateDOM() {
45
+ return false;
46
+ }
47
+ static importJSON(serializedNode) {
48
+ const node = $createProblemSelectNode({
49
+ key: serializedNode.key,
50
+ selections: serializedNode.selections,
51
+ selected: serializedNode.selected,
52
+ });
53
+ return node;
54
+ }
55
+ exportJSON() {
56
+ return {
57
+ version: 1,
58
+ type: "problem-select",
59
+ selections: this.__selections,
60
+ selected: this.__selected,
61
+ };
62
+ }
63
+ decorate() {
64
+ return (_jsx(SelectComponent, { selections: this.__selections, selected: this.__selected, nodeKey: this.getKey() }));
65
+ }
66
+ }
67
+ export function $createProblemSelectNode({ selections, selected, key, }) {
68
+ return $applyNodeReplacement(new ProblemSelectNode(selections, selected, key));
69
+ }
70
+ export function $isProblemSelectNode(node) {
71
+ return node instanceof ProblemSelectNode;
72
+ }
@@ -0,0 +1,12 @@
1
+ /** @jsxImportSource @emotion/react */
2
+ import { ImagePayload } from "../ImageNode";
3
+ export interface SelectBoxProps {
4
+ index: number;
5
+ isSelected?: boolean;
6
+ isAnswer?: boolean;
7
+ image?: ImagePayload;
8
+ text: string;
9
+ onClick: () => void;
10
+ fullWidth?: boolean;
11
+ }
12
+ export default function SelectBox(props: SelectBoxProps): import("@emotion/react/jsx-runtime").JSX.Element;
@@ -0,0 +1,66 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "@emotion/react/jsx-runtime";
2
+ import styled from "@emotion/styled";
3
+ import { css, useTheme } from "@emotion/react";
4
+ import { CheckFillIcon, CheckboxCircleFillIcon } from "../../../../icons";
5
+ export default function SelectBox(props) {
6
+ const { index, isSelected, isAnswer, image, text, onClick, fullWidth } = props;
7
+ const theme = useTheme();
8
+ return (_jsxs(Container, Object.assign({ isSelected: isSelected, fullWidth: fullWidth, onClick: onClick }, { children: [_jsx(Index, Object.assign({ isSelected: isSelected }, { children: isSelected ? (_jsx(CheckFillIcon, { css: css `
9
+ width: 12px;
10
+ height: 12px;
11
+ ` })) : (index) })), _jsx(Content, { children: text }), isAnswer && (_jsx(CheckboxCircleFillIcon, { color: theme.color.foreground.success, css: css `
12
+ width: 16px;
13
+ height: 16px;
14
+ ` }))] })));
15
+ }
16
+ const Container = styled.div(({ theme, isSelected, fullWidth }) => css `
17
+ cursor: pointer;
18
+ display: flex;
19
+ box-sizing: border-box;
20
+ width: ${fullWidth ? "100%" : "400px"};
21
+ padding: 8px;
22
+ gap: 8px;
23
+ border-radius: 8px;
24
+ background: ${isSelected
25
+ ? theme.color.container.primaryContainer
26
+ : theme.color.background.neutralAlt};
27
+ border: ${isSelected
28
+ ? `1px solid ${theme.color.foreground.primary}`
29
+ : "1px solid transparent"};
30
+ color: ${isSelected
31
+ ? theme.color.container.primaryOnContainer
32
+ : theme.color.foreground.neutralBase};
33
+ `);
34
+ const Index = styled.div(({ theme, isSelected }) => css `
35
+ display: flex;
36
+ box-sizing: border-box;
37
+ width: 20px;
38
+ height: 20px;
39
+ padding: 4px;
40
+ justify-content: center;
41
+ align-items: center;
42
+ border-radius: 4px;
43
+ border: ${isSelected
44
+ ? "none"
45
+ : `1px solid ${theme.color.background.neutralAltActive}`};
46
+ background: ${isSelected
47
+ ? theme.color.background.primary
48
+ : theme.color.background.neutralBase};
49
+ color: ${isSelected
50
+ ? theme.color.foreground.neutralAlt
51
+ : theme.color.foreground.neutralBaseDisabled};
52
+ font-family: ${theme.fontFamily.ui};
53
+ font-size: 14px;
54
+ font-weight: 800;
55
+ line-height: 16px;
56
+ `);
57
+ const Content = styled.div(({ theme }) => css `
58
+ display: flex;
59
+ flex-direction: column;
60
+ gap: 12px;
61
+ flex: 1;
62
+ font-family: ${theme.fontFamily.ui};
63
+ font-size: 14px;
64
+ font-weight: 400;
65
+ line-height: 20px;
66
+ `);
@@ -0,0 +1,8 @@
1
+ /** @jsxImportSource @emotion/react */
2
+ import { NodeKey } from "lexical";
3
+ import { Selection } from "./ProblemSelectNode";
4
+ export declare function SelectComponent(props: {
5
+ selections: Selection[];
6
+ selected: string[];
7
+ nodeKey: NodeKey;
8
+ }): import("@emotion/react/jsx-runtime").JSX.Element;
@@ -0,0 +1,73 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "@emotion/react/jsx-runtime";
2
+ /** @jsxImportSource @emotion/react */
3
+ import { $getNodeByKey } from "lexical";
4
+ import { $isProblemSelectNode, } from "./ProblemSelectNode";
5
+ import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext";
6
+ import { useState } from "react";
7
+ import useLexicalEditable from "@lexical/react/useLexicalEditable";
8
+ import { useFieldArray, useForm } from "react-hook-form";
9
+ import SelectBox from "./SelectBox";
10
+ import { css } from "@emotion/react";
11
+ import SquareButton from "../../../../components/SquareButton";
12
+ import { Settings3FillIcon } from "../../../../icons";
13
+ import SettingForm from "./SettingForm";
14
+ export function SelectComponent(props) {
15
+ const { selections, selected, nodeKey } = props;
16
+ const [editor] = useLexicalComposerContext();
17
+ const [settingOpen, setSettingOpen] = useState(false);
18
+ const isEditable = useLexicalEditable();
19
+ const { control, handleSubmit } = useForm({
20
+ mode: "all",
21
+ defaultValues: {
22
+ selections,
23
+ selected,
24
+ },
25
+ });
26
+ const { fields, append, remove, update } = useFieldArray({
27
+ control,
28
+ name: "selections",
29
+ keyName: "uid",
30
+ });
31
+ // 학생 view
32
+ if (!isEditable) {
33
+ return (_jsx(_Fragment, { children: selections.map((selection, index) => {
34
+ return (_jsx(SelectBox, { index: index + 1, isSelected: selected.includes(selection.value), text: selection.show.text, onClick: () => {
35
+ const isSelected = selected.includes(selection.value);
36
+ if (isSelected) {
37
+ editor.update(() => {
38
+ const node = $getNodeByKey(nodeKey);
39
+ if (!$isProblemSelectNode(node)) {
40
+ return;
41
+ }
42
+ const newSelected = [...selected];
43
+ const index = newSelected.indexOf(selection.value);
44
+ newSelected.splice(index, 1);
45
+ node.setSelected(newSelected);
46
+ });
47
+ }
48
+ else {
49
+ editor.update(() => {
50
+ const node = $getNodeByKey(nodeKey);
51
+ if (!$isProblemSelectNode(node)) {
52
+ return;
53
+ }
54
+ const newSelected = [...selected];
55
+ newSelected.push(selection.value);
56
+ node.setSelected(newSelected);
57
+ });
58
+ }
59
+ }, fullWidth: true }, index));
60
+ }) }));
61
+ }
62
+ // 교사 edit view
63
+ return (_jsxs(_Fragment, { children: [_jsxs("div", Object.assign({ css: css `
64
+ display: flex;
65
+ gap: 4px;
66
+ ` }, { children: [_jsx("div", Object.assign({ css: css `
67
+ display: flex;
68
+ flex-direction: column;
69
+ gap: 4px;
70
+ ` }, { children: fields.map((field, index) => (_jsx(SelectBox, { index: index + 1, isAnswer: field.isAnswer, text: field.show.text || `${index + 1}번 선택지`, onClick: () => setSettingOpen(true) }, index))) })), _jsx(SquareButton, { size: "small", color: "icon", icon: _jsx(Settings3FillIcon, {}), onClick: () => {
71
+ setSettingOpen(true);
72
+ } })] })), settingOpen && (_jsx(SettingForm, { control: control, handleSubmit: handleSubmit, fields: fields, append: append, remove: remove, update: update, nodeKey: nodeKey, onClose: () => setSettingOpen(false) }))] }));
73
+ }
@@ -0,0 +1,11 @@
1
+ import { ProblemSelectPayload } from "../ProblemSelectNode";
2
+ import { Control, FieldArrayWithId, UseFieldArrayUpdate } from "react-hook-form";
3
+ export interface FormSelectionProps {
4
+ index: number;
5
+ control: Control<ProblemSelectPayload, any>;
6
+ field: FieldArrayWithId<ProblemSelectPayload, "selections", "uid">;
7
+ update: UseFieldArrayUpdate<ProblemSelectPayload, "selections">;
8
+ rules?: any;
9
+ onDelete?: () => void;
10
+ }
11
+ export declare function FormSelection(props: FormSelectionProps): import("@emotion/react/jsx-runtime").JSX.Element;
@@ -0,0 +1,64 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "@emotion/react/jsx-runtime";
2
+ /** @jsxImportSource @emotion/react */
3
+ import styled from "@emotion/styled";
4
+ import { Controller, } from "react-hook-form";
5
+ import { css } from "@emotion/react";
6
+ import Input from "../../../../../components/Input";
7
+ import { DeleteBinLineIcon, ErrorWarningFillIcon, ImageAddFillIcon, } from "../../../../../icons";
8
+ import SquareButton from "../../../../../components/SquareButton";
9
+ import Switch from "../../../../../components/Switch";
10
+ export function FormSelection(props) {
11
+ const { index, control, field, update, rules, onDelete } = props;
12
+ return (_jsxs(Container, { children: [_jsx(Index, { children: index + 1 }), _jsx(Controller, { name: `selections.${index}.show.text`, control: control, rules: rules, render: ({ field: { value, onChange }, fieldState: { invalid, error }, }) => (_jsx(Input, { size: "small", color: invalid ? "activeDanger" : "default", value: value, onChange: onChange, inputProps: {
13
+ onBlur: (_e) => {
14
+ // onBlur시에 선택지 미리보기에 반영합니다.
15
+ update(index, Object.assign(Object.assign({}, field), { show: Object.assign(Object.assign({}, field.show), { text: value }) }));
16
+ },
17
+ }, placeholder: `${index + 1}번 선택지`, hintIcon: invalid ? _jsx(ErrorWarningFillIcon, {}) : undefined, hintText: error === null || error === void 0 ? void 0 : error.message, multiline: true, fullWidth: true, css: css `
18
+ flex: 1;
19
+ ` })) }), _jsx(SquareButton, { color: "icon", size: "xsmall", icon: _jsx(ImageAddFillIcon, {}), onClick: () => {
20
+ // TODO: 이미지 추가
21
+ } }), _jsx(Controller, { name: `selections.${index}.isAnswer`, control: control, render: ({ field: { value, onChange } }) => (_jsxs(Answer, Object.assign({ onClick: () => {
22
+ onChange(!value);
23
+ // 선택지 미리보기에 정답여부를 반영합니다.
24
+ update(index, Object.assign(Object.assign({}, field), { isAnswer: !value }));
25
+ } }, { children: ["\uC815\uB2F5", _jsx(Switch, { checked: value, size: "small" })] }))) }), onDelete && (_jsx(SquareButton, { color: "white", size: "xsmall", icon: _jsx(DeleteBinLineIcon, {}), onClick: onDelete }))] }));
26
+ }
27
+ const Container = styled.div(({ theme }) => css `
28
+ display: flex;
29
+ padding: 4px 12px;
30
+ gap: 8px;
31
+ align-items: center;
32
+ border-radius: 8px;
33
+ background: ${theme.color.background.neutralAlt};
34
+ `);
35
+ const Index = styled.div(({ theme }) => css `
36
+ display: flex;
37
+ box-sizing: border-box;
38
+ width: 20px;
39
+ height: 20px;
40
+ padding: 4px;
41
+ justify-content: center;
42
+ align-items: center;
43
+ border-radius: 4px;
44
+ border: 1px solid ${theme.color.background.neutralAltActive};
45
+ background: ${theme.color.background.neutralBase};
46
+ color: ${theme.color.foreground.neutralBaseDisabled};
47
+ font-family: ${theme.fontFamily.ui};
48
+ font-size: 14px;
49
+ font-weight: 800;
50
+ line-height: 16px;
51
+ `);
52
+ const Answer = styled.div(({ theme }) => css `
53
+ display: flex;
54
+ padding-right: 4px;
55
+ gap: 8px;
56
+ color: ${theme.color.foreground.neutralBase};
57
+ cursor: pointer;
58
+ /* Default/Label/14px-Md */
59
+ font-family: ${theme.fontFamily.ui};
60
+ font-size: 14px;
61
+ font-style: normal;
62
+ font-weight: 500;
63
+ line-height: 16px; /* 114.286% */
64
+ `);
@@ -0,0 +1,15 @@
1
+ /** @jsxImportSource @emotion/react */
2
+ import { Control, FieldArrayWithId, UseFieldArrayAppend, UseFieldArrayRemove, UseFieldArrayUpdate, UseFormHandleSubmit } from "react-hook-form";
3
+ import { ProblemSelectPayload } from "../ProblemSelectNode";
4
+ import { NodeKey } from "lexical";
5
+ export interface SettingFormProps {
6
+ control: Control<ProblemSelectPayload, any>;
7
+ handleSubmit: UseFormHandleSubmit<ProblemSelectPayload, undefined>;
8
+ fields: FieldArrayWithId<ProblemSelectPayload, "selections", "uid">[];
9
+ append: UseFieldArrayAppend<ProblemSelectPayload, "selections">;
10
+ remove: UseFieldArrayRemove;
11
+ update: UseFieldArrayUpdate<ProblemSelectPayload, "selections">;
12
+ nodeKey: NodeKey;
13
+ onClose: () => void;
14
+ }
15
+ export default function SettingForm(props: SettingFormProps): import("@emotion/react/jsx-runtime").JSX.Element;
@@ -0,0 +1,104 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "@emotion/react/jsx-runtime";
2
+ import { $isProblemSelectNode, } from "../ProblemSelectNode";
3
+ import { $getNodeByKey } from "lexical";
4
+ import { css } from "@emotion/react";
5
+ import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext";
6
+ import styled from "@emotion/styled";
7
+ import shadows from "../../../../../foundation/shadows";
8
+ import { AddFillIcon, ListRadioIcon } from "../../../../../icons";
9
+ import Button from "../../../../../components/Button";
10
+ import { FormSelection } from "./FormSelection";
11
+ export default function SettingForm(props) {
12
+ const { control, handleSubmit, fields, append, remove, update, nodeKey, onClose, } = props;
13
+ const [editor] = useLexicalComposerContext();
14
+ const onSettingSubmit = (data) => {
15
+ editor.update(() => {
16
+ const node = $getNodeByKey(nodeKey);
17
+ if (!$isProblemSelectNode(node)) {
18
+ return;
19
+ }
20
+ node.setSolutions(data.selections);
21
+ });
22
+ onClose();
23
+ };
24
+ function validateDuplicatedSelection(fields, index) {
25
+ if (index === 0)
26
+ return true;
27
+ const duplicatedIndex = fields
28
+ .slice(0, index)
29
+ .findIndex((prevField) => prevField.show.text === fields[index].show.text);
30
+ if (duplicatedIndex < 0)
31
+ return true;
32
+ return `${duplicatedIndex + 1}번 선택지와 같은 내용입니다.`;
33
+ }
34
+ return (_jsxs(Form, Object.assign({ onSubmit: handleSubmit(onSettingSubmit) }, { children: [_jsxs(Title, { children: [_jsx(ListRadioIcon, { css: css `
35
+ width: 12px;
36
+ height: 12px;
37
+ ` }), "\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, field: field, update: update, rules: {
38
+ required: "필수 입력 항목입니다.",
39
+ validate: () => validateDuplicatedSelection(fields, index),
40
+ }, onDelete: index !== 0
41
+ ? () => {
42
+ remove(index);
43
+ }
44
+ : undefined }, field.uid)))] }), _jsx(Button, { color: "grey", size: "small", startIcon: _jsx(AddFillIcon, {}), label: "\uC120\uD0DD\uC9C0 \uCD94\uAC00", onClick: () => {
45
+ append({
46
+ isAnswer: false,
47
+ show: {
48
+ text: "",
49
+ },
50
+ value: (fields.length + 1).toString(),
51
+ });
52
+ } })] }), _jsxs(Buttons, { children: [_jsx(Button, { color: "grey", size: "xsmall", label: "\uB2EB\uAE30", onClick: onClose }), _jsx(Button, { color: "primary", size: "xsmall", label: "\uC774\uB300\uB85C \uB123\uAE30", type: "submit" })] })] })));
53
+ }
54
+ const Form = styled.form(({ theme }) => css `
55
+ display: flex;
56
+ width: 620px;
57
+ flex-direction: column;
58
+ border-radius: 6px;
59
+ border: 1px solid ${theme.color.background.neutralAltActive};
60
+ background: ${theme.color.background.neutralBase};
61
+ box-shadow: ${shadows.shadow08};
62
+ `);
63
+ const Title = styled.div(({ theme }) => css `
64
+ display: flex;
65
+ padding: 8px 12px;
66
+ gap: 4px;
67
+ align-items: center;
68
+ color: ${theme.color.foreground.neutralBase};
69
+ /* Default/Label/12px-Md */
70
+ font-family: ${theme.fontFamily.ui};
71
+ font-size: 12px;
72
+ font-style: normal;
73
+ font-weight: 500;
74
+ line-height: 16px; /* 133.333% */
75
+ `);
76
+ const Content = styled.div(({ theme }) => css `
77
+ display: flex;
78
+ flex-direction: column;
79
+ gap: 12px;
80
+ padding: 12px;
81
+ border-top: 1px solid ${theme.color.background.neutralAltActive};
82
+ border-bottom: 1px solid ${theme.color.background.neutralAltActive};
83
+ `);
84
+ const FormArea = styled.div `
85
+ display: flex;
86
+ flex-direction: column;
87
+ gap: 8px;
88
+ `;
89
+ const Label = styled.div(({ theme }) => css `
90
+ color: ${theme.color.foreground.neutralBaseDisabled};
91
+ /* Default/Label/12px-Md */
92
+ font-family: ${theme.fontFamily.ui};
93
+ font-size: 12px;
94
+ font-style: normal;
95
+ font-weight: 500;
96
+ line-height: 16px; /* 133.333% */
97
+ `);
98
+ const Buttons = styled.div `
99
+ display: flex;
100
+ padding: 12px;
101
+ justify-content: flex-end;
102
+ align-items: center;
103
+ gap: 8px;
104
+ `;
@@ -0,0 +1 @@
1
+ export { default } from "./SettingForm";
@@ -0,0 +1 @@
1
+ export { default } from "./SettingForm";
@@ -0,0 +1,2 @@
1
+ export * from "./ProblemSelectNode";
2
+ export * from "./SelectComponent";
@@ -0,0 +1,2 @@
1
+ export * from "./ProblemSelectNode";
2
+ export * from "./SelectComponent";
@@ -25,6 +25,7 @@ import { TextIcon, H1Icon, H2Icon, H3Icon, ListUnorderedIcon, ListOrderedIcon, D
25
25
  import { ZINDEX } from "../../../../utils/zIndex";
26
26
  import { css, useTheme } from "@emotion/react";
27
27
  import { INSERT_PROBLEM_INPUT_COMMAND } from "../ProblemInputPlugin";
28
+ import { INSERT_PROBLEM_SELECT_COMMAND } from "../ProblemSelectPlugin";
28
29
  // import useModal from "../../hooks/useModal";
29
30
  // import catTypingGif from "../../images/cat-typing.gif";
30
31
  // import { INSERT_IMAGE_COMMAND, InsertImageDialog } from "../ImagesPlugin";
@@ -86,7 +87,18 @@ function getQuizContextOptions(editor, theme) {
86
87
  icon: _jsx(ListRadioIcon, { color: theme.color.foreground.primary }),
87
88
  keywords: ["problem select", "객관식 입력"],
88
89
  onSelect: () => {
89
- // TODO: 객관식 입력 칸 추가
90
+ editor.dispatchCommand(INSERT_PROBLEM_SELECT_COMMAND, {
91
+ selections: [
92
+ {
93
+ isAnswer: false,
94
+ show: {
95
+ text: "",
96
+ },
97
+ value: "1",
98
+ },
99
+ ],
100
+ selected: [],
101
+ });
90
102
  },
91
103
  }),
92
104
  new ComponentDrawerOption("메뉴구분선", (_jsx("div", { css: css `
@@ -0,0 +1,5 @@
1
+ /// <reference types="react" />
2
+ import { LexicalCommand } from "lexical";
3
+ import { ProblemSelectPayload } from "../../nodes/ProblemSelectNode";
4
+ export declare const INSERT_PROBLEM_SELECT_COMMAND: LexicalCommand<ProblemSelectPayload>;
5
+ export default function ProblemInputPlugin(): JSX.Element | null;
@@ -0,0 +1,20 @@
1
+ import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext";
2
+ import { $insertNodeToNearestRoot } from "@lexical/utils";
3
+ import { COMMAND_PRIORITY_EDITOR, createCommand, } from "lexical";
4
+ import { useEffect } from "react";
5
+ import { $createProblemSelectNode, ProblemSelectNode, } from "../../nodes/ProblemSelectNode";
6
+ export const INSERT_PROBLEM_SELECT_COMMAND = createCommand("INSERT_PROBLEM_SELECT_COMMAND");
7
+ export default function ProblemInputPlugin() {
8
+ const [editor] = useLexicalComposerContext();
9
+ useEffect(() => {
10
+ if (!editor.hasNodes([ProblemSelectNode])) {
11
+ throw new Error("ProblemSelectNode: ProblemSelectNode not registered on editor");
12
+ }
13
+ editor.registerCommand(INSERT_PROBLEM_SELECT_COMMAND, (payload) => {
14
+ const problemInputNode = $createProblemSelectNode(payload);
15
+ $insertNodeToNearestRoot(problemInputNode);
16
+ return true;
17
+ }, COMMAND_PRIORITY_EDITOR);
18
+ }, [editor]);
19
+ return null;
20
+ }
@@ -29,4 +29,5 @@ export declare function getTheme(theme: Theme): {
29
29
  strikethrough: string;
30
30
  };
31
31
  problemInput: string;
32
+ problemSelect: string;
32
33
  };
@@ -252,6 +252,13 @@ export function getTheme(theme) {
252
252
  flex: 1;
253
253
  gap: 4px;
254
254
  margin-bottom: 8px;
255
+ `,
256
+ problemSelect: css `
257
+ display: flex;
258
+ flex-direction: column;
259
+ flex: 1;
260
+ gap: 4px;
261
+ margin-bottom: 8px;
255
262
  `,
256
263
  };
257
264
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@team-monolith/cds",
3
- "version": "1.8.8",
3
+ "version": "1.9.0",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "sideEffects": false,