@nine-lab/nine-ux 0.1.141 → 0.1.143

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.
@@ -1,162 +1,388 @@
1
1
  @charset "utf-8";
2
2
 
3
3
  /* ==========================================================================
4
- [1] 메인 컴포넌트 (SideMenu - 호스트 스타일)
4
+ [MAIN INTEGRATED SIDE MENU COMPONENT]
5
+ 부모가 Shadow DOM이므로 최상단은 :host로 시작해야 외부 분리 파일에서도
6
+ 컴포넌트 자체를 정상적으로 인식합니다.
5
7
  ========================================================================== */
6
- :host {
7
- display: flex;
8
- flex-direction: column;
9
- }
8
+ :host(nine-side-menu) {
9
+ opacity: 0.95;
10
+ position: fixed;
11
+ top: 0;
12
+ bottom: 0;
13
+ left: 0;
14
+ display: block;
15
+ z-index: 1002;
16
+ color: #fff;
17
+ font-weight: 200;
18
+ background: #181A31;
19
+ --background: #333;
20
+ -webkit-box-shadow: 4px 4px 10px rgba(69, 65, 78, .06);
21
+ -moz-box-shadow: 4px 4px 10px rgba(69, 65, 78, .06);
22
+ box-shadow: 4px 4px 10px rgba(69, 65, 78, .06);
23
+ overflow: hidden;
24
+ width: var(--min-width);
25
+ --transition: all .3s;
26
+ transition: width 0.2s ease-in-out;
10
27
 
11
- /* ==========================================================================
12
- [2] 헤더 영역 (SideMenuHead)
13
- ========================================================================== */
14
- nine-side-menu-head {
15
- display: flex;
16
- align-items: center;
17
- padding: 10px;
18
- height: 40px;
19
- justify-content: center;
20
- }
28
+ /* 상태별 전체 너비 및 바운스 애니메이션 */
29
+ &.collapse {
30
+ width: var(--min-width);
31
+ }
32
+ &:not(.collapse) {
33
+ width: var(--max-width);
34
+ }
35
+ &.collapse.hover {
36
+ width: var(--max-width);
37
+ animation: nineMenuBounce 0.5s ease-in-out 1;
38
+ }
21
39
 
22
- nine-side-menu-head .logo-box {
23
- color: #f0f0f0;
24
- white-space: nowrap;
25
- overflow: hidden;
26
- text-overflow: ellipsis;
27
- text-align: center;
28
- width: 100%;
29
- }
40
+ /* 본문 스크롤 감싸는 영역 (Shadow DOM 내부 div) */
41
+ .body-wrapper {
42
+ display: flex;
43
+ flex-direction: column;
44
+ align-items: flex-start;
45
+ margin-top: 32px;
46
+ height: 100%;
47
+ overflow: auto;
48
+ }
30
49
 
31
- /* 접힌 상태일 때 로고 너비 축소 */
32
- nine-side-menu.collapse nine-side-menu-head .logo-box {
33
- width: 0;
34
- transition: width 0.5s ease-out;
35
- }
36
- /* 펼쳐진 상태이거나 호버 시 로고 보임 */
37
- nine-side-menu:not(.collapse) nine-side-menu-head .logo-box,
38
- nine-side-menu.hover nine-side-menu-head .logo-box {
39
- width: 100%;
40
- }
50
+ /* --------------------------------------------------------------------------
51
+ [1] 헤더 영역 (nine-side-menu-head)
52
+ -------------------------------------------------------------------------- */
53
+ & ::slotted(nine-side-menu-head) {
54
+ display: flex;
55
+ align-items: center;
56
+ padding: 10px;
57
+ height: 40px;
58
+ justify-content: center;
41
59
 
42
- nine-side-menu-head .icon-box {
43
- display: flex;
44
- align-items: center;
45
- overflow: hidden;
46
- }
60
+ .logo-box {
61
+ color: #f0f0f0;
62
+ white-space: nowrap;
63
+ overflow: hidden;
64
+ text-overflow: ellipsis;
65
+ text-align: center;
66
+ width: 100%;
67
+ }
47
68
 
48
- nine-side-menu-head .icon {
49
- fill: var(--icon-color, #eee);
50
- cursor: pointer;
51
- transition: opacity 0.3s ease-in-out;
52
- }
69
+ .icon-box {
70
+ display: flex;
71
+ align-items: center;
72
+ overflow: hidden;
53
73
 
54
- nine-side-menu-head .icon-box svg {
55
- display: none;
56
- opacity: 0;
57
- transition: opacity 0.3s ease-in-out;
58
- }
74
+ svg {
75
+ display: none;
76
+ opacity: 0;
77
+ transition: opacity 0.3s ease-in-out;
78
+ }
79
+ }
59
80
 
60
- /* 케이스 A: 메뉴가 접혀있을 때 -> 1번 노출 */
61
- nine-side-menu.collapse nine-side-menu-head .icon:nth-of-type(1) {
62
- display: block;
63
- opacity: 1;
64
- }
81
+ .icon {
82
+ fill: var(--icon-color, #eee);
83
+ cursor: pointer;
84
+ transition: opacity 0.3s ease-in-out;
85
+ }
86
+ }
65
87
 
66
- /* 케이스 B: 메뉴가 펼쳐져 있을 때 -> 1번 숨김, 2번 노출 */
67
- nine-side-menu:not(.collapse) nine-side-menu-head .icon:nth-of-type(1) {
68
- display: none;
69
- opacity: 0;
70
- }
71
- nine-side-menu:not(.collapse) nine-side-menu-head .icon:nth-of-type(2) {
72
- display: block;
73
- opacity: 1;
74
- }
75
- nine-side-menu:not(.collapse) nine-side-menu-head .icon:nth-of-type(3) {
76
- display: none;
77
- opacity: 0;
78
- }
88
+ /* 헤더 상태별 유기적 스위칭 명세 */
89
+ &.collapse ::slotted(nine-side-menu-head) .logo-box {
90
+ width: 0;
91
+ justify-content: center;
92
+ transition: width 0.5s ease-out;
93
+ }
94
+ &.collapse.hover ::slotted(nine-side-menu-head) .logo-box,
95
+ &:not(.collapse) ::slotted(nine-side-menu-head) .logo-box {
96
+ width: 100%;
97
+ justify-content: space-between;
98
+ }
79
99
 
80
- /* 케이스 C: 아이콘 박스 호버 시 -> 2번 숨김, 3번 노출 */
81
- nine-side-menu:not(.collapse) nine-side-menu-head .icon-box:hover .icon:nth-of-type(2) {
82
- display: none;
83
- opacity: 0;
84
- }
85
- nine-side-menu:not(.collapse) nine-side-menu-head .icon-box:hover .icon:nth-of-type(3) {
86
- display: block;
87
- opacity: 1;
88
- }
100
+ &.collapse ::slotted(nine-side-menu-head) .icon:nth-of-type(1) {
101
+ display: block;
102
+ opacity: 1;
103
+ }
89
104
 
90
- /* ==========================================================================
91
- [3] 푸터 영역 (SideMenuFoot)
92
- ========================================================================== */
93
- nine-side-menu-foot {
94
- display: flex;
95
- align-items: center;
96
- padding: 10px;
97
- height: 40px;
98
- justify-content: center;
99
- }
105
+ &:not(.collapse) {
106
+ ::slotted(nine-side-menu-head) .icon:nth-of-type(1) {
107
+ display: none;
108
+ opacity: 0;
109
+ }
110
+ ::slotted(nine-side-menu-head) .icon:nth-of-type(2) {
111
+ display: block;
112
+ opacity: 1;
113
+ }
114
+ ::slotted(nine-side-menu-head) .icon:nth-of-type(3) {
115
+ display: none;
116
+ opacity: 0;
117
+ }
118
+ ::slotted(nine-side-menu-head) .icon-box:hover {
119
+ .icon:nth-of-type(2) {
120
+ display: none;
121
+ opacity: 0;
122
+ }
123
+ .icon:nth-of-type(3) {
124
+ display: block;
125
+ opacity: 1;
126
+ }
127
+ }
128
+ }
100
129
 
101
- nine-side-menu-foot .logo-box {
102
- color: #f0f0f0;
103
- white-space: nowrap;
104
- overflow: hidden;
105
- text-overflow: ellipsis;
106
- text-align: center;
107
- width: 100%;
108
- }
130
+ /* --------------------------------------------------------------------------
131
+ [2] 바디 컨테이너 영역 (nine-side-menu-body)
132
+ -------------------------------------------------------------------------- */
133
+ & ::slotted(nine-side-menu-body) {
134
+ display: flex;
135
+ align-items: center;
136
+ padding: 10px;
137
+ height: 40px;
138
+ }
109
139
 
110
- nine-side-menu.collapse nine-side-menu-foot .logo-box {
111
- width: 0;
112
- transition: width 0.5s ease-out;
113
- }
114
- nine-side-menu:not(.collapse) nine-side-menu-foot .logo-box,
115
- nine-side-menu.hover nine-side-menu-foot .logo-box {
116
- width: 100%;
117
- }
140
+ /* --------------------------------------------------------------------------
141
+ [3] 푸터 영역 (nine-side-menu-foot)
142
+ -------------------------------------------------------------------------- */
143
+ & ::slotted(nine-side-menu-foot) {
144
+ display: flex;
145
+ align-items: center;
146
+ padding: 10px;
147
+ height: 40px;
148
+ justify-content: center;
118
149
 
119
- nine-side-menu-foot .icon-box {
120
- display: flex;
121
- align-items: center;
122
- overflow: hidden;
123
- }
150
+ .logo-box {
151
+ color: #f0f0f0;
152
+ white-space: nowrap;
153
+ overflow: hidden;
154
+ text-overflow: ellipsis;
155
+ text-align: center;
156
+ width: 100%;
157
+ }
124
158
 
125
- nine-side-menu-foot .icon {
126
- fill: var(--icon-color, #eee);
127
- cursor: pointer;
128
- transition: opacity 0.3s ease-in-out;
129
- }
159
+ .icon-box {
160
+ display: flex;
161
+ align-items: center;
162
+ overflow: hidden;
130
163
 
131
- nine-side-menu-foot .icon-box svg {
132
- display: none;
133
- opacity: 0;
134
- transition: opacity 0.3s ease-in-out;
135
- }
164
+ svg {
165
+ display: none;
166
+ opacity: 0;
167
+ transition: opacity 0.3s ease-in-out;
168
+ }
169
+ }
136
170
 
137
- nine-side-menu.collapse nine-side-menu-foot .icon:nth-of-type(1) {
138
- display: block;
139
- opacity: 1;
140
- }
171
+ .icon {
172
+ fill: var(--icon-color, #eee);
173
+ cursor: pointer;
174
+ transition: opacity 0.3s ease-in-out;
175
+ }
176
+ }
141
177
 
142
- nine-side-menu:not(.collapse) nine-side-menu-foot .icon:nth-of-type(1) {
143
- display: none;
144
- opacity: 0;
145
- }
146
- nine-side-menu:not(.collapse) nine-side-menu-foot .icon:nth-of-type(2) {
147
- display: block;
148
- opacity: 1;
178
+ /* 푸터 상태별 유기적 스위칭 명세 */
179
+ &.collapse ::slotted(nine-side-menu-foot) .logo-box {
180
+ width: 0;
181
+ justify-content: center;
182
+ transition: width 0.5s ease-out;
183
+ }
184
+ &.collapse.hover ::slotted(nine-side-menu-foot) .logo-box,
185
+ &:not(.collapse) ::slotted(nine-side-menu-foot) .logo-box {
186
+ width: 100%;
187
+ justify-content: space-between;
188
+ }
189
+
190
+ &.collapse ::slotted(nine-side-menu-foot) .icon:nth-of-type(1) {
191
+ display: block;
192
+ opacity: 1;
193
+ }
194
+
195
+ &:not(.collapse) {
196
+ ::slotted(nine-side-menu-foot) .icon:nth-of-type(1) {
197
+ display: none;
198
+ opacity: 0;
199
+ }
200
+ ::slotted(nine-side-menu-foot) .icon:nth-of-type(2) {
201
+ display: block;
202
+ opacity: 1;
203
+ }
204
+ ::slotted(nine-side-menu-foot) .icon:nth-of-type(3) {
205
+ display: none;
206
+ opacity: 0;
207
+ }
208
+ ::slotted(nine-side-menu-foot) .icon-box:hover {
209
+ .icon:nth-of-type(2) {
210
+ display: none;
211
+ opacity: 0;
212
+ }
213
+ .icon:nth-of-type(3) {
214
+ display: block;
215
+ opacity: 1;
216
+ }
217
+ }
218
+ }
219
+
220
+ /* --------------------------------------------------------------------------
221
+ [4] 메뉴 아이템 목록 영역 (nine-side-menu-item)
222
+ -------------------------------------------------------------------------- */
223
+ & ::slotted(nine-side-menu-item) {
224
+ padding: 0;
225
+ margin: 0;
226
+ position: relative;
227
+ display: flex;
228
+ align-items: unset;
229
+ background-color: unset;
230
+ cursor: pointer;
231
+ max-height: 64px;
232
+ opacity: 1;
233
+ transition: opacity 0.5s ease-out, height 0.5s ease-out, max-height 0.5s ease-in-out;
234
+
235
+ &.group {
236
+ height: 48px;
237
+
238
+ .menubar { font-weight: 700; }
239
+ li { list-style-type: none; }
240
+ }
241
+
242
+ &:not(.group) {
243
+ height: 32px;
244
+
245
+ .menubar { font-weight: 400; }
246
+
247
+ li {
248
+ list-style-type: unset;
249
+
250
+ &::before {
251
+ content: '•';
252
+ margin-right: 8px;
253
+ margin-left: 16px;
254
+ }
255
+ }
256
+
257
+ .icon { display: none; }
258
+ }
259
+
260
+ &.active {
261
+ color: #9FF2FF;
262
+ }
263
+
264
+ &:hover {
265
+ filter: brightness(90%);
266
+ }
267
+
268
+ /* 아코디언 하이드 규칙 */
269
+ &.hide {
270
+ max-height: 0;
271
+ height: 0 !important;
272
+ opacity: 0;
273
+ padding: 0;
274
+ overflow: hidden;
275
+ }
276
+
277
+ .menubar {
278
+ display: inline-block;
279
+ white-space: nowrap;
280
+ overflow: hidden;
281
+ text-overflow: ellipsis;
282
+ transition: height 0.5s ease-out, padding 0.5s ease-out, width 0.5s ease-out, padding-left 0.5s ease-out;
283
+ text-align: left;
284
+ margin-left: 8px;
285
+ }
286
+
287
+ li {
288
+ display: flex;
289
+ align-items: center;
290
+ padding: 0;
291
+ margin: 0;
292
+ position: relative;
293
+ color: unset;
294
+ font-weight: bold;
295
+ width: calc(100% + 36px);
296
+ }
297
+
298
+ .icon {
299
+ position: relative;
300
+ left: 0;
301
+ width: 16px;
302
+ height: 16px;
303
+ background-repeat: no-repeat;
304
+ background-position: center center;
305
+ background-size: contain;
306
+ margin-left: 8px;
307
+ transition: width 0.5s ease-out, height 0.5s ease-out;
308
+
309
+ &.icon-home {
310
+ background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="lightgray" viewBox="0 0 16 16"><path d="M6.5 14.5v-3.505c0-.245.25-.495.5-.495h2c.25 0 .5.25.5.5v3.5a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5v-7a.5.5 0 0 0-.146-.354L13 5.793V2.5a.5.5 0 0 0-.5-.5h-1a.5.5 0 0 0-.5.5v1.293L8.354 1.146a.5.5 0 0 0-.708 0l-6 6A.5.5 0 0 0 1.5 7.5v7a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5"/></svg>');
311
+ }
312
+
313
+ &.icon-base {
314
+ background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="white" viewBox="0 0 16 16"><path d="M0 .5A.5.5 0 0 1 .5 0h15a.5.5 0 0 1 .5.5v3a.5.5 0 0 1-.5.5H14v2h1.5a.5.5 0 0 1 .5.5v3a.5.5 0 0 1-.5.5H14v2h1.5a.5.5 0 0 1-.5.5H.5a.5.5 0 0 1-.5-.5v-3a.5.5 0 0 1 .5-.5H2v-2H.5a.5.5 0 0 1-.5-.5v-3A.5.5 0 0 1 .5 6H2V4H.5a.5.5 0 0 1-.5-.5zM3 4v2h4.5V4zm5.5 0v2H13V4zM3 10v2h4.5v-2zm5.5 0v2H13v-2zM1 1v2h3.5V1zm4.5 0v2h5V1zm6 0v2H15V1zM1 7v2h3.5V7zm4.5 0v2h5V7zm6 0v2H15V7zM1 13v2h3.5v-2zm4.5 0v2h5v-2zm6 0v2H15v-2z"/></svg>');
315
+ }
316
+ }
317
+
318
+ .expand-icon {
319
+ width: 8px;
320
+ height: 8px;
321
+ position: absolute;
322
+ right: 16px;
323
+ background-repeat: no-repeat;
324
+ background-position: center;
325
+ background-size: auto;
326
+ background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="8" height="8" fill="white" class="bi bi-caret-down-fill" viewBox="0 0 16 16"><path d="M7.247 11.14 2.451 5.658C1.885 5.013 2.345 4 3.204 4h9.592a1 1 0 0 1 .753 1.659l-4.796 5.48a1 1 0 0 1-1.506 0z"/></svg>');
327
+ animation: nineMenuRotateOut 0.3s ease-in-out forwards;
328
+ }
329
+
330
+ &:not(.expand) .expand-icon {
331
+ transform: rotate(180deg);
332
+ animation: nineMenuRotateIn 0.3s ease-in-out forwards;
333
+ }
334
+
335
+ a {
336
+ color: #999;
337
+ text-decoration: none;
338
+
339
+ &:hover {
340
+ color: #ccc;
341
+ text-decoration: underline;
342
+ }
343
+ }
344
+ }
345
+
346
+ /* 아이템 상태별 축소 명세 */
347
+ &.collapse:not(.hover) {
348
+ ::slotted(nine-side-menu-item:not(.group)) {
349
+ opacity: 0;
350
+ height: 0;
351
+ }
352
+ ::slotted(nine-side-menu-item) .menubar {
353
+ width: 0;
354
+ padding-left: unset;
355
+ display: none;
356
+ }
357
+ ::slotted(nine-side-menu-item) li {
358
+ justify-content: center;
359
+ }
360
+ ::slotted(nine-side-menu-item) .icon {
361
+ width: 20px;
362
+ height: 20px;
363
+ margin-left: 4px;
364
+ }
365
+ ::slotted(nine-side-menu-item) .expand-icon {
366
+ display: none;
367
+ }
368
+ }
149
369
  }
150
- nine-side-menu:not(.collapse) nine-side-menu-foot .icon:nth-of-type(3) {
151
- display: none;
152
- opacity: 0;
370
+
371
+ /* ==========================================================================
372
+ [GLOBAL KEYFRAMES]
373
+ 키프레임 애니메이션은 중첩 안쪽에 있으면 동작하지 않으므로 바깥에 둡니다.
374
+ ========================================================================== */
375
+ @keyframes nineMenuBounce {
376
+ 0%, 20%, 50%, 80%, 100% { transform: translateX(0); }
377
+ 40% { transform: translateX(-10px); }
378
+ 60% { left: -10px; transform: translateX(10px); }
153
379
  }
154
380
 
155
- nine-side-menu:not(.collapse) nine-side-menu-foot .icon-box:hover .icon:nth-of-type(2) {
156
- display: none;
157
- opacity: 0;
381
+ @keyframes nineMenuRotateIn {
382
+ from { transform: rotate(0deg); }
383
+ to { transform: rotate(180deg); }
158
384
  }
159
- nine-side-menu:not(.collapse) nine-side-menu-foot .icon-box:hover .icon:nth-of-type(3) {
160
- display: block;
161
- opacity: 1;
385
+ @keyframes nineMenuRotateOut {
386
+ from { transform: rotate(180deg); }
387
+ to { transform: rotate(0deg); }
162
388
  }