@team-monolith/cds 1.10.0 → 1.10.1

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 (41) hide show
  1. package/dist/patterns/LexicalEditor/LexicalCustomConfigContext.d.ts +6 -0
  2. package/dist/patterns/LexicalEditor/LexicalCustomConfigContext.js +5 -0
  3. package/dist/patterns/LexicalEditor/LexicalEditor.d.ts +4 -0
  4. package/dist/patterns/LexicalEditor/LexicalEditor.js +5 -2
  5. package/dist/patterns/LexicalEditor/Plugins.d.ts +1 -0
  6. package/dist/patterns/LexicalEditor/Plugins.js +4 -3
  7. package/dist/patterns/LexicalEditor/nodes/ProblemInputNode/InputComponent.js +16 -9
  8. package/dist/patterns/LexicalEditor/nodes/ProblemSelectNode/ProblemSelectNode.d.ts +45 -0
  9. package/dist/patterns/LexicalEditor/nodes/ProblemSelectNode/ProblemSelectNode.js +74 -0
  10. package/dist/patterns/LexicalEditor/nodes/ProblemSelectNode/SelectBox.d.ts +11 -0
  11. package/dist/patterns/LexicalEditor/nodes/ProblemSelectNode/SelectBox.js +73 -0
  12. package/dist/patterns/LexicalEditor/nodes/ProblemSelectNode/SelectComponent.d.ts +9 -0
  13. package/dist/patterns/LexicalEditor/nodes/ProblemSelectNode/SelectComponent.js +85 -0
  14. package/dist/patterns/LexicalEditor/nodes/ProblemSelectNode/SettingForm/FormSelection.d.ts +14 -0
  15. package/dist/patterns/LexicalEditor/nodes/ProblemSelectNode/SettingForm/FormSelection.js +91 -0
  16. package/dist/patterns/LexicalEditor/nodes/ProblemSelectNode/SettingForm/InsertImageDialog.d.ts +10 -0
  17. package/dist/patterns/LexicalEditor/nodes/ProblemSelectNode/SettingForm/InsertImageDialog.js +109 -0
  18. package/dist/patterns/LexicalEditor/nodes/ProblemSelectNode/SettingForm/InsertImageUploadedDialogBody.d.ts +9 -0
  19. package/dist/patterns/LexicalEditor/nodes/ProblemSelectNode/SettingForm/InsertImageUploadedDialogBody.js +50 -0
  20. package/dist/patterns/LexicalEditor/nodes/ProblemSelectNode/SettingForm/InsertImageUriDialogBody.d.ts +8 -0
  21. package/dist/patterns/LexicalEditor/nodes/ProblemSelectNode/SettingForm/InsertImageUriDialogBody.js +17 -0
  22. package/dist/patterns/LexicalEditor/nodes/ProblemSelectNode/SettingForm/SettingForm.d.ts +8 -0
  23. package/dist/patterns/LexicalEditor/nodes/ProblemSelectNode/SettingForm/SettingForm.js +146 -0
  24. package/dist/patterns/LexicalEditor/nodes/ProblemSelectNode/SettingForm/index.d.ts +1 -0
  25. package/dist/patterns/LexicalEditor/nodes/ProblemSelectNode/SettingForm/index.js +1 -0
  26. package/dist/patterns/LexicalEditor/nodes/ProblemSelectNode/index.d.ts +2 -0
  27. package/dist/patterns/LexicalEditor/nodes/ProblemSelectNode/index.js +2 -0
  28. package/dist/patterns/LexicalEditor/plugins/ActionsPlugin/index.d.ts +9 -0
  29. package/dist/patterns/LexicalEditor/plugins/ActionsPlugin/index.js +24 -0
  30. package/dist/patterns/LexicalEditor/plugins/ComponentAdderPlugin/useContextMenuOptions.js +15 -0
  31. package/dist/patterns/LexicalEditor/plugins/ComponentPickerMenuPlugin/ComponentPickerMenuPlugin.js +13 -1
  32. package/dist/patterns/LexicalEditor/plugins/ImagesPlugin/InsertImageDialog.js +7 -6
  33. package/dist/patterns/LexicalEditor/plugins/ImagesPlugin/InsertImageUploadedDialogBody.d.ts +13 -4
  34. package/dist/patterns/LexicalEditor/plugins/ImagesPlugin/InsertImageUploadedDialogBody.js +5 -6
  35. package/dist/patterns/LexicalEditor/plugins/ImagesPlugin/InsertImageUriDialogBody.d.ts +9 -4
  36. package/dist/patterns/LexicalEditor/plugins/ImagesPlugin/InsertImageUriDialogBody.js +4 -7
  37. package/dist/patterns/LexicalEditor/plugins/ProblemSelectPlugin/index.d.ts +5 -0
  38. package/dist/patterns/LexicalEditor/plugins/ProblemSelectPlugin/index.js +20 -0
  39. package/dist/patterns/LexicalEditor/theme.d.ts +1 -0
  40. package/dist/patterns/LexicalEditor/theme.js +7 -0
  41. package/package.json +1 -1
