@zenkigen-inc/component-ui 1.14.5 → 1.15.1

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.js CHANGED
@@ -612,6 +612,109 @@ function Loading({
612
612
  });
613
613
  }
614
614
 
615
+ /**
616
+ * モーダル表示時にバックグラウンドのスクロールを防止するコンポーネント。
617
+ * コンポーネントがマウントされている間、position: fixedアプローチを使用して
618
+ * body要素にスクロールロックを適用します。
619
+ * 縦横両方のスクロール位置を保存し、コンポーネントのアンマウント時に復元します。
620
+ * スクロールバーの有無を検出し、その幅を考慮してレイアウトシフトを防止します。
621
+ * position、top、left、width、overflow、padding-rightを変更し、
622
+ * 最小限の変更でスクロールを防止します。
623
+ * グローバルCSSに依存せず、すべてインラインスタイルで実装されています。
624
+ * このコンポーネントは実際のDOM要素をレンダリングせず、効果のみを適用します。
625
+ *
626
+ * @example
627
+ * // モーダルコンポーネント内で使用する例
628
+ * const Modal = ({ isOpen, children }) => {
629
+ * return (
630
+ * <>
631
+ * {isOpen && <BodyScrollLock />}
632
+ * {isOpen && (
633
+ * <div className="modal">
634
+ * {children}
635
+ * </div>
636
+ * )}
637
+ * </>
638
+ * );
639
+ * };
640
+ */
641
+ const BodyScrollLock = () => {
642
+ useLayoutEffect(() => {
643
+ // 現在の縦横スクロール位置を記録
644
+ const {
645
+ scrollX,
646
+ scrollY
647
+ } = window;
648
+ const {
649
+ body
650
+ } = document;
651
+ // スクロールバーの有無と幅を検出
652
+ const hasVerticalScrollbar = document.documentElement.scrollHeight > document.documentElement.clientHeight;
653
+ const scrollbarWidth = hasVerticalScrollbar ? window.innerWidth - document.documentElement.clientWidth : 0;
654
+ // 元のインラインスタイルの値を保存
655
+ const originalInlineStyles = {
656
+ position: body.style.position,
657
+ top: body.style.top,
658
+ left: body.style.left,
659
+ width: body.style.width,
660
+ overflow: body.style.overflow,
661
+ paddingRight: body.style.paddingRight
662
+ };
663
+ // スクロールロックスタイルを適用
664
+ body.style.position = 'fixed';
665
+ body.style.top = `-${scrollY}px`;
666
+ body.style.left = `-${scrollX}px`;
667
+ body.style.width = '100%';
668
+ body.style.overflow = 'hidden';
669
+ // スクロールバーがある場合、その幅分だけpadding-rightを調整
670
+ if (hasVerticalScrollbar && scrollbarWidth > 0) {
671
+ // 現在のpadding-rightの値を取得
672
+ const {
673
+ paddingRight
674
+ } = window.getComputedStyle(body);
675
+ const paddingRightValue = paddingRight !== '' ? parseInt(paddingRight, 10) : 0;
676
+ // スクロールバーの幅を加算
677
+ body.style.paddingRight = `${paddingRightValue + scrollbarWidth}px`;
678
+ }
679
+ // クリーンアップ関数
680
+ return () => {
681
+ // 元のスタイル値を取得
682
+ const {
683
+ position,
684
+ top,
685
+ left,
686
+ width,
687
+ overflow,
688
+ paddingRight
689
+ } = originalInlineStyles;
690
+ // プロパティごとに元の値を復元
691
+ restoreProperty(body, 'position', position);
692
+ restoreProperty(body, 'top', top);
693
+ restoreProperty(body, 'left', left);
694
+ restoreProperty(body, 'width', width);
695
+ restoreProperty(body, 'overflow', overflow);
696
+ restoreProperty(body, 'padding-right', paddingRight);
697
+ // スクロール位置を復元
698
+ window.scrollTo(scrollX, scrollY);
699
+ };
700
+ }, []); // 空の依存配列を指定して初回のみ実行
701
+ // DOM要素をレンダリングせず、nullを返す
702
+ return null;
703
+ };
704
+ /**
705
+ * 元のスタイル値を復元するヘルパー関数
706
+ * @param element スタイルを復元する要素
707
+ * @param property 復元するCSSプロパティ名
708
+ * @param value 復元する値
709
+ */
710
+ function restoreProperty(element, property, value) {
711
+ if (value !== '') {
712
+ element.style.setProperty(property, value);
713
+ } else {
714
+ element.style.removeProperty(property);
715
+ }
716
+ }
717
+
615
718
  function ModalBody({
616
719
  children
617
720
  }) {
@@ -679,22 +782,24 @@ function Modal({
679
782
  useEffect(() => {
680
783
  setIsMounted(true);
681
784
  }, []);
682
- return isMounted && isOpen ? /*#__PURE__*/createPortal(/*#__PURE__*/jsx(ModalContext.Provider, {
683
- value: {
684
- onClose
685
- },
686
- children: /*#__PURE__*/jsx("div", {
687
- className: "fixed left-0 top-0 z-overlay flex size-full items-center justify-center bg-backgroundOverlayBlack py-4",
785
+ return isMounted && isOpen ? /*#__PURE__*/jsxs(Fragment, {
786
+ children: [/*#__PURE__*/jsx(BodyScrollLock, {}), /*#__PURE__*/createPortal(/*#__PURE__*/jsx(ModalContext.Provider, {
787
+ value: {
788
+ onClose
789
+ },
688
790
  children: /*#__PURE__*/jsx("div", {
689
- className: "grid max-h-full min-h-[120px] grid-rows-[max-content_1fr_max-content] flex-col rounded-lg bg-uiBackground01 shadow-modalShadow",
690
- style: {
691
- width: renderWidth,
692
- height: renderHeight
693
- },
694
- children: children
791
+ className: "fixed left-0 top-0 z-overlay flex size-full items-center justify-center bg-backgroundOverlayBlack py-4",
792
+ children: /*#__PURE__*/jsx("div", {
793
+ className: "grid max-h-full min-h-[120px] grid-rows-[max-content_1fr_max-content] flex-col rounded-lg bg-uiBackground01 shadow-modalShadow",
794
+ style: {
795
+ width: renderWidth,
796
+ height: renderHeight
797
+ },
798
+ children: children
799
+ })
695
800
  })
696
- })
697
- }), (portalTargetRef == null ? void 0 : portalTargetRef.current) != null ? portalTargetRef.current : document.body) : null;
801
+ }), (portalTargetRef == null ? void 0 : portalTargetRef.current) != null ? portalTargetRef.current : document.body)]
802
+ }) : null;
698
803
  }
699
804
  Modal.Body = ModalBody;
700
805
  Modal.Header = ModalHeader;
@@ -1719,7 +1824,7 @@ function Toggle({
1719
1824
  'bg-disabledOn': isDisabled && isChecked,
1720
1825
  'bg-disabled01': isDisabled && !isChecked,
1721
1826
  'bg-interactive01 peer-hover:bg-hover01': !isDisabled && isChecked,
1722
- 'bg-interactive02 peer-hover:bg-hover02Dark': !isDisabled && !isChecked,
1827
+ 'bg-interactive02 peer-hover:bg-hoverGray': !isDisabled && !isChecked,
1723
1828
  'w-8 h-4 px-[3px]': size === 'small',
1724
1829
  'w-12 h-6 px-1': size === 'medium' || size === 'large'
1725
1830
  });