@team-monolith/cds 1.40.0 → 1.42.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 (44) hide show
  1. package/@types/emotion.d.ts +38 -16
  2. package/dist/CodleDesignSystemProvider.d.ts +38 -16
  3. package/dist/CodleDesignSystemProvider.js +67 -45
  4. package/dist/components/Banner.d.ts +1 -1
  5. package/dist/components/Banner.js +12 -12
  6. package/dist/components/Button.js +3 -3
  7. package/dist/components/DecoratedNumber/Default/DefaultNumberLarge.d.ts +7 -0
  8. package/dist/components/DecoratedNumber/Default/DefaultNumberLarge.js +29 -0
  9. package/dist/components/DecoratedNumber/Default/DefaultNumberSmall.d.ts +6 -0
  10. package/dist/components/DecoratedNumber/Default/DefaultNumberSmall.js +27 -0
  11. package/dist/components/DecoratedNumber/Default/index.d.ts +2 -0
  12. package/dist/components/DecoratedNumber/Default/index.js +2 -0
  13. package/dist/components/DecoratedNumber/index.d.ts +1 -0
  14. package/dist/components/DecoratedNumber/index.js +1 -0
  15. package/dist/components/SquareButton.js +3 -3
  16. package/dist/components/Tag.js +18 -18
  17. package/dist/foundation/color.d.ts +7 -1
  18. package/dist/foundation/color.js +7 -1
  19. package/dist/index.d.ts +1 -0
  20. package/dist/index.js +1 -0
  21. package/dist/patterns/LexicalEditor/nodes/ProblemInputNode/InputComponent.d.ts +1 -0
  22. package/dist/patterns/LexicalEditor/nodes/ProblemInputNode/InputComponent.js +3 -3
  23. package/dist/patterns/LexicalEditor/nodes/ProblemInputNode/ProblemInputNode.d.ts +7 -2
  24. package/dist/patterns/LexicalEditor/nodes/ProblemInputNode/ProblemInputNode.js +8 -5
  25. package/dist/patterns/LexicalEditor/nodes/ProblemInputNode/SegmentedInput.d.ts +1 -0
  26. package/dist/patterns/LexicalEditor/nodes/ProblemInputNode/SegmentedInput.js +13 -3
  27. package/dist/patterns/LexicalEditor/nodes/ProblemInputNode/TextInput.d.ts +1 -0
  28. package/dist/patterns/LexicalEditor/nodes/ProblemInputNode/TextInput.js +20 -3
  29. package/dist/patterns/LexicalEditor/nodes/ProblemSelectNode/ProblemSelectNode.d.ts +2 -1
  30. package/dist/patterns/LexicalEditor/nodes/ProblemSelectNode/SelectBox/SelectBoxComponent.d.ts +19 -0
  31. package/dist/patterns/LexicalEditor/nodes/ProblemSelectNode/SelectBox/SelectBoxComponent.js +92 -0
  32. package/dist/patterns/LexicalEditor/nodes/ProblemSelectNode/SelectBox/SelectBoxEdit.d.ts +9 -0
  33. package/dist/patterns/LexicalEditor/nodes/ProblemSelectNode/SelectBox/SelectBoxEdit.js +13 -0
  34. package/dist/patterns/LexicalEditor/nodes/ProblemSelectNode/SelectBox/SelectBoxView.d.ts +10 -0
  35. package/dist/patterns/LexicalEditor/nodes/ProblemSelectNode/SelectBox/SelectBoxView.js +42 -0
  36. package/dist/patterns/LexicalEditor/nodes/ProblemSelectNode/SelectBox/index.d.ts +2 -0
  37. package/dist/patterns/LexicalEditor/nodes/ProblemSelectNode/SelectBox/index.js +2 -0
  38. package/dist/patterns/LexicalEditor/nodes/ProblemSelectNode/SelectComponent.js +4 -4
  39. package/dist/patterns/LexicalEditor/nodes/ProblemSelectNode/SettingForm/FormSelection.js +1 -1
  40. package/dist/patterns/LexicalEditor/plugins/ComponentAdderPlugin/useContextMenuOptions.js +8 -8
  41. package/dist/patterns/LexicalEditor/theme.js +8 -8
  42. package/package.json +1 -1
  43. package/dist/patterns/LexicalEditor/nodes/ProblemSelectNode/SelectBox.d.ts +0 -11
  44. package/dist/patterns/LexicalEditor/nodes/ProblemSelectNode/SelectBox.js +0 -76
