@bigtablet/design-system 2.2.3 → 2.2.4

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
@@ -586,11 +586,11 @@ interface DatePickerProps {
586
586
  * @deprecated `fullWidth` 사용 또는 CSS로 처리
587
587
  */
588
588
  width?: number | string;
589
- /** 연도 select의 aria-label 및 빈 옵션 텍스트 (기본값: "Year") */
589
+ /** 연도 select의 라벨/플레이스홀더 (기본값: "Year") */
590
590
  yearLabel?: string;
591
- /** 월 select의 aria-label 및 빈 옵션 텍스트 (기본값: "Month") */
591
+ /** 월 select의 라벨/플레이스홀더 (기본값: "Month") */
592
592
  monthLabel?: string;
593
- /** 일 select의 aria-label 및 빈 옵션 텍스트 (기본값: "Day") */
593
+ /** 일 select의 라벨/플레이스홀더 (기본값: "Day") */
594
594
  dayLabel?: string;
595
595
  /**
596
596
  * minDate 설정 시 스크린리더에 전달할 안내 문구 포맷.
@@ -605,7 +605,7 @@ interface DatePickerProps {
605
605
  }
606
606
  /**
607
607
  * 연/월/일 선택형 데이트 피커를 렌더링한다.
608
- * 입력 값과 선택 범위를 기준으로 옵션을 계산하고, 선택 변경을 상위로 전달한다.
608
+ * 내부적으로 DS Select 3개를 조합해 드롭다운 UX 일관성을 유지한다.
609
609
  */
610
610
  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
611
 
package/dist/index.js CHANGED
@@ -803,6 +803,217 @@ var Chip = ({
803
803
  )
804
804
  ] });
805
805
  };
