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