@bigtablet/design-system 2.0.7 → 2.2.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/dist/index.css CHANGED
@@ -1205,6 +1205,70 @@
1205
1205
  pointer-events: none;
1206
1206
  }
1207
1207
 
1208
+ /* src/ui/otp-input/style.scss */
1209
+ @keyframes skeleton_loading {
1210
+ 0% {
1211
+ background-position: 100% 0;
1212
+ }
1213
+ 100% {
1214
+ background-position: 0 0;
1215
+ }
1216
+ }
1217
+ .otp_input {
1218
+ display: inline-flex;
1219
+ flex-direction: column;
1220
+ align-items: flex-start;
1221
+ }
1222
+ .otp_input_boxes {
1223
+ display: flex;
1224
+ gap: 6px;
1225
+ }
1226
+ .otp_input_box {
1227
+ width: 48px;
1228
+ min-height: 56px;
1229
+ padding: 4px;
1230
+ border: 1px solid #E5E5E5;
1231
+ border-radius: 12px;
1232
+ background: none;
1233
+ font-family: "Pretendard", sans-serif;
1234
+ font-size: 14px;
1235
+ font-weight: 400;
1236
+ line-height: 20px;
1237
+ color: #121212;
1238
+ text-align: center;
1239
+ outline: none;
1240
+ transition: border-color 0.1s ease-in-out, box-shadow 0.1s ease-in-out;
1241
+ }
1242
+ .otp_input_box:hover:not(:disabled):not(:focus) {
1243
+ border-color: #B3B3B3;
1244
+ }
1245
+ .otp_input_box:focus {
1246
+ border-color: #121212;
1247
+ border-width: 2px;
1248
+ padding: calc(4px - 1px);
1249
+ }
1250
+ .otp_input_box_disabled {
1251
+ cursor: not-allowed;
1252
+ color: #666666;
1253
+ }
1254
+ .otp_input_box_error {
1255
+ border-color: #EF4444;
1256
+ }
1257
+ .otp_input_box_error:focus {
1258
+ border-color: #EF4444;
1259
+ }
1260
+ .otp_input_supporting {
1261
+ padding-top: 4px;
1262
+ font-family: "Pretendard", sans-serif;
1263
+ font-size: 12px;
1264
+ font-weight: 400;
1265
+ line-height: 16px;
1266
+ color: #666666;
1267
+ }
1268
+ .otp_input_supporting_error {
1269
+ color: #EF4444;
1270
+ }
1271
+
1208
1272
  /* src/ui/modal/style.scss */
