@team-monolith/cds 1.9.3 → 1.9.5

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.
@@ -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 };
@@ -8,6 +8,9 @@ export interface LexicalEditorProps {
8
8
  * initialConfig에 전달되므로 마운트 된 이후 수정해도 반영되지 않음
9
9
  */
10
10
  editable?: boolean;
11
+ /** freeze. 문제 블록을 수정하지 못하도록 함
12
+ */
13
+ freezeProblemNode?: boolean;
11
14
  /** 외부에서 플러그인을 주입하는 경우 활용함 */
12
15
  children?: JSX.Element | string | (JSX.Element | string)[];
13
16
  }
@@ -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 { LexicalCustomConfigContext } from "./LexicalCustomConfigContext";
14
15
  import { ProblemSelectNode } from "./nodes/ProblemSelectNode";
15
16
  function validateValue(value) {
16
17
  var _a, _b;
@@ -29,7 +30,7 @@ function validateValue(value) {
29
30
  return true;
30
31
  }
31
32
  export function LexicalEditor(props) {
32
- const { className, value, onChange, editable = true, children } = props;
33
+ const { className, value, onChange, editable = true, freezeProblemNode, children, } = props;
33
34
  const theme = useTheme();
34
35
  const initialConfig = {
35
36
  namespace: "CodleLexicalEditor",
@@ -62,5 +63,5 @@ export function LexicalEditor(props) {
62
63
  editorState: validateValue(value) ? JSON.stringify(value) : undefined,
63
64
  editable: editable,
64
65
  };
65
- 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, onChange: onChange }), _jsx(_Fragment, { children: children })] })) })));
66
67
  }
@@ -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) => {
40
- editor.update(() => {
41
- const node = $getNodeByKey(nodeKey);
42
- if (!$isProblemInputNode(node)) {
43
- return;
44
- }
45
- node.setAnswer(answerInput);
46
- });
47
- },
48
- }, color: "default", fullWidth: true }));
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) => setAnswerInput(e.target.value),
42
+ // 한글 입력시에 onChange마다 update 일어나는 것을 방지하기 위해 입력 완료후 onBlur시에 update하는 전략을 사용합니다.
43
+ // 이를 위해 answerInput을 state로 관리합니다.
44
+ inputProps: {
45
+ onBlur: (_e) => {
46
+ editor.update(() => {
47
+ const node = $getNodeByKey(nodeKey);
48
+ if (!$isProblemInputNode(node)) {
49
+ return;
50
+ }
51
+ node.setAnswer(answerInput);
52
+ });
53
+ },
54
+ }, color: "default", fullWidth: true }));
55
+ }
49
56
  }
50
57
  // 교사 edit view
