@team-monolith/cds 1.60.2 → 1.61.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.
@@ -0,0 +1,17 @@
1
+ import React from "react";
2
+ export interface BookProps {
3
+ className?: string;
4
+ title?: string;
5
+ chapter?: string;
6
+ courseType?: 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
+ }
16
+ declare const _default: React.ForwardRefExoticComponent<BookProps & React.RefAttributes<HTMLDivElement>>;
17
+ export default _default;
@@ -0,0 +1,265 @@
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
+ backgrond: theme.color.background.primary,
58
+ text: theme.color.foreground.neutralAlt,
59
+ });
60
+ /**
61
+ * Framer Motion의 whileHover를 사용하지 않고 직접 애니메이션 상태를 관리합니다.
62
+ * 내부적으로는 간단한 애니메이션 상태 관리가 있으며, 필요한 경우
63
+ * open 상태를 전달하여 외부에서 관리할 수 있습니다.
64
+ *
65
+ * 사유1)
66
+ * 모바일에서는 hover가 없기 때문에 whileHover를 사용할 수 없습니다.
67
+ * 클릭(탭)을 통해 열고 닫을 수 있어야 합니다.
68
+ *
69
+ * 사유2)
70
+ * 관리/배부 드랍다운 Modal이 Card 위로 그려져 hoverEnd를 유발하기 때문에
71
+ * 특성 상황에서는 hoverEnd가 발생했어도 Card를 열고 있어야 합니다.
72
+ * @param props
73
+ * @param ref
74
+ * @returns
75
+ */
76
+ function Book(props, ref) {
77
+ const { className, title, chapter, courseType, isHidden = false, buttons, open: controlledOpen, onHoverStart, onHoverEnd, onCoverClick, icon, } = props;
78
+ const theme = useTheme();
79
+ const [open, setOpen] = useState(false);
80
+ return (_jsxs(Container, Object.assign({ initial: "closed", animate: (controlledOpen !== undefined ? controlledOpen : open)
81
+ ? "open"
82
+ : "closed", ref: ref, onHoverStart: () => {
83
+ setOpen(true);
84
+ onHoverStart === null || onHoverStart === void 0 ? void 0 : onHoverStart();
85
+ }, onHoverEnd: () => {
86
+ setOpen(false);
87
+ onHoverEnd === null || onHoverEnd === void 0 ? void 0 : onHoverEnd();
88
+ } }, { children: [_jsxs(Cover, Object.assign({ className: className, variants: coverMotion, onClick: () => {
89
+ setOpen(!open);
90
+ onCoverClick === null || onCoverClick === void 0 ? void 0 : onCoverClick();
91
+ } }, { children: [icon && _jsx(CoverIcon, { children: icon }), isHidden && (_jsxs(_Fragment, { children: [_jsx(Hide, {}), _jsx(EyeOffFillIcon, { color: theme.color.foreground.neutralAlt, css: css `
92
+ position: absolute;
93
+ z-index: ${ZINDEX_HIDE};
94
+ left: 50%;
95
+ top: 50%;
96
+ transform: translate(-50%, -50%);
97
+ opacity: 0.8;
98
+
99
+ width: 24px;
100
+ height: 24px;
101
+ ` })] })), _jsxs(CoverTexts, Object.assign({ variants: buttons ? textMotion : undefined }, { children: [_jsx(Chapter, { children: chapter }), _jsx(CourseType, { children: courseType }), _jsx(CoverTitle, { children: title })] })), _jsx(BookGradient, {}), _jsx(Buttons, Object.assign({ variants: buttonsMotion, onClick: (e) => {
102
+ // Button 클릭 시에도 CoverClick 이벤트가 발생하지 않도록 막습니다.
103
+ e.stopPropagation();
104
+ } }, { children: buttons }))] })), _jsx(Shelf, {})] })));
105
+ }
106
+ export default forwardRef(Book);
107
+ const Container = styled(motion.div) `
108
+ position: relative;
109
+
110
+ width: 200px;
111
+ height: ${BOOK_HEIGHT + HOVER_Y_OFFSET + SHELF_HEIGHT + BOOK_SHELF_GAP}px;
112
+
113
+ padding-top: ${HOVER_Y_OFFSET}px;
114
+ `;
115
+ const Cover = styled(motion.div)(({ theme }) => css `
116
+ height: ${BOOK_HEIGHT}px;
117
+
118
+ border-radius: 8px;
119
+ background: ${CARD_COLOR_PALETTE(theme).backgrond};
120
+ box-shadow: ${shadows.shadow04};
121
+
122
+ display: flex;
123
+ flex-direction: row;
124
+ gap: 12px;
125
+ overflow: hidden;
126
+
127
+ position: relative;
128
+ bottom: 0px;
129
+ z-index: ${ZINDEX_COVER};
130
+ `);
131
+ const CoverTexts = styled(motion.div) `
132
+ width: 100%;
133
+ height: 100%;
134
+ padding: 12px 12px 12px 24px;
135
+
136
+ position: absolute;
137
+ top: 0;
138
+ left: 0;
139
+ z-index: ${ZINDEX_TEXT};
140
+
141
+ display: flex;
142
+ flex-direction: column;
143
+ gap: 4px;
144
+ `;
145
+ const CoverTitle = styled.h1(({ theme }) => css `
146
+ word-wrap: break-word;
147
+ word-break: keep-all;
148
+
149
+ margin: auto 0 8px 0;
150
+ color: ${CARD_COLOR_PALETTE(theme).text};
151
+
152
+ /* Alt/Paragraph/16px-Eb */
153
+ font-family: ${theme.fontFamily.title};
154
+ font-size: 16px;
155
+ font-style: normal;
156
+ font-weight: 800;
157
+ line-height: 24px;
158
+
159
+ /* multiline 말줄임말 적용 css */
160
+ overflow: hidden;
161
+ text-overflow: ellipsis;
162
+ display: -webkit-box;
163
+ -webkit-line-clamp: 7;
164
+ -webkit-box-orient: vertical;
165
+ `);
166
+ const BookGradient = styled.div `
167
+ width: 200px;
168
+ height: ${BOOK_HEIGHT}px;
169
+ background: linear-gradient(
170
+ 180deg,
171
+ rgba(255, 255, 255, 0) 0%,
172
+ rgba(0, 0, 0, 0.75) 100%
173
+ ),
174
+ linear-gradient(
175
+ 90deg,
176
+ #999 0%,
177
+ #fbfbfb 2%,
178
+ #fbfbfb 4%,
179
+ #cdcdcd 6%,
180
+ #fbfbfb 10%,
181
+ #fbfbfb 100%
182
+ );
183
+ mix-blend-mode: multiply;
184
+
185
+ position: absolute;
186
+ top: 0;
187
+ left: 0;
188
+ border-radius: 8px;
189
+ z-index: ${ZINDEX_COVER};
190
+ `;
191
+ const Buttons = styled(motion.div)(({ theme }) => css `
192
+ display: flex;
193
+ gap: 8px;
194
+ width: 100%;
195
+ padding: 0 12px 0 24px;
196
+
197
+ position: absolute;
198
+ bottom: 12px;
199
+ z-index: ${ZINDEX_BUTTONS};
200
+
201
+ svg {
202
+ color: ${CARD_COLOR_PALETTE(theme).text};
203
+ }
204
+ `);
205
+ const Shelf = styled.div(({ theme }) => css `
206
+ height: ${SHELF_HEIGHT}px;
207
+ width: calc(100% + 32px);
208
+ border-radius: 4px;
209
+ background: ${theme.color.background.neutralAltActive};
210
+
211
+ position: absolute;
212
+ bottom: 0px;
213
+ left: -16px;
214
+ `);
215
+ const Hide = styled.div(({ theme }) => css `
216
+ position: absolute;
217
+ left: 0;
218
+ top: 0;
219
+
220
+ width: 100%;
221
+ height: 100%;
222
+
223
+ background: ${theme.color.blanket.neutral};
224
+ border-radius: 8px;
225
+
226
+ z-index: ${ZINDEX_HIDE};
227
+ `);
228
+ const Chapter = styled.div(({ theme }) => css `
229
+ color: ${CARD_COLOR_PALETTE(theme).text};
230
+
231
+ /* Default/Label/12px-Md */
232
+ font-family: ${theme.fontFamily.ui};
233
+ font-size: 12px;
234
+ font-style: normal;
235
+ font-weight: 500;
236
+ line-height: 16px; /* 133.333% */
237
+
238
+ opacity: 0.8;
239
+ `);
240
+ const CourseType = styled.div(({ theme }) => css `
241
+ color: ${CARD_COLOR_PALETTE(theme).text};
242
+
243
+ /* Default/Label/12px-Eb */
244
+ font-family: ${theme.fontFamily.ui};
245
+ font-size: 12px;
246
+ font-style: normal;
247
+ font-weight: 800;
248
+ line-height: 16px; /* 133.333% */
249
+
250
+ opacity: 0.8;
251
+ `);
252
+ const CoverIcon = styled.div(({ theme }) => css `
253
+ position: absolute;
254
+ top: 50%;
255
+ left: 100px;
256
+ transform: translateY(-50%);
257
+
258
+ svg {
259
+ width: 120px;
260
+ height: 120px;
261
+ color: ${theme.color.blanket.neutral};
262
+ }
263
+
264
+ z-index: ${ZINDEX_ICON};
265
+ `);
@@ -0,0 +1 @@
1
+ export { default } from "./Book";
@@ -0,0 +1 @@
1
+ export { default } from "./Book";
package/dist/index.d.ts CHANGED
@@ -3,6 +3,7 @@ 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";
6
7
  export { default as Button } from "./components/Button";
7
8
  export * from "./components/Button";
8
9
  export { default as CheckboxInput } from "./components/CheckboxInput";
package/dist/index.js CHANGED
@@ -3,6 +3,7 @@ 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";
6
7
  export { default as Button } from "./components/Button";
7
8
  export * from "./components/Button";
8
9
  export { default as CheckboxInput } from "./components/CheckboxInput";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@team-monolith/cds",
3
- "version": "1.60.2",
3
+ "version": "1.61.0",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "sideEffects": false,
@@ -13,6 +13,7 @@
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",
16
17
  "hex-to-css-filter": "^5.4.0",
17
18
  "lexical": "^0.12.4",
18
19
  "lodash": "^4.17.21",