@bigtablet/design-system 1.16.1 → 1.17.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.
- package/README.md +29 -0
- package/dist/index.css +0 -5
- package/dist/index.js +49 -74
- package/dist/vanilla/bigtablet.css +1060 -0
- package/dist/vanilla/bigtablet.css.map +1 -0
- package/dist/vanilla/bigtablet.js +778 -0
- package/dist/vanilla/bigtablet.min.css +1 -0
- package/dist/vanilla/bigtablet.min.css.map +1 -0
- package/dist/vanilla/bigtablet.min.js +35 -0
- package/dist/vanilla/examples/index.html +548 -0
- package/package.json +17 -4
package/README.md
CHANGED
|
@@ -20,6 +20,7 @@ Bigtablet의 공식 디자인 시스템으로, Foundation(디자인 토큰)과 C
|
|
|
20
20
|
- [주요 특징](#주요-특징)
|
|
21
21
|
- [설치](#설치)
|
|
22
22
|
- [빠른 시작](#빠른-시작)
|
|
23
|
+
- [Vanilla JS (HTML/CSS/JS)](#vanilla-js-htmlcssjs)
|
|
23
24
|
- [컴포넌트](#컴포넌트)
|
|
24
25
|
- [Foundation (디자인 토큰)](#foundation-디자인-토큰)
|
|
25
26
|
- [개발 가이드](#개발-가이드)
|
|
@@ -32,6 +33,7 @@ Bigtablet의 공식 디자인 시스템으로, Foundation(디자인 토큰)과 C
|
|
|
32
33
|
- **React 19 지원** - 최신 React 버전 완벽 지원
|
|
33
34
|
- **TypeScript** - 완전한 타입 안정성
|
|
34
35
|
- **Pure React / Next.js** - 프레임워크별 최적화된 번들 제공
|
|
36
|
+
- **Vanilla JS** - Thymeleaf, JSP 등 서버 템플릿 지원
|
|
35
37
|
- **디자인 토큰** - 일관된 색상, 타이포그래피, 간격 시스템
|
|
36
38
|
- **접근성(a11y)** - 키보드 네비게이션, 스크린 리더 호환
|
|
37
39
|
- **Storybook** - 인터랙티브 문서화
|
|
@@ -100,6 +102,33 @@ export default function Layout({ children }) {
|
|
|
100
102
|
|
|
101
103
|
---
|
|
102
104
|
|
|
105
|
+
## Vanilla JS (HTML/CSS/JS)
|
|
106
|
+
|
|
107
|
+
React 없이 **Thymeleaf, JSP, PHP** 등 서버 템플릿 환경에서 사용할 수 있습니다.
|
|
108
|
+
|
|
109
|
+
```html
|
|
110
|
+
<!-- CSS -->
|
|
111
|
+
<link rel="stylesheet" href="https://unpkg.com/@bigtablet/design-system/dist/vanilla/bigtablet.min.css">
|
|
112
|
+
|
|
113
|
+
<!-- JS (선택) -->
|
|
114
|
+
<script src="https://unpkg.com/@bigtablet/design-system/dist/vanilla/bigtablet.min.js"></script>
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
```html
|
|
118
|
+
<button class="bt-button bt-button--md bt-button--primary">버튼</button>
|
|
119
|
+
|
|
120
|
+
<div class="bt-text-field">
|
|
121
|
+
<label class="bt-text-field__label">이메일</label>
|
|
122
|
+
<div class="bt-text-field__wrap">
|
|
123
|
+
<input type="text" class="bt-text-field__input bt-text-field__input--outline bt-text-field__input--md">
|
|
124
|
+
</div>
|
|
125
|
+
</div>
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
👉 **[Vanilla JS 전체 문서 보기](./docs/VANILLA.md)**
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
103
132
|
## 컴포넌트
|
|
104
133
|
|
|
105
134
|
### General
|
package/dist/index.css
CHANGED
package/dist/index.js
CHANGED
|
@@ -316,10 +316,8 @@ var Select = ({
|
|
|
316
316
|
const [isOpen, setIsOpen] = React3.useState(false);
|
|
317
317
|
const [activeIndex, setActiveIndex] = React3.useState(-1);
|
|
318
318
|
const [dropUp, setDropUp] = React3.useState(false);
|
|
319
|
-
const [listPosition, setListPosition] = React3.useState({ top: 0, left: 0, width: 0 });
|
|
320
319
|
const wrapperRef = React3.useRef(null);
|
|
321
320
|
const controlRef = React3.useRef(null);
|
|
322
|
-
const listRef = React3.useRef(null);
|
|
323
321
|
const currentOption = React3.useMemo(
|
|
324
322
|
() => options.find((o) => o.value === currentValue) ?? null,
|
|
325
323
|
[options, currentValue]
|
|
@@ -334,10 +332,10 @@ var Select = ({
|
|
|
334
332
|
);
|
|
335
333
|
React3.useEffect(() => {
|
|
336
334
|
const onDocClick = (e) => {
|
|
337
|
-
|
|
338
|
-
if (wrapperRef.current
|
|
339
|
-
|
|
340
|
-
|
|
335
|
+
if (!wrapperRef.current) return;
|
|
336
|
+
if (!wrapperRef.current.contains(e.target)) {
|
|
337
|
+
setIsOpen(false);
|
|
338
|
+
}
|
|
341
339
|
};
|
|
342
340
|
document.addEventListener("mousedown", onDocClick);
|
|
343
341
|
return () => document.removeEventListener("mousedown", onDocClick);
|
|
@@ -408,33 +406,14 @@ var Select = ({
|
|
|
408
406
|
const idx = options.findIndex((o) => o.value === currentValue && !o.disabled);
|
|
409
407
|
setActiveIndex(idx >= 0 ? idx : Math.max(0, options.findIndex((o) => !o.disabled)));
|
|
410
408
|
}, [isOpen, options, currentValue]);
|
|
411
|
-
|
|
412
|
-
if (!controlRef.current) return;
|
|
409
|
+
React3.useLayoutEffect(() => {
|
|
410
|
+
if (!isOpen || !controlRef.current) return;
|
|
413
411
|
const rect = controlRef.current.getBoundingClientRect();
|
|
414
412
|
const listHeight = Math.min(options.length * 40, 288);
|
|
415
413
|
const spaceBelow = window.innerHeight - rect.bottom;
|
|
416
414
|
const spaceAbove = rect.top;
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
setListPosition({
|
|
420
|
-
top: shouldDropUp ? rect.top - listHeight - 4 : rect.bottom + 4,
|
|
421
|
-
left: rect.left,
|
|
422
|
-
width: rect.width
|
|
423
|
-
});
|
|
424
|
-
}, [options.length]);
|
|
425
|
-
React3.useLayoutEffect(() => {
|
|
426
|
-
if (!isOpen) return;
|
|
427
|
-
updatePosition();
|
|
428
|
-
}, [isOpen, updatePosition]);
|
|
429
|
-
React3.useEffect(() => {
|
|
430
|
-
if (!isOpen) return;
|
|
431
|
-
window.addEventListener("scroll", updatePosition, true);
|
|
432
|
-
window.addEventListener("resize", updatePosition);
|
|
433
|
-
return () => {
|
|
434
|
-
window.removeEventListener("scroll", updatePosition, true);
|
|
435
|
-
window.removeEventListener("resize", updatePosition);
|
|
436
|
-
};
|
|
437
|
-
}, [isOpen, updatePosition]);
|
|
415
|
+
setDropUp(spaceBelow < listHeight && spaceAbove > spaceBelow);
|
|
416
|
+
}, [isOpen, options.length]);
|
|
438
417
|
const rootClassName = ["select", className ?? ""].filter(Boolean).join(" ");
|
|
439
418
|
const controlClassName = [
|
|
440
419
|
"select_control",
|
|
@@ -443,50 +422,10 @@ var Select = ({
|
|
|
443
422
|
isOpen && "is_open",
|
|
444
423
|
disabled && "is_disabled"
|
|
445
424
|
].filter(Boolean).join(" ");
|
|
446
|
-
const
|
|
447
|
-
"
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
id: `${selectId}_listbox`,
|
|
451
|
-
role: "listbox",
|
|
452
|
-
className: `select_list select_list_portal${dropUp ? " select_list_up" : ""}`,
|
|
453
|
-
style: {
|
|
454
|
-
position: "fixed",
|
|
455
|
-
top: listPosition.top,
|
|
456
|
-
left: listPosition.left,
|
|
457
|
-
width: listPosition.width
|
|
458
|
-
},
|
|
459
|
-
children: options.map((opt, i) => {
|
|
460
|
-
const selected = currentValue === opt.value;
|
|
461
|
-
const active = i === activeIndex;
|
|
462
|
-
const optionClassName = [
|
|
463
|
-
"select_option",
|
|
464
|
-
selected && "is_selected",
|
|
465
|
-
active && "is_active",
|
|
466
|
-
opt.disabled && "is_disabled"
|
|
467
|
-
].filter(Boolean).join(" ");
|
|
468
|
-
return /* @__PURE__ */ jsxs(
|
|
469
|
-
"li",
|
|
470
|
-
{
|
|
471
|
-
role: "option",
|
|
472
|
-
"aria-selected": selected,
|
|
473
|
-
className: optionClassName,
|
|
474
|
-
onMouseEnter: () => !opt.disabled && setActiveIndex(i),
|
|
475
|
-
onClick: () => {
|
|
476
|
-
if (opt.disabled) return;
|
|
477
|
-
setValue(opt.value);
|
|
478
|
-
setIsOpen(false);
|
|
479
|
-
},
|
|
480
|
-
children: [
|
|
481
|
-
/* @__PURE__ */ jsx("span", { children: opt.label }),
|
|
482
|
-
selected && /* @__PURE__ */ jsx(Check, { size: 16, "aria-hidden": "true" })
|
|
483
|
-
]
|
|
484
|
-
},
|
|
485
|
-
opt.value
|
|
486
|
-
);
|
|
487
|
-
})
|
|
488
|
-
}
|
|
489
|
-
);
|
|
425
|
+
const listClassName = [
|
|
426
|
+
"select_list",
|
|
427
|
+
dropUp && "select_list_up"
|
|
428
|
+
].filter(Boolean).join(" ");
|
|
490
429
|
return /* @__PURE__ */ jsxs("div", { ref: wrapperRef, className: rootClassName, style: fullWidth ? { width: "100%" } : void 0, children: [
|
|
491
430
|
label && /* @__PURE__ */ jsx("label", { htmlFor: selectId, className: "select_label", children: label }),
|
|
492
431
|
/* @__PURE__ */ jsxs(
|
|
@@ -515,7 +454,43 @@ var Select = ({
|
|
|
515
454
|
]
|
|
516
455
|
}
|
|
517
456
|
),
|
|
518
|
-
isOpen &&
|
|
457
|
+
isOpen && /* @__PURE__ */ jsx(
|
|
458
|
+
"ul",
|
|
459
|
+
{
|
|
460
|
+
id: `${selectId}_listbox`,
|
|
461
|
+
role: "listbox",
|
|
462
|
+
className: listClassName,
|
|
463
|
+
children: options.map((opt, i) => {
|
|
464
|
+
const selected = currentValue === opt.value;
|
|
465
|
+
const active = i === activeIndex;
|
|
466
|
+
const optionClassName = [
|
|
467
|
+
"select_option",
|
|
468
|
+
selected && "is_selected",
|
|
469
|
+
active && "is_active",
|
|
470
|
+
opt.disabled && "is_disabled"
|
|
471
|
+
].filter(Boolean).join(" ");
|
|
472
|
+
return /* @__PURE__ */ jsxs(
|
|
473
|
+
"li",
|
|
474
|
+
{
|
|
475
|
+
role: "option",
|
|
476
|
+
"aria-selected": selected,
|
|
477
|
+
className: optionClassName,
|
|
478
|
+
onMouseEnter: () => !opt.disabled && setActiveIndex(i),
|
|
479
|
+
onClick: () => {
|
|
480
|
+
if (opt.disabled) return;
|
|
481
|
+
setValue(opt.value);
|
|
482
|
+
setIsOpen(false);
|
|
483
|
+
},
|
|
484
|
+
children: [
|
|
485
|
+
/* @__PURE__ */ jsx("span", { children: opt.label }),
|
|
486
|
+
selected && /* @__PURE__ */ jsx(Check, { size: 16, "aria-hidden": "true" })
|
|
487
|
+
]
|
|
488
|
+
},
|
|
489
|
+
opt.value
|
|
490
|
+
);
|
|
491
|
+
})
|
|
492
|
+
}
|
|
493
|
+
)
|
|
519
494
|
] });
|
|
520
495
|
};
|
|
521
496
|
var Switch = ({
|