@@ -25,39 +25,39 @@ const COLOR_TO_STYLE = (theme, color) => ({
25
25
  `,
26
26
  red: css `
27
27
  box-sizing: border-box;
28
- background: ${theme.color.container.dangerContainer};
29
- border: 1px solid ${theme.color.container.dangerOnContainer};
30
- color: ${theme.color.container.dangerOnContainer};
28
+ background: ${theme.color.container.redContainer};
29
+ border: 1px solid ${theme.color.container.redOnContainer};
30
+ color: ${theme.color.container.redOnContainer};
31
31
  `,
32
32
  teal: css `
33
33
  box-sizing: border-box;
34
- background: ${theme.color.container.infoContainer};
35
- border: 1px solid ${theme.color.container.infoOnContainer};
36
- color: ${theme.color.container.infoOnContainer};
34
+ background: ${theme.color.container.tealContainer};
35
+ border: 1px solid ${theme.color.container.tealOnContainer};
36
+ color: ${theme.color.container.tealOnContainer};
37
37
  `,
38
38
  orange: css `
39
39
  box-sizing: border-box;
40
- background: ${theme.color.container.secondaryContainer};
41
- border: 1px solid ${theme.color.container.secondaryOnContainer};
42
- color: ${theme.color.container.secondaryOnContainer};
40
+ background: ${theme.color.container.orangeContainer};
41
+ border: 1px solid ${theme.color.container.orangeOnContainer};
42
+ color: ${theme.color.container.orangeOnContainer};
43
43
  `,
44
44
  blue: css `
45
45
  box-sizing: border-box;
46
- background: ${theme.color.container.primaryContainer};
47
- border: 1px solid ${theme.color.container.primaryOnContainer};
48
- color: ${theme.color.container.primaryOnContainer};
46
+ background: ${theme.color.container.blueContainer};
47
+ border: 1px solid ${theme.color.container.blueOnContainer};
48
+ color: ${theme.color.container.blueOnContainer};
49
49
  `,
50
50
  green: css `
51
51
  box-sizing: border-box;
52
- background: ${theme.color.container.successContainer};
53
- border: 1px solid ${theme.color.container.successOnContainer};
54
- color: ${theme.color.container.successOnContainer};
52
+ background: ${theme.color.container.greenContainer};
53
+ border: 1px solid ${theme.color.container.greenOnContainer};
54
+ color: ${theme.color.container.greenOnContainer};
55
55
  `,
56
56
  yellow: css `
57
57
  box-sizing: border-box;
58
- background: ${theme.color.container.warningContainer};
59
- border: 1px solid ${theme.color.container.warningOnContainer};
60
- color: ${theme.color.container.warningOnContainer};
58
+ background: ${theme.color.container.yellowContainer};
59
+ border: 1px solid ${theme.color.container.yellowOnContainer};
60
+ color: ${theme.color.container.yellowOnContainer};
61
61
  `,
62
62
  black: css `
63
63
  background: ${theme.color.container.obsidianContainer};
@@ -1,6 +1,6 @@
1
1
  declare const _default: {
2
2
  blue10: string;
3
- priamry09: string;
3
+ blue09: string;
4
4
  blue08: string;
5
5
  blue07: string;
6
6
  blue06: string;
@@ -71,5 +71,11 @@ declare const _default: {
71
71
  grey02: string;
72
72
  grey01: string;
73
73
  white: string;
74
+ alpha065: string;
75
+ alpha030: string;
76
+ alpha015: string;
77
+ alphaf50: string;
78
+ alphaf20: string;
79
+ alphaf10: string;
74
80
  };
75
81
  export default _default;
@@ -1,7 +1,7 @@
1
1
  // eslint-disable-next-line import/no-anonymous-default-export
2
2
  export default {
3
3
  blue10: "#1C3D73",
4
- priamry09: "#234E91",
4
+ blue09: "#234E91",
5
5
  blue08: "#2C61B5",
6
6
  blue07: "#3574D9",
7
7
  blue06: "#3E88FF",
@@ -72,4 +72,10 @@ export default {
72
72
  grey02: "#F6F6F6",
73
73
  grey01: "#FDFDFD",
74
74
  white: "#FFFFFF",
75
+ alpha065: "#000000A6",
76
+ alpha030: "#0000004D",
77
+ alpha015: "#00000026",
78
+ alphaf50: "#FFFFFF80",
79
+ alphaf20: "#FFFFFF33",
80
+ alphaf10: "#FFFFFF1A",
75
81
  };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  /// <reference path="../@types/emotion.d.ts" />
2
2
  export * from "./components/AlertDialog";
3
+ export * from "./components/DecoratedNumber";
3
4
  export { default as Banner } from "./components/Banner";
4
5
  export * from "./components/Banner";
5
6
  export { default as Button } from "./components/Button";
package/dist/index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  /// <reference path="../../@types/emotion.d.ts" />
2
2
  export * from "./components/AlertDialog";
3
+ export * from "./components/DecoratedNumber";
3
4
  export { default as Banner } from "./components/Banner";
4
5
  export * from "./components/Banner";
5
6
  export { default as Button } from "./components/Button";
@@ -5,5 +5,6 @@ export declare function InputComponent(props: {
5
5
  showCharacterNumber: boolean;
6
6
  placeholder: string;
7
7
  answer: string;
8
+ isCorrect?: boolean;
8
9
  nodeKey: NodeKey;
9
10
  }): import("@emotion/react/jsx-runtime").JSX.Element;
@@ -25,7 +25,7 @@ import { LexicalCustomConfigContext } from "../../LexicalCustomConfigContext";
25
25
  import TextInput from "./TextInput";
26
26
  import SegmentedInput from "./SegmentedInput";
27
27
  export function InputComponent(props) {
28
- const { answer } = props, settingFormProps = __rest(props, ["answer"]);
28
+ const { answer, isCorrect } = props, settingFormProps = __rest(props, ["answer", "isCorrect"]);
29
29
  const { solutions, showCharacterNumber, placeholder, nodeKey } = settingFormProps;
30
30
  const [editor] = useLexicalComposerContext();
31
31
  const [settingOpen, setSettingOpen] = useState(false);
@@ -51,9 +51,9 @@ export function InputComponent(props) {
51
51
  // 학생 view
52
52
  if (!isEditable) {
53
53
  if (showCharacterNumber) {
54
- return (_jsx(SegmentedInput, { readOnly: lexicalCustomConfig.freezeProblemNode, answerFormat: answerFormat, placeholder: placeholder || "여기에 입력하세요.", value: answerInput, onChange: handleChange }));
54
+ return (_jsx(SegmentedInput, { readOnly: lexicalCustomConfig.freezeProblemNode, isCorrect: isCorrect, answerFormat: answerFormat, placeholder: placeholder || "여기에 입력하세요.", value: answerInput, onChange: handleChange }));
55
55
  }
56
- return (_jsx(TextInput, { readOnly: lexicalCustomConfig.freezeProblemNode, size: "small", color: "default", placeholder: placeholder || "여기에 입력하세요.", value: answerInput, onChange: (e) => {
56
+ return (_jsx(TextInput, { readOnly: lexicalCustomConfig.freezeProblemNode, isCorrect: isCorrect, size: "small", color: "default", placeholder: placeholder || "여기에 입력하세요.", value: answerInput, onChange: (e) => {
57
57
  handleChange(e.target.value);
58
58
  }, fullWidth: true }));
59
59
  }
@@ -7,8 +7,12 @@ export interface Solution {
7
7
  export interface ProblemInputPayload {
8
8
  showCharacterNumber: boolean;
9
9
  placeholder: string;
10
+ /** solution은 채점하기 전의 학생에게는 검열되어 노출됩니다.
11
+ * (ex: "코들 입니다" => "** ***") */
10
12
  solutions: Solution[];
11
13
  answer: string;
14
+ /** FE에서만 사용하는 값. 문제의 정오답 여부를 주입하여 스타일을 변경합니다. */
15
+ isCorrect?: boolean;
12
16
  key?: NodeKey;
13
17
  }
14
18
  export type SerializedProblemInputNode = Spread<ProblemInputPayload, SerializedLexicalNode>;
@@ -21,6 +25,7 @@ export declare class ProblemInputNode extends DecoratorNode<ReactNode> {
21
25
  __showCharacterNumber: boolean;
22
26
  __placeholder: string;
23
27
  __answer: string;
28
+ __isCorrect?: boolean;
24
29
  isInline(): boolean;
25
30
  static getType(): string;
26
31
  getSolutions(): Solution[];
@@ -32,12 +37,12 @@ export declare class ProblemInputNode extends DecoratorNode<ReactNode> {
32
37
  setPlaceholder(placeholder: string): void;
33
38
  setAnswer(answer: string): void;
34
39
  static clone(node: ProblemInputNode): ProblemInputNode;
35
- constructor(showCharacterNumber: boolean, placeholder: string, solutions: Solution[], answer: string, key?: NodeKey);
40
+ constructor(showCharacterNumber: boolean, placeholder: string, solutions: Solution[], answer: string, isCorrect?: boolean, key?: NodeKey);
36
41
  createDOM(config: EditorConfig): HTMLElement;
37
42
  updateDOM(): boolean;
38
43
  static importJSON(serializedNode: SerializedProblemInputNode): ProblemInputNode;
39
44
  exportJSON(): SerializedProblemInputNode;
40
45
  decorate(): ReactNode;
41
46
  }
42
- export declare function $createProblemInputNode({ showCharacterNumber, placeholder, solutions, answer, key, }: ProblemInputPayload): ProblemInputNode;
47
+ export declare function $createProblemInputNode({ showCharacterNumber, placeholder, solutions, answer, isCorrect, key, }: ProblemInputPayload): ProblemInputNode;
43
48
  export declare function $isProblemInputNode(node: LexicalNode | null | undefined): node is ProblemInputNode;
@@ -42,14 +42,15 @@ export class ProblemInputNode extends DecoratorNode {
42
42
  self.__answer = answer;
43
43
  }
44
44
  static clone(node) {
45
- return new ProblemInputNode(node.__showCharacterNumber, node.__placeholder, node.__solutions, node.__answer, node.__key);
45
+ return new ProblemInputNode(node.__showCharacterNumber, node.__placeholder, node.__solutions, node.__answer, node.__isCorrect, node.__key);
46
46
  }
47
- constructor(showCharacterNumber, placeholder, solutions, answer, key) {
47
+ constructor(showCharacterNumber, placeholder, solutions, answer, isCorrect, key) {
48
48
  super(key);
49
49
  this.__showCharacterNumber = showCharacterNumber;
50
50
  this.__placeholder = placeholder;
51
51
  this.__solutions = solutions;
52
52
  this.__answer = answer;
53
+ this.__isCorrect = isCorrect;
53
54
  }
54
55
  createDOM(config) {
55
56
  // Define the DOM element here
@@ -67,6 +68,7 @@ export class ProblemInputNode extends DecoratorNode {
67
68
  showCharacterNumber: serializedNode.showCharacterNumber,
68
69
  placeholder: serializedNode.placeholder,
69
70
  answer: serializedNode.answer,
71
+ isCorrect: serializedNode.isCorrect,
70
72
  });
71
73
  return node;
72
74
  }
@@ -77,15 +79,16 @@ export class ProblemInputNode extends DecoratorNode {
77
79
  showCharacterNumber: this.__showCharacterNumber,
78
80
  placeholder: this.__placeholder,
79
81
  solutions: this.__solutions,
82
+ isCorrect: this.__isCorrect,
80
83
  answer: this.__answer,
81
84
  };
82
85
  }
83
86
  decorate() {
84
- return (_jsx(InputComponent, { solutions: this.__solutions, showCharacterNumber: this.__showCharacterNumber, placeholder: this.__placeholder, answer: this.__answer, nodeKey: this.getKey() }));
87
+ return (_jsx(InputComponent, { solutions: this.__solutions, showCharacterNumber: this.__showCharacterNumber, placeholder: this.__placeholder, answer: this.__answer, isCorrect: this.__isCorrect, nodeKey: this.getKey() }));
85
88
  }
86
89
  }
87
- export function $createProblemInputNode({ showCharacterNumber, placeholder, solutions, answer, key, }) {
88
- return $applyNodeReplacement(new ProblemInputNode(showCharacterNumber, placeholder, solutions, answer, key));
90
+ export function $createProblemInputNode({ showCharacterNumber, placeholder, solutions, answer, isCorrect, key, }) {
91
+ return $applyNodeReplacement(new ProblemInputNode(showCharacterNumber, placeholder, solutions, answer, isCorrect, key));
89
92
  }
90
93
  export function $isProblemInputNode(node) {
91
94
  return node instanceof ProblemInputNode;
@@ -1,5 +1,6 @@
1
1
  export interface SegmentedInputProps {
2
2
  readOnly: boolean;
3
+ isCorrect?: boolean;
3
4
  answerFormat: string;
4
5
  placeholder: string;
5
6
  value: string;
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "@emotion/react/jsx-runtime";
2
2
  /** @jsxImportSource @emotion/react */
3
- import { css } from "@emotion/react";
3
+ import { css, useTheme } from "@emotion/react";
4
4
  import styled from "@emotion/styled";
5
5
  import { InputBase } from "../../../../components/InputBase";
6
6
  import { useRef, useState } from "react";
@@ -9,7 +9,8 @@ import { useRef, useState } from "react";
9
9
  * 그리고 그 value는 split되어 segmented input에 표시됩니다.
10
10
  */
11
11
  export default function SegmentedInput(props) {
12
- const { readOnly, answerFormat, placeholder, value, onChange } = props;
12
+ const { readOnly, isCorrect, answerFormat, placeholder, value, onChange } = props;
13
+ const theme = useTheme();
13
14
  // "** ***" => ["*", "*", " ", "*", "*", "*"]
14
15
  const splitedFormat = answerFormat.split("");
15
16
  // ["*", "*", " ", "*", "*", "*", " ", "*", "*"] => [2, 5]
@@ -58,7 +59,13 @@ export default function SegmentedInput(props) {
58
59
  display: flex;
59
60
  align-items: center;
60
61
  gap: 4px;
61
- ` }, { children: despacedFormat.map((_format, i) => (_jsx(SquareInput, { leftSpaced: leftSpacedIndexs.includes(i), size: "small", color: focusedIndex === i ? "activePrimary" : "default", inputProps: {
62
+ ` }, { children: despacedFormat.map((_format, i) => (_jsx(SquareInput, { leftSpaced: leftSpacedIndexs.includes(i), size: "small", color: isCorrect === true
63
+ ? "activeSuccess"
64
+ : isCorrect === false
65
+ ? "activeDanger"
66
+ : focusedIndex === i
67
+ ? "activePrimary"
68
+ : "default", inputProps: {
62
69
  readOnly,
63
70
  onFocus: () => {
64
71
  var _a, _b;
@@ -68,6 +75,9 @@ export default function SegmentedInput(props) {
68
75
  (_a = hiddenRef.current) === null || _a === void 0 ? void 0 : _a.focus();
69
76
  (_b = hiddenRef.current) === null || _b === void 0 ? void 0 : _b.setSelectionRange(i, i);
70
77
  },
78
+ color: isCorrect === false
79
+ ? theme.color.foreground.danger
80
+ : undefined,
71
81
  }, value: splitedValues[i] || "", onChange: () => { } }, i))) })), _jsx(InputMarker, {})] }), _jsx(Text, { children: placeholder })] }));
72
82
  }
73
83
  const Container = styled.div `
@@ -1,6 +1,7 @@
1
1
  import { InputProps } from "../../../../components/Input";
2
2
  export type TextInputProps = InputProps & {
3
3
  readOnly: boolean;
4
+ isCorrect?: boolean;
4
5
  };
5
6
  /** 읽기모드/편집모드가 가능한 input 컴포넌트 */
6
7
  export default function TextInput(props: TextInputProps): import("react/jsx-runtime").JSX.Element;
@@ -12,16 +12,33 @@ var __rest = (this && this.__rest) || function (s, e) {
12
12
  import { jsx as _jsx } from "react/jsx-runtime";
13
13
  import { useState } from "react";
14
14
  import Input from "../../../../components/Input";
15
+ import { css as cssToClassName } from "@emotion/css";
16
+ import { useTheme } from "@emotion/react";
15
17
  /** 읽기모드/편집모드가 가능한 input 컴포넌트 */
16
18
  export default function TextInput(props) {
17
- const { readOnly } = props, InputOriginalProps = __rest(props, ["readOnly"]);
19
+ const { readOnly, isCorrect } = props, inputOriginalProps = __rest(props, ["readOnly", "isCorrect"]);
18
20
  const [focus, setFocus] = useState(false);
21
+ const theme = useTheme();
19
22
  if (readOnly) {
20
- return (_jsx(Input, Object.assign({}, InputOriginalProps, { inputProps: {
23
+ return (_jsx(Input, Object.assign({}, inputOriginalProps, { color: isCorrect === true
24
+ ? "activeSuccess"
25
+ : isCorrect === false
26
+ ? "activeDanger"
27
+ : inputOriginalProps.color, inputProps: {
21
28
  readOnly: true,
29
+ className: isCorrect === false
30
+ ? cssToClassName `
31
+ color: ${theme.color.foreground.danger};
32
+ &::placeholder {
33
+ // inputComponent의 placeholder 스타일을 덮어쓰기 위해 부득이하게 !important 사용
34
+ // TODO: static classname을 사용하여 !important 제거
35
+ color: ${theme.color.foreground.dangerDisabled} !important;
36
+ }
37
+ `
38
+ : undefined,
22
39
  } })));
23
40
  }
24
- return (_jsx(Input, Object.assign({}, InputOriginalProps, { color: focus ? "activePrimary" : "default", inputProps: {
41
+ return (_jsx(Input, Object.assign({}, inputOriginalProps, { color: focus ? "activePrimary" : inputOriginalProps.color, inputProps: {
25
42
  onFocus: () => setFocus(true),
26
43
  onBlur: () => setFocus(false),
27
44
  } })));
@@ -5,7 +5,8 @@ export interface ImageProps {
5
5
  altText: string;
6
6
  }
7
7
  export interface Selection {
8
- isAnswer: boolean;
8
+ /** 해당 선택지의 정답 유무는 채점하기 전의 학생에게는 노출하지 않습니다. */
9
+ isAnswer?: boolean;
9
10
  show: {
10
11
  image: ImageProps | null;
11
12
  text: string;
@@ -0,0 +1,19 @@
1
+ import React from "react";
2
+ import { ImageProps } from "../ProblemSelectNode";
3
+ export type SelectBoxType = "primary" | "success" | "danger";
4
+ /** 비지니스 로직과 무관한 SelectBox를 그리는 공통 컴포넌트입니다. */
5
+ export declare function SelectBoxComponent(props: {
6
+ className?: string;
7
+ indexClassName?: string;
8
+ contentClassName?: string;
9
+ /** SelectBox의 스타일 타입. 전달하지 않으면 기본 스타일이 적용됩니다. */
10
+ type?: SelectBoxType;
11
+ /** 문제 번호 박스 내부를 그립니다. */
12
+ index: React.ReactNode;
13
+ image?: ImageProps | null;
14
+ text: React.ReactNode;
15
+ /** SelectBox content 우측에 표시될 부분 */
16
+ endIcon?: React.ReactNode;
17
+ /** 박스를 클릭할 때 실행될 콜백. 존재하지 않으면 cursor: pointer가 적용되지 않습니다. */
18
+ onClick?: () => void;
19
+ }): import("@emotion/react/jsx-runtime").JSX.Element;
@@ -0,0 +1,92 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "@emotion/react/jsx-runtime";
2
+ /** @jsxImportSource @emotion/react */
3
+ import { css, useTheme } from "@emotion/react";
4
+ import styled from "@emotion/styled";
5
+ const TYPE_TO_CONTAINER_STYLE = (theme, type) => ({
6
+ primary: css `
7
+ background: ${theme.color.container.blueContainer};
8
+ border: 1px solid ${theme.color.foreground.primary};
9
+ color: ${theme.color.container.blueOnContainer};
10
+ `,
11
+ success: css `
12
+ background: ${theme.color.container.greenContainer};
13
+ border: 1px solid ${theme.color.background.successActive};
14
+ color: ${theme.color.container.greenOnContainer};
15
+ `,
16
+ danger: css `
17
+ background: ${theme.color.container.redContainer};
18
+ border: 1px solid ${theme.color.background.dangerActive};
19
+ color: ${theme.color.container.redOnContainer};
20
+ `,
21
+ })[type];
22
+ const TYPE_TO_INDEX_STYLE = (theme, type) => ({
23
+ primary: css `
24
+ background: ${theme.color.background.primary};
25
+ border: 1px solid ${theme.color.background.primary};
26
+ color: ${theme.color.foreground.neutralAlt};
27
+ `,
28
+ success: css `
29
+ background: ${theme.color.background.successActive};
30
+ border: 1px solid ${theme.color.background.successActive};
31
+ color: ${theme.color.foreground.neutralAlt};
32
+ `,
33
+ danger: css `
34
+ background: ${theme.color.background.dangerActive};
35
+ border: 1px solid ${theme.color.background.dangerActive};
36
+ color: ${theme.color.foreground.neutralAlt};
37
+ `,
38
+ })[type];
39
+ /** 비지니스 로직과 무관한 SelectBox를 그리는 공통 컴포넌트입니다. */
40
+ export function SelectBoxComponent(props) {
41
+ const { className, indexClassName, contentClassName, type, index, image, text, endIcon, onClick, } = props;
42
+ const theme = useTheme();
43
+ return (_jsxs(Container, Object.assign({ className: className, css: type ? TYPE_TO_CONTAINER_STYLE(theme, type) : undefined, onClick: onClick }, { children: [_jsx(Index, Object.assign({ className: indexClassName, css: type ? TYPE_TO_INDEX_STYLE(theme, type) : undefined }, { children: index })), _jsxs(Content, Object.assign({ className: contentClassName }, { children: [image && (_jsx("img", { src: image.src, alt: image.altText, css: css `
44
+ height: auto;
45
+ // 이미지로 인해 좌우로 스크롤이 생기는 것을 방지
46
+ max-width: 100%;
47
+ width: fit-content;
48
+ border-radius: 6px;
49
+ ` })), text] })), endIcon] })));
50
+ }
51
+ const Container = styled.div(({ theme, onClick }) => css `
52
+ ${onClick && "cursor: pointer;"}
53
+ display: flex;
54
+ box-sizing: border-box;
55
+ width: 400px;
56
+ padding: 12px;
57
+ gap: 12px;
58
+ border-radius: 8px;
59
+ background: ${theme.color.background.neutralAlt};
60
+ border: 1px solid transparent;
61
+ color: ${theme.color.foreground.neutralBase};
62
+ `);
63
+ const Index = styled.div(({ theme }) => css `
64
+ display: flex;
65
+ box-sizing: border-box;
66
+ width: 20px;
67
+ height: 20px;
68
+ padding: 4px;
69
+
70
+ user-select: none; /* Standard syntax */
71
+
72
+ justify-content: center;
73
+ align-items: center;
74
+ border-radius: 4px;
75
+ border: 1px solid ${theme.color.background.neutralAltActive};
76
+ background: ${theme.color.background.neutralBase};
77
+ color: ${theme.color.foreground.neutralBaseDisabled};
78
+ font-family: ${theme.fontFamily.ui};
79
+ font-size: 14px;
80
+ font-weight: 800;
81
+ line-height: 16px;
82
+ `);
83
+ const Content = styled.div(({ theme }) => css `
84
+ display: flex;
85
+ flex-direction: column;
86
+ gap: 12px;
87
+ flex: 1;
88
+ font-family: ${theme.fontFamily.ui};
89
+ font-size: 16px;
90
+ font-weight: 400;
91
+ line-height: 20px;
92
+ `);
@@ -0,0 +1,9 @@
1
+ import { ImageProps } from "../ProblemSelectNode";
2
+ export interface SelectBoxEditProps {
3
+ index: number;
4
+ isAnswer: boolean;
5
+ image?: ImageProps | null;
6
+ text: string;
7
+ onClick: () => void;
8
+ }
9
+ export declare function SelectBoxEdit(props: SelectBoxEditProps): import("@emotion/react/jsx-runtime").JSX.Element;
@@ -0,0 +1,13 @@
1
+ import { jsx as _jsx } from "@emotion/react/jsx-runtime";
2
+ /** @jsxImportSource @emotion/react */
3
+ import { css, useTheme } from "@emotion/react";
4
+ import { CheckboxCircleFillIcon } from "../../../../../icons";
5
+ import { SelectBoxComponent } from "./SelectBoxComponent";
6
+ export function SelectBoxEdit(props) {
7
+ const { index, isAnswer, image, text, onClick } = props;
8
+ const theme = useTheme();
9
+ return (_jsx(SelectBoxComponent, { index: index, image: image, text: text, endIcon: isAnswer && (_jsx(CheckboxCircleFillIcon, { color: theme.color.foreground.success, css: css `
10
+ width: 16px;
11
+ height: 16px;
12
+ ` })), onClick: onClick }));
13
+ }
@@ -0,0 +1,10 @@
1
+ import { ImageProps } from "../ProblemSelectNode";
2
+ export interface SelectBoxViewProps {
3
+ index: number;
4
+ isSelected: boolean;
5
+ isAnswer?: boolean;
6
+ image?: ImageProps | null;
7
+ text: string;
8
+ onClick?: () => void;
9
+ }
10
+ export declare function SelectBoxView(props: SelectBoxViewProps): import("@emotion/react/jsx-runtime").JSX.Element;
@@ -0,0 +1,42 @@
1
+ import { jsx as _jsx } from "@emotion/react/jsx-runtime";
2
+ /** @jsxImportSource @emotion/react */
3
+ import { css } from "@emotion/react";
4
+ import { CheckboxCircleFillIcon, CheckFillIcon, CloseCircleFillIcon, } from "../../../../../icons";
5
+ import { SelectBoxComponent } from "./SelectBoxComponent";
6
+ import Tag from "../../../../../components/Tag";
7
+ const TYPE_TO_INDEX_ICON = (type) => ({
8
+ primary: (_jsx(CheckFillIcon, { css: css `
9
+ width: 12px;
10
+ height: 12px;
11
+ ` })),
12
+ success: (_jsx(CheckboxCircleFillIcon, { css: css `
13
+ width: 12px;
14
+ height: 12px;
15
+ ` })),
16
+ danger: (_jsx(CloseCircleFillIcon, { css: css `
17
+ width: 12px;
18
+ height: 12px;
19
+ ` })),
20
+ })[type];
21
+ export function SelectBoxView(props) {
22
+ const { index, isSelected, isAnswer, image, text, onClick } = props;
23
+ const selectBoxType = isSelected
24
+ ? isAnswer === undefined
25
+ ? "primary"
26
+ : isAnswer
27
+ ? "success"
28
+ : "danger"
29
+ : undefined;
30
+ return (_jsx(SelectBoxComponent, { css: css `
31
+ width: 100%;
32
+ `, type: selectBoxType, index: selectBoxType ? TYPE_TO_INDEX_ICON(selectBoxType) : index, image: image, text: text,
33
+ // isAnswer가 존재하면 채점 후의 상태이므로 unclickable을 위해 onClick을 전달하지 않음
34
+ onClick: isAnswer === undefined ? onClick : undefined,
35
+ // 선택되지 않았으나 정답일 때 정답 태그를 표시
36
+ endIcon: isAnswer &&
37
+ !isSelected && (_jsx(Tag, { label: "\uC815\uB2F5", icon: _jsx(CheckboxCircleFillIcon, {}), color: "green", css: css `
38
+ span {
39
+ font-weight: 700;
40
+ }
41
+ ` })) }));
42
+ }
@@ -0,0 +1,2 @@
1
+ export * from "./SelectBoxEdit";
2
+ export * from "./SelectBoxView";
@@ -0,0 +1,2 @@
1
+ export * from "./SelectBoxEdit";
2
+ export * from "./SelectBoxView";
@@ -16,7 +16,7 @@ import { $isProblemSelectNode } from "./ProblemSelectNode";
16
16
  import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext";
17
17
  import { useContext, useState } from "react";
18
18
  import useLexicalEditable from "@lexical/react/useLexicalEditable";
19
- import SelectBox from "./SelectBox";
19
+ import { SelectBoxEdit, SelectBoxView } from "./SelectBox";
20
20
  import { css } from "@emotion/react";
21
21
  import SquareButton from "../../../../components/SquareButton";
22
22
  import { AlarmWarningFillIcon, Settings3FillIcon } from "../../../../icons";
@@ -35,7 +35,7 @@ export function SelectComponent(props) {
35
35
  return (_jsxs(_Fragment, { children: [hasMultipleAnswers && (_jsxs(Alert, { children: [_jsx(AlarmWarningFillIcon, { css: css `
36
36
  width: 14px;
37
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: () => {
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(SelectBoxView, { index: index + 1, isAnswer: selection.isAnswer, isSelected: selected.includes(selection.value), image: selection.show.image, text: selection.show.text, onClick: () => {
39
39
  if (lexicalCustomConfig.freezeProblemNode)
40
40
  return;
41
41
  const isSelected = selected.includes(selection.value);
@@ -62,7 +62,7 @@ export function SelectComponent(props) {
62
62
  }
63
63
  });
64
64
  }
65
- }, fullWidth: true }, index)))] }));
65
+ } }, index)))] }));
66
66
  }