806
+ var Select = ({
807
+ id,
808
+ label,
809
+ placeholder = "Select\u2026",
810
+ options,
811
+ value,
812
+ onChange,
813
+ defaultValue = null,
814
+ disabled,
815
+ size = "md",
816
+ variant = "outline",
817
+ fullWidth,
818
+ className,
819
+ textAlign = "left"
820
+ }) => {
821
+ const internalId = React5.useId();
822
+ const selectId = id ?? internalId;
823
+ const isControlled = value !== void 0;
824
+ const [internalValue, setInternalValue] = React5.useState(defaultValue);
825
+ const currentValue = isControlled ? value ?? null : internalValue;
826
+ const [isOpen, setIsOpen] = React5.useState(false);
827
+ const [activeIndex, setActiveIndex] = React5.useState(-1);
828
+ const [dropUp, setDropUp] = React5.useState(false);
829
+ const wrapperRef = React5.useRef(null);
830
+ const controlRef = React5.useRef(null);
831
+ const currentOption = React5.useMemo(
832
+ () => options.find((o) => o.value === currentValue) ?? null,
833
+ [options, currentValue]
834
+ );
835
+ const setValue = React5.useCallback(
836
+ (next) => {
837
+ const option = options.find((o) => o.value === next) ?? null;
838
+ if (!isControlled) setInternalValue(next);
839
+ onChange?.(next, option);
840
+ },
841
+ [isControlled, onChange, options]
842
+ );
843
+ const handleOutsideClick = React5.useEffectEvent((e) => {
844
+ if (!wrapperRef.current?.contains(e.target)) {
845
+ setIsOpen(false);
846
+ }
847
+ });
848
+ React5.useEffect(() => {
849
+ document.addEventListener("mousedown", handleOutsideClick);
850
+ return () => document.removeEventListener("mousedown", handleOutsideClick);
851
+ }, []);
852
+ const moveActive = (dir) => {
853
+ if (!isOpen) {
854
+ setIsOpen(true);
855
+ return;
856
+ }
857
+ let i = activeIndex;
858
+ const len = options.length;
859
+ for (let step = 0; step < len; step++) {
860
+ i = (i + dir + len) % len;
861
+ if (!options[i].disabled) {
862
+ setActiveIndex(i);
863
+ break;
864
+ }
865
+ }
866
+ };
867
+ const commitActive = () => {
868
+ if (activeIndex < 0 || activeIndex >= options.length) return;
869
+ const opt = options[activeIndex];
870
+ if (!opt.disabled) {
871
+ setValue(opt.value);
872
+ setIsOpen(false);
873
+ }
874
+ };
875
+ const onKeyDown = (e) => {
876
+ if (disabled) return;
877
+ switch (e.key) {
878
+ case " ":
879
+ case "Enter":
880
+ e.preventDefault();
881
+ if (!isOpen) setIsOpen(true);
882
+ else commitActive();
883
+ break;
884
+ case "ArrowDown":
885
+ e.preventDefault();
886
+ moveActive(1);
887
+ break;
888
+ case "ArrowUp":
889
+ e.preventDefault();
890
+ moveActive(-1);
891
+ break;
892
+ case "Home":
893
+ e.preventDefault();
894
+ setIsOpen(true);
895
+ setActiveIndex(options.findIndex((o) => !o.disabled));
896
+ break;
897
+ case "End":
898
+ e.preventDefault();
899
+ setIsOpen(true);
900
+ for (let i = options.length - 1; i >= 0; i--) {
901
+ if (!options[i].disabled) {
902
+ setActiveIndex(i);
903
+ break;
904
+ }
905
+ }
906
+ break;
907
+ case "Escape":
908
+ e.preventDefault();
909
+ setIsOpen(false);
910
+ break;
911
+ }
912
+ };
913
+ React5.useEffect(() => {
914
+ if (!isOpen) return;
915
+ const idx = options.findIndex((o) => o.value === currentValue && !o.disabled);
916
+ setActiveIndex(
917
+ idx >= 0 ? idx : Math.max(
918
+ 0,
919
+ options.findIndex((o) => !o.disabled)
920
+ )
921
+ );
922
+ }, [isOpen, options, currentValue]);
923
+ React5.useLayoutEffect(() => {
924
+ if (!isOpen || !controlRef.current) return;
925
+ const rect = controlRef.current.getBoundingClientRect();
926
+ const listHeight = Math.min(options.length * 40, 288);
927
+ const spaceBelow = window.innerHeight - rect.bottom;
928
+ const spaceAbove = rect.top;
929
+ setDropUp(spaceBelow < listHeight && spaceAbove > spaceBelow);
930
+ }, [isOpen, options.length]);
931
+ const rootClassName = cn("select", { select_has_label: !!label }, className);
932
+ const fieldsetClassName = cn(
933
+ "select_fieldset",
934
+ `select_variant_${variant}`,
935
+ `select_size_${size}`,
936
+ { is_open: isOpen, is_disabled: disabled }
937
+ );
938
+ const listClassName = cn("select_list", { select_list_up: dropUp });
939
+ return /* @__PURE__ */ jsxs(
940
+ "div",
941
+ {
942
+ ref: wrapperRef,
943
+ className: rootClassName,
944
+ style: fullWidth ? { width: "100%" } : void 0,
945
+ children: [
946
+ /* @__PURE__ */ jsxs("fieldset", { className: fieldsetClassName, children: [
947
+ label && /* @__PURE__ */ jsx("legend", { className: "select_label", children: /* @__PURE__ */ jsx("label", { htmlFor: selectId, children: label }) }),
948
+ /* @__PURE__ */ jsx("div", { className: "select_inner", children: /* @__PURE__ */ jsxs(
949
+ "button",
950
+ {
951
+ ref: controlRef,
952
+ id: selectId,
953
+ type: "button",
954
+ className: cn("select_control", { is_disabled: disabled }),
955
+ "aria-haspopup": "listbox",
956
+ "aria-expanded": isOpen,
957
+ "aria-controls": `${selectId}_listbox`,
958
+ onClick: () => !disabled && setIsOpen((o) => !o),
959
+ onKeyDown,
960
+ disabled,
961
+ children: [
962
+ /* @__PURE__ */ jsx(
963
+ "span",
964
+ {
965
+ className: currentOption ? "select_value" : "select_placeholder",
966
+ style: textAlign === "left" ? { textAlign: "start" } : void 0,
967
+ children: currentOption ? currentOption.label : placeholder
968
+ }
969
+ ),
970
+ /* @__PURE__ */ jsx("span", { className: "select_icon", "aria-hidden": "true", children: /* @__PURE__ */ jsx(ChevronDown, { size: 16 }) })
971
+ ]
972
+ }
973
+ ) })
974
+ ] }),
975
+ isOpen && /* @__PURE__ */ jsx("div", { id: `${selectId}_listbox`, role: "listbox", className: listClassName, children: options.map((opt, i) => {
976
+ const selected = currentValue === opt.value;
977
+ const active = i === activeIndex;
978
+ const optionClassName = cn("select_option", {
979
+ is_selected: selected,
980
+ is_active: active,
981
+ is_disabled: opt.disabled
982
+ });
983
+ return /* @__PURE__ */ jsxs(
984
+ "div",
985
+ {
986
+ role: "option",
987
+ tabIndex: -1,
988
+ "aria-selected": selected,
989
+ "aria-disabled": opt.disabled ? true : void 0,
990
+ className: optionClassName,
991
+ onMouseEnter: () => !opt.disabled && setActiveIndex(i),
992
+ onClick: () => {
993
+ if (opt.disabled) return;
994
+ setValue(opt.value);
995
+ setIsOpen(false);
996
+ },
997
+ onKeyDown: (e) => {
998
+ if (opt.disabled) return;
999
+ if (e.key === "Enter" || e.key === " ") {
1000
+ e.preventDefault();
1001
+ setValue(opt.value);
1002
+ setIsOpen(false);
1003
+ }
1004
+ },
1005
+ children: [
1006
+ /* @__PURE__ */ jsx("span", { children: opt.label }),
1007
+ selected && /* @__PURE__ */ jsx(Check, { size: 16, "aria-hidden": "true" })
1008
+ ]
1009
+ },
1010
+ opt.value
1011
+ );
1012
+ }) })
1013
+ ]
1014
+ }
1015
+ );
1016
+ };
806
1017
  var pad = (n) => String(n).padStart(2, "0");