@@ -0,0 +1,6 @@
1
+ /// <reference types="react" />
2
+ export type LexicalCustomConfig = {
3
+ freezeProblemNode: boolean;
4
+ };
5
+ declare const LexicalCustomConfigContext: import("react").Context<LexicalCustomConfig>;
6
+ export { LexicalCustomConfigContext };
@@ -0,0 +1,5 @@
1
+ import { createContext } from "react";
2
+ const LexicalCustomConfigContext = createContext({
3
+ freezeProblemNode: false,
4
+ });
5
+ export { LexicalCustomConfigContext };
@@ -2,12 +2,16 @@
2
2
  import { SerializedEditorState, SerializedLexicalNode } from "lexical";
3
3
  export interface LexicalEditorProps {
4
4
  className?: string;
5
+ contentEditableClassName?: string;
5
6
  value?: any;
6
7
  onChange?: (blocks: SerializedEditorState<SerializedLexicalNode>) => void;
7
8
  /** editable. 수정 모드 / 읽기 모드 여부
8
9
  * initialConfig에 전달되므로 마운트 된 이후 수정해도 반영되지 않음
9
10
  */
10
11
  editable?: boolean;
12
+ /** freeze. 문제 블록을 수정하지 못하도록 함
13
+ */
14
+ freezeProblemNode?: boolean;
11
15
  /** 외부에서 플러그인을 주입하는 경우 활용함 */
12
16
  children?: JSX.Element | string | (JSX.Element | string)[];
13
17
  }