67
67
  // 교사 edit view
68
68
  return (_jsxs(_Fragment, { children: [_jsxs("div", Object.assign({ css: css `
@@ -72,7 +72,7 @@ export function SelectComponent(props) {
72
72
  display: flex;
73
73
  flex-direction: column;
74
74
  gap: 4px;
75
- ` }, { 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: () => {
75
+ ` }, { children: selections.map((selection, index) => (_jsx(SelectBoxEdit, { index: index + 1, isAnswer: Boolean(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: () => {
76
76
  setSettingOpen(true);
77
77
  } })] })), settingOpen && (_jsx(SettingForm, Object.assign({}, settingFormProps, { onClose: () => setSettingOpen(false) })))] }));
78
78
  }
@@ -46,7 +46,7 @@ export function FormSelection(props) {
46
46
  setImageOpen(true);
47
47
  } }), _jsx(Controller, { name: `selections.${index}.isAnswer`, control: control, render: ({ field: { value, onChange } }) => (_jsxs(Answer, Object.assign({ onClick: () => {
48
48
  onChange(!value);
49
- } }, { children: ["\uC815\uB2F5", _jsx(Switch, { checked: value, size: "small" })] }))) }), onDelete && (_jsx(SquareButton, { color: "white", size: "xsmall", icon: _jsx(DeleteBinLineIcon, {}), onClick: onDelete }))] }))] }));
49
+ } }, { children: ["\uC815\uB2F5", _jsx(Switch, { checked: Boolean(value), size: "small" })] }))) }), onDelete && (_jsx(SquareButton, { color: "white", size: "xsmall", icon: _jsx(DeleteBinLineIcon, {}), onClick: onDelete }))] }))] }));
50
50
  }
51
51
  const Container = styled.div(({ theme }) => css `
52
52
  display: flex;
@@ -212,9 +212,9 @@ function getColoredQuoteContextMenuOptions(editor, theme, node) {
212
212
  },
213
213
  }),
214
214
  new ComponentPickerOption("빨간색", {
215
- icon: (_jsx(InputMethodLineIcon, { color: theme.color.container.dangerOnContainer })),
215
+ icon: (_jsx(InputMethodLineIcon, { color: theme.color.container.redOnContainer })),
216
216
  iconContainerClassName: css `
217
- background: ${theme.color.container.dangerContainer};
217
+ background: ${theme.color.container.redContainer};
218
218
  `,
219
219
  keywords: ["red"],
220
220
  onSelect: () => {
@@ -224,9 +224,9 @@ function getColoredQuoteContextMenuOptions(editor, theme, node) {
224
224
  },
225
225
  }),
226
226
  new ComponentPickerOption("노란색", {
227
- icon: (_jsx(InputMethodLineIcon, { color: theme.color.container.warningOnContainer })),
227
+ icon: (_jsx(InputMethodLineIcon, { color: theme.color.container.yellowOnContainer })),
228
228
  iconContainerClassName: css `
229
- background: ${theme.color.container.warningContainer};
229
+ background: ${theme.color.container.yellowContainer};
230
230
  `,
231
231
  keywords: ["yellow"],
232
232
  onSelect: () => {
@@ -236,9 +236,9 @@ function getColoredQuoteContextMenuOptions(editor, theme, node) {
236
236
  },
237
237
  }),
238
238
  new ComponentPickerOption("파란색", {
239
- icon: (_jsx(InputMethodLineIcon, { color: theme.color.container.primaryOnContainer })),
239
+ icon: (_jsx(InputMethodLineIcon, { color: theme.color.container.blueOnContainer })),
240
240
  iconContainerClassName: css `
241
- background: ${theme.color.container.primaryContainer};
241
+ background: ${theme.color.container.blueContainer};
242
242
  `,
243
243
  keywords: ["blue"],
244
244
  onSelect: () => {
@@ -248,9 +248,9 @@ function getColoredQuoteContextMenuOptions(editor, theme, node) {
248
248
  },
249
249
  }),
250
250
  new ComponentPickerOption("초록색", {
251
- icon: (_jsx(InputMethodLineIcon, { color: theme.color.container.successOnContainer })),
251
+ icon: (_jsx(InputMethodLineIcon, { color: theme.color.container.greenOnContainer })),
252
252
  iconContainerClassName: css `
253
- background: ${theme.color.container.successContainer};
253
+ background: ${theme.color.container.greenContainer};
254
254
  `,
255
255
  keywords: ["green"],
256
256
  onSelect: () => {