1209
1273
  @keyframes skeleton_loading {
1210
1274
  0% {
@@ -1459,16 +1523,22 @@
1459
1523
  display: inline-flex;
1460
1524
  flex-direction: column;
1461
1525
  width: 100%;
1462
- gap: 4px;
1463
1526
  font-family: "Pretendard", sans-serif;
1464
1527
  }
1465
1528
  .select_label {
1529
+ position: absolute;
1530
+ top: -8px;
1531
+ left: 16px;
1532
+ padding: 0 4px;
1533
+ background: var(--select-surface, #FFFFFF);
1534
+ font-family: "Pretendard", sans-serif;
1466
1535
  font-size: 12px;
1467
1536
  font-weight: 400;
1468
1537
  line-height: 16px;
1469
- letter-spacing: 0px;
1470
- font-weight: 500;
1538
+ letter-spacing: 0.32px;
1471
1539
  color: #121212;
1540
+ white-space: nowrap;
1541
+ pointer-events: none;
1472
1542
  }
1473
1543
  .select_control {
1474
1544
  width: 100%;
@@ -1478,8 +1548,8 @@
1478
1548
  gap: 8px;
1479
1549
  cursor: pointer;
1480
1550
  color: #121212;
1481
- background: #FFFFFF;
1482
- border-radius: 8px;
1551
+ background: var(--select-surface, #FFFFFF);
1552
+ border-radius: 12px;
1483
1553
  transition:
1484
1554
  border-color 0.2s ease-in-out,
1485
1555
  box-shadow 0.2s ease-in-out,
@@ -1514,7 +1584,7 @@
1514
1584
  background: rgb(251.7, 251.7, 251.7);
1515
1585
  }
1516
1586
  .select_variant_filled.is_open {
1517
- background: #FFFFFF;
1587
+ background: var(--select-surface, #FFFFFF);
1518
1588
  border-color: #121212;
1519
1589
  box-shadow: 0 0 0 3px rgba(18, 18, 18, 0.15);
1520
1590
  }
@@ -1526,11 +1596,12 @@
1526
1596
  background: rgba(0, 0, 0, 0.05);
1527
1597
  }
1528
1598
  .select_variant_ghost.is_open {
1529
- background: #FFFFFF;
1599
+ background: var(--select-surface, #FFFFFF);
1530
1600
  border-color: #121212;
1531
1601
  box-shadow: 0 0 0 3px rgba(18, 18, 18, 0.15);
1532
1602
  }
1533
1603
  .select_size_sm {
1604
+ min-height: 40px;
1534
1605
  padding: 8px 16px;
1535
1606
  font-size: 12px;
1536
1607
  font-weight: 400;
@@ -1538,6 +1609,7 @@
1538
1609
  letter-spacing: 0px;
1539
1610
  }
1540
1611
  .select_size_md {
1612
+ min-height: 52px;
1541
1613
  padding: 12px 20px;
1542
1614
  font-size: 15px;
1543
1615
  font-weight: 400;
@@ -1545,6 +1617,7 @@
1545
1617
  letter-spacing: 0px;
1546
1618
  }
1547
1619
  .select_size_lg {
1620
+ min-height: 52px;
1548
1621
  padding: 16px 24px;
1549
1622
  font-size: 16px;
1550
1623
  font-weight: 400;
@@ -1639,6 +1712,10 @@
1639
1712
  cursor: not-allowed;
1640
1713
  color: #888888;
1641
1714
  }
1715
+ .select:has(.select_control:disabled) .select_label,
1716
+ .select:has(.select_control.is_disabled) .select_label {
1717
+ color: rgba(26, 26, 26, 0.38);
1718
+ }
1642
1719
 
1643
1720
  /* src/ui/spinner/style.scss */
1644
1721
  @keyframes skeleton_loading {
@@ -1790,12 +1867,22 @@
1790
1867
  padding: 4px 0;
1791
1868
  border: 1px solid #E5E5E5;
1792
1869
  border-radius: 12px;
1793
- background: #FFFFFF;
1870
+ background: inherit;
1794
1871
  transition: border-color 0.2s ease-in-out;
1795
1872
  }
1796
1873
  .text_field_container:hover {
1797
1874
  border-color: #B3B3B3;
1798
1875
  }
1876
+ .text_field_size_sm .text_field_container {
1877
+ min-height: 40px;
1878
+ }
1879
+ .text_field_size_sm .text_field_input_wrap {
1880
+ padding: 8px 16px;
1881
+ }
1882
+ .text_field_size_sm .text_field_input {
1883
+ font-size: 14px;
1884
+ line-height: 20px;
1885
+ }
1799
1886
  .text_field_container:focus-within {
1800
1887
  border-color: #121212;
1801
1888
  }
@@ -1804,7 +1891,7 @@
1804
1891
  top: -8px;
1805
1892
  left: 16px;
1806
1893
  padding: 0 4px;
1807
- background: #FFFFFF;
1894
+ background: var(--text-field-surface, #FFFFFF);
1808
1895
  font-family: "Pretendard", sans-serif;
1809
1896
  font-size: 12px;
1810
1897
  font-weight: 400;
package/dist/index.d.ts CHANGED
@@ -748,6 +748,37 @@ interface ListItemProps extends Omit<React$1.HTMLAttributes<HTMLDivElement>, "on
748
748
  */
749
749
  declare const ListItem: ({ overline, label, supportingText, metadata, leadingElement, trailingElement, alignment, disabled, onClick, className, ...props }: ListItemProps) => react_jsx_runtime.JSX.Element;
750
750
 
751
+ interface OtpInputProps {
752
+ /** OTP 자릿수 (기본값: 6) */
753
+ length?: 4 | 6;
754
+ /** 제어형 값 */
755
+ value?: string;
756
+ /** 값 변경 콜백 */
757
+ onChange?: (value: string) => void;
758
+ /** 에러 상태 */
759
+ error?: boolean;
760
+ /** 비활성화 */
761
+ disabled?: boolean;
762
+ /** 하단 도움말 텍스트 */
763
+ supportingText?: string;
764
+ /** 첫 번째 입력에 자동 포커스 */
765
+ autoFocus?: boolean;
766
+ /** 접근성 레이블 */
767
+ ariaLabel?: string;
768
+ /** 추가 className */
769
+ className?: string;
770
+ }
771
+ /**
772
+ * OTP 입력 컴포넌트를 렌더링한다.
773
+ * 각 자리를 개별 입력으로 분리하고, 자동 포커스 이동·붙여넣기를 지원한다.
774
+ * @param props OTP 입력 속성
775
+ * @returns 렌더링된 OTP 입력 요소
776
+ */
777
+ declare const OtpInput: {
778
+ ({ length, value, onChange, error, disabled, supportingText, autoFocus, ariaLabel, className, }: OtpInputProps): react_jsx_runtime.JSX.Element;
779
+ displayName: string;
780
+ };
781
+
751
782
  interface ModalProps extends Omit<React$1.HTMLAttributes<HTMLDivElement>, "title"> {
752
783
  /** 모달 열림 여부 */
753
784
  open: boolean;
@@ -893,7 +924,10 @@ declare const Toggle: {
893
924
  displayName: string;
894
925
  };
895
926
 
927
+ type TextFieldSize = "sm" | "md";
896
928
  interface TextFieldProps extends Omit<React$1.InputHTMLAttributes<HTMLInputElement>, "size" | "onChange" | "value" | "defaultValue"> {
929
+ /** 입력 필드 크기 (기본값: "md") */
930
+ size?: TextFieldSize;
897
931
  /** 입력 필드 위에 표시할 라벨 텍스트 */
898
932
  label?: string;
899
933
  /** 라벨 표시 여부 (기본값: true) */
@@ -928,7 +962,7 @@ interface TextFieldProps extends Omit<React$1.InputHTMLAttributes<HTMLInputEleme
928
962
  * @returns 렌더링된 텍스트 필드 UI
929
963
  */
930
964
  declare const TextField: {
931
- ({ id, label, showLabel, supportingText, error, leadingIcon, trailingIcon, clearable, fullWidth, className, onChangeAction, value, defaultValue, transformValue, ref, ...props }: TextFieldProps): react_jsx_runtime.JSX.Element;
965
+ ({ id, label, showLabel, supportingText, error, leadingIcon, trailingIcon, clearable, fullWidth, size, className, onChangeAction, value, defaultValue, transformValue, ref, ...props }: TextFieldProps): react_jsx_runtime.JSX.Element;
932
966
  displayName: string;
933
967
  };
934
968
 
@@ -986,4 +1020,4 @@ interface TopLoadingProps {
986
1020
  */
987
1021
  declare const TopLoading: ({ progress, color, height, isLoading, ariaLabel, }: TopLoadingProps) => react_jsx_runtime.JSX.Element | null;
988
1022
 
989
- export { AlertProvider, Button, type ButtonProps, Card, type CardProps, Checkbox, type CheckboxProps, Chip, type ChipProps, type ChipType, DatePicker, type DatePickerProps, Divider, type DividerProps, FAB, type FABProps, type FABVariant, FileInput, type FileInputProps, Icon, IconButton, type IconButtonProps, type IconButtonSize, type IconButtonVariant, type IconName, type IconProps, type IconWeight, LinearProgress, type LinearProgressProps, ListItem, type ListItemProps, Modal, type ModalProps, Pagination, type PaginationProps, Radio, type RadioProps, Select, type SelectOption, type SelectProps, Spinner, type SpinnerProps, TextField, type TextFieldProps, ToastProvider, Toggle, type ToggleProps, TopLoading, type TopLoadingProps, a11y, baseBorderWidth, baseColors, baseTypography, borderWidth, breakpoints, colors, elevation, motion, opacity, radius, skeleton, spacing, typography, useAlert, useToast, zIndex };
1023
+ export { AlertProvider, Button, type ButtonProps, Card, type CardProps, Checkbox, type CheckboxProps, Chip, type ChipProps, type ChipType, DatePicker, type DatePickerProps, Divider, type DividerProps, FAB, type FABProps, type FABVariant, FileInput, type FileInputProps, Icon, IconButton, type IconButtonProps, type IconButtonSize, type IconButtonVariant, type IconName, type IconProps, type IconWeight, LinearProgress, type LinearProgressProps, ListItem, type ListItemProps, Modal, type ModalProps, OtpInput, type OtpInputProps, Pagination, type PaginationProps, Radio, type RadioProps, Select, type SelectOption, type SelectProps, Spinner, type SpinnerProps, TextField, type TextFieldProps, ToastProvider, Toggle, type ToggleProps, TopLoading, type TopLoadingProps, a11y, baseBorderWidth, baseColors, baseTypography, borderWidth, breakpoints, colors, elevation, motion, opacity, radius, skeleton, spacing, typography, useAlert, useToast, zIndex };
package/dist/index.js CHANGED
@@ -1756,6 +1756,112 @@ var ListItem = ({
1756
1756
  }
1757
1757
  );
1758
1758
  };
1759
+ var OtpInput = ({
1760
+ length = 6,
1761
+ value = "",
1762
+ onChange,
1763
+ error = false,
1764
+ disabled = false,
1765
+ supportingText,
1766
+ autoFocus = false,
1767
+ ariaLabel = "OTP \uC785\uB825",
1768
+ className
1769
+ }) => {
1770
+ const inputsRef = React5.useRef([]);
1771
+ const digits = React5.useMemo(() => {
1772
+ const arr = value.split("").slice(0, length);
1773
+ while (arr.length < length) arr.push("");
1774
+ return arr;
1775
+ }, [value, length]);
1776
+ const focusInput = (index) => {
1777
+ inputsRef.current[index]?.focus();
1778
+ };
1779
+ const updateValue = (newDigits) => {
1780
+ onChange?.(newDigits.join(""));
1781
+ };
1782
+ const handleChange = (index, e) => {
1783
+ const val = e.target.value;
1784
+ if (val && !/^\d$/.test(val)) return;
1785
+ const newDigits = [...digits];
1786
+ newDigits[index] = val;
1787
+ updateValue(newDigits);
1788
+ if (val && index < length - 1) {
1789
+ focusInput(index + 1);
1790
+ }
1791
+ };
1792
+ const handleFocus = (index) => {
1793
+ const firstEmpty = digits.findIndex((d) => d === "");
1794
+ if (firstEmpty !== -1 && firstEmpty < index) {
1795
+ focusInput(firstEmpty);
1796
+ }
1797
+ };
1798
+ const handleKeyDown = (index, e) => {
1799
+ if (e.key === "Backspace") {
1800
+ if (digits[index]) {
1801
+ const newDigits = [...digits];
1802
+ newDigits[index] = "";
1803
+ updateValue(newDigits);
1804
+ } else if (index > 0) {
1805
+ focusInput(index - 1);
1806
+ const newDigits = [...digits];
1807
+ newDigits[index - 1] = "";
1808
+ updateValue(newDigits);
1809
+ }
1810
+ e.preventDefault();
1811
+ }
1812
+ if (e.key === "ArrowLeft" && index > 0) {
1813
+ focusInput(index - 1);
1814
+ e.preventDefault();
1815
+ }
1816
+ if (e.key === "ArrowRight" && index < length - 1) {
1817
+ focusInput(index + 1);
1818
+ e.preventDefault();
1819
+ }
1820
+ };
1821
+ const handlePaste = (e) => {
1822
+ e.preventDefault();
1823
+ const pasted = e.clipboardData.getData("text").replace(/\D/g, "").slice(0, length);
1824
+ if (!pasted) return;
1825
+ const newDigits = [...digits];
1826
+ for (let i = 0; i < pasted.length; i++) {
1827
+ newDigits[i] = pasted[i];
1828
+ }
1829
+ updateValue(newDigits);
1830
+ const nextIndex = Math.min(pasted.length, length - 1);
1831
+ focusInput(nextIndex);
1832
+ };
1833
+ const rootClassName = cn("otp_input", className);
1834
+ return /* @__PURE__ */ jsxs("div", { className: rootClassName, role: "group", "aria-label": ariaLabel, children: [
1835
+ /* @__PURE__ */ jsx("div", { className: "otp_input_boxes", children: digits.map((digit, i) => /* @__PURE__ */ jsx(
1836
+ "input",
1837
+ {
1838
+ ref: (el) => {
1839
+ inputsRef.current[i] = el;
1840
+ },
1841
+ type: "text",
1842
+ inputMode: "numeric",
1843
+ pattern: "\\d*",
1844
+ maxLength: 1,
1845
+ value: digit,
1846
+ onChange: (e) => handleChange(i, e),
1847
+ onFocus: () => handleFocus(i),
1848
+ onKeyDown: (e) => handleKeyDown(i, e),
1849
+ onPaste: handlePaste,
1850
+ disabled,
1851
+ autoFocus: autoFocus && i === 0,
1852
+ "aria-label": `${i + 1}\uBC88\uC9F8 \uC790\uB9AC`,
1853
+ className: cn(
1854
+ "otp_input_box",
1855
+ error && "otp_input_box_error",
1856
+ disabled && "otp_input_box_disabled"
1857
+ )
1858
+ },
1859
+ i
1860
+ )) }),
1861
+ supportingText && /* @__PURE__ */ jsx("span", { className: cn("otp_input_supporting", error && "otp_input_supporting_error"), children: supportingText })
1862
+ ] });
1863
+ };
1864
+ OtpInput.displayName = "OtpInput";
1759
1865
  var Modal = ({
1760
1866
  open,
1761
1867
  onClose,
@@ -2050,7 +2156,7 @@ var Select = ({
2050
2156
  const spaceAbove = rect.top;
2051
2157
  setDropUp(spaceBelow < listHeight && spaceAbove > spaceBelow);
2052
2158
  }, [isOpen, options.length]);
2053
- const rootClassName = cn("select", className);
2159
+ const rootClassName = cn("select", { select_has_label: !!label }, className);
2054
2160
  const controlClassName = cn(
2055
2161
  "select_control",
2056
2162
  `select_variant_${variant}`,
@@ -2199,6 +2305,7 @@ var TextField = ({
2199
2305
  trailingIcon,
2200
2306
  clearable,
2201
2307
  fullWidth,
2308
+ size = "md",
2202
2309
  className,
2203
2310
  onChangeAction,
2204
2311
  value,
@@ -2227,6 +2334,7 @@ var TextField = ({
2227
2334
  }, [onChangeAction]);
2228
2335
  const rootClassName = cn(
2229
2336
  "text_field",
2337
+ size === "sm" && "text_field_size_sm",
2230
2338
  fullWidth && "text_field_full_width",
2231
2339
  error && "text_field_error",
2232
2340
  props.disabled && "text_field_disabled",
@@ -2415,4 +2523,4 @@ var TopLoading = ({
2415
2523
  );
2416
2524
  };
2417
2525
 
2418
- export { AlertProvider, Button, Card, Checkbox, Chip, DatePicker, Divider, FAB, FileInput, Icon, IconButton, LinearProgress, ListItem, Modal, Pagination, Radio, Select, Spinner, TextField, ToastProvider, Toggle, TopLoading, a11y, baseBorderWidth, baseColors, baseTypography, borderWidth, breakpoints, colors, elevation, motion, opacity, radius, skeleton, spacing, typography, useAlert, useToast, zIndex };
2526
+ export { AlertProvider, Button, Card, Checkbox, Chip, DatePicker, Divider, FAB, FileInput, Icon, IconButton, LinearProgress, ListItem, Modal, OtpInput, Pagination, Radio, Select, Spinner, TextField, ToastProvider, Toggle, TopLoading, a11y, baseBorderWidth, baseColors, baseTypography, borderWidth, breakpoints, colors, elevation, motion, opacity, radius, skeleton, spacing, typography, useAlert, useToast, zIndex };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bigtablet/design-system",
3
- "version": "2.0.7",
3
+ "version": "2.2.0",
4
4
  "description": "Bigtablet Design System UI Components",
5
5
  "type": "module",
6
6
  "types": "dist/index.d.ts",