@bigtablet/design-system 2.2.3 → 2.3.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
@@ -379,6 +379,7 @@
379
379
  font-weight: 400;
380
380
  line-height: 28px;
381
381
  letter-spacing: 0px;
382
+ margin-top: 0;
382
383
  margin-bottom: 12px;
383
384
  color: #121212;
384
385
  }
@@ -624,7 +625,7 @@
624
625
  cursor: not-allowed;
625
626
  }
626
627
 
627
- /* src/ui/date-picker/style.scss */
628
+ /* src/ui/select/style.scss */
628
629
  @keyframes skeleton_loading {
629
630
  0% {
630
631
  background-position: 100% 0;
@@ -633,67 +634,234 @@
633
634
  background-position: 0 0;
634
635
  }
635
636
  }
636
- .date_picker {
637
- display: flex;
637
+ .select {
638
+ position: relative;
639
+ display: inline-flex;
638
640
  flex-direction: column;
639
- gap: 4px;
640
- }
641
- .date_picker select {
642
- min-width: 88px;
643
- height: 44px;
644
- padding: 0 12px;
641
+ width: 100%;
645
642
  font-family: "Pretendard", sans-serif;
646
- font-size: 15px;
647
- font-weight: 400;
648
- line-height: 22.5px;
649
- letter-spacing: 0px;
650
- color: #121212;
643
+ }
644
+ .select_fieldset {
645
+ padding: 0;
646
+ margin: 0;
647
+ min-inline-size: 0;
648
+ border-radius: 12px;
651
649
  background-color: #FFFFFF;
650
+ transition: border-color 0.2s ease-in-out, background-color 0.2s ease-in-out;
651
+ }
652
+ .select_variant_outline {
652
653
  border: 1px solid #E5E5E5;
653
- border-radius: 6px;
654
- cursor: pointer;
655
- transition:
656
- border-color 0.1s ease-in-out,
657
- box-shadow 0.1s ease-in-out,
658
- background-color 0.1s ease-in-out;
659
- appearance: none;
660
- background-image:
661
- linear-gradient(
662
- 45deg,
663
- transparent 50%,
664
- #666666 50%),
665
- linear-gradient(
666
- 135deg,
667
- #666666 50%,
668
- transparent 50%);
669
- background-position: calc(100% - 16px) calc(50% - 3px), calc(100% - 11px) calc(50% - 3px);
670
- background-size: 5px 5px;
671
- background-repeat: no-repeat;
672
- }
673
- .date_picker select:hover:not(:disabled) {
654
+ }
655
+ .select_variant_outline:hover:not(.is_disabled) {
656
+ border-color: #B3B3B3;
657
+ }
658
+ .select_variant_outline.is_open {
659
+ border-color: #121212;
660
+ }
661
+ .select_variant_filled {
662
+ background: #F4F4F4;
663
+ border: 1px solid transparent;
664
+ }
665
+ .select_variant_filled:hover:not(.is_disabled) {
666
+ background: rgb(251.7, 251.7, 251.7);
667
+ }
668
+ .select_variant_filled.is_open {
669
+ background: #FFFFFF;
670
+ border-color: #121212;
671
+ }
672
+ .select_variant_ghost {
673
+ background: transparent;
674
+ border: 1px solid transparent;
675
+ }
676
+ .select_variant_ghost:hover:not(.is_disabled) {
677
+ background: rgba(0, 0, 0, 0.05);
678
+ }
679
+ .select_variant_ghost.is_open {
680
+ background: #FFFFFF;
674
681
  border-color: #121212;
682
+ }
683
+ .select_fieldset.is_disabled {
675
684
  background-color: #F4F4F4;
676
685
  }
677
- .date_picker select:focus-visible {
686
+ .select_inner {
687
+ display: flex;
688
+ align-items: stretch;
689
+ padding: 4px 0;
690
+ }
691
+ .select_size_sm .select_inner {
692
+ min-height: 40px;
693
+ }
694
+ .select_size_md .select_inner {
695
+ min-height: 52px;
696
+ }
697
+ .select_size_lg .select_inner {
698
+ min-height: 52px;
699
+ }
700
+ .select_label {
701
+ margin-inline-start: 12px;
702
+ padding: 0 4px;
703
+ color: #121212;
704
+ transition: color 0.2s ease-in-out;
705
+ }
706
+ .select_label > label {
707
+ display: block;
708
+ font-family: "Pretendard", sans-serif;
709
+ font-size: 12px;
710
+ font-weight: 400;
711
+ line-height: 16px;
712
+ letter-spacing: 0.32px;
713
+ color: inherit;
714
+ pointer-events: none;
715
+ cursor: default;
716
+ }
717
+ .select_fieldset.is_disabled .select_label {
718
+ color: rgba(26, 26, 26, 0.38);
719
+ }
720
+ .select_control {
721
+ flex: 1;
722
+ display: inline-flex;
723
+ align-items: center;
724
+ justify-content: space-between;
725
+ gap: 8px;
726
+ cursor: pointer;
727
+ background: transparent;
728
+ border: none;
678
729
  outline: none;
679
- border-color: #121212;
730
+ color: #121212;
731
+ border-radius: 12px;
732
+ }
733
+ .select_control:focus-visible {
680
734
  box-shadow: 0 0 0 3px rgba(18, 18, 18, 0.15);
681
- background-color: #FFFFFF;
682
735
  }
683
- .date_picker select:disabled {
736
+ .select_control:disabled,
737
+ .select_control.is_disabled {
684
738
  cursor: not-allowed;
685
- color: rgba(26, 26, 26, 0.38);
686
- background-color: #F4F4F4;
687
- border-color: rgba(0, 0, 0, 0.08);
739
+ opacity: 0.38;
740
+ color: #888888;
741
+ }
742
+ .select_size_sm .select_control {
743
+ padding: 8px 16px;
744
+ font-size: 12px;
745
+ font-weight: 400;
746
+ line-height: 16px;
747
+ letter-spacing: 0px;
748
+ }
749
+ .select_size_md .select_control {
750
+ padding: 12px 20px;
751
+ font-size: 15px;
752
+ font-weight: 400;
753
+ line-height: 22.5px;
754
+ letter-spacing: 0px;
755
+ }
756
+ .select_size_lg .select_control {
757
+ padding: 16px 24px;
758
+ font-size: 16px;
759
+ font-weight: 400;
760
+ line-height: 24px;
761
+ letter-spacing: 0px;
762
+ }
763
+ .select_value {
764
+ flex: 1 1 auto;
765
+ min-width: 0;
766
+ overflow: hidden;
767
+ text-overflow: ellipsis;
768
+ white-space: nowrap;
769
+ text-align: start;
770
+ font-size: 15px;
771
+ font-weight: 400;
772
+ line-height: 22.5px;
773
+ letter-spacing: 0px;
774
+ }
775
+ .select_placeholder {
776
+ flex: 1 1 auto;
777
+ min-width: 0;
778
+ overflow: hidden;
779
+ text-overflow: ellipsis;
780
+ white-space: nowrap;
781
+ text-align: start;
782
+ font-size: 15px;
783
+ font-weight: 400;
784
+ line-height: 22.5px;
785
+ letter-spacing: 0px;
786
+ color: #888888;
787
+ }
788
+ .select_icon {
789
+ display: inline-flex;
790
+ align-items: center;
791
+ justify-content: center;
792
+ color: #666666;
688
793
  }
689
- .date_picker select option {
794
+ .select_list {
795
+ position: absolute;
796
+ z-index: 1000;
797
+ top: 100%;
798
+ left: 0;
799
+ width: 100%;
800
+ min-width: 100%;
801
+ box-sizing: border-box;
802
+ margin-top: 4px;
803
+ background: #FFFFFF;
804
+ border: 1px solid #E5E5E5;
805
+ border-radius: 8px;
806
+ box-shadow: 0 2px 2px -2px rgba(0, 0, 0, 0.2), 0 6px 6px 0px rgba(0, 0, 0, 0.12);
807
+ max-height: 18rem;
808
+ overflow-y: auto;
809
+ overflow-x: hidden;
810
+ padding: 4px 0;
811
+ }
812
+ .select_list_up {
813
+ top: auto;
814
+ bottom: 100%;
815
+ margin-top: 0;
816
+ margin-bottom: 4px;
817
+ }
818
+ .select_option {
819
+ width: 100%;
820
+ box-sizing: border-box;
821
+ display: grid;
822
+ grid-template-columns: 1fr auto;
823
+ align-items: center;
824
+ gap: 8px;
825
+ padding: 8px 16px;
826
+ cursor: pointer;
690
827
  color: #121212;
828
+ background: transparent;
829
+ font-size: 15px;
830
+ font-weight: 400;
831
+ line-height: 22.5px;
832
+ letter-spacing: 0px;
691
833
  }
692
- @media (max-width: 599px) {
693
- .date_picker select {
694
- flex: 1;
695
- min-width: 0;
834
+ .select_option > span:first-child {
835
+ min-width: 0;
836
+ overflow: hidden;
837
+ text-overflow: ellipsis;
838
+ white-space: nowrap;
839
+ }
840
+ .select_option:hover,
841
+ .select_option.is_active {
842
+ background: #F4F4F4;
843
+ }
844
+ .select_option.is_selected {
845
+ font-weight: 500;
846
+ }
847
+ .select_option.is_disabled {
848
+ cursor: not-allowed;
849
+ color: #888888;
850
+ }
851
+
852
+ /* src/ui/date-picker/style.scss */
853
+ @keyframes skeleton_loading {
854
+ 0% {
855
+ background-position: 100% 0;
696
856
  }
857
+ 100% {
858
+ background-position: 0 0;
859
+ }
860
+ }
861
+ .date_picker {
862
+ display: flex;
863
+ flex-direction: column;
864
+ gap: 4px;
697
865
  }
698
866
  .date_picker_label {
699
867
  color: #121212;
@@ -709,6 +877,7 @@
709
877
  .date_picker_fields {
710
878
  display: flex;
711
879
  gap: 8px;
880
+ align-items: flex-end;
712
881
  }
713
882
  @media (max-width: 599px) {
714
883
  .date_picker_fields {
@@ -721,7 +890,7 @@
721
890
  .date_picker_full_width .date_picker_fields {
722
891
  width: 100%;
723
892
  }
724
- .date_picker_full_width select {
893
+ .date_picker_full_width .date_picker_fields > * {
725
894
  flex: 1;
726
895
  min-width: 0;
727
896
  }
@@ -1509,224 +1678,6 @@
1509
1678
  height: 10px;
1510
1679
  }
1511
1680
 
1512
- /* src/ui/select/style.scss */
1513
- @keyframes skeleton_loading {
1514
- 0% {
1515
- background-position: 100% 0;
1516
- }
1517
- 100% {
1518
- background-position: 0 0;
1519
- }
1520
- }
1521
- .select {
1522
- position: relative;
1523
- display: inline-flex;
1524
- flex-direction: column;
1525
- width: 100%;
1526
- font-family: "Pretendard", sans-serif;
1527
- }
1528
- .select_fieldset {
1529
- padding: 0;
1530
- margin: 0;
1531
- min-inline-size: 0;
1532
- border-radius: 12px;
1533
- background-color: #FFFFFF;
1534
- transition: border-color 0.2s ease-in-out, background-color 0.2s ease-in-out;
1535
- }
1536
- .select_variant_outline {
1537
- border: 1px solid #E5E5E5;
1538
- }
1539
- .select_variant_outline:hover:not(.is_disabled) {
1540
- border-color: rgba(0, 0, 0, 0.08);
1541
- }
1542
- .select_variant_outline.is_open {
1543
- border-color: #121212;
1544
- box-shadow: 0 0 0 3px rgba(18, 18, 18, 0.15);
1545
- }
1546
- .select_variant_filled {
1547
- background: #F4F4F4;
1548
- border: 1px solid transparent;
1549
- }
1550
- .select_variant_filled:hover:not(.is_disabled) {
1551
- background: rgb(251.7, 251.7, 251.7);
1552
- }
1553
- .select_variant_filled.is_open {
1554
- background: #FFFFFF;
1555
- border-color: #121212;
1556
- box-shadow: 0 0 0 3px rgba(18, 18, 18, 0.15);
1557
- }
1558
- .select_variant_ghost {
1559
- background: transparent;
1560
- border: 1px solid transparent;
1561
- }
1562
- .select_variant_ghost:hover:not(.is_disabled) {
1563
- background: rgba(0, 0, 0, 0.05);
1564
- }
1565
- .select_variant_ghost.is_open {
1566
- background: #FFFFFF;
1567
- border-color: #121212;
1568
- box-shadow: 0 0 0 3px rgba(18, 18, 18, 0.15);
1569
- }
1570
- .select_fieldset.is_disabled {
1571
- background-color: #F4F4F4;
1572
- }
1573
- .select_size_sm {
1574
- min-height: 40px;
1575
- }
1576
- .select_size_md {
1577
- min-height: 52px;
1578
- }
1579
- .select_size_lg {
1580
- min-height: 52px;
1581
- }
1582
- .select_label {
1583
- margin-inline-start: 12px;
1584
- padding: 0 4px;
1585
- color: #121212;
1586
- transition: color 0.2s ease-in-out;
1587
- }
1588
- .select_label > label {
1589
- display: block;
1590
- font-family: "Pretendard", sans-serif;
1591
- font-size: 12px;
1592
- font-weight: 400;
1593
- line-height: 16px;
1594
- letter-spacing: 0.32px;
1595
- color: inherit;
1596
- pointer-events: none;
1597
- cursor: default;
1598
- }
1599
- .select_fieldset.is_disabled .select_label {
1600
- color: rgba(26, 26, 26, 0.38);
1601
- }
1602
- .select_control {
1603
- width: 100%;
1604
- min-height: inherit;
1605
- display: inline-flex;
1606
- align-items: center;
1607
- justify-content: space-between;
1608
- gap: 8px;
1609
- cursor: pointer;
1610
- background: transparent;
1611
- border: none;
1612
- color: #121212;
1613
- }
1614
- .select_control:disabled,
1615
- .select_control.is_disabled {
1616
- cursor: not-allowed;
1617
- opacity: 0.38;
1618
- color: #888888;
1619
- }
1620
- .select_size_sm .select_control {
1621
- padding: 8px 16px;
1622
- font-size: 12px;
1623
- font-weight: 400;
1624
- line-height: 16px;
1625
- letter-spacing: 0px;
1626
- }
1627
- .select_size_md .select_control {
1628
- padding: 12px 20px;
1629
- font-size: 15px;
1630
- font-weight: 400;
1631
- line-height: 22.5px;
1632
- letter-spacing: 0px;
1633
- }
1634
- .select_size_lg .select_control {
1635
- padding: 16px 24px;
1636
- font-size: 16px;
1637
- font-weight: 400;
1638
- line-height: 24px;
1639
- letter-spacing: 0px;
1640
- }
1641
- .select_value {
1642
- flex: 1 1 auto;
1643
- min-width: 0;
1644
- overflow: hidden;
1645
- text-overflow: ellipsis;
1646
- white-space: nowrap;
1647
- text-align: start;
1648
- font-size: 15px;
1649
- font-weight: 400;
1650
- line-height: 22.5px;
1651
- letter-spacing: 0px;
1652
- }
1653
- .select_placeholder {
1654
- flex: 1 1 auto;
1655
- min-width: 0;
1656
- overflow: hidden;
1657
- text-overflow: ellipsis;
1658
- white-space: nowrap;
1659
- text-align: start;
1660
- font-size: 15px;
1661
- font-weight: 400;
1662
- line-height: 22.5px;
1663
- letter-spacing: 0px;
1664
- color: #888888;
1665
- }
1666
- .select_icon {
1667
- display: inline-flex;
1668
- align-items: center;
1669
- justify-content: center;
1670
- color: #666666;
1671
- }
1672
- .select_list {
1673
- position: absolute;
1674
- z-index: 1000;
1675
- top: 100%;
1676
- left: 0;
1677
- width: 100%;
1678
- min-width: 100%;
1679
- box-sizing: border-box;
1680
- margin-top: 4px;
1681
- background: #FFFFFF;
1682
- border: 1px solid #E5E5E5;
1683
- border-radius: 8px;
1684
- box-shadow: 0 2px 2px -2px rgba(0, 0, 0, 0.2), 0 6px 6px 0px rgba(0, 0, 0, 0.12);
1685
- max-height: 18rem;
1686
- overflow-y: auto;
1687
- overflow-x: hidden;
1688
- padding: 4px 0;
1689
- }
1690
- .select_list_up {
1691
- top: auto;
1692
- bottom: 100%;
1693
- margin-top: 0;
1694
- margin-bottom: 4px;
1695
- }
1696
- .select_option {
1697
- width: 100%;
1698
- box-sizing: border-box;
1699
- display: grid;
1700
- grid-template-columns: 1fr auto;
1701
- align-items: center;
1702
- gap: 8px;
1703
- padding: 8px 16px;
1704
- cursor: pointer;
1705
- color: #121212;
1706
- background: transparent;
1707
- font-size: 15px;
1708
- font-weight: 400;
1709
- line-height: 22.5px;
1710
- letter-spacing: 0px;
1711
- }
1712
- .select_option > span:first-child {
1713
- min-width: 0;
1714
- overflow: hidden;
1715
- text-overflow: ellipsis;
1716
- white-space: nowrap;
1717
- }
1718
- .select_option:hover,
1719
- .select_option.is_active {
1720
- background: #F4F4F4;
1721
- }
1722
- .select_option.is_selected {
1723
- font-weight: 500;
1724
- }
1725
- .select_option.is_disabled {
1726
- cursor: not-allowed;
1727
- color: #888888;
1728
- }
1729
-
1730
1681
  /* src/ui/spinner/style.scss */
1731
1682
  @keyframes skeleton_loading {
1732
1683
  0% {
package/dist/index.d.ts CHANGED
@@ -36,8 +36,9 @@ declare const baseColors: {
36
36
  readonly neutral300: "#999999";
37
37
  readonly neutral400: "#B3B3B3";
38
38
  readonly neutral500: "#888888";
39
- readonly neutral600: "#6B6B6B";
39
+ readonly neutral600: "#777777";
40
40
  readonly neutral700: "#666666";
41
+ readonly neutral800: "#333333";
41
42
  readonly neutral900: "#121212";
42
43
  readonly statusError: "#EF4444";
43
44
  readonly statusSuccess: "#10B981";
@@ -71,6 +72,7 @@ declare const colors: {
71
72
  readonly additive: "rgba(0, 0, 0, 0.05)";
72
73
  readonly disabled: "rgba(26, 26, 26, 0.12)";
73
74
  readonly overlay: "rgba(0, 0, 0, 0.50)";
75
+ readonly inverseSurface: "#333333";
74
76
  };
75
77
  readonly state: {
76
78
  readonly hoverOnLight: "rgba(0, 0, 0, 0.05)";
@@ -586,11 +588,11 @@ interface DatePickerProps {
586
588
  * @deprecated `fullWidth` 사용 또는 CSS로 처리
587
589
  */
588
590
  width?: number | string;
589
- /** 연도 select의 aria-label 및 빈 옵션 텍스트 (기본값: "Year") */
591
+ /** 연도 select의 라벨/플레이스홀더 (기본값: "Year") */
590
592
  yearLabel?: string;
591
- /** 월 select의 aria-label 및 빈 옵션 텍스트 (기본값: "Month") */
593
+ /** 월 select의 라벨/플레이스홀더 (기본값: "Month") */
592
594
  monthLabel?: string;
593
- /** 일 select의 aria-label 및 빈 옵션 텍스트 (기본값: "Day") */
595
+ /** 일 select의 라벨/플레이스홀더 (기본값: "Day") */
594
596
  dayLabel?: string;
595
597
  /**
596
598
  * minDate 설정 시 스크린리더에 전달할 안내 문구 포맷.
@@ -605,7 +607,7 @@ interface DatePickerProps {
605
607
  }
606
608
  /**
607
609
  * 연/월/일 선택형 데이트 피커를 렌더링한다.
608
- * 입력 값과 선택 범위를 기준으로 옵션을 계산하고, 선택 변경을 상위로 전달한다.
610
+ * 내부적으로 DS Select 3개를 조합해 드롭다운 UX 일관성을 유지한다.
609
611
  */
610
612
  declare const DatePicker: ({ label, value, onChange, mode, startYear, endYear: endYearProp, minDate, selectableRange, disabled, fullWidth, width, yearLabel, monthLabel, dayLabel, minDateSrFormat, selectableRangeUntilTodaySrText, }: DatePickerProps) => react_jsx_runtime.JSX.Element;
611
613
 
package/dist/index.js CHANGED
@@ -48,8 +48,9 @@ var baseColors = {
48
48
  neutral300: "#999999",
49
49
  neutral400: "#B3B3B3",
50
50
  neutral500: "#888888",
51
- neutral600: "#6B6B6B",
51
+ neutral600: "#777777",
52
52
  neutral700: "#666666",
53
+ neutral800: "#333333",
53
54
  neutral900: "#121212",
54
55
  statusError: "#EF4444",
55
56
  statusSuccess: "#10B981",
@@ -82,7 +83,8 @@ var colors = {
82
83
  solidDim: baseColors.neutral50,
83
84
  additive: baseColors.alphaBlack5,
84
85
  disabled: baseColors.alphaBlack12,
85
- overlay: baseColors.alphaBlack50
86
+ overlay: baseColors.alphaBlack50,
87
+ inverseSurface: baseColors.neutral800
86
88
  },
87
89
  state: {
88
90
  hoverOnLight: baseColors.alphaBlack5,
@@ -803,6 +805,217 @@ var Chip = ({
803
805
  )
804
806
  ] });
805
807
  };
808
+ var Select = ({
809
+ id,
810
+ label,
811
+ placeholder = "Select\u2026",
812
+ options,
813
+ value,
814
+ onChange,
815
+ defaultValue = null,
816
+ disabled,
817
+ size = "md",
818
+ variant = "outline",
819
+ fullWidth,
820
+ className,
821
+ textAlign = "left"
822
+ }) => {
823
+ const internalId = React5.useId();
824
+ const selectId = id ?? internalId;
825
+ const isControlled = value !== void 0;
826
+ const [internalValue, setInternalValue] = React5.useState(defaultValue);
827
+ const currentValue = isControlled ? value ?? null : internalValue;
828
+ const [isOpen, setIsOpen] = React5.useState(false);
829
+ const [activeIndex, setActiveIndex] = React5.useState(-1);
830
+ const [dropUp, setDropUp] = React5.useState(false);
831
+ const wrapperRef = React5.useRef(null);
832
+ const controlRef = React5.useRef(null);
833
+ const currentOption = React5.useMemo(
834
+ () => options.find((o) => o.value === currentValue) ?? null,
835
+ [options, currentValue]
836
+ );
837
+ const setValue = React5.useCallback(
838
+ (next) => {
839
+ const option = options.find((o) => o.value === next) ?? null;
840
+ if (!isControlled) setInternalValue(next);
841
+ onChange?.(next, option);
842
+ },
843
+ [isControlled, onChange, options]
844
+ );
845
+ const handleOutsideClick = React5.useEffectEvent((e) => {
846
+ if (!wrapperRef.current?.contains(e.target)) {
847
+ setIsOpen(false);
848
+ }
849
+ });
850
+ React5.useEffect(() => {
851
+ document.addEventListener("mousedown", handleOutsideClick);
852
+ return () => document.removeEventListener("mousedown", handleOutsideClick);
853
+ }, []);
854
+ const moveActive = (dir) => {
855
+ if (!isOpen) {
856
+ setIsOpen(true);
857
+ return;
858
+ }
859
+ let i = activeIndex;
860
+ const len = options.length;
861
+ for (let step = 0; step < len; step++) {
862
+ i = (i + dir + len) % len;
863
+ if (!options[i].disabled) {
864
+ setActiveIndex(i);
865
+ break;
866
+ }
867
+ }
868
+ };
869
+ const commitActive = () => {
870
+ if (activeIndex < 0 || activeIndex >= options.length) return;
871
+ const opt = options[activeIndex];
872
+ if (!opt.disabled) {
873
+ setValue(opt.value);
874
+ setIsOpen(false);
875
+ }
876
+ };
877
+ const onKeyDown = (e) => {
878
+ if (disabled) return;
879
+ switch (e.key) {
880
+ case " ":
881
+ case "Enter":
882
+ e.preventDefault();
883
+ if (!isOpen) setIsOpen(true);
884
+ else commitActive();
885
+ break;
886
+ case "ArrowDown":
887
+ e.preventDefault();
888
+ moveActive(1);
889
+ break;
890
+ case "ArrowUp":
891
+ e.preventDefault();
892
+ moveActive(-1);
893
+ break;
894
+ case "Home":
895
+ e.preventDefault();
896
+ setIsOpen(true);
897
+ setActiveIndex(options.findIndex((o) => !o.disabled));
898
+ break;
899
+ case "End":
900
+ e.preventDefault();
901
+ setIsOpen(true);
902
+ for (let i = options.length - 1; i >= 0; i--) {
903
+ if (!options[i].disabled) {
904
+ setActiveIndex(i);
905
+ break;
906
+ }
907
+ }
908
+ break;
909
+ case "Escape":
910
+ e.preventDefault();
911
+ setIsOpen(false);
912
+ break;
913
+ }
914
+ };
915
+ React5.useEffect(() => {
916
+ if (!isOpen) return;
917
+ const idx = options.findIndex((o) => o.value === currentValue && !o.disabled);
918
+ setActiveIndex(
919
+ idx >= 0 ? idx : Math.max(
920
+ 0,
921
+ options.findIndex((o) => !o.disabled)
922
+ )
923
+ );
924
+ }, [isOpen, options, currentValue]);
925
+ React5.useLayoutEffect(() => {
926
+ if (!isOpen || !controlRef.current) return;
927
+ const rect = controlRef.current.getBoundingClientRect();
928
+ const listHeight = Math.min(options.length * 40, 288);
929
+ const spaceBelow = window.innerHeight - rect.bottom;
930
+ const spaceAbove = rect.top;
931
+ setDropUp(spaceBelow < listHeight && spaceAbove > spaceBelow);
932
+ }, [isOpen, options.length]);
933
+ const rootClassName = cn("select", { select_has_label: !!label }, className);
934
+ const fieldsetClassName = cn(
935
+ "select_fieldset",
936
+ `select_variant_${variant}`,
937
+ `select_size_${size}`,
938
+ { is_open: isOpen, is_disabled: disabled }
939
+ );
940
+ const listClassName = cn("select_list", { select_list_up: dropUp });
941
+ return /* @__PURE__ */ jsxs(
942
+ "div",
943
+ {
944
+ ref: wrapperRef,
945
+ className: rootClassName,
946
+ style: fullWidth ? { width: "100%" } : void 0,
947
+ children: [
948
+ /* @__PURE__ */ jsxs("fieldset", { className: fieldsetClassName, children: [
949
+ label && /* @__PURE__ */ jsx("legend", { className: "select_label", children: /* @__PURE__ */ jsx("label", { htmlFor: selectId, children: label }) }),
950
+ /* @__PURE__ */ jsx("div", { className: "select_inner", children: /* @__PURE__ */ jsxs(
951
+ "button",
952
+ {
953
+ ref: controlRef,
954
+ id: selectId,
955
+ type: "button",
956
+ className: cn("select_control", { is_disabled: disabled }),
957
+ "aria-haspopup": "listbox",
958
+ "aria-expanded": isOpen,
959
+ "aria-controls": `${selectId}_listbox`,
960
+ onClick: () => !disabled && setIsOpen((o) => !o),
961
+ onKeyDown,
962
+ disabled,
963
+ children: [
964
+ /* @__PURE__ */ jsx(
965
+ "span",
966
+ {
967
+ className: currentOption ? "select_value" : "select_placeholder",
968
+ style: textAlign === "left" ? { textAlign: "start" } : void 0,
969
+ children: currentOption ? currentOption.label : placeholder
970
+ }
971
+ ),
972
+ /* @__PURE__ */ jsx("span", { className: "select_icon", "aria-hidden": "true", children: /* @__PURE__ */ jsx(ChevronDown, { size: 16 }) })
973
+ ]
974
+ }
975
+ ) })
976
+ ] }),
977
+ isOpen && /* @__PURE__ */ jsx("div", { id: `${selectId}_listbox`, role: "listbox", className: listClassName, children: options.map((opt, i) => {
978
+ const selected = currentValue === opt.value;
979
+ const active = i === activeIndex;
980
+ const optionClassName = cn("select_option", {
981
+ is_selected: selected,
982
+ is_active: active,
983
+ is_disabled: opt.disabled
984
+ });
985
+ return /* @__PURE__ */ jsxs(
986
+ "div",
987
+ {
988
+ role: "option",
989
+ tabIndex: -1,
990
+ "aria-selected": selected,
991
+ "aria-disabled": opt.disabled ? true : void 0,
992
+ className: optionClassName,
993
+ onMouseEnter: () => !opt.disabled && setActiveIndex(i),
994
+ onClick: () => {
995
+ if (opt.disabled) return;
996
+ setValue(opt.value);
997
+ setIsOpen(false);
998
+ },
999
+ onKeyDown: (e) => {
1000
+ if (opt.disabled) return;
1001
+ if (e.key === "Enter" || e.key === " ") {
1002
+ e.preventDefault();
1003
+ setValue(opt.value);
1004
+ setIsOpen(false);
1005
+ }
1006
+ },
1007
+ children: [
1008
+ /* @__PURE__ */ jsx("span", { children: opt.label }),
1009
+ selected && /* @__PURE__ */ jsx(Check, { size: 16, "aria-hidden": "true" })
1010
+ ]
1011
+ },
1012
+ opt.value
1013
+ );
1014
+ }) })
1015
+ ]
1016
+ }
1017
+ );
1018
+ };
806
1019
  var pad = (n) => String(n).padStart(2, "0");
807
1020
  var getDaysInMonth = (year, month) => new Date(year, month, 0).getDate();
808
1021
  var normalizeWidth = (v) => typeof v === "number" ? `${v}px` : v;
@@ -856,9 +1069,12 @@ var DatePicker = ({
856
1069
  }, [minDate]);
857
1070
  const { year, month, day } = parsed;
858
1071
  const maxYear = selectableRange === "until-today" ? todayYear : endYear;
859
- const minMonth = min.year > 0 && year === min.year ? min.month : 1;
1072
+ const minMonth = min.year > 0 && year === min.year ? Math.min(12, Math.max(1, min.month)) : 1;
860
1073
  const maxMonth = selectableRange === "until-today" && year === todayYear ? todayMonth : 12;
861
- const minDay = min.year > 0 && min.month > 0 && year === min.year && month === min.month ? min.day : 1;
1074
+ const minDay = Math.min(31, Math.max(
1075
+ 1,
1076
+ min.year > 0 && min.month > 0 && year === min.year && month === min.month ? min.day : 1
1077
+ ));
862
1078
  const maxDay = React5.useMemo(() => {
863
1079
  if (!year || !month) return 31;
864
1080
  const daysInMonth = getDaysInMonth(year, month);
@@ -868,15 +1084,24 @@ var DatePicker = ({
868
1084
  return daysInMonth;
869
1085
  }, [year, month, selectableRange, todayYear, todayMonth, todayDay]);
870
1086
  const yearOptions = React5.useMemo(
871
- () => range(startYear, maxYear),
1087
+ () => range(startYear, maxYear).map((y) => ({
1088
+ value: String(y),
1089
+ label: String(y)
1090
+ })),
872
1091
  [startYear, maxYear]
873
1092
  );
874
1093
  const monthOptions = React5.useMemo(
875
- () => range(minMonth, Math.max(minMonth, maxMonth)),
1094
+ () => range(minMonth, Math.max(minMonth, maxMonth)).map((m) => ({
1095
+ value: String(m),
1096
+ label: pad(m)
1097
+ })),
876
1098
  [minMonth, maxMonth]
877
1099
  );
878
1100
  const dayOptions = React5.useMemo(
879
- () => range(minDay, Math.max(minDay, maxDay)),
1101
+ () => range(minDay, Math.max(minDay, maxDay)).map((d) => ({
1102
+ value: String(d),
1103
+ label: pad(d)
1104
+ })),
880
1105
  [minDay, maxDay]
881
1106
  );
882
1107
  const emit = React5.useCallback(
@@ -891,7 +1116,9 @@ var DatePicker = ({
891
1116
  [mode, onChange]
892
1117
  );
893
1118
  const handleYearChange = React5.useCallback(
894
- (newYear) => {
1119
+ (raw) => {
1120
+ if (!raw) return;
1121
+ const newYear = Number(raw);
895
1122
  let newMonth = month;
896
1123
  const newMinMonth = min.year > 0 && newYear === min.year ? min.month : 1;
897
1124
  const newMaxMonth = selectableRange === "until-today" && newYear === todayYear ? todayMonth : 12;
@@ -906,22 +1133,23 @@ var DatePicker = ({
906
1133
  [month, day, min.year, min.month, selectableRange, todayYear, todayMonth, emit]
907
1134
  );
908
1135
  const handleMonthChange = React5.useCallback(
909
- (newMonth) => {
910
- if (!year) return;
911
- emit(year, newMonth, day || void 0);
1136
+ (raw) => {
1137
+ if (!raw || !year) return;
1138
+ emit(year, Number(raw), day || void 0);
912
1139
  },
913
1140
  [year, day, emit]
914
1141
  );
915
1142
  const handleDayChange = React5.useCallback(
916
- (newDay) => {
917
- if (!year || !month) return;
918
- emit(year, month, newDay);
1143
+ (raw) => {
1144
+ if (!raw || !year || !month) return;
1145
+ emit(year, month, Number(raw));
919
1146
  },
920
1147
  [year, month, emit]
921
1148
  );
922
1149
  const containerStyle = width ? { width: normalizeWidth(width) } : void 0;
923
1150
  const rootClassName = cn("date_picker", {
924
- date_picker_full_width: fullWidth && !width
1151
+ date_picker_full_width: fullWidth && !width,
1152
+ date_picker_disabled: disabled
925
1153
  });
926
1154
  const constraintParts = [];
927
1155
  if (minDate) constraintParts.push(minDateSrFormat.replace("{date}", minDate));
@@ -939,43 +1167,43 @@ var DatePicker = ({
939
1167
  "aria-labelledby": label ? groupId : void 0,
940
1168
  "aria-describedby": constraintDesc ? constraintId : void 0,
941
1169
  children: [
942
- /* @__PURE__ */ jsxs(
943
- "select",
1170
+ /* @__PURE__ */ jsx(
1171
+ Select,
944
1172
  {
945
- "aria-label": yearLabel,
946
- value: year || "",
947
- disabled,
948
- onChange: (e) => handleYearChange(Number(e.target.value)),
949
- children: [
950
- /* @__PURE__ */ jsx("option", { value: "", children: yearLabel }),
951
- yearOptions.map((y) => /* @__PURE__ */ jsx("option", { value: y, children: y }, y))
952
- ]
1173
+ size: "sm",
1174
+ fullWidth: true,
1175
+ label: yearLabel,
1176
+ placeholder: yearLabel,
1177
+ options: yearOptions,
1178
+ value: year ? String(year) : null,
1179
+ onChange: handleYearChange,
1180
+ disabled
953
1181
  }
954
1182
  ),
955
- /* @__PURE__ */ jsxs(
956
- "select",
1183
+ /* @__PURE__ */ jsx(
1184
+ Select,
957
1185
  {
958
- "aria-label": monthLabel,
959
- value: month || "",
960
- disabled: disabled || !year,
961
- onChange: (e) => handleMonthChange(Number(e.target.value)),
962
- children: [
963
- /* @__PURE__ */ jsx("option", { value: "", children: monthLabel }),
964
- monthOptions.map((m) => /* @__PURE__ */ jsx("option", { value: m, children: pad(m) }, m))
965
- ]
1186
+ size: "sm",
1187
+ fullWidth: true,
1188
+ label: monthLabel,
1189
+ placeholder: monthLabel,
1190
+ options: monthOptions,
1191
+ value: month ? String(month) : null,
1192
+ onChange: handleMonthChange,
1193
+ disabled: disabled || !year
966
1194
  }
967
1195
  ),
968
- mode === "year-month-day" && /* @__PURE__ */ jsxs(
969
- "select",
1196
+ mode === "year-month-day" && /* @__PURE__ */ jsx(
1197
+ Select,
970
1198
  {
971
- "aria-label": dayLabel,
972
- value: day || "",
973
- disabled: disabled || !month,
974
- onChange: (e) => handleDayChange(Number(e.target.value)),
975
- children: [
976
- /* @__PURE__ */ jsx("option", { value: "", children: dayLabel }),
977
- dayOptions.map((d) => /* @__PURE__ */ jsx("option", { value: d, children: pad(d) }, d))
978
- ]
1199
+ size: "sm",
1200
+ fullWidth: true,
1201
+ label: dayLabel,
1202
+ placeholder: dayLabel,
1203
+ options: dayOptions,
1204
+ value: day ? String(day) : null,
1205
+ onChange: handleDayChange,
1206
+ disabled: disabled || !month
979
1207
  }
980
1208
  )
981
1209
  ]
@@ -2031,217 +2259,6 @@ var Radio = ({ label, size = "md", className, ref, ...props }) => {
2031
2259
  ] });
2032
2260
  };
2033
2261
  Radio.displayName = "Radio";
2034
- var Select = ({
2035
- id,
2036
- label,
2037
- placeholder = "Select\u2026",
2038
- options,
2039
- value,
2040
- onChange,
2041
- defaultValue = null,
2042
- disabled,
2043
- size = "md",
2044
- variant = "outline",
2045
- fullWidth,
2046
- className,
2047
- textAlign = "left"
2048
- }) => {
2049
- const internalId = React5.useId();
2050
- const selectId = id ?? internalId;
2051
- const isControlled = value !== void 0;
2052
- const [internalValue, setInternalValue] = React5.useState(defaultValue);
2053
- const currentValue = isControlled ? value ?? null : internalValue;
2054
- const [isOpen, setIsOpen] = React5.useState(false);
2055
- const [activeIndex, setActiveIndex] = React5.useState(-1);
2056
- const [dropUp, setDropUp] = React5.useState(false);
2057
- const wrapperRef = React5.useRef(null);
2058
- const controlRef = React5.useRef(null);
2059
- const currentOption = React5.useMemo(
2060
- () => options.find((o) => o.value === currentValue) ?? null,
2061
- [options, currentValue]
2062
- );
2063
- const setValue = React5.useCallback(
2064
- (next) => {
2065
- const option = options.find((o) => o.value === next) ?? null;
2066
- if (!isControlled) setInternalValue(next);
2067
- onChange?.(next, option);
2068
- },
2069
- [isControlled, onChange, options]
2070
- );
2071
- const handleOutsideClick = React5.useEffectEvent((e) => {
2072
- if (!wrapperRef.current?.contains(e.target)) {
2073
- setIsOpen(false);
2074
- }
2075
- });
2076
- React5.useEffect(() => {
2077
- document.addEventListener("mousedown", handleOutsideClick);
2078
- return () => document.removeEventListener("mousedown", handleOutsideClick);
2079
- }, []);
2080
- const moveActive = (dir) => {
2081
- if (!isOpen) {
2082
- setIsOpen(true);
2083
- return;
2084
- }
2085
- let i = activeIndex;
2086
- const len = options.length;
2087
- for (let step = 0; step < len; step++) {
2088
- i = (i + dir + len) % len;
2089
- if (!options[i].disabled) {
2090
- setActiveIndex(i);
2091
- break;
2092
- }
2093
- }
2094
- };
2095
- const commitActive = () => {
2096
- if (activeIndex < 0 || activeIndex >= options.length) return;
2097
- const opt = options[activeIndex];
2098
- if (!opt.disabled) {
2099
- setValue(opt.value);
2100
- setIsOpen(false);
2101
- }
2102
- };
2103
- const onKeyDown = (e) => {
2104
- if (disabled) return;
2105
- switch (e.key) {
2106
- case " ":
2107
- case "Enter":
2108
- e.preventDefault();
2109
- if (!isOpen) setIsOpen(true);
2110
- else commitActive();
2111
- break;
2112
- case "ArrowDown":
2113
- e.preventDefault();
2114
- moveActive(1);
2115
- break;
2116
- case "ArrowUp":
2117
- e.preventDefault();
2118
- moveActive(-1);
2119
- break;
2120
- case "Home":
2121
- e.preventDefault();
2122
- setIsOpen(true);
2123
- setActiveIndex(options.findIndex((o) => !o.disabled));
2124
- break;
2125
- case "End":
2126
- e.preventDefault();
2127
- setIsOpen(true);
2128
- for (let i = options.length - 1; i >= 0; i--) {
2129
- if (!options[i].disabled) {
2130
- setActiveIndex(i);
2131
- break;
2132
- }
2133
- }
2134
- break;
2135
- case "Escape":
2136
- e.preventDefault();
2137
- setIsOpen(false);
2138
- break;
2139
- }
2140
- };
2141
- React5.useEffect(() => {
2142
- if (!isOpen) return;
2143
- const idx = options.findIndex((o) => o.value === currentValue && !o.disabled);
2144
- setActiveIndex(
2145
- idx >= 0 ? idx : Math.max(
2146
- 0,
2147
- options.findIndex((o) => !o.disabled)
2148
- )
2149
- );
2150
- }, [isOpen, options, currentValue]);
2151
- React5.useLayoutEffect(() => {
2152
- if (!isOpen || !controlRef.current) return;
2153
- const rect = controlRef.current.getBoundingClientRect();
2154
- const listHeight = Math.min(options.length * 40, 288);
2155
- const spaceBelow = window.innerHeight - rect.bottom;
2156
- const spaceAbove = rect.top;
2157
- setDropUp(spaceBelow < listHeight && spaceAbove > spaceBelow);
2158
- }, [isOpen, options.length]);
2159
- const rootClassName = cn("select", { select_has_label: !!label }, className);
2160
- const fieldsetClassName = cn(
2161
- "select_fieldset",
2162
- `select_variant_${variant}`,
2163
- `select_size_${size}`,
2164
- { is_open: isOpen, is_disabled: disabled }
2165
- );
2166
- const listClassName = cn("select_list", { select_list_up: dropUp });
2167
- return /* @__PURE__ */ jsxs(
2168
- "div",
2169
- {
2170
- ref: wrapperRef,
2171
- className: rootClassName,
2172
- style: fullWidth ? { width: "100%" } : void 0,
2173
- children: [
2174
- /* @__PURE__ */ jsxs("fieldset", { className: fieldsetClassName, children: [
2175
- label && /* @__PURE__ */ jsx("legend", { className: "select_label", children: /* @__PURE__ */ jsx("label", { htmlFor: selectId, children: label }) }),
2176
- /* @__PURE__ */ jsxs(
2177
- "button",
2178
- {
2179
- ref: controlRef,
2180
- id: selectId,
2181
- type: "button",
2182
- className: cn("select_control", { is_disabled: disabled }),
2183
- "aria-haspopup": "listbox",
2184
- "aria-expanded": isOpen,
2185
- "aria-controls": `${selectId}_listbox`,
2186
- onClick: () => !disabled && setIsOpen((o) => !o),
2187
- onKeyDown,
2188
- disabled,
2189
- children: [
2190
- /* @__PURE__ */ jsx(
2191
- "span",
2192
- {
2193
- className: currentOption ? "select_value" : "select_placeholder",
2194
- style: textAlign === "left" ? { textAlign: "start" } : void 0,
2195
- children: currentOption ? currentOption.label : placeholder
2196
- }
2197
- ),
2198
- /* @__PURE__ */ jsx("span", { className: "select_icon", "aria-hidden": "true", children: /* @__PURE__ */ jsx(ChevronDown, { size: 16 }) })
2199
- ]
2200
- }
2201
- )
2202
- ] }),
2203
- isOpen && /* @__PURE__ */ jsx("div", { id: `${selectId}_listbox`, role: "listbox", className: listClassName, children: options.map((opt, i) => {
2204
- const selected = currentValue === opt.value;
2205
- const active = i === activeIndex;
2206
- const optionClassName = cn("select_option", {
2207
- is_selected: selected,
2208
- is_active: active,
2209
- is_disabled: opt.disabled
2210
- });
2211
- return /* @__PURE__ */ jsxs(
2212
- "div",
2213
- {
2214
- role: "option",
2215
- tabIndex: -1,
2216
- "aria-selected": selected,
2217
- "aria-disabled": opt.disabled ? true : void 0,
2218
- className: optionClassName,
2219
- onMouseEnter: () => !opt.disabled && setActiveIndex(i),
2220
- onClick: () => {
2221
- if (opt.disabled) return;
2222
- setValue(opt.value);
2223
- setIsOpen(false);
2224
- },
2225
- onKeyDown: (e) => {
2226
- if (opt.disabled) return;
2227
- if (e.key === "Enter" || e.key === " ") {
2228
- e.preventDefault();
2229
- setValue(opt.value);
2230
- setIsOpen(false);
2231
- }
2232
- },
2233
- children: [
2234
- /* @__PURE__ */ jsx("span", { children: opt.label }),
2235
- selected && /* @__PURE__ */ jsx(Check, { size: 16, "aria-hidden": "true" })
2236
- ]
2237
- },
2238
- opt.value
2239
- );
2240
- }) })
2241
- ]
2242
- }
2243
- );
2244
- };
2245
2262
  var Spinner = ({ size = 24, ariaLabel = "Loading" }) => {
2246
2263
  return /* @__PURE__ */ jsx(
2247
2264
  "span",
@@ -8,8 +8,9 @@ $color_base_neutral_200: #E5E5E5;
8
8
  $color_base_neutral_300: #999999;
9
9
  $color_base_neutral_400: #B3B3B3;
10
10
  $color_base_neutral_500: #888888;
11
- $color_base_neutral_600: #6B6B6B;
11
+ $color_base_neutral_600: #777777;
12
12
  $color_base_neutral_700: #666666;
13
+ $color_base_neutral_800: #333333;
13
14
  $color_base_neutral_900: #121212;
14
15
  $color_base_status_error: #EF4444;
15
16
  $color_base_status_success: #10B981;
@@ -45,6 +46,7 @@ $color_bg_solid_dim: $color_base_neutral_50;
45
46
  $color_bg_additive: $color_base_alpha_black_5;
46
47
  $color_bg_disabled: $color_base_alpha_black_12;
47
48
  $color_bg_overlay: $color_base_alpha_black_50;
49
+ $color_bg_inverse_surface: $color_base_neutral_800;
48
50
 
49
51
  // ── Semantic: State ───────────────────────────────────────────────────────────
50
52
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bigtablet/design-system",
3
- "version": "2.2.3",
3
+ "version": "2.3.0",
4
4
  "description": "Bigtablet Design System UI Components",
5
5
  "type": "module",
6
6
  "types": "dist/index.d.ts",