51
58
  return (_jsxs(_Fragment, { children: [_jsxs("div", Object.assign({ css: css `
@@ -15,6 +15,7 @@ export interface Selection {
15
15
  export interface ProblemSelectPayload {
16
16
  selections: Selection[];
17
17
  selected: string[];
18
+ hasMultipleAnswers?: boolean;
18
19
  key?: NodeKey;
19
20
  }
20
21
  export type SerializedProblemSelectNode = Spread<ProblemSelectPayload, SerializedLexicalNode>;
@@ -25,6 +26,7 @@ export type SerializedProblemSelectNode = Spread<ProblemSelectPayload, Serialize
25
26
  export declare class ProblemSelectNode extends DecoratorNode<ReactNode> {
26
27
  __selections: Selection[];
27
28
  __selected: string[];
29
+ __hasMultipleAnswers: boolean | undefined;
28
30
  isInline(): boolean;
29
31
  static getType(): string;
30
32
  getSelections(): Selection[];
@@ -32,12 +34,12 @@ export declare class ProblemSelectNode extends DecoratorNode<ReactNode> {
32
34
  setSolutions(selections: Selection[]): void;
33
35
  setSelected(selected: string[]): void;
34
36
  static clone(node: ProblemSelectNode): ProblemSelectNode;
35
- constructor(selections: Selection[], selected: string[], key?: NodeKey);
37
+ constructor(selections: Selection[], selected: string[], hasMultipleAnswers?: boolean, key?: NodeKey);
36
38
  createDOM(config: EditorConfig): HTMLElement;
37
39
  updateDOM(): boolean;
38
40
  static importJSON(serializedNode: SerializedProblemSelectNode): ProblemSelectNode;
39
41
  exportJSON(): SerializedProblemSelectNode;
40
42
  decorate(): ReactNode;
41
43
  }
42
- export declare function $createProblemSelectNode({ selections, selected, key, }: ProblemSelectPayload): ProblemSelectNode;
44
+ export declare function $createProblemSelectNode({ selections, selected, hasMultipleAnswers, key, }: ProblemSelectPayload): ProblemSelectNode;
43
45
  export declare function $isProblemSelectNode(node: LexicalNode | null | undefined): node is ProblemSelectNode;
@@ -28,12 +28,13 @@ export class ProblemSelectNode extends DecoratorNode {
28
28
  self.__selected = selected;
29
29
  }
30
30
  static clone(node) {
31
- return new ProblemSelectNode(node.__selections, node.__selected, node.__key);
31
+ return new ProblemSelectNode(node.__selections, node.__selected, node.__hasMultipleAnswers, node.__key);
32
32
  }
33
- constructor(selections, selected, key) {
33
+ constructor(selections, selected, hasMultipleAnswers, key) {
34
34
  super(key);
35
35
  this.__selections = selections;
36
36
  this.__selected = selected;
37
+ this.__hasMultipleAnswers = hasMultipleAnswers;
37
38
  }
38
39
  createDOM(config) {
39
40
  // Define the DOM element here
@@ -49,6 +50,7 @@ export class ProblemSelectNode extends DecoratorNode {
49
50
  key: serializedNode.key,
50
51
  selections: serializedNode.selections,
51
52
  selected: serializedNode.selected,
53
+ hasMultipleAnswers: serializedNode.hasMultipleAnswers,
52
54
  });
53
55
  return node;
54
56
  }
@@ -61,11 +63,11 @@ export class ProblemSelectNode extends DecoratorNode {
61
63
  };
62
64
  }
63
65
  decorate() {
64
- return (_jsx(SelectComponent, { selections: this.__selections, selected: this.__selected, nodeKey: this.getKey() }));
66
+ return (_jsx(SelectComponent, { selections: this.__selections, selected: this.__selected, hasMultipleAnswers: this.__hasMultipleAnswers, nodeKey: this.getKey() }));
65
67
  }
66
68
  }
67
- export function $createProblemSelectNode({ selections, selected, key, }) {
68
- return $applyNodeReplacement(new ProblemSelectNode(selections, selected, key));
69
+ export function $createProblemSelectNode({ selections, selected, hasMultipleAnswers, key, }) {
70
+ return $applyNodeReplacement(new ProblemSelectNode(selections, selected, hasMultipleAnswers, key));
69
71
  }
70
72
  export function $isProblemSelectNode(node) {
71
73
  return node instanceof ProblemSelectNode;
@@ -5,7 +5,7 @@ export interface SelectBoxProps {
5
5
  isAnswer?: boolean;
6
6
  image?: ImageProps;
7
7
  text: string;
8
- onClick: () => void;
8
+ onClick?: () => void;
9
9
  fullWidth?: boolean;
10
10
  }
11
11
  export default function SelectBox(props: SelectBoxProps): import("@emotion/react/jsx-runtime").JSX.Element;
@@ -4,5 +4,6 @@ import { Selection } from "./ProblemSelectNode";
4
4
  export declare function SelectComponent(props: {
5
5
  selections: Selection[];
6
6
  selected: string[];
7
+ hasMultipleAnswers?: boolean;
7
8
  nodeKey: NodeKey;
8
9
  }): import("@emotion/react/jsx-runtime").JSX.Element;
@@ -1,18 +1,20 @@
1
- import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "@emotion/react/jsx-runtime";
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
4
  import { $isProblemSelectNode, } from "./ProblemSelectNode";
5
5
  import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext";
6
- import { useState } from "react";
6
+ import { useContext, useState } from "react";
7
7
  import useLexicalEditable from "@lexical/react/useLexicalEditable";
8
8
  import { useFieldArray, useForm } from "react-hook-form";
9
9
  import SelectBox from "./SelectBox";
10
10
  import { css } from "@emotion/react";
11
11
  import SquareButton from "../../../../components/SquareButton";
12
- import { Settings3FillIcon } from "../../../../icons";
12
+ import { AlarmWarningFillIcon, Settings3FillIcon } from "../../../../icons";
13
13
  import SettingForm from "./SettingForm";
14
+ import styled from "@emotion/styled";
15
+ import { LexicalCustomConfigContext } from "../../LexicalCustomConfigContext";
14
16
  export function SelectComponent(props) {
15
- const { selections, selected, nodeKey } = props;
17
+ const { selections, selected, hasMultipleAnswers, nodeKey } = props;
16
18
  const [editor] = useLexicalComposerContext();
17
19
  const [settingOpen, setSettingOpen] = useState(false);
18
20
  const isEditable = useLexicalEditable();
@@ -28,34 +30,44 @@ export function SelectComponent(props) {
28
30
  name: "selections",
29
31
  keyName: "uid",
30
32
  });
33
+ const lexicalCustomConfig = useContext(LexicalCustomConfigContext);
31
34
  // 학생 view
32
35
  if (!isEditable) {
33
- return (_jsx(_Fragment, { children: fields.map((field, index) => (_jsx(SelectBox, { index: index + 1, isSelected: selected.includes(field.value), image: field.show.image, text: field.show.text, onClick: () => {
34
- const isSelected = selected.includes(field.value);
35
- if (isSelected) {
36
- editor.update(() => {
37
- const node = $getNodeByKey(nodeKey);
38
- if (!$isProblemSelectNode(node)) {
39
- return;
40
- }
41
- const newSelected = [...selected];
42
- const index = newSelected.indexOf(field.value);
43
- newSelected.splice(index, 1);
44
- node.setSelected(newSelected);
45
- });
46
- }
47
- else {
48
- editor.update(() => {
49
- const node = $getNodeByKey(nodeKey);
50
- if (!$isProblemSelectNode(node)) {
51
- return;
52
- }
53
- const newSelected = [...selected];
54
- newSelected.push(field.value);
55
- node.setSelected(newSelected);
56
- });
57
- }
58
- }, fullWidth: true }, index))) }));
36
+ if (lexicalCustomConfig.freezeProblemNode) {
37
+ return (_jsxs(_Fragment, { children: [hasMultipleAnswers && (_jsxs(Alert, { children: [_jsx(AlarmWarningFillIcon, { css: css `
38
+ width: 14px;
39
+ height: 14px;
40
+ ` }), "\uC9C8\uBB38\uC5D0 \uD574\uB2F9\uD558\uB294 \uB2F5\uC744 \uBAA8\uB450 \uACE0\uB974\uB294 \uBB38\uC81C\uC785\uB2C8\uB2E4."] })), fields.map((field, index) => (_jsx(SelectBox, { index: index + 1, isSelected: selected.includes(field.value), image: field.show.image, text: field.show.text, fullWidth: true }, index)))] }));
41
+ }
42
+ return (_jsxs(_Fragment, { children: [hasMultipleAnswers && (_jsxs(Alert, { children: [_jsx(AlarmWarningFillIcon, { css: css `
43
+ width: 14px;
44
+ height: 14px;
45
+ ` }), "\uC9C8\uBB38\uC5D0 \uD574\uB2F9\uD558\uB294 \uB2F5\uC744 \uBAA8\uB450 \uACE0\uB974\uB294 \uBB38\uC81C\uC785\uB2C8\uB2E4."] })), fields.map((field, index) => (_jsx(SelectBox, { index: index + 1, isSelected: selected.includes(field.value), image: field.show.image, text: field.show.text, onClick: () => {
46
+ const isSelected = selected.includes(field.value);
47
+ if (isSelected) {
48
+ editor.update(() => {
49
+ const node = $getNodeByKey(nodeKey);
50
+ if (!$isProblemSelectNode(node)) {
51
+ return;
52
+ }
53
+ const newSelected = [...selected];
54
+ const index = newSelected.indexOf(field.value);
55
+ newSelected.splice(index, 1);
56
+ node.setSelected(newSelected);
57
+ });
58
+ }
59
+ else {
60
+ editor.update(() => {
61
+ const node = $getNodeByKey(nodeKey);
62
+ if (!$isProblemSelectNode(node)) {
63
+ return;
64
+ }
65
+ const newSelected = [...selected];
66
+ newSelected.push(field.value);
67
+ node.setSelected(newSelected);
68
+ });
69
+ }
70
+ }, fullWidth: true }, index)))] }));
59
71
  }
60
72
  // 교사 edit view
61
73
  return (_jsxs(_Fragment, { children: [_jsxs("div", Object.assign({ css: css `
@@ -69,3 +81,15 @@ export function SelectComponent(props) {
69
81
  setSettingOpen(true);
70
82
  } })] })), settingOpen && (_jsx(SettingForm, { control: control, handleSubmit: handleSubmit, fields: fields, append: append, remove: remove, update: update, nodeKey: nodeKey, onClose: () => setSettingOpen(false) }))] }));
