@team-monolith/cds 1.61.3 → 1.63.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 (24) hide show
  1. package/@types/emotion.d.ts +0 -14
  2. package/dist/CodleDesignSystemProvider.js +0 -28
  3. package/dist/index.d.ts +0 -1
  4. package/dist/index.js +0 -1
  5. package/dist/patterns/LexicalEditor/LexicalEditor.js +2 -43
  6. package/dist/patterns/LexicalEditor/convertToMarkdown.d.ts +2 -0
  7. package/dist/patterns/LexicalEditor/convertToMarkdown.js +21 -0
  8. package/dist/patterns/LexicalEditor/index.d.ts +1 -0
  9. package/dist/patterns/LexicalEditor/index.js +1 -0
  10. package/dist/patterns/LexicalEditor/nodes/ProblemSelectNode/SelectBox/SelectBoxComponent.d.ts +9 -9
  11. package/dist/patterns/LexicalEditor/nodes/ProblemSelectNode/SelectBox/SelectBoxComponent.js +16 -4
  12. package/dist/patterns/LexicalEditor/nodes/ProblemSelectNode/SelectBox/SelectBoxView.js +22 -1
  13. package/dist/patterns/LexicalEditor/nodes/SheetSelectNode/SelectComponent/SelectBox/SelectBoxComponent.d.ts +5 -5
  14. package/dist/patterns/LexicalEditor/nodes/SheetSelectNode/SelectComponent/SelectBox/SelectBoxComponent.js +16 -4
  15. package/dist/patterns/LexicalEditor/nodes/SheetSelectNode/SelectComponent/SelectBox/SelectBoxView.js +22 -1
  16. package/dist/patterns/LexicalEditor/nodes/index.d.ts +1 -0
  17. package/dist/patterns/LexicalEditor/nodes/index.js +1 -0
  18. package/dist/patterns/LexicalEditor/nodes/nodes.d.ts +19 -0
  19. package/dist/patterns/LexicalEditor/nodes/nodes.js +44 -0
  20. package/package.json +3 -3
  21. package/dist/components/Book/Book.d.ts +0 -18
  22. package/dist/components/Book/Book.js +0 -252
  23. package/dist/components/Book/index.d.ts +0 -1
  24. package/dist/components/Book/index.js +0 -1
@@ -106,20 +106,6 @@ interface CodleColors {
106
106
  ebookAlt: string;
107
107
  makecode: string;
108
108
  makecodeAlt: string;
109
- decorativeRed: string;
110
- decorativePink: string;
111
- decorativeAmber: string;
112
- decorativeGrass: string;
113
- decorativeGreen: string;
114
- decorativeBlue: string;
115
- decorativeIndigo: string;
116
- decorativePurple: string;
117
- decorativeBrown: string;
118
- decorativeCopper: string;
119
- decorativeGold: string;
120
- decorativeSilver: string;
121
- decorativeGray: string;
122
- decorativeBlack: string;
123
109
  };