807
1018
  var getDaysInMonth = (year, month) => new Date(year, month, 0).getDate();
808
1019
  var normalizeWidth = (v) => typeof v === "number" ? `${v}px` : v;
@@ -856,9 +1067,12 @@ var DatePicker = ({
856
1067
  }, [minDate]);
857
1068
  const { year, month, day } = parsed;
858
1069
  const maxYear = selectableRange === "until-today" ? todayYear : endYear;
859
- const minMonth = min.year > 0 && year === min.year ? min.month : 1;
1070
+ const minMonth = min.year > 0 && year === min.year ? Math.min(12, Math.max(1, min.month)) : 1;
860
1071
  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;
1072
+ const minDay = Math.min(31, Math.max(
1073
+ 1,
1074
+ min.year > 0 && min.month > 0 && year === min.year && month === min.month ? min.day : 1
1075
+ ));
862
1076
  const maxDay = React5.useMemo(() => {
863
1077
  if (!year || !month) return 31;
864
1078
  const daysInMonth = getDaysInMonth(year, month);
@@ -868,15 +1082,24 @@ var DatePicker = ({
868
1082
  return daysInMonth;
869
1083
  }, [year, month, selectableRange, todayYear, todayMonth, todayDay]);
870
1084
  const yearOptions = React5.useMemo(
871
- () => range(startYear, maxYear),
1085
+ () => range(startYear, maxYear).map((y) => ({
1086
+ value: String(y),
1087
+ label: String(y)
1088
+ })),
872
1089
  [startYear, maxYear]
873
1090
  );
874
1091
  const monthOptions = React5.useMemo(
875
- () => range(minMonth, Math.max(minMonth, maxMonth)),
1092
+ () => range(minMonth, Math.max(minMonth, maxMonth)).map((m) => ({
1093
+ value: String(m),
1094
+ label: pad(m)
1095
+ })),
876
1096
  [minMonth, maxMonth]
877
1097
  );
878
1098
  const dayOptions = React5.useMemo(
879
- () => range(minDay, Math.max(minDay, maxDay)),
1099
+ () => range(minDay, Math.max(minDay, maxDay)).map((d) => ({
1100
+ value: String(d),
1101
+ label: pad(d)
1102
+ })),
880
1103
  [minDay, maxDay]
881
1104
  );
882
1105
  const emit = React5.useCallback(
@@ -891,7 +1114,9 @@ var DatePicker = ({
891
1114
  [mode, onChange]
892
1115
  );
893
1116
  const handleYearChange = React5.useCallback(
894
- (newYear) => {
1117
+ (raw) => {
1118
+ if (!raw) return;
1119
+ const newYear = Number(raw);
895
1120
  let newMonth = month;
896
1121
  const newMinMonth = min.year > 0 && newYear === min.year ? min.month : 1;
897
1122
  const newMaxMonth = selectableRange === "until-today" && newYear === todayYear ? todayMonth : 12;
@@ -906,22 +1131,23 @@ var DatePicker = ({
906
1131
  [month, day, min.year, min.month, selectableRange, todayYear, todayMonth, emit]
907
1132
  );
908
1133
  const handleMonthChange = React5.useCallback(
909
- (newMonth) => {
910
- if (!year) return;
911
- emit(year, newMonth, day || void 0);
1134
+ (raw) => {
1135
+ if (!raw || !year) return;
1136
+ emit(year, Number(raw), day || void 0);
912
1137
  },
913
1138
  [year, day, emit]
914
1139
  );
915
1140
  const handleDayChange = React5.useCallback(
916
- (newDay) => {
917
- if (!year || !month) return;
918
- emit(year, month, newDay);
1141
+ (raw) => {
1142
+ if (!raw || !year || !month) return;
1143
+ emit(year, month, Number(raw));
919
1144
  },
920
1145
  [year, month, emit]
921
1146
  );
922
1147
  const containerStyle = width ? { width: normalizeWidth(width) } : void 0;
923
1148
  const rootClassName = cn("date_picker", {
924
- date_picker_full_width: fullWidth && !width
1149
+ date_picker_full_width: fullWidth && !width,
1150
+ date_picker_disabled: disabled
925
1151
  });
926
1152
  const constraintParts = [];
927
1153
  if (minDate) constraintParts.push(minDateSrFormat.replace("{date}", minDate));
@@ -939,43 +1165,43 @@ var DatePicker = ({
939
1165
  "aria-labelledby": label ? groupId : void 0,
940
1166
  "aria-describedby": constraintDesc ? constraintId : void 0,
941
1167
  children: [
942
- /* @__PURE__ */ jsxs(
943
- "select",
1168
+ /* @__PURE__ */ jsx(
1169
+ Select,
944
1170
  {
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
- ]
1171
+ size: "sm",
1172
+ fullWidth: true,
1173
+ label: yearLabel,
1174
+ placeholder: yearLabel,
1175
+ options: yearOptions,
1176
+ value: year ? String(year) : null,
1177
+ onChange: handleYearChange,
1178
+ disabled
953
1179
  }
954
1180
  ),
955
- /* @__PURE__ */ jsxs(
956
- "select",
1181
+ /* @__PURE__ */ jsx(
1182
+ Select,
957
1183
  {
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
- ]
1184
+ size: "sm",
1185
+ fullWidth: true,
1186
+ label: monthLabel,
1187
+ placeholder: monthLabel,
1188
+ options: monthOptions,
1189
+ value: month ? String(month) : null,
1190
+ onChange: handleMonthChange,
1191
+ disabled: disabled || !year
966
1192
  }
967
1193
  ),
968
- mode === "year-month-day" && /* @__PURE__ */ jsxs(
969
- "select",
1194
+ mode === "year-month-day" && /* @__PURE__ */ jsx(
1195
+ Select,
970
1196
  {
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
- ]
1197
+ size: "sm",
1198
+ fullWidth: true,
1199
+ label: dayLabel,
1200
+ placeholder: dayLabel,
1201
+ options: dayOptions,
1202
+ value: day ? String(day) : null,
1203
+ onChange: handleDayChange,
1204
+ disabled: disabled || !month
979
1205
  }
980
1206
  )
981
1207
  ]
@@ -2031,217 +2257,6 @@ var Radio = ({ label, size = "md", className, ref, ...props }) => {
2031
2257
  ] });
2032
2258
  };
2033
2259
  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
2260
  var Spinner = ({ size = 24, ariaLabel = "Loading" }) => {
2246
2261
  return /* @__PURE__ */ jsx(
2247
2262
  "span",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bigtablet/design-system",
3
- "version": "2.2.3",
3
+ "version": "2.2.4",
4
4
  "description": "Bigtablet Design System UI Components",
5
5
  "type": "module",
6
6
  "types": "dist/index.d.ts",