@team-monolith/cds 1.52.1 → 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.
@@ -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 {
@@ -1,10 +1,9 @@
1
1
  import { ImageProps } from "../../insertImageDialog";
2
- import React from "react";
3
2
  export interface SelectBoxEditProps {
4
3
  index: number;
5
4
  isAnswer: boolean;
6
5
  image?: ImageProps | null;
7
- text: React.ReactNode;
6
+ text: string;
8
7
  onClick: () => void;
9
8
  }
10
9
  export declare function SelectBoxEdit(props: SelectBoxEditProps): import("@emotion/react/jsx-runtime").JSX.Element;
@@ -65,8 +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 ||
69
- (selection.show.image ? null : `${index + 1}번 선택지`), onClick: () => setSettingOpen(true) }, index))) })), _jsx(SquareButton, { size: "small", color: "icon", icon: _jsx(Settings3FillIcon, {}), onClick: () => {
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: () => {
70
71
  setSettingOpen(true);
71
72
  } })] })), settingOpen && (_jsx(SettingForm, { selections: selections, nodeKey: nodeKey, onClose: () => setSettingOpen(false) }))] }));
72
73
  }
@@ -1,14 +1,9 @@
1
- import { Selection } from "../ProblemSelectNode";
2
- import { Control, UseFormWatch } from "react-hook-form";
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
- selections: Selection[];
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
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 { Controller } from "react-hook-form";
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, watch, rules, onDelete } = props;
13
+ const { index, control, rules, onDelete } = props;
14
14
  const [imageOpen, setImageOpen] = useState(false);
15
15
  const [inputFocused, setInputFocused] = useState(false);
16
- return (_jsxs(Container, { children: [_jsx(Index, { children: index + 1 }), _jsx(Controller, { name: `selections.${index}.show`, control: control, rules: rules, render: ({ field: { value, onChange }, fieldState: { invalid, error }, }) => (_jsxs("div", Object.assign({ css: css `
17
- display: flex;
18
- flex: 1;
19
- flex-direction: column;
20
- gap: 4px;
21
- ` }, { 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 `
22
- height: auto;
23
- // 이미지로 인해 좌우로 스크롤이 생기는 것을 방지
24
- max-width: min(400px, 100%);
25
- width: fit-content;
26
- border-radius: 6px;
27
- `, draggable: "false" })), _jsx(Input, { size: "small", color: invalid
28
- ? "activeDanger"
29
- : inputFocused
30
- ? "activePrimary"
31
- : "default", value: value.text, onChange: (e) => onChange(Object.assign(Object.assign({}, value), { text: e.target.value })), inputProps: {
32
- onFocus: (_e) => {
33
- setInputFocused(true);
34
- },
35
- onBlur: (_e) => {
36
- setInputFocused(false);
37
- },
38
- }, placeholder: `${index + 1}번 선택지`, hintIcon: invalid ? _jsx(ErrorWarningFillIcon, {}) : undefined, hintText: error === null || error === void 0 ? void 0 : error.message, multiline: true, fullWidth: true, css: css `
39
- flex: 1;
40
- ` })] }))) }), _jsxs("div", Object.assign({ css: css `
16
+ const { field: { value, onChange }, fieldState: { invalid, error }, } = useController({
17
+ control,
18
+ name: `selections.${index}`,
19
+ rules,
20
+ });
21
+ return (_jsxs(Container, { children: [_jsx(Index, { children: index + 1 }), _jsxs("div", Object.assign({ css: css `
22
+ display: flex;
23
+ flex: 1;
24
+ flex-direction: column;
25
+ gap: 4px;
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: watch(`selections.${index}.show.image`) ? (_jsx(ImageEditFillIcon, {})) : (_jsx(ImageAddFillIcon, {})), onClick: () => {
50
+ ` }, { children: [_jsx(SquareButton, { color: "icon", size: "xsmall", icon: value.show.image ? _jsx(ImageEditFillIcon, {}) : _jsx(ImageAddFillIcon, {}), onClick: () => {
46
51
  setImageOpen(true);
47
- } }), _jsx(Controller, { name: `selections.${index}.isAnswer`, control: control, render: ({ field: { value, onChange } }) => (_jsxs(Answer, Object.assign({ onClick: () => {
48
- onChange(!value);
49
- } }, { children: ["\uC815\uB2F5", _jsx(Switch, { checked: Boolean(value), size: "small" })] }))) }), onDelete && (_jsx(SquareButton, { color: "white", size: "xsmall", icon: _jsx(DeleteBinLineIcon, {}), onClick: onDelete }))] }))] }));
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;
@@ -5,4 +5,7 @@ export interface SettingFormProps {
5
5
  nodeKey: NodeKey;
6
6
  onClose: () => void;
7
7
  }
8
+ export interface SettingFormData {
9
+ selections: Selection[];
10
+ }
8
11
  export default function SettingForm(props: SettingFormProps): import("@emotion/react/jsx-runtime").JSX.Element;
@@ -38,33 +38,25 @@ export default function SettingForm(props) {
38
38
  });
39
39
  onClose();
40
40
  };
41
- function validateRequiredSelection(index) {
42
- const selections = watch("selections");
43
- const selection = selections[index];
44
- if (selection.show.image || selection.show.text)
41
+ function validateRequired(value) {
42
+ if (value.show.image || value.show.text)
45
43
  return true;
46
44
  return "필수 입력 항목입니다.";
47
45
  }
48
- function validateDuplicatedSelection(index) {
49
- const selections = watch("selections");
50
- if (index === 0)
51
- return true;
52
- const image = selections[index].show.image;
53
- const text = selections[index].show.text;
54
- const duplicatedIndex = selections
55
- .slice(0, index)
56
- .findIndex((selection) => {
57
- var _a;
58
- // text를 비교하고, text가 같은 경우 image도 비교합니다.
59
- if (selection.show.text !== text)
60
- return false;
61
- if (((_a = selection.show.image) === null || _a === void 0 ? void 0 : _a.src) !== (image === null || image === void 0 ? void 0 : image.src))
62
- return false;
63
- return true;
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 [];
64
54
  });
65
- if (duplicatedIndex < 0)
55
+ if (duplicatedIndexes.length < 2)
66
56
  return true;
67
- return `${duplicatedIndex + 1}번 선택지와 같은 내용입니다.`;
57
+ return `${duplicatedIndexes
58
+ .map((i) => i + 1)
59
+ .join(",")}번 선택지가 같은 내용입니다.`;
68
60
  }
69
61
  const answersCount = watch("selections").filter((selection) => selection.isAnswer).length;
70
62
  const hasMultipleAnswers = answersCount > 1;
@@ -72,13 +64,10 @@ export default function SettingForm(props) {
72
64
  return (_jsxs(Form, Object.assign({ onSubmit: handleSubmit(onSettingSubmit) }, { children: [_jsxs(Title, { children: [_jsx(ListRadioIcon, { css: css `
73
65
  width: 12px;
74
66
  height: 12px;
75
- ` }), "\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, watch: watch, rules: {
76
- validate: () => {
77
- const required = validateRequiredSelection(index);
78
- if (required !== true) {
79
- return required;
80
- }
81
- return validateDuplicatedSelection(index);
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,
82
71
  },
83
72
  }, onDelete: index !== 0
84
73
  ? () => {
@@ -37,15 +37,9 @@ export function InputComponent(props) {
37
37
  };
38
38
  // view
39
39
  if (!isEditable) {
40
- return (_jsx(Input, { multiline: multiline, css: css `
40
+ return (_jsx(Input, { multiline: multiline, disableAutosize: true, css: css `
41
41
  textarea {
42
- // textarea의 height 고정시키기 위해 max-height와 min-height를 설정합니다.
43
- max-height: ${TEXTAREA_HEIGHT}px;
44
- min-height: ${TEXTAREA_HEIGHT}px;
45
- // https://github.com/mui/material-ui/blob/next/packages/mui-base/src/TextareaAutosize/TextareaAutosize.tsx
46
- // MUI TextareaAutoSize는 overflow를 hidden으로 설정하는 로직이 있습니다.
47
- // 그래서 부득이하게 !important를 사용합니다.
48
- overflow: auto !important;
42
+ height: ${TEXTAREA_HEIGHT}px;
49
43
  }
50
44
  `, inputProps: freezeProblemNode
51
45
  ? { readOnly: true }
@@ -1,9 +1,8 @@
1
- import React from "react";
2
1
  import { ImageProps } from "../../../insertImageDialog";
3
2
  /** SheetSelectNode의 하나의 선택지 edit모드 컴포넌트입니다. */
4
3
  export declare function SelectBoxEdit(props: {
5
4
  index: number;
6
5
  image?: ImageProps | null;
7
- text: React.ReactNode;
6
+ text: string;
8
7
  onClick: () => void;
9
8
  }): import("react/jsx-runtime").JSX.Element;
@@ -60,8 +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 ||
64
- (selection.show.image ? null : `${index + 1}번 선택지`), onClick: () => setSettingOpen((open) => !open) }, index))) })), _jsx(SquareButton, { size: "small", color: "icon", icon: _jsx(Settings3FillIcon, {}), onClick: () => {
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: () => {
65
66
  setSettingOpen((open) => !open);
66
67
  } })] })), settingOpen && (_jsx(SettingForm, { data: {
67
68
  selections,
@@ -1,11 +1,10 @@
1
1
  /** @jsxImportSource @emotion/react */
2
- import { Control, UseFormWatch } from "react-hook-form";
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
- watch: UseFormWatch<SettingFormData>;
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;
@@ -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 { Controller } from "react-hook-form";
3
+ import { useController } from "react-hook-form";
4
4
  import styled from "@emotion/styled";
5
5
  import { css } from "@emotion/react";
6
6
  import { useState } from "react";
@@ -8,40 +8,45 @@ import { InsertImageDialog } from "../../../insertImageDialog";
8
8
  import { DeleteBinLineIcon, ErrorWarningFillIcon, ImageAddFillIcon, ImageEditFillIcon, Input, SquareButton, } from "../../../../../..";
9
9
  /** SheetSelectNode SettingForm의 단일 선택지 폼입니다. */
10
10
  export default function FormSelection(props) {
11
- const { index, control, watch, rules, onDelete } = props;
11
+ const { index, control, rules, onDelete } = props;
12
12
  const [imageOpen, setImageOpen] = useState(false);
13
13
  const [inputFocused, setInputFocused] = useState(false);
14
- return (_jsxs(Container, { children: [_jsx(Index, { children: index + 1 }), _jsx(Controller, { name: `selections.${index}.show`, control: control, rules: rules, render: ({ field: { value, onChange }, fieldState: { invalid, error }, }) => (_jsxs("div", Object.assign({ css: css `
15
- display: flex;
16
- flex: 1;
17
- flex-direction: column;
18
- gap: 4px;
19
- ` }, { 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 `
20
- height: auto;
21
- // 이미지로 인해 좌우로 스크롤이 생기는 것을 방지
22
- max-width: min(400px, 100%);
23
- width: fit-content;
24
- border-radius: 6px;
25
- `, draggable: "false" })), _jsx(Input, { size: "small", color: invalid
26
- ? "activeDanger"
27
- : inputFocused
28
- ? "activePrimary"
29
- : "default", value: value.text, onChange: (e) => onChange(Object.assign(Object.assign({}, value), { text: e.target.value })), 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 `
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 `
39
44
  display: flex;
40
45
  // 이미지가 들어가서 container height가 커져도 높이가 유지되도록 설정
41
46
  height: 36px;
42
47
  gap: 8px;
43
48
  align-items: center;
44
- ` }, { children: [_jsx(SquareButton, { color: "icon", size: "xsmall", icon: watch(`selections.${index}.show.image`) ? (_jsx(ImageEditFillIcon, {})) : (_jsx(ImageAddFillIcon, {})), onClick: () => {
49
+ ` }, { children: [_jsx(SquareButton, { color: "icon", size: "xsmall", icon: value.image ? _jsx(ImageEditFillIcon, {}) : _jsx(ImageAddFillIcon, {}), onClick: () => {
45
50
  setImageOpen(true);
46
51
  } }), onDelete && (_jsx(SquareButton, { color: "white", size: "xsmall", icon: _jsx(DeleteBinLineIcon, {}), onClick: onDelete }))] }))] }));
47
52
  }
@@ -70,17 +75,3 @@ const Index = styled.div(({ theme }) => css `
70
75
  font-weight: 800;
71
76
  line-height: 16px;
72
77
  `);
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
- `);
@@ -38,44 +38,32 @@ export function SettingForm(props) {
38
38
  });
39
39
  onClose();
40
40
  };
41
- function validateRequiredSelection(index) {
42
- const selections = watch("selections");
43
- const selection = selections[index];
44
- if (selection.show.image || selection.show.text)
41
+ function validateRequired(value) {
42
+ if (value.image || value.text)
45
43
  return true;
46
44
  return "필수 입력 항목입니다.";
47
45
  }
48
- function validateDuplicatedSelection(index) {
49
- const selections = watch("selections");
50
- if (index === 0)
51
- return true;
52
- const image = selections[index].show.image;
53
- const text = selections[index].show.text;
54
- const duplicatedIndex = selections
55
- .slice(0, index)
56
- .findIndex((selection) => {
57
- var _a;
58
- // text를 비교하고, text가 같은 경우 image도 비교합니다.
59
- if (selection.show.text !== text)
60
- return false;
61
- if (((_a = selection.show.image) === null || _a === void 0 ? void 0 : _a.src) !== (image === null || image === void 0 ? void 0 : image.src))
62
- return false;
63
- return true;
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 [];
64
53
  });
65
- if (duplicatedIndex < 0)
54
+ if (duplicatedIndexes.length < 2)
66
55
  return true;
67
- return `${duplicatedIndex + 1}번 선택지와 같은 내용입니다.`;
56
+ return `${duplicatedIndexes
57
+ .map((i) => i + 1)
58
+ .join(",")}번 선택지가 같은 내용입니다.`;
68
59
  }
69
60
  return (_jsxs(Form, Object.assign({ onSubmit: handleSubmit(onSettingSubmit) }, { children: [_jsxs(Title, { children: [_jsx(ListRadioIcon, { css: css `
70
61
  width: 12px;
71
62
  height: 12px;
72
- ` }), "\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, watch: watch, rules: {
73
- validate: () => {
74
- const required = validateRequiredSelection(index);
75
- if (required !== true) {
76
- return required;
77
- }
78
- return validateDuplicatedSelection(index);
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,
79
67
  },
80
68
  }, onDelete: index !== 0
81
69
  ? () => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@team-monolith/cds",
3
- "version": "1.52.1",
3
+ "version": "1.52.2",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "sideEffects": false,