124
110
  blanket: {
125
111
  neutral: string;
@@ -111,20 +111,6 @@ export const light = {
111
111
  ebookAlt: "#E0F2FF",
112
112
  makecode: "#AA278F",
113
113
  makecodeAlt: "#FFE0F9",
114
- decorativeRed: "#D73152",
115
- decorativePink: "#D13D78",
116
- decorativeAmber: "#CC4827",
117
- decorativeGrass: "#6E7B3F",
118
- decorativeGreen: "#26864B",
119
- decorativeBlue: "#0076D0",
120
- decorativeIndigo: "#5863FF",
121
- decorativePurple: "#9A3BBC",
122
- decorativeBrown: "#996C49",
123
- decorativeCopper: "#8A6470",
124
- decorativeGold: "#876E3F",
125
- decorativeSilver: "#6A7781",
126
- decorativeGray: "#747474",
127
- decorativeBlack: "#474658",
128
114
  },
129
115
  blanket: {
130
116
  neutral: COLOR.alpha030,
@@ -242,20 +228,6 @@ export const dark = {
242
228
  ebookAlt: "#475966",
243
229
  makecode: "#AA278F",
244
230
  makecodeAlt: "#664760",
245
- decorativeRed: "#D73152",
246
- decorativePink: "#D13D78",
247
- decorativeAmber: "#CC4827",
248
- decorativeGrass: "#6E7B3F",
249
- decorativeGreen: "#26864B",
250
- decorativeBlue: "#0076D0",
251
- decorativeIndigo: "#5863FF",
252
- decorativePurple: "#9A3BBC",
253
- decorativeBrown: "#996C49",
254
- decorativeCopper: "#8A6470",
255
- decorativeGold: "#876E3F",
256
- decorativeSilver: "#6A7781",
257
- decorativeGray: "#747474",
258
- decorativeBlack: "#474658",
259
231
  },
260
232
  blanket: {
261
233
  neutral: COLOR.alphaf20,
package/dist/index.d.ts CHANGED
@@ -3,7 +3,6 @@ export * from "./components/AlertDialog";
3
3
  export * from "./components/DecoratedNumber";
4
4
  export { default as Banner } from "./components/Banner";
5
5
  export * from "./components/Banner";
6
- export { default as Book } from "./components/Book";
7
6
  export { default as Button } from "./components/Button";
8
7
  export * from "./components/Button";
9
8
  export { default as CheckboxInput } from "./components/CheckboxInput";
package/dist/index.js CHANGED
@@ -3,7 +3,6 @@ export * from "./components/AlertDialog";
3
3
  export * from "./components/DecoratedNumber";
4
4
  export { default as Banner } from "./components/Banner";
5
5
  export * from "./components/Banner";
6
- export { default as Book } from "./components/Book";
7
6
  export { default as Button } from "./components/Button";
8
7
  export * from "./components/Button";
9
8
  export { default as CheckboxInput } from "./components/CheckboxInput";
@@ -1,24 +1,11 @@
1
1
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { LexicalComposer, } from "@lexical/react/LexicalComposer";
3
- import { HeadingNode, QuoteNode } from "@lexical/rich-text";
4
- import { TableCellNode, TableNode, TableRowNode } from "@lexical/table";
5
- import { ListItemNode, ListNode } from "@lexical/list";
6
- import { CodeHighlightNode, CodeNode } from "@lexical/code";
7
- import { AutoLinkNode, LinkNode } from "@lexical/link";
8
- import { ImageNode } from "./nodes/ImageNode";
9
- import { HorizontalRuleNode } from "@lexical/react/LexicalHorizontalRuleNode";
10
3
  import { getTheme } from "./theme";
11
4
  import { useTheme } from "@emotion/react";
12
5
  import Plugins from "./Plugins";
13
- import { ColoredQuoteNode, ProblemInputNode } from "./nodes";
6
+ import { nodes } from "./nodes";
14
7
  import { LexicalCustomConfigContext } from "./LexicalCustomConfigContext";
15
- import { ProblemSelectNode } from "./nodes/ProblemSelectNode";
16
- import { LayoutContainerNode } from "./nodes/LayoutContainerNode";
17
- import { LayoutItemNode } from "./nodes/LayoutItemNode";
18
8
  import _ from "lodash";
19
- import { SheetSelectNode } from "./nodes/SheetSelectNode";
20
- import { SheetInputNode } from "./nodes/SheetInputNode";
21
- import { SelfEvaluationNode } from "./nodes/SelfEvaluationNode";
22
9
  function validateValue(value) {
23
10
  var _a, _b;
24
11
  if (value && typeof value !== "object") {
@@ -60,35 +47,7 @@ export function LexicalEditor(props) {
60
47
  const initialConfig = {
61
48
  namespace: "CodleLexicalEditor",
62
49
  onError: (error) => console.error(error),
63
- nodes: [
64
- ProblemInputNode,
65
- ProblemSelectNode,
66
- HeadingNode,
67
- ListNode,
68
- ListItemNode,
69
- QuoteNode,
70
- CodeNode,
71
- CodeHighlightNode,
72
- TableNode,
73
- TableCellNode,
74
- TableRowNode,
75
- AutoLinkNode,
76
- LinkNode,
77
- ImageNode,
78
- HorizontalRuleNode,
79
- ColoredQuoteNode,
80
- LayoutContainerNode,
81
- LayoutItemNode,
82
- SheetSelectNode,
83
- SheetInputNode,
84
- SelfEvaluationNode,
85
- {
86
- replace: QuoteNode,
87
- with: (_node) => {
88
- return new ColoredQuoteNode("grey");
89
- },
90
- },
91
- ],
50
+ nodes,
92
51
  // 마운트 된 이후 editable 수정해도 theme에 반영되지 않음 유의
93
52
  theme: getTheme(theme, editable),
94
53
  editorState: validateValue(value) ? JSON.stringify(value) : undefined,
@@ -0,0 +1,2 @@
1
+ import { SerializedEditorState } from "lexical";
2
+ export declare function convertToMarkdown(serializedLexical: SerializedEditorState): string;
@@ -0,0 +1,21 @@
1
+ import { createEditor } from "lexical";
2
+ import { $convertToMarkdownString } from "@lexical/markdown";
3
+ import { CODLE_TRANSFORMERS } from "./plugins/MarkdownTransformers";
4
+ import { nodes } from "./nodes";
5
+ export function convertToMarkdown(serializedLexical) {
6
+ // Create a Lexical editor instance
7
+ const editor = createEditor({
8
+ nodes,
9
+ });
10
+ // Update the editor state with the serialized Lexical state
11
+ editor.update(() => {
12
+ const editorState = editor.parseEditorState(serializedLexical);
13
+ editor.setEditorState(editorState);
14
+ });
15
+ // Convert the Lexical editor state to Markdown string
16
+ let markdown = "";
17
+ editor.update(() => {
18
+ markdown = $convertToMarkdownString(CODLE_TRANSFORMERS);
19
+ });
20
+ return markdown;
21
+ }
@@ -1,2 +1,3 @@
1
1
  export * from "./LexicalEditor";
2
2
  export * from "./plugins/MarkdownTransformers";
3
+ export * from "./convertToMarkdown";
@@ -1,2 +1,3 @@
1
1
  export * from "./LexicalEditor";
2
2
  export * from "./plugins/MarkdownTransformers";
3
+ export * from "./convertToMarkdown";
@@ -2,18 +2,18 @@ import React from "react";
2
2
  import { ImageProps } from "../../../components/InsertImageDialog";
3
3
  export type SelectBoxType = "primary" | "success" | "danger";
4
4
  /** 비지니스 로직과 무관한 SelectBox를 그리는 공통 컴포넌트입니다. */
5
- export declare function SelectBoxComponent(props: {
6
- className?: string;
7
- indexClassName?: string;
8
- contentClassName?: string;
5
+ export declare const SelectBoxComponent: React.ForwardRefExoticComponent<{
6
+ className?: string | undefined;
7
+ indexClassName?: string | undefined;
8
+ contentClassName?: string | undefined;
9
9
  /** SelectBox의 스타일 타입. 전달하지 않으면 기본 스타일이 적용됩니다. */
10
- type?: SelectBoxType;
10
+ type?: SelectBoxType | undefined;
11
11
  /** 문제 번호 박스 내부를 그립니다. */
12
12
  index: React.ReactNode;
13
- image?: ImageProps | null;
13
+ image?: ImageProps | null | undefined;
14
14
  text: React.ReactNode;
15
15
  /** SelectBox content 우측에 표시될 부분 */
16
16
  endIcon?: React.ReactNode;
17
- /** 박스를 클릭할 때 실행될 콜백. 존재하지 않으면 cursor: pointer가 적용되지 않습니다. */
18
- onClick?: () => void;
19
- }): import("@emotion/react/jsx-runtime").JSX.Element;
17
+ /** 박스를 클릭할 때 실행될 콜백. 전달하지 않으면 cursor: pointer가 적용되지 않습니다. */
18
+ onClick?: (() => void) | undefined;
19
+ } & Partial<React.HTMLAttributes<HTMLDivElement>> & React.RefAttributes<HTMLDivElement>>;
@@ -1,7 +1,19 @@
1
+ var __rest = (this && this.__rest) || function (s, e) {
2
+ var t = {};
3
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
4
+ t[p] = s[p];
5
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
6
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
7
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
8
+ t[p[i]] = s[p[i]];
9
+ }
10
+ return t;
11
+ };
1
12
  import { jsx as _jsx, jsxs as _jsxs } from "@emotion/react/jsx-runtime";
2
13
  /** @jsxImportSource @emotion/react */
3
14
  import { css, useTheme } from "@emotion/react";
4
15
  import styled from "@emotion/styled";
16
+ import { forwardRef } from "react";
5
17
  const TYPE_TO_CONTAINER_STYLE = (theme, type) => ({
6
18
  primary: css `
7
19
  background: ${theme.color.container.blueContainer};
@@ -37,17 +49,17 @@ const TYPE_TO_INDEX_STYLE = (theme, type) => ({
37
49
  `,
38
50
  })[type];
39
51
  /** 비지니스 로직과 무관한 SelectBox를 그리는 공통 컴포넌트입니다. */
40
- export function SelectBoxComponent(props) {
41
- const { className, indexClassName, contentClassName, type, index, image, text, endIcon, onClick, } = props;
52
+ export const SelectBoxComponent = forwardRef(function SelectBoxComponent(props, ref) {
53
+ const { className, indexClassName, contentClassName, type, index, image, text, endIcon, onClick } = props, restProps = __rest(props, ["className", "indexClassName", "contentClassName", "type", "index", "image", "text", "endIcon", "onClick"]);
42
54
  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 `
55
+ return (_jsxs(Container, Object.assign({ className: className, ref: ref }, restProps, { css: type ? TYPE_TO_CONTAINER_STYLE(theme, type) : undefined, onClick: onClick }, { children: [_jsx(Index, Object.assign({ className: indexClassName, "aria-hidden": "true", css: type ? TYPE_TO_INDEX_STYLE(theme, type) : undefined }, { children: index })), _jsxs(Content, Object.assign({ className: contentClassName, "aria-hidden": "true" }, { children: [image && (_jsx("img", { "aria-hidden": "true", src: image.src, alt: image.altText, css: css `
44
56
  height: auto;
45
57
  // 이미지로 인해 좌우로 스크롤이 생기는 것을 방지
46
58
  max-width: 100%;
47
59
  width: fit-content;
48
60
  border-radius: 6px;
49
61
  ` })), text] })), endIcon] })));
50
- }
62
+ });
51
63
  const Container = styled.div(({ theme, onClick }) => css `
52
64
  ${onClick && "cursor: pointer;"}
53
65
  display: flex;
@@ -4,6 +4,8 @@ import { css } from "@emotion/react";
4
4
  import { CheckboxCircleFillIcon, CheckFillIcon, CloseCircleFillIcon, } from "../../../../../icons";
5
5
  import { SelectBoxComponent } from "./SelectBoxComponent";
6
6
  import Tag from "../../../../../components/Tag";
7
+ import { useRef } from "react";
8
+ import { useEventListener } from "usehooks-ts";
7
9
  const TYPE_TO_INDEX_ICON = (type) => ({
8
10
  primary: (_jsx(CheckFillIcon, { css: css `
9
11
  width: 12px;
@@ -27,9 +29,28 @@ export function SelectBoxView(props) {
27
29
  ? "success"
28
30
  : "danger"
29
31
  : undefined;
32
+ const boxRef = useRef(null);
33
+ const handleKeyDown = (e) => {
34
+ // onClick이 없으면 아무것도 하지 않음.
35
+ if (!onClick) {
36
+ return;
37
+ }
38
+ // tab을 통해 focus된 상태가 아니면 아무것도 하지 않음.
39
+ if (document.activeElement !== boxRef.current) {
40
+ return;
41
+ }
42
+ // spacebar 또는 엔터를 누르면 onClick 실행
43
+ if (e.key === " " || e.key === "Enter") {
44
+ e.preventDefault();
45
+ onClick();
46
+ }
47
+ };
48
+ useEventListener("keydown", handleKeyDown);
49
+ // text가 있으면 text, 없으면 image의 altText, 없으면 index를 aria-label로 사용
50
+ const ariaText = text || (image && image.altText) || index.toString() + "번 선택지";
30
51
  return (_jsx(SelectBoxComponent, { css: css `
31
52
  width: 100%;
32
- `, type: selectBoxType, index: selectBoxType ? TYPE_TO_INDEX_ICON(selectBoxType) : index, image: image, text: text, onClick: onClick,
53
+ `, ref: boxRef, role: onClick ? "checkbox" : undefined, "aria-checked": isSelected, "aria-label": onClick ? ariaText + (isSelected ? " 선택됨" : " 선택하기") : ariaText, tabIndex: onClick ? 0 : -1, type: selectBoxType, index: selectBoxType ? TYPE_TO_INDEX_ICON(selectBoxType) : index, image: image, text: text, onClick: onClick,
33
54
  // 선택되지 않았으나 정답일 때 정답 태그를 표시
34
55
  endIcon: isAnswer &&
35
56
  !isSelected && (_jsx(Tag, { label: "\uC815\uB2F5", icon: _jsx(CheckboxCircleFillIcon, {}), color: "green", css: css `
@@ -7,12 +7,12 @@ export declare const SelectBoxClasses: {
7
7
  };
8
8
  export type SelectBoxType = "primary" | "normal";
9
9
  /** 비지니스 로직과 무관한 SelectBox를 그리는 공통 컴포넌트입니다. */
10
- export declare function SelectBoxComponent(props: {
11
- className?: string;
10
+ export declare const SelectBoxComponent: import("react").ForwardRefExoticComponent<{
11
+ className?: string | undefined;
12
12
  type: SelectBoxType;
13
13
  index: React.ReactNode;
14
- image?: ImageProps | null;
14
+ image?: ImageProps | null | undefined;
15
15
  text: React.ReactNode;
16
16
  /** 박스를 클릭할 때 실행될 콜백. 존재하지 않으면 cursor: pointer가 적용되지 않습니다. */
17
- onClick?: () => void;
18
- }): import("@emotion/react/jsx-runtime").JSX.Element;
17
+ onClick?: (() => void) | undefined;
18
+ } & Partial<import("react").HTMLAttributes<HTMLDivElement>> & import("react").RefAttributes<HTMLDivElement>>;
@@ -1,7 +1,19 @@
1
+ var __rest = (this && this.__rest) || function (s, e) {
2
+ var t = {};
3
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
4
+ t[p] = s[p];
5
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
6
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
7
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
8
+ t[p[i]] = s[p[i]];
9
+ }
10
+ return t;
11
+ };
1
12
  import { jsx as _jsx, jsxs as _jsxs } from "@emotion/react/jsx-runtime";
2
13
  /** @jsxImportSource @emotion/react */
3
14
  import { css, useTheme } from "@emotion/react";
4
15
  import styled from "@emotion/styled";
16
+ import { forwardRef } from "react";
5
17
  export const SelectBoxClasses = {
6
18
  container: "SheetSelectNode-SelectBox-container",
7
19
  index: "SheetSelectNode-SelectBox-index",
@@ -32,17 +44,17 @@ const TYPE_TO_INDEX_STYLE = (theme, type) => ({
32
44
  `,
33
45
  })[type];
34
46
  /** 비지니스 로직과 무관한 SelectBox를 그리는 공통 컴포넌트입니다. */
35
- export function SelectBoxComponent(props) {
36
- const { className, type, index, image, text, onClick } = props;
47
+ export const SelectBoxComponent = forwardRef(function SelectBoxComponent(props, ref) {
48
+ const { className, type, index, image, text, onClick } = props, restProps = __rest(props, ["className", "type", "index", "image", "text", "onClick"]);
37
49
  const theme = useTheme();
38
- return (_jsxs(Container, Object.assign({ className: `${className} ${SelectBoxClasses.container}`, css: TYPE_TO_CONTAINER_STYLE(theme, type), onClick: onClick }, { children: [_jsx(Index, Object.assign({ className: SelectBoxClasses.index, css: TYPE_TO_INDEX_STYLE(theme, type) }, { children: index })), _jsxs(Content, Object.assign({ className: SelectBoxClasses.content }, { children: [image && (_jsx("img", { src: image.src, alt: image.altText, css: css `
50
+ return (_jsxs(Container, Object.assign({ className: `${className} ${SelectBoxClasses.container}`, ref: ref }, restProps, { css: TYPE_TO_CONTAINER_STYLE(theme, type), onClick: onClick }, { children: [_jsx(Index, Object.assign({ className: SelectBoxClasses.index, "aria-hidden": "true", css: TYPE_TO_INDEX_STYLE(theme, type) }, { children: index })), _jsxs(Content, Object.assign({ className: SelectBoxClasses.content, "aria-hidden": "true" }, { children: [image && (_jsx("img", { "aria-hidden": "true", src: image.src, alt: image.altText, css: css `
39
51
  height: auto;
40
52
  // 이미지로 인해 좌우로 스크롤이 생기는 것을 방지
41
53
  max-width: 100%;
42
54
  width: fit-content;
43
55
  border-radius: 6px;
44
56
  ` })), text] }))] })));
45
- }
57
+ });
46
58
  const Container = styled.div(({ onClick }) => css `
47
59
  ${onClick && "cursor: pointer;"}
48
60
  display: flex;
@@ -3,6 +3,8 @@ import { jsx as _jsx } from "@emotion/react/jsx-runtime";
3
3
  import { css } from "@emotion/react";
4
4
  import { SelectBoxComponent } from "./SelectBoxComponent";
5
5
  import { CheckFillIcon } from "../../../../../../icons";
6
+ import { useRef } from "react";
7
+ import { useEventListener } from "usehooks-ts";
6
8
  function getIndexIcon(type, index) {
7
9
  return {
8
10
  primary: (_jsx(CheckFillIcon, { css: css `
@@ -16,7 +18,26 @@ function getIndexIcon(type, index) {
16
18
  export function SelectBoxView(props) {
17
19
  const { index, isSelected, image, text, onClick } = props;
18
20
  const selectBoxType = isSelected ? "primary" : "normal";
21
+ const boxRef = useRef(null);
22
+ const handleKeyDown = (e) => {
23
+ // onClick이 없으면 아무것도 하지 않음.
24
+ if (!onClick) {
25
+ return;
26
+ }
27
+ // tab을 통해 focus된 상태가 아니면 아무것도 하지 않음.
28
+ if (document.activeElement !== boxRef.current) {
29
+ return;
30
+ }
31
+ // spacebar 또는 엔터를 누르면 onClick 실행
32
+ if (e.key === " " || e.key === "Enter") {
33
+ e.preventDefault();
34
+ onClick();
35
+ }
36
+ };
37
+ useEventListener("keydown", handleKeyDown);
38
+ // text가 있으면 text, 없으면 image의 altText, 없으면 index를 aria-label로 사용
39
+ const ariaText = text || (image && image.altText) || index.toString() + "번 선택지";
19
40
  return (_jsx(SelectBoxComponent, { css: css `
20
41
  width: 100%;
21
- `, type: selectBoxType, index: getIndexIcon(selectBoxType, index), image: image, text: text, onClick: onClick }));
42
+ `, ref: boxRef, role: onClick ? "checkbox" : undefined, "aria-checked": isSelected, "aria-label": onClick ? ariaText + (isSelected ? " 선택됨" : " 선택하기") : ariaText, tabIndex: onClick ? 0 : -1, type: selectBoxType, index: getIndexIcon(selectBoxType, index), image: image, text: text, onClick: onClick }));
22
43
  }
@@ -1,3 +1,4 @@
1
1
  export * from "./ColoredQuoteNode";
2
2
  export * from "./ImageNode";
3
3
  export * from "./ProblemInputNode";
4
+ export * from "./nodes";
@@ -1,3 +1,4 @@
1
1
  export * from "./ColoredQuoteNode";
2
2
  export * from "./ImageNode";
3
3
  export * from "./ProblemInputNode";
4
+ export * from "./nodes";
@@ -0,0 +1,19 @@
1
+ import { HorizontalRuleNode } from "@lexical/react/LexicalHorizontalRuleNode";
2
+ import { ColoredQuoteNode } from "./ColoredQuoteNode";
3
+ import { ImageNode } from "./ImageNode";
4
+ import { LayoutContainerNode } from "./LayoutContainerNode";
5
+ import { LayoutItemNode } from "./LayoutItemNode";
6
+ import { ProblemInputNode } from "./ProblemInputNode";
7
+ import { ProblemSelectNode } from "./ProblemSelectNode";
8
+ import { SelfEvaluationNode } from "./SelfEvaluationNode";
9
+ import { SheetInputNode } from "./SheetInputNode";
10
+ import { SheetSelectNode } from "./SheetSelectNode";
11
+ import { HeadingNode, QuoteNode } from "@lexical/rich-text";
12
+ import { TableCellNode, TableNode, TableRowNode } from "@lexical/table";
13
+ import { ListItemNode, ListNode } from "@lexical/list";
14
+ import { CodeHighlightNode, CodeNode } from "@lexical/code";
15
+ import { LinkNode } from "@lexical/link";
16
+ export declare const nodes: (typeof ColoredQuoteNode | typeof QuoteNode | typeof ImageNode | typeof ProblemInputNode | typeof LayoutContainerNode | typeof LayoutItemNode | typeof ProblemSelectNode | typeof SelfEvaluationNode | typeof SheetInputNode | typeof SheetSelectNode | typeof HeadingNode | typeof ListNode | typeof ListItemNode | typeof CodeNode | typeof CodeHighlightNode | typeof TableNode | typeof TableCellNode | typeof TableRowNode | typeof LinkNode | typeof HorizontalRuleNode | {
17
+ replace: typeof QuoteNode;
18
+ with: () => ColoredQuoteNode;
19
+ })[];
@@ -0,0 +1,44 @@
1
+ import { HorizontalRuleNode } from "@lexical/react/LexicalHorizontalRuleNode";
2
+ import { ColoredQuoteNode } from "./ColoredQuoteNode";
3
+ import { ImageNode } from "./ImageNode";
4
+ import { LayoutContainerNode } from "./LayoutContainerNode";
5
+ import { LayoutItemNode } from "./LayoutItemNode";
6
+ import { ProblemInputNode } from "./ProblemInputNode";
7
+ import { ProblemSelectNode } from "./ProblemSelectNode";
8
+ import { SelfEvaluationNode } from "./SelfEvaluationNode";
9
+ import { SheetInputNode } from "./SheetInputNode";
10
+ import { SheetSelectNode } from "./SheetSelectNode";
11
+ import { HeadingNode, QuoteNode } from "@lexical/rich-text";
12
+ import { TableCellNode, TableNode, TableRowNode } from "@lexical/table";
13
+ import { ListItemNode, ListNode } from "@lexical/list";
14
+ import { CodeHighlightNode, CodeNode } from "@lexical/code";
15
+ import { AutoLinkNode, LinkNode } from "@lexical/link";
16
+ export const nodes = [
17
+ ProblemInputNode,
18
+ ProblemSelectNode,
19
+ HeadingNode,
20
+ ListNode,
21
+ ListItemNode,
22
+ QuoteNode,
23
+ CodeNode,
24
+ CodeHighlightNode,
25
+ TableNode,
26
+ TableCellNode,
27
+ TableRowNode,
28
+ AutoLinkNode,
29
+ LinkNode,
30
+ ImageNode,
31
+ HorizontalRuleNode,
32
+ ColoredQuoteNode,
33
+ LayoutContainerNode,
34
+ LayoutItemNode,
35
+ SheetSelectNode,
36
+ SheetInputNode,
37
+ SelfEvaluationNode,
38
+ {
39
+ replace: QuoteNode,
40
+ with: () => {
41
+ return new ColoredQuoteNode("grey");
42
+ },
43
+ },
44
+ ];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@team-monolith/cds",
3
- "version": "1.61.3",
3
+ "version": "1.63.0",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "sideEffects": false,
@@ -13,7 +13,6 @@
13
13
  "@types/node": "^16.11.26",
14
14
  "@types/react": "^18.2.28",
15
15
  "@types/react-dom": "^18.2.13",
16
- "framer-motion": "^11.3.19",
17
16
  "hex-to-css-filter": "^5.4.0",
18
17
  "lexical": "^0.12.4",
19
18
  "lodash": "^4.17.21",
@@ -22,7 +21,8 @@
22
21
  "react-hook-form": "^7.48.2",
23
22
  "remixicon": "^3.4.0",
24
23
  "typescript": "^4.9.5",
25
- "uid": "^2.0.2"
24
+ "uid": "^2.0.2",
25
+ "usehooks-ts": "^2.9.1"
26
26
  },
27
27
  "files": [
28
28
  "dist/**/*",
@@ -1,18 +0,0 @@
1
- import React from "react";
2
- export interface BookProps {
3
- className?: string;
4
- title?: string;
5
- subtitle?: string;
6
- subtitleBold?: string;
7
- isHidden?: boolean;
8
- buttons?: React.ReactNode;
9
- /** 전달하면 controlled component로 애니메이션을 제어할 수 있다 */
10
- open?: boolean;
11
- onHoverStart?: () => void;
12
- onHoverEnd?: () => void;
13
- onCoverClick?: () => void;
14
- icon?: React.ReactNode;
15
- coverColor: string;
16
- }
17
- declare const _default: React.ForwardRefExoticComponent<BookProps & React.RefAttributes<HTMLDivElement>>;
18
- export default _default;
@@ -1,252 +0,0 @@
1
- import { jsx as _jsx, Fragment as _Fragment, 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
- import { forwardRef, useState } from "react";
6
- import shadows from "../../foundation/shadows";
7
- import { EyeOffFillIcon } from "../../icons";
8
- import { motion } from "framer-motion";
9
- // 이 ZINDEX는 Book 컴포넌트 내의 Stacking Context 에서 활용되므로
10
- // 다른 글로벌 ZINDEX와 충돌을 고려할 필요가 없습니다.
11
- const ZINDEX_BUTTONS = 5;
12
- const ZINDEX_HIDE = 4;
13
- const ZINDEX_TEXT = 3;
14
- const ZINDEX_COVER = 2;
15
- const ZINDEX_ICON = 1;
16
- const BOOK_HEIGHT = 235;
17
- const HOVER_Y_OFFSET = 12;
18
- const SHELF_HEIGHT = 16;
19
- const BOOK_SHELF_GAP = 10;
20
- const BUTTONS_HEIGHT = 36; // Button의 높이는 36px (small)로 가정합니다.
21
- const coverMotion = {
22
- closed: {
23
- y: 0,
24
- boxShadow: shadows.shadow04,
25
- transition: { duration: 0.3 },
26
- },
27
- open: {
28
- y: -HOVER_Y_OFFSET,
29
- boxShadow: shadows.shadow16,
30
- transition: { duration: 0.3 },
31
- },
32
- };
33
- const textMotion = {
34
- closed: {
35
- paddingBottom: 12,
36
- transition: { duration: 0.3 },
37
- },
38
- open: {
39
- paddingBottom: 12 + BUTTONS_HEIGHT + 4,
40
- transition: { duration: 0.3 },
41
- },
42
- };
43
- const buttonsMotion = {
44
- closed: {
45
- opacity: 0,
46
- pointerEvents: "none",
47
- transition: { duration: 0.3 },
48
- },
49
- open: {
50
- opacity: 1,
51
- pointerEvents: "auto",
52
- transition: { duration: 0.3 },
53
- },
54
- };
55
- const CARD_COLOR_PALETTE = (theme) => ({
56
- border: theme.color.foreground.primaryDisabled,
57
- text: theme.color.foreground.neutralAlt,
58
- });
59
- /**
60
- * Framer Motion의 whileHover를 사용하지 않고 직접 애니메이션 상태를 관리합니다.
61
- * 내부적으로는 간단한 애니메이션 상태 관리가 있으며, 필요한 경우
62
- * open 상태를 전달하여 외부에서 관리할 수 있습니다.
63
- *
64
- * 사유1)
65
- * 모바일에서는 hover가 없기 때문에 whileHover를 사용할 수 없습니다.
66
- * 클릭(탭)을 통해 열고 닫을 수 있어야 합니다.
67
- *
68
- * 사유2)
69
- * 관리/배부 드랍다운 Modal이 Card 위로 그려져 hoverEnd를 유발하기 때문에
70
- * 특성 상황에서는 hoverEnd가 발생했어도 Card를 열고 있어야 합니다.
71
- * @param props
72
- * @param ref
73
- * @returns
74
- */
75
- function Book(props, ref) {
76
- const { className, title, subtitle, subtitleBold, coverColor, isHidden = false, buttons, open: controlledOpen, onHoverStart, onHoverEnd, onCoverClick, icon, } = props;
77
- const theme = useTheme();
78
- const [open, setOpen] = useState(false);
79
- return (_jsxs(Container, Object.assign({ initial: "closed", animate: (controlledOpen !== undefined ? controlledOpen : open)
80
- ? "open"
81
- : "closed", ref: ref, onHoverStart: () => {
82
- setOpen(true);
83
- onHoverStart === null || onHoverStart === void 0 ? void 0 : onHoverStart();
84
- }, onHoverEnd: () => {
85
- setOpen(false);
86
- onHoverEnd === null || onHoverEnd === void 0 ? void 0 : onHoverEnd();
87
- } }, { children: [_jsxs(Cover, Object.assign({ className: className, backgroundColor: coverColor, variants: coverMotion, onClick: () => {
88
- setOpen(!open);
89
- onCoverClick === null || onCoverClick === void 0 ? void 0 : onCoverClick();
90
- } }, { children: [icon && _jsx(CoverIcon, { children: icon }), isHidden && (_jsxs(_Fragment, { children: [_jsx(Hide, {}), _jsx(EyeOffFillIcon, { color: theme.color.foreground.neutralAlt, css: css `
91
- position: absolute;
92
- z-index: ${ZINDEX_HIDE};
93
- left: 50%;
94
- top: 50%;
95
- transform: translate(-50%, -50%);
96
- opacity: 0.8;
97
-
98
- width: 24px;
99
- height: 24px;
100
- ` })] })), _jsxs(CoverTexts, Object.assign({ variants: buttons ? textMotion : undefined }, { children: [_jsx(Subtitle, { children: subtitle }), _jsx(Subtitle, Object.assign({ css: css `
101
- font-weight: 800;
102
- ` }, { children: subtitleBold })), _jsx(CoverTitle, { children: title })] })), _jsx(BookGradient, {}), _jsx(Buttons, Object.assign({ variants: buttonsMotion, onClick: (e) => {
103
- // Button 클릭 시에도 CoverClick 이벤트가 발생하지 않도록 막습니다.
104
- e.stopPropagation();
105
- } }, { children: buttons }))] })), _jsx(Shelf, {})] })));
106
- }
107
- export default forwardRef(Book);
108
- const Container = styled(motion.div) `
109
- position: relative;
110
-
111
- width: 200px;
112
- height: ${BOOK_HEIGHT + HOVER_Y_OFFSET + SHELF_HEIGHT + BOOK_SHELF_GAP}px;
113
-
114
- padding-top: ${HOVER_Y_OFFSET}px;
115
- `;
116
- const Cover = styled(motion.div)(({ backgroundColor }) => css `
117
- height: ${BOOK_HEIGHT}px;
118
-
119
- border-radius: 8px;
120
- background: ${backgroundColor};
121
- box-shadow: ${shadows.shadow04};
122
-
123
- display: flex;
124
- flex-direction: row;
125
- gap: 12px;
126
- overflow: hidden;
127
-
128
- position: relative;
129
- bottom: 0px;
130
- z-index: ${ZINDEX_COVER};
131
- `);
132
- const CoverTexts = styled(motion.div) `
133
- width: 100%;
134
- height: 100%;
135
- padding: 12px 12px 12px 24px;
136
-
137
- position: absolute;
138
- top: 0;
139
- left: 0;
140
- z-index: ${ZINDEX_TEXT};
141
-
142
- display: flex;
143
- flex-direction: column;
144
- gap: 4px;
145
- `;
146
- const CoverTitle = styled.h1(({ theme }) => css `
147
- word-wrap: break-word;
148
- word-break: keep-all;
149
-
150
- margin: auto 0 8px 0;
151
- color: ${CARD_COLOR_PALETTE(theme).text};
152
-
153
- /* Alt/Paragraph/16px-Eb */
154
- font-family: ${theme.fontFamily.title};
155
- font-size: 16px;
156
- font-style: normal;
157
- font-weight: 800;
158
- line-height: 24px;
159
-
160
- /* multiline 말줄임말 적용 css */
161
- overflow: hidden;
162
- text-overflow: ellipsis;
163
- display: -webkit-box;
164
- -webkit-line-clamp: 7;
165
- -webkit-box-orient: vertical;
166
- `);
167
- const BookGradient = styled.div `
168
- width: 200px;
169
- height: ${BOOK_HEIGHT}px;
170
- background: linear-gradient(
171
- 180deg,
172
- rgba(255, 255, 255, 0) 0%,
173
- rgba(0, 0, 0, 0.75) 100%
174
- ),
175
- linear-gradient(
176
- 90deg,
177
- #999 0%,
178
- #fbfbfb 2%,
179
- #fbfbfb 4%,
180
- #cdcdcd 6%,
181
- #fbfbfb 10%,
182
- #fbfbfb 100%
183
- );
184
- mix-blend-mode: multiply;
185
-
186
- position: absolute;
187
- top: 0;
188
- left: 0;
189
- border-radius: 8px;
190
- z-index: ${ZINDEX_COVER};
191
- `;
192
- const Buttons = styled(motion.div)(({ theme }) => css `
193
- display: flex;
194
- gap: 8px;
195
- width: 100%;
196
- padding: 0 12px 0 24px;
197
-
198
- position: absolute;
199
- bottom: 12px;
200
- z-index: ${ZINDEX_BUTTONS};
201
-
202
- svg {
203
- color: ${CARD_COLOR_PALETTE(theme).text};
204
- }
205
- `);
206
- const Shelf = styled.div(({ theme }) => css `
207
- height: ${SHELF_HEIGHT}px;
208
- width: calc(100% + 32px);
209
- border-radius: 4px;
210
- background: ${theme.color.background.neutralAltActive};
211
-
212
- position: absolute;
213
- bottom: 0px;
214
- left: -16px;
215
- `);
216
- const Hide = styled.div(({ theme }) => css `
217
- position: absolute;
218
- left: 0;
219
- top: 0;
220
-
221
- width: 100%;
222
- height: 100%;
223
-
224
- background: ${theme.color.blanket.neutral};
225
- border-radius: 8px;
226
-
227
- z-index: ${ZINDEX_HIDE};
228
- `);
229
- const Subtitle = styled.div(({ theme }) => css `
230
- color: ${CARD_COLOR_PALETTE(theme).text};
231
-
232
- /* Default/Label/12px-Md */
233
- font-family: ${theme.fontFamily.ui};
234
- font-size: 12px;
235
- font-style: normal;
236
- font-weight: 500;
237
- line-height: 16px; /* 133.333% */
238
- `);
239
- const CoverIcon = styled.div(({ theme }) => css `
240
- position: absolute;
241
- top: 50%;
242
- left: 100px;
243
- transform: translateY(-50%);
244
-
245
- svg {
246
- width: 120px;
247
- height: 120px;
248
- color: ${theme.color.blanket.neutral};
249
- }
250
-
251
- z-index: ${ZINDEX_ICON};
252
- `);
@@ -1 +0,0 @@
1
- export { default } from "./Book";
@@ -1 +0,0 @@
1
- export { default } from "./Book";