71
83
  }
84
+ const Alert = styled.div(({ theme }) => css `
85
+ display: flex;
86
+ gap: 4px;
87
+ margin-bottom: 12px;
88
+ color: ${theme.color.foreground.neutralBaseDisabled};
89
+ /* Default/Label/12px-Md */
90
+ font-family: ${theme.fontFamily.ui};
91
+ font-size: 12px;
92
+ font-style: normal;
93
+ font-weight: 500;
94
+ line-height: 16px; /* 133.333% */
95
+ `);
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ */
8
+ import { ReactElement } from "react";
9
+ export default function ActionsPlugin(): ReactElement;
@@ -0,0 +1,24 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ /**
3
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
4
+ *
5
+ * This source code is licensed under the MIT license found in the
6
+ * LICENSE file in the root directory of this source tree.
7
+ *
8
+ */
9
+ import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext";
10
+ import { mergeRegister } from "@lexical/utils";
11
+ import { useEffect, useState } from "react";
12
+ import { Button, LockFillIcon, LockUnlockFillIcon } from "../../../..";
13
+ export default function ActionsPlugin() {
14
+ const [editor] = useLexicalComposerContext();
15
+ const [isEditable, setIsEditable] = useState(() => editor.isEditable());
16
+ useEffect(() => {
17
+ return mergeRegister(editor.registerEditableListener((editable) => {
18
+ setIsEditable(editable);
19
+ }));
20
+ }, [editor]);
21
+ return (_jsx(Button, { color: "primary", size: "small", startIcon: isEditable ? _jsx(LockFillIcon, {}) : _jsx(LockUnlockFillIcon, {}), onClick: () => {
22
+ editor.setEditable(!editor.isEditable());
23
+ }, title: "Read-Only Mode", label: `${!isEditable ? "수정 모드" : "읽기 모드"}로 변경`, "aria-label": `${!isEditable ? "Unlock" : "Lock"} read-only mode` }));
24
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@team-monolith/cds",
3
- "version": "1.9.3",
3
+ "version": "1.9.5",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "sideEffects": false,