@@ -11,6 +11,8 @@ 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 { LexicalCustomConfigContext } from "./LexicalCustomConfigContext";
15
+ import { ProblemSelectNode } from "./nodes/ProblemSelectNode";
14
16
  function validateValue(value) {
15
17
  var _a, _b;
16
18
  if (value && typeof value !== "object") {
@@ -28,13 +30,14 @@ function validateValue(value) {
28
30
  return true;
29
31
  }
30
32
  export function LexicalEditor(props) {
31
- const { className, value, onChange, editable = true, children } = props;
33
+ const { className, contentEditableClassName, value, onChange, editable = true, freezeProblemNode, children, } = props;
32
34
  const theme = useTheme();
33
35
  const initialConfig = {
34
36
  namespace: "CodleLexicalEditor",
35
37
  onError: (error) => console.error(error),
36
38
  nodes: [
37
39
  ProblemInputNode,
40
+ ProblemSelectNode,
38
41
  HeadingNode,
39
42
  ListNode,
40
43
  ListItemNode,
@@ -60,5 +63,5 @@ export function LexicalEditor(props) {
60
63
  editorState: validateValue(value) ? JSON.stringify(value) : undefined,
61
64
  editable: editable,
62
65
  };
63
- return (_jsxs(LexicalComposer, Object.assign({ initialConfig: initialConfig }, { children: [_jsx(Plugins, { className: className, onChange: onChange }), _jsx(_Fragment, { children: children })] })));
66
+ return (_jsx(LexicalCustomConfigContext.Provider, Object.assign({ value: { freezeProblemNode: freezeProblemNode !== null && freezeProblemNode !== void 0 ? freezeProblemNode : false } }, { children: _jsxs(LexicalComposer, Object.assign({ initialConfig: initialConfig }, { children: [_jsx(Plugins, { className: className, contentEditableClassName: contentEditableClassName, onChange: onChange }), _jsx(_Fragment, { children: children })] })) })));
64
67
  }
@@ -6,6 +6,7 @@ import { ReactElement } from "react";
6
6
  import { SerializedEditorState, SerializedLexicalNode } from "lexical";
7
7
  export interface PluginsProps {
8
8
  className?: string;
9
+ contentEditableClassName?: string;
9
10
  onChange?: (blocks: SerializedEditorState<SerializedLexicalNode>) => void;
10
11
  }
11
12
  export default function Plugins(props: PluginsProps): ReactElement;
@@ -28,8 +28,9 @@ 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
- const { className, onChange } = props;
33
+ const { className, contentEditableClassName, onChange } = props;
33
34
  const isEditable = useLexicalEditable();
34
35
  const [floatingAnchorElem, setFloatingAnchorElem] = useState(null);
35
36
  const [isLinkEditMode, setIsLinkEditMode] = useState(false);
@@ -38,11 +39,11 @@ export default function Plugins(props) {
38
39
  setFloatingAnchorElem(_floatingAnchorElem);
39
40
  }
40
41
  };
41
- return (_jsxs(_Fragment, { children: [_jsx(RichTextPlugin, { contentEditable: _jsx(ScrollArea, Object.assign({ className: className }, { children: _jsx(FloatingAnchor, Object.assign({ ref: onRef }, { children: _jsx(StyledContentEditable, { isEditable: isEditable }) })) })), placeholder: null, ErrorBoundary: LexicalErrorBoundary }), _jsx(OnChangePlugin, { onChange: (editorState) => {
42
+ return (_jsxs(_Fragment, { children: [_jsx(RichTextPlugin, { contentEditable: _jsx(ScrollArea, Object.assign({ className: className }, { children: _jsx(FloatingAnchor, Object.assign({ ref: onRef }, { children: _jsx(StyledContentEditable, { className: contentEditableClassName, isEditable: isEditable }) })) })), placeholder: null, ErrorBoundary: LexicalErrorBoundary }), _jsx(OnChangePlugin, { onChange: (editorState) => {
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;
@@ -12,7 +12,7 @@ var __rest = (this && this.__rest) || function (s, e) {
12
12
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "@emotion/react/jsx-runtime";
13
13
  /** @jsxImportSource @emotion/react */
14
14
  import { css } from "@emotion/react";
15
- import { useState } from "react";
15
+ import { useContext, useState } from "react";
16
16
  import Input from "../../../../components/Input";
17
17
  import SquareButton from "../../../../components/SquareButton";
18
18
  import { Settings3FillIcon } from "../../../../icons";
@@ -22,6 +22,7 @@ import useLexicalEditable from "@lexical/react/useLexicalEditable";
22
22
  import { $isProblemInputNode } from "./ProblemInputNode";
23
23
  import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext";
24
24
  import { $getNodeByKey } from "lexical";
25
+ import { LexicalCustomConfigContext } from "../../LexicalCustomConfigContext";
25
26
  export function InputComponent(props) {
26
27
  const { answer } = props, settingFormProps = __rest(props, ["answer"]);
27
28
  const { placeholder, nodeKey } = settingFormProps;
@@ -29,23 +30,29 @@ export function InputComponent(props) {
29
30
  const [settingOpen, setSettingOpen] = useState(false);
30
31
  const isEditable = useLexicalEditable();
31
32
  const [answerInput, setAnswerInput] = useState(answer);
33
+ const lexicalCustomConfig = useContext(LexicalCustomConfigContext);
32
34
  // 학생 view
33
35
  // TODO: "글자 수대로" 옵션시에 글자 수대로 입력칸을 표시해야 합니다.
34
36
  if (!isEditable) {
35
- return (_jsx(Input, { size: "small", placeholder: placeholder || "여기에 입력하세요.", value: answerInput, onChange: (e) => setAnswerInput(e.target.value),
36
- // 한글 입력시에 onChange마다 update가 일어나는 것을 방지하기 위해 입력 완료후 onBlur시에 update하는 전략을 사용합니다.
37
- // 이를 위해 answerInput을 state로 관리합니다.
38
- inputProps: {
39
- onBlur: (_e) => {
37
+ if (lexicalCustomConfig.freezeProblemNode) {
38
+ return (_jsx(Input, { size: "small", placeholder: placeholder || "여기에 입력하세요.", value: answerInput, color: "default", fullWidth: true, inputProps: { readOnly: true } }));
39
+ }
40
+ else {
41
+ return (_jsx(Input, { size: "small", placeholder: placeholder || "여기에 입력하세요.", value: answerInput, onChange: (e) => {
42
+ // SoT를 EditorState로 설정하는 경우 한글 입력시 문제가 생김.
43
+ // ex) "나비" 입력시 -> "ㄴ나납ㅂ비"
44
+ // SoT를 내부 State로 관리하고 EditorState를 따로 설정.
45
+ // 문제를 더 파악하고 추후 개선 필요.
46
+ setAnswerInput(e.target.value);
40
47
  editor.update(() => {
41
48
  const node = $getNodeByKey(nodeKey);
42
49
  if (!$isProblemInputNode(node)) {
43
50
  return;
44
51
  }
45
- node.setAnswer(answerInput);
52
+ node.setAnswer(e.target.value);
46
53
  });
47
- },
48
- }, color: "default", fullWidth: true }));
54
+ }, color: "default", fullWidth: true }));
55
+ }
49
56
  }
50
57
  // 교사 edit view
51
58
  return (_jsxs(_Fragment, { children: [_jsxs("div", Object.assign({ css: css `
@@ -0,0 +1,45 @@
1
+ import { DecoratorNode, EditorConfig, LexicalNode, NodeKey, SerializedLexicalNode, Spread } from "lexical";
2
+ import { ReactNode } from "react";
3
+ export interface ImageProps {
4
+ src: string;
5
+ altText: string;
6
+ }
7
+ export interface Selection {
8
+ isAnswer: boolean;
9
+ show: {
10
+ image?: ImageProps | null;
11
+ text: string;
12
+ };
13
+ value: string;
14
+ }
15
+ export interface ProblemSelectPayload {
16
+ selections: Selection[];
17
+ selected: string[];
18
+ hasMultipleAnswers?: boolean;
19
+ key?: NodeKey;
20
+ }
21
+ export type SerializedProblemSelectNode = Spread<ProblemSelectPayload, SerializedLexicalNode>;
22
+ /**
23
+ * selections는 Selection타입의 배열로서 객관식 정보를 담고 있습니다. (교사용)
24
+ * selected는 학생이 선택한 답의 value를 담고 있습니다.(학생용)
25
+ */
26
+ export declare class ProblemSelectNode extends DecoratorNode<ReactNode> {
27
+ __selections: Selection[];
28
+ __selected: string[];
29
+ __hasMultipleAnswers: boolean | undefined;
30
+ isInline(): boolean;
31
+ static getType(): string;
32
+ getSelections(): Selection[];
33
+ getSelected(): string[];
34
+ setSelections(selections: Selection[]): void;
35
+ setSelected(selected: string[]): void;
36
+ static clone(node: ProblemSelectNode): ProblemSelectNode;
37
+ constructor(selections: Selection[], selected: string[], hasMultipleAnswers?: boolean, key?: NodeKey);
38
+ createDOM(config: EditorConfig): HTMLElement;
39
+ updateDOM(): boolean;
40
+ static importJSON(serializedNode: SerializedProblemSelectNode): ProblemSelectNode;
41
+ exportJSON(): SerializedProblemSelectNode;
42
+ decorate(): ReactNode;
43
+ }
44
+ export declare function $createProblemSelectNode({ selections, selected, hasMultipleAnswers, key, }: ProblemSelectPayload): ProblemSelectNode;
45
+ export declare function $isProblemSelectNode(node: LexicalNode | null | undefined): node is ProblemSelectNode;
@@ -0,0 +1,74 @@
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
+ setSelections(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.__hasMultipleAnswers, node.__key);
32
+ }
33
+ constructor(selections, selected, hasMultipleAnswers, key) {
34
+ super(key);
35
+ this.__selections = selections;
36
+ this.__selected = selected;
37
+ this.__hasMultipleAnswers = hasMultipleAnswers;
38
+ }
39
+ createDOM(config) {
40
+ // Define the DOM element here
41
+ const root = document.createElement("div");
42
+ addClassNamesToElement(root, config.theme.problemSelect);
43
+ return root;
44
+ }
45
+ updateDOM() {
46
+ return false;
47
+ }
48
+ static importJSON(serializedNode) {
49
+ const node = $createProblemSelectNode({
50
+ key: serializedNode.key,
51
+ selections: serializedNode.selections,
52
+ selected: serializedNode.selected,
53
+ hasMultipleAnswers: serializedNode.hasMultipleAnswers,
54
+ });
55
+ return node;
56
+ }
57
+ exportJSON() {
58
+ return {
59
+ version: 1,
60
+ type: "problem-select",
61
+ selections: this.__selections,
62
+ selected: this.__selected,
63
+ };
64
+ }
65
+ decorate() {
66
+ return (_jsx(SelectComponent, { selections: this.__selections, selected: this.__selected, hasMultipleAnswers: this.__hasMultipleAnswers, nodeKey: this.getKey() }));
67
+ }
68
+ }
69
+ export function $createProblemSelectNode({ selections, selected, hasMultipleAnswers, key, }) {
70
+ return $applyNodeReplacement(new ProblemSelectNode(selections, selected, hasMultipleAnswers, key));
71
+ }
72
+ export function $isProblemSelectNode(node) {
73
+ return node instanceof ProblemSelectNode;
74
+ }
@@ -0,0 +1,11 @@
1
+ import { ImageProps } from "./ProblemSelectNode";
2
+ export interface SelectBoxProps {
3
+ index: number;
4
+ isSelected?: boolean;
5
+ isAnswer?: boolean;
6
+ image?: ImageProps | null;
7
+ text: string;
8
+ onClick?: () => void;
9
+ fullWidth?: boolean;
10
+ }
11
+ export default function SelectBox(props: SelectBoxProps): import("@emotion/react/jsx-runtime").JSX.Element;
@@ -0,0 +1,73 @@
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 { css, useTheme } from "@emotion/react";
5
+ import { CheckFillIcon, CheckboxCircleFillIcon } from "../../../../icons";
6
+ export default function SelectBox(props) {
7
+ const { index, isSelected, isAnswer, image, text, onClick, fullWidth } = props;
8
+ const theme = useTheme();
9
+ return (_jsxs(Container, Object.assign({ isSelected: isSelected, fullWidth: fullWidth, onClick: onClick }, { children: [_jsx(Index, Object.assign({ isSelected: isSelected }, { children: isSelected ? (_jsx(CheckFillIcon, { css: css `
10
+ width: 12px;
11
+ height: 12px;
12
+ ` })) : (index) })), _jsxs(Content, { children: [image && (_jsx("img", { src: image.src, alt: image.altText, css: css `
13
+ height: auto;
14
+ // 이미지로 인해 좌우로 스크롤이 생기는 것을 방지
15
+ max-width: 100%;
16
+ width: fit-content;
17
+ border-radius: 6px;
18
+ ` })), text] }), isAnswer && (_jsx(CheckboxCircleFillIcon, { color: theme.color.foreground.success, css: css `
19
+ width: 16px;
20
+ height: 16px;
21
+ ` }))] })));
22
+ }
23
+ const Container = styled.div(({ theme, isSelected, fullWidth }) => css `
24
+ cursor: pointer;
25
+ display: flex;
26
+ box-sizing: border-box;
27
+ width: ${fullWidth ? "100%" : "400px"};
28
+ padding: 8px;
29
+ gap: 8px;
30
+ border-radius: 8px;
31
+ background: ${isSelected
32
+ ? theme.color.container.primaryContainer
33
+ : theme.color.background.neutralAlt};
34
+ border: ${isSelected
35
+ ? `1px solid ${theme.color.foreground.primary}`
36
+ : "1px solid transparent"};
37
+ color: ${isSelected
38
+ ? theme.color.container.primaryOnContainer
39
+ : theme.color.foreground.neutralBase};
40
+ `);
41
+ const Index = styled.div(({ theme, isSelected }) => css `
42
+ display: flex;
43
+ box-sizing: border-box;
44
+ width: 20px;
45
+ height: 20px;
46
+ padding: 4px;
47
+ justify-content: center;
48
+ align-items: center;
49
+ border-radius: 4px;
50
+ border: ${isSelected
51
+ ? "none"
52
+ : `1px solid ${theme.color.background.neutralAltActive}`};
53
+ background: ${isSelected
54
+ ? theme.color.background.primary
55
+ : theme.color.background.neutralBase};
56
+ color: ${isSelected
57
+ ? theme.color.foreground.neutralAlt
58
+ : theme.color.foreground.neutralBaseDisabled};
59
+ font-family: ${theme.fontFamily.ui};
60
+ font-size: 14px;
61
+ font-weight: 800;
62
+ line-height: 16px;
63
+ `);
64
+ const Content = styled.div(({ theme }) => css `
65
+ display: flex;
66
+ flex-direction: column;
67
+ gap: 12px;
68
+ flex: 1;
69
+ font-family: ${theme.fontFamily.ui};
70
+ font-size: 14px;
71
+ font-weight: 400;
72
+ line-height: 20px;
73
+ `);
@@ -0,0 +1,9 @@
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
+ hasMultipleAnswers?: boolean;
8
+ nodeKey: NodeKey;
9
+ }): import("@emotion/react/jsx-runtime").JSX.Element;
@@ -0,0 +1,85 @@
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 { $getNodeByKey } from "lexical";
15
+ import { $isProblemSelectNode } from "./ProblemSelectNode";
16
+ import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext";
17
+ import { useContext, useState } from "react";
18
+ import useLexicalEditable from "@lexical/react/useLexicalEditable";
19
+ import SelectBox from "./SelectBox";
20
+ import { css } from "@emotion/react";
21
+ import SquareButton from "../../../../components/SquareButton";
22
+ import { AlarmWarningFillIcon, Settings3FillIcon } from "../../../../icons";
23
+ import SettingForm from "./SettingForm";
24
+ import styled from "@emotion/styled";
25
+ import { LexicalCustomConfigContext } from "../../LexicalCustomConfigContext";
26
+ export function SelectComponent(props) {
27
+ const { selected, hasMultipleAnswers } = props, settingFormProps = __rest(props, ["selected", "hasMultipleAnswers"]);
28
+ const { selections, nodeKey } = settingFormProps;
29
+ const [editor] = useLexicalComposerContext();
30
+ const [settingOpen, setSettingOpen] = useState(false);
31
+ const isEditable = useLexicalEditable();
32
+ const lexicalCustomConfig = useContext(LexicalCustomConfigContext);
33
+ // 학생 view
34
+ if (!isEditable) {
35
+ return (_jsxs(_Fragment, { children: [hasMultipleAnswers && (_jsxs(Alert, { children: [_jsx(AlarmWarningFillIcon, { css: css `
36
+ width: 14px;
37
+ height: 14px;
38
+ ` }), "\uC9C8\uBB38\uC5D0 \uD574\uB2F9\uD558\uB294 \uB2F5\uC744 \uBAA8\uB450 \uACE0\uB974\uB294 \uBB38\uC81C\uC785\uB2C8\uB2E4."] })), selections.map((selection, index) => (_jsx(SelectBox, { index: index + 1, isSelected: selected.includes(selection.value), image: selection.show.image, text: selection.show.text, onClick: () => {
39
+ if (lexicalCustomConfig.freezeProblemNode)
40
+ return;
41
+ const isSelected = selected.includes(selection.value);
42
+ if (isSelected) {
43
+ editor.update(() => {
44
+ const node = $getNodeByKey(nodeKey);
45
+ if (!$isProblemSelectNode(node)) {
46
+ return;
47
+ }
48
+ node.setSelected(selected.filter((v) => v !== selection.value));
49
+ });
50
+ }
51
+ else {
52
+ editor.update(() => {
53
+ const node = $getNodeByKey(nodeKey);
54
+ if (!$isProblemSelectNode(node)) {
55
+ return;
56
+ }
57
+ node.setSelected([...selected, selection.value]);
58
+ });
59
+ }
60
+ }, fullWidth: true }, index)))] }));
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: selections.map((selection, index) => (_jsx(SelectBox, { index: index + 1, isAnswer: 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: () => {
71
+ setSettingOpen(true);
72
+ } })] })), settingOpen && (_jsx(SettingForm, Object.assign({}, settingFormProps, { onClose: () => setSettingOpen(false) })))] }));
73
+ }
74
+ const Alert = styled.div(({ theme }) => css `
75
+ display: flex;
76
+ gap: 4px;
77
+ margin-bottom: 12px;
78
+ color: ${theme.color.foreground.neutralBaseDisabled};
79
+ /* Default/Label/12px-Md */
80
+ font-family: ${theme.fontFamily.ui};
81
+ font-size: 12px;
82
+ font-style: normal;
83
+ font-weight: 500;
84
+ line-height: 16px; /* 133.333% */
85
+ `);
@@ -0,0 +1,14 @@
1
+ import { Selection } from "../ProblemSelectNode";
2
+ import { Control, UseFormWatch } from "react-hook-form";
3
+ export interface FormSelectionProps {
4
+ index: number;
5
+ control: Control<{
6
+ selections: Selection[];
7
+ }, any>;
8
+ watch: UseFormWatch<{
9
+ selections: Selection[];
10
+ }>;
11
+ rules?: any;
12
+ onDelete?: () => void;
13
+ }
14
+ export declare function FormSelection(props: FormSelectionProps): import("@emotion/react/jsx-runtime").JSX.Element;
@@ -0,0 +1,91 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, 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, ImageEditFillIcon, } from "../../../../../icons";
8
+ import SquareButton from "../../../../../components/SquareButton";
9
+ import Switch from "../../../../../components/Switch";
10
+ import { useState } from "react";
11
+ import { InsertImageDialog } from "./InsertImageDialog";
12
+ export function FormSelection(props) {
13
+ const { index, control, rules, watch, onDelete } = props;
14
+ const [imageOpen, setImageOpen] = useState(false);
15
+ const [inputFocused, setInputFocused] = useState(false);
16
+ return (_jsxs(Container, { children: [_jsx(Index, { children: index + 1 }), _jsxs("div", Object.assign({ css: css `
17
+ display: flex;
18
+ flex: 1;
19
+ flex-direction: column;
20
+ 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) => {
22
+ onChange(props);
23
+ }, deleteButton: value !== null }), value && value.src && (_jsx("img", { src: value.src, alt: value.altText, css: css `
24
+ height: auto;
25
+ // 이미지로 인해 좌우로 스크롤이 생기는 것을 방지
26
+ max-width: min(400px, 100%);
27
+ width: fit-content;
28
+ border-radius: 6px;
29
+ `, 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
30
+ ? "activeDanger"
31
+ : inputFocused
32
+ ? "activePrimary"
33
+ : "default", value: value, onChange: onChange, inputProps: {
34
+ onFocus: (_e) => {
35
+ setInputFocused(true);
36
+ },
37
+ onBlur: (_e) => {
38
+ setInputFocused(false);
39
+ },
40
+ }, placeholder: `${index + 1}번 선택지`, hintIcon: invalid ? _jsx(ErrorWarningFillIcon, {}) : undefined, hintText: error === null || error === void 0 ? void 0 : error.message, multiline: true, fullWidth: true, css: css `
41
+ flex: 1;
42
+ ` })) })] })), _jsxs("div", Object.assign({ css: css `
43
+ display: flex;
44
+ height: 36px;
45
+ gap: 8px;
46
+ align-items: center;
47
+ ` }, { children: [_jsx(SquareButton, { color: "icon", size: "xsmall", icon: watch(`selections.${index}.show.image`) ? (_jsx(ImageEditFillIcon, {})) : (_jsx(ImageAddFillIcon, {})), onClick: () => {
48
+ setImageOpen(true);
49
+ } }), _jsx(Controller, { name: `selections.${index}.isAnswer`, control: control, render: ({ field: { value, onChange } }) => (_jsxs(Answer, Object.assign({ onClick: () => {
50
+ onChange(!value);
51
+ } }, { children: ["\uC815\uB2F5", _jsx(Switch, { checked: value, size: "small" })] }))) }), 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
+ `);
78
+ const Answer = styled.div(({ theme }) => css `
79
+ display: flex;
80
+ align-items: center;
81
+ padding-right: 4px;
82
+ gap: 8px;
83
+ color: ${theme.color.foreground.neutralBase};
84
+ cursor: pointer;
85
+ /* Default/Label/14px-Md */
86
+ font-family: ${theme.fontFamily.ui};
87
+ font-size: 14px;
88
+ font-style: normal;
89
+ font-weight: 500;
90
+ line-height: 16px; /* 114.286% */
91
+ `);
@@ -0,0 +1,10 @@
1
+ import { ImageProps } from "../ProblemSelectNode";
2
+ export interface InsertImageDialogProps {
3
+ title: string;
4
+ open: boolean;
5
+ onClose: () => void;
6
+ updateImg: (props: ImageProps | null) => void;
7
+ /** 전달하면 "이미지 삭제하기" 버튼이 생깁니다. */
8
+ deleteButton?: boolean;
9
+ }
10
+ export declare function InsertImageDialog(props: InsertImageDialogProps): import("@emotion/react/jsx-runtime").JSX.Element;