@intlayer/design-system 9.3.2 → 9.4.0-canary.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/esm/api/hooks/analytics.mjs +6 -5
- package/dist/esm/api/hooks/analytics.mjs.map +1 -1
- package/dist/esm/api/hooks/auth.mjs +2 -0
- package/dist/esm/api/hooks/auth.mjs.map +1 -1
- package/dist/esm/api/index.mjs +2 -2
- package/dist/esm/api/useAuth/useOAuth2.mjs +1 -1
- package/dist/esm/api/useAuth/useSession.mjs +1 -1
- package/dist/esm/components/Accordion/Accordion.mjs +2 -2
- package/dist/esm/components/Accordion/Accordion.mjs.map +1 -1
- package/dist/esm/components/Browser/Browser.mjs +1 -1
- package/dist/esm/components/Browser/Browser.mjs.map +1 -1
- package/dist/esm/components/ContentEditor/ContentEditorTextArea.mjs +1 -1
- package/dist/esm/components/CopyButton/index.mjs +2 -1
- package/dist/esm/components/CopyButton/index.mjs.map +1 -1
- package/dist/esm/components/DictionaryFieldEditor/ContentEditor.mjs +4 -5
- package/dist/esm/components/DictionaryFieldEditor/ContentEditor.mjs.map +1 -1
- package/dist/esm/components/DictionaryFieldEditor/ContentEditorView/TextEditor.mjs +6 -10
- package/dist/esm/components/DictionaryFieldEditor/ContentEditorView/TextEditor.mjs.map +1 -1
- package/dist/esm/components/DictionaryFieldEditor/DictionaryCreationForm/DictionaryCreationForm.mjs +1 -1
- package/dist/esm/components/DictionaryFieldEditor/DictionaryDetails/DictionaryDetailsForm.mjs +6 -6
- package/dist/esm/components/DictionaryFieldEditor/DictionaryDetails/DictionaryDetailsForm.mjs.map +1 -1
- package/dist/esm/components/DictionaryFieldEditor/DictionaryFieldEditor.mjs +3 -7
- package/dist/esm/components/DictionaryFieldEditor/DictionaryFieldEditor.mjs.map +1 -1
- package/dist/esm/components/DictionaryFieldEditor/NavigationView/NavigationViewNode.mjs +1 -1
- package/dist/esm/components/DictionaryFieldEditor/SaveForm/SaveForm.mjs +2 -2
- package/dist/esm/components/DictionaryFieldEditor/StructureView/StructureView.mjs +1 -1
- package/dist/esm/components/ExpandCollapse/ExpandCollapse.mjs +19 -12
- package/dist/esm/components/ExpandCollapse/ExpandCollapse.mjs.map +1 -1
- package/dist/esm/components/Form/elements/OTPElement.mjs +4 -4
- package/dist/esm/components/Form/elements/OTPElement.mjs.map +1 -1
- package/dist/esm/components/HTMLRender/HTMLRender.mjs +2 -2
- package/dist/esm/components/HTMLRender/HTMLRender.mjs.map +1 -1
- package/dist/esm/components/IDE/Code.mjs +3 -3
- package/dist/esm/components/IDE/Code.mjs.map +1 -1
- package/dist/esm/components/IDE/CodeBlockClient.mjs +16 -4
- package/dist/esm/components/IDE/CodeBlockClient.mjs.map +1 -1
- package/dist/esm/components/IDE/CodeBlockHighlight.mjs +14 -5
- package/dist/esm/components/IDE/CodeBlockHighlight.mjs.map +1 -1
- package/dist/esm/components/IDE/CodeBlockServer.mjs +5 -3
- package/dist/esm/components/IDE/CodeBlockServer.mjs.map +1 -1
- package/dist/esm/components/IDE/CodeBlockShiki.mjs +18 -18
- package/dist/esm/components/IDE/CodeBlockShiki.mjs.map +1 -1
- package/dist/esm/components/IDE/IDE.mjs +2 -5
- package/dist/esm/components/IDE/IDE.mjs.map +1 -1
- package/dist/esm/components/IDE/MarkDownRender.mjs +1 -2
- package/dist/esm/components/IDE/MarkDownRender.mjs.map +1 -1
- package/dist/esm/components/IDE/index.mjs +2 -2
- package/dist/esm/components/IDE/shikiThemes.mjs +18 -0
- package/dist/esm/components/IDE/shikiThemes.mjs.map +1 -0
- package/dist/esm/components/Link/Link.mjs +10 -5
- package/dist/esm/components/Link/Link.mjs.map +1 -1
- package/dist/esm/components/LocaleSwitcherContentDropDown/LocaleSwitcherContent.mjs +1 -1
- package/dist/esm/components/MarkDownRender/MarkDownIframe.mjs +105 -4
- package/dist/esm/components/MarkDownRender/MarkDownIframe.mjs.map +1 -1
- package/dist/esm/components/MarkDownRender/MarkDownRender.mjs +16 -16
- package/dist/esm/components/MarkDownRender/MarkDownRender.mjs.map +1 -1
- package/dist/esm/components/MaxHeightSmoother/index.mjs +1 -1
- package/dist/esm/components/MaxHeightSmoother/index.mjs.map +1 -1
- package/dist/esm/components/MaxWidthSmoother/index.mjs +1 -1
- package/dist/esm/components/MaxWidthSmoother/index.mjs.map +1 -1
- package/dist/esm/components/Modal/Modal.mjs +2 -2
- package/dist/esm/components/Navbar/MobileNavbar.mjs +1 -1
- package/dist/esm/components/Pagination/Pagination.mjs +4 -4
- package/dist/esm/components/Pagination/Pagination.mjs.map +1 -1
- package/dist/esm/components/Pattern/GridDistortionPattern.mjs +103 -17
- package/dist/esm/components/Pattern/GridDistortionPattern.mjs.map +1 -1
- package/dist/esm/components/Popover/dynamic.mjs +73 -28
- package/dist/esm/components/Popover/dynamic.mjs.map +1 -1
- package/dist/esm/components/Popover/index.mjs +2 -2
- package/dist/esm/components/Popover/static.mjs +47 -18
- package/dist/esm/components/Popover/static.mjs.map +1 -1
- package/dist/esm/components/RightDrawer/RightDrawer.mjs +5 -4
- package/dist/esm/components/RightDrawer/RightDrawer.mjs.map +1 -1
- package/dist/esm/components/SwitchSelector/useSwitchSelector.mjs +2 -2
- package/dist/esm/components/SwitchSelector/useSwitchSelector.mjs.map +1 -1
- package/dist/esm/components/Tab/Tab.mjs +38 -17
- package/dist/esm/components/Tab/Tab.mjs.map +1 -1
- package/dist/esm/components/TabSelector/TabSelector.mjs +2 -2
- package/dist/esm/components/TabSelector/TabSelector.mjs.map +1 -1
- package/dist/esm/components/TextArea/AutoSizeTextArea.mjs +3 -2
- package/dist/esm/components/TextArea/AutoSizeTextArea.mjs.map +1 -1
- package/dist/esm/components/ThemeSwitcherDropDown/DesktopThemeSwitcher.mjs +10 -18
- package/dist/esm/components/ThemeSwitcherDropDown/DesktopThemeSwitcher.mjs.map +1 -1
- package/dist/esm/components/ThemeSwitcherDropDown/MobileThemeSwitcher.mjs +14 -13
- package/dist/esm/components/ThemeSwitcherDropDown/MobileThemeSwitcher.mjs.map +1 -1
- package/dist/esm/components/index.mjs +3 -3
- package/dist/esm/hooks/index.mjs +9 -9
- package/dist/esm/hooks/useDevice.mjs +16 -6
- package/dist/esm/hooks/useDevice.mjs.map +1 -1
- package/dist/esm/hooks/useItemSelector.mjs +110 -112
- package/dist/esm/hooks/useItemSelector.mjs.map +1 -1
- package/dist/esm/hooks/useScrollY.mjs +64 -45
- package/dist/esm/hooks/useScrollY.mjs.map +1 -1
- package/dist/esm/hooks/useStickToBottom.mjs +69 -0
- package/dist/esm/hooks/useStickToBottom.mjs.map +1 -0
- package/dist/esm/libs/auth.mjs +16 -3
- package/dist/esm/libs/auth.mjs.map +1 -1
- package/dist/esm/providers/ThemeProvider.mjs +143 -0
- package/dist/esm/providers/ThemeProvider.mjs.map +1 -0
- package/dist/esm/providers/index.mjs +2 -1
- package/dist/esm/routes.mjs +2 -1
- package/dist/esm/routes.mjs.map +1 -1
- package/dist/esm/structured-data/buildCreativeWorkJsonLd.mjs +1 -1
- package/dist/esm/structured-data/buildCreativeWorkJsonLd.mjs.map +1 -1
- package/dist/esm/utils/index.mjs +3 -1
- package/dist/esm/utils/observeScrollExtent.mjs +39 -0
- package/dist/esm/utils/observeScrollExtent.mjs.map +1 -0
- package/dist/esm/utils/scheduleFrameTask.mjs +44 -0
- package/dist/esm/utils/scheduleFrameTask.mjs.map +1 -0
- package/dist/types/api/hooks/analytics.d.ts +5 -3
- package/dist/types/api/hooks/analytics.d.ts.map +1 -1
- package/dist/types/api/hooks/auth.d.ts.map +1 -1
- package/dist/types/components/Browser/Browser.d.ts +7 -1
- package/dist/types/components/Browser/Browser.d.ts.map +1 -1
- package/dist/types/components/CopyButton/index.d.ts +9 -3
- package/dist/types/components/CopyButton/index.d.ts.map +1 -1
- package/dist/types/components/DictionaryFieldEditor/ContentEditor.d.ts +0 -1
- package/dist/types/components/DictionaryFieldEditor/ContentEditor.d.ts.map +1 -1
- package/dist/types/components/DictionaryFieldEditor/ContentEditorView/TextEditor.d.ts +0 -1
- package/dist/types/components/DictionaryFieldEditor/ContentEditorView/TextEditor.d.ts.map +1 -1
- package/dist/types/components/DictionaryFieldEditor/DictionaryDetails/DictionaryDetailsForm.d.ts +0 -1
- package/dist/types/components/DictionaryFieldEditor/DictionaryDetails/DictionaryDetailsForm.d.ts.map +1 -1
- package/dist/types/components/ExpandCollapse/ExpandCollapse.d.ts.map +1 -1
- package/dist/types/components/HTMLRender/HTMLRender.d.ts +1 -2
- package/dist/types/components/HTMLRender/HTMLRender.d.ts.map +1 -1
- package/dist/types/components/IDE/Code.d.ts +6 -1
- package/dist/types/components/IDE/Code.d.ts.map +1 -1
- package/dist/types/components/IDE/CodeBlockClient.d.ts +18 -2
- package/dist/types/components/IDE/CodeBlockClient.d.ts.map +1 -1
- package/dist/types/components/IDE/CodeBlockHighlight.d.ts +3 -2
- package/dist/types/components/IDE/CodeBlockHighlight.d.ts.map +1 -1
- package/dist/types/components/IDE/CodeBlockServer.d.ts +0 -1
- package/dist/types/components/IDE/CodeBlockServer.d.ts.map +1 -1
- package/dist/types/components/IDE/CodeBlockShiki.d.ts +0 -1
- package/dist/types/components/IDE/CodeBlockShiki.d.ts.map +1 -1
- package/dist/types/components/IDE/IDE.d.ts +0 -1
- package/dist/types/components/IDE/IDE.d.ts.map +1 -1
- package/dist/types/components/IDE/MarkDownRender.d.ts +0 -1
- package/dist/types/components/IDE/MarkDownRender.d.ts.map +1 -1
- package/dist/types/components/IDE/index.d.ts +2 -2
- package/dist/types/components/IDE/shikiThemes.d.ts +18 -0
- package/dist/types/components/IDE/shikiThemes.d.ts.map +1 -0
- package/dist/types/components/MarkDownRender/MarkDownIframe.d.ts.map +1 -1
- package/dist/types/components/MarkDownRender/MarkDownRender.d.ts +1 -2
- package/dist/types/components/MarkDownRender/MarkDownRender.d.ts.map +1 -1
- package/dist/types/components/Pattern/GridDistortionPattern.d.ts.map +1 -1
- package/dist/types/components/Popover/dynamic.d.ts.map +1 -1
- package/dist/types/components/Popover/index.d.ts +2 -2
- package/dist/types/components/Popover/static.d.ts +6 -1
- package/dist/types/components/Popover/static.d.ts.map +1 -1
- package/dist/types/components/Tab/Tab.d.ts +2 -2
- package/dist/types/components/Tab/Tab.d.ts.map +1 -1
- package/dist/types/components/ThemeSwitcherDropDown/DesktopThemeSwitcher.d.ts +1 -1
- package/dist/types/components/ThemeSwitcherDropDown/DesktopThemeSwitcher.d.ts.map +1 -1
- package/dist/types/components/ThemeSwitcherDropDown/MobileThemeSwitcher.d.ts +1 -1
- package/dist/types/components/ThemeSwitcherDropDown/MobileThemeSwitcher.d.ts.map +1 -1
- package/dist/types/components/ThemeSwitcherDropDown/types.d.ts +3 -1
- package/dist/types/components/ThemeSwitcherDropDown/types.d.ts.map +1 -1
- package/dist/types/components/index.d.ts +3 -3
- package/dist/types/hooks/index.d.ts +2 -2
- package/dist/types/hooks/useItemSelector.d.ts +2 -0
- package/dist/types/hooks/useItemSelector.d.ts.map +1 -1
- package/dist/types/hooks/useScrollY.d.ts +10 -0
- package/dist/types/hooks/useScrollY.d.ts.map +1 -1
- package/dist/types/hooks/useStickToBottom.d.ts +41 -0
- package/dist/types/hooks/useStickToBottom.d.ts.map +1 -0
- package/dist/types/libs/auth.d.ts +3 -0
- package/dist/types/libs/auth.d.ts.map +1 -1
- package/dist/types/providers/ThemeProvider.d.ts +41 -0
- package/dist/types/providers/ThemeProvider.d.ts.map +1 -0
- package/dist/types/providers/index.d.ts +2 -1
- package/dist/types/routes.d.ts +2 -1
- package/dist/types/routes.d.ts.map +1 -1
- package/dist/types/structured-data/buildCreativeWorkJsonLd.d.ts +9 -3
- package/dist/types/structured-data/buildCreativeWorkJsonLd.d.ts.map +1 -1
- package/dist/types/utils/index.d.ts +3 -1
- package/dist/types/utils/observeScrollExtent.d.ts +22 -0
- package/dist/types/utils/observeScrollExtent.d.ts.map +1 -0
- package/dist/types/utils/scheduleFrameTask.d.ts +25 -0
- package/dist/types/utils/scheduleFrameTask.d.ts.map +1 -0
- package/package.json +46 -41
- package/tailwind.css +27 -1
- package/dist/esm/hooks/useIsDarkMode.mjs +0 -17
- package/dist/esm/hooks/useIsDarkMode.mjs.map +0 -1
- package/dist/types/hooks/useIsDarkMode.d.ts +0 -5
- package/dist/types/hooks/useIsDarkMode.d.ts.map +0 -1
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { scheduleFrameTask } from "../../utils/scheduleFrameTask.mjs";
|
|
4
|
+
import { Detail as Detail$1, PopoverStatic, usePopoverIds } from "./static.mjs";
|
|
4
5
|
import { useLayoutEffect, useRef, useState } from "react";
|
|
5
6
|
import { jsx } from "react/jsx-runtime";
|
|
6
7
|
|
|
7
8
|
//#region src/components/Popover/dynamic.tsx
|
|
9
|
+
/** Gap kept between the trigger and the panel, mirroring the `1rem` offset of the static styles. */
|
|
10
|
+
const TRIGGER_GAP = 16;
|
|
11
|
+
/** Space kept between the panel and the edges of the viewport. */
|
|
12
|
+
const VIEWPORT_PADDING = 16;
|
|
8
13
|
/**
|
|
9
14
|
* Popover Component (Client-side)
|
|
10
15
|
*
|
|
@@ -28,52 +33,58 @@ const PopoverComponent = (props) => {
|
|
|
28
33
|
* - Adds automatic positioning adjustment based on viewport
|
|
29
34
|
* - Calculates optimal X/Y alignment to prevent overflow
|
|
30
35
|
* - Dynamically adjusts max-width based on available space
|
|
31
|
-
* -
|
|
36
|
+
* - Measures only while the panel is open, on a frame shared with the rest of
|
|
37
|
+
* the page
|
|
32
38
|
*
|
|
33
39
|
* @param props - Popover Detail component props
|
|
34
40
|
* @returns Positioned popover content with animations and accessibility
|
|
35
41
|
*/
|
|
36
42
|
const Detail = ({ xAlign = "start", yAlign = "below", ...props }) => {
|
|
37
43
|
const popoverRef = useRef(null);
|
|
44
|
+
const { triggerId } = usePopoverIds(props.identifier);
|
|
38
45
|
const [computedXAlign, setComputedXAlign] = useState(xAlign);
|
|
39
46
|
const [computedYAlign, setComputedYAlign] = useState(yAlign);
|
|
40
47
|
const [maxWidth, setMaxWidth] = useState(void 0);
|
|
41
48
|
useLayoutEffect(() => {
|
|
42
|
-
|
|
43
|
-
|
|
49
|
+
/** Cancels the measurement queued for the next frame, if any. */
|
|
50
|
+
let cancelScheduledMeasurement = null;
|
|
51
|
+
/** Cancels the read that follows the max-width write, if any. */
|
|
52
|
+
let cancelScheduledRead = null;
|
|
53
|
+
/** Whether the pointer or the focus is currently inside the trigger. */
|
|
54
|
+
let isOpen = false;
|
|
55
|
+
const measurePosition = () => {
|
|
44
56
|
const popoverElement = popoverRef.current;
|
|
45
|
-
const triggerElement = document.getElementById(
|
|
46
|
-
if (!triggerElement) return;
|
|
57
|
+
const triggerElement = document.getElementById(triggerId);
|
|
58
|
+
if (!popoverElement || !triggerElement) return;
|
|
47
59
|
const triggerRect = triggerElement.getBoundingClientRect();
|
|
48
60
|
const viewportWidth = window.innerWidth;
|
|
49
61
|
const viewportHeight = window.innerHeight;
|
|
50
|
-
const
|
|
51
|
-
const
|
|
52
|
-
const maxWidthFromLeft = viewportWidth - triggerRect.left - padding;
|
|
53
|
-
const maxWidthFromRight = triggerRect.right - padding;
|
|
62
|
+
const maxWidthFromLeft = viewportWidth - triggerRect.left - VIEWPORT_PADDING;
|
|
63
|
+
const maxWidthFromRight = triggerRect.right - VIEWPORT_PADDING;
|
|
54
64
|
setMaxWidth(Math.max(maxWidthFromLeft, maxWidthFromRight));
|
|
55
65
|
const wasInvisible = popoverElement.classList.contains("invisible");
|
|
56
66
|
if (wasInvisible) {
|
|
57
67
|
popoverElement.style.visibility = "hidden";
|
|
58
68
|
popoverElement.classList.remove("invisible");
|
|
59
69
|
}
|
|
60
|
-
|
|
70
|
+
cancelScheduledRead = scheduleFrameTask(() => {
|
|
71
|
+
cancelScheduledRead = null;
|
|
61
72
|
const popoverRect = popoverElement.getBoundingClientRect();
|
|
62
73
|
if (wasInvisible) {
|
|
63
74
|
popoverElement.style.visibility = "";
|
|
64
75
|
popoverElement.classList.add("invisible");
|
|
65
76
|
}
|
|
66
77
|
let newYAlign = yAlign;
|
|
67
|
-
const spaceBelow = viewportHeight - triggerRect.bottom -
|
|
68
|
-
const spaceAbove = triggerRect.top -
|
|
78
|
+
const spaceBelow = viewportHeight - triggerRect.bottom - TRIGGER_GAP;
|
|
79
|
+
const spaceAbove = triggerRect.top - TRIGGER_GAP;
|
|
69
80
|
if (yAlign === "below" && spaceBelow < popoverRect.height) {
|
|
70
81
|
if (spaceAbove >= popoverRect.height) newYAlign = "above";
|
|
71
82
|
} else if (yAlign === "above" && spaceAbove < popoverRect.height) {
|
|
72
83
|
if (spaceBelow >= popoverRect.height) newYAlign = "below";
|
|
73
84
|
}
|
|
74
85
|
let newXAlign = xAlign;
|
|
75
|
-
const spaceRight = viewportWidth - triggerRect.left -
|
|
76
|
-
const spaceLeft = triggerRect.right -
|
|
86
|
+
const spaceRight = viewportWidth - triggerRect.left - VIEWPORT_PADDING;
|
|
87
|
+
const spaceLeft = triggerRect.right - VIEWPORT_PADDING;
|
|
77
88
|
if (xAlign === "start" && spaceRight < popoverRect.width) {
|
|
78
89
|
if (spaceLeft >= popoverRect.width) newXAlign = "end";
|
|
79
90
|
} else if (xAlign === "end" && spaceLeft < popoverRect.width) {
|
|
@@ -83,30 +94,64 @@ const Detail = ({ xAlign = "start", yAlign = "below", ...props }) => {
|
|
|
83
94
|
setComputedXAlign(newXAlign);
|
|
84
95
|
});
|
|
85
96
|
};
|
|
86
|
-
|
|
87
|
-
|
|
97
|
+
/**
|
|
98
|
+
* Defers the measurement to the next frame, and to a single one however
|
|
99
|
+
* many events fire.
|
|
100
|
+
*
|
|
101
|
+
* Reading `getBoundingClientRect()` straight from a scroll, resize or
|
|
102
|
+
* observer callback forces a synchronous reflow. Sharing one frame with
|
|
103
|
+
* every other measurement the page schedules keeps the reads in a single
|
|
104
|
+
* batch, so React commits them all after the last one.
|
|
105
|
+
*/
|
|
106
|
+
const scheduleMeasurement = () => {
|
|
107
|
+
if (cancelScheduledMeasurement) return;
|
|
108
|
+
cancelScheduledMeasurement = scheduleFrameTask(() => {
|
|
109
|
+
cancelScheduledMeasurement = null;
|
|
110
|
+
measurePosition();
|
|
111
|
+
});
|
|
112
|
+
};
|
|
113
|
+
const handleOpen = () => {
|
|
114
|
+
if (!isOpen) {
|
|
115
|
+
isOpen = true;
|
|
116
|
+
window.addEventListener("scroll", scheduleMeasurement, {
|
|
117
|
+
passive: true,
|
|
118
|
+
capture: true
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
scheduleMeasurement();
|
|
122
|
+
};
|
|
123
|
+
const handleClose = () => {
|
|
124
|
+
if (!isOpen) return;
|
|
125
|
+
isOpen = false;
|
|
126
|
+
window.removeEventListener("scroll", scheduleMeasurement, true);
|
|
127
|
+
};
|
|
128
|
+
const triggerElement = document.getElementById(triggerId);
|
|
88
129
|
if (triggerElement) {
|
|
89
|
-
triggerElement.addEventListener("mouseenter",
|
|
90
|
-
triggerElement.addEventListener("focusin",
|
|
130
|
+
triggerElement.addEventListener("mouseenter", handleOpen, { passive: true });
|
|
131
|
+
triggerElement.addEventListener("focusin", handleOpen, { passive: true });
|
|
132
|
+
triggerElement.addEventListener("mouseleave", handleClose, { passive: true });
|
|
133
|
+
triggerElement.addEventListener("focusout", handleClose, { passive: true });
|
|
91
134
|
}
|
|
92
135
|
const resizeObserver = new ResizeObserver(() => {
|
|
93
|
-
|
|
136
|
+
if (isOpen) scheduleMeasurement();
|
|
94
137
|
});
|
|
95
138
|
if (popoverRef.current) resizeObserver.observe(popoverRef.current);
|
|
96
|
-
window.addEventListener("resize",
|
|
97
|
-
window.addEventListener("scroll", adjustPosition, true);
|
|
139
|
+
window.addEventListener("resize", scheduleMeasurement, { passive: true });
|
|
98
140
|
return () => {
|
|
99
|
-
|
|
141
|
+
cancelScheduledMeasurement?.();
|
|
142
|
+
cancelScheduledRead?.();
|
|
100
143
|
if (triggerElement) {
|
|
101
|
-
triggerElement.removeEventListener("mouseenter",
|
|
102
|
-
triggerElement.removeEventListener("focusin",
|
|
144
|
+
triggerElement.removeEventListener("mouseenter", handleOpen);
|
|
145
|
+
triggerElement.removeEventListener("focusin", handleOpen);
|
|
146
|
+
triggerElement.removeEventListener("mouseleave", handleClose);
|
|
147
|
+
triggerElement.removeEventListener("focusout", handleClose);
|
|
103
148
|
}
|
|
104
149
|
resizeObserver.disconnect();
|
|
105
|
-
window.removeEventListener("resize",
|
|
106
|
-
window.removeEventListener("scroll",
|
|
150
|
+
window.removeEventListener("resize", scheduleMeasurement);
|
|
151
|
+
window.removeEventListener("scroll", scheduleMeasurement, true);
|
|
107
152
|
};
|
|
108
153
|
}, [
|
|
109
|
-
|
|
154
|
+
triggerId,
|
|
110
155
|
xAlign,
|
|
111
156
|
yAlign
|
|
112
157
|
]);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dynamic.mjs","names":["StaticDetail"],"sources":["../../../../src/components/Popover/dynamic.tsx"],"sourcesContent":["'use client';\n\nimport type { FC } from 'react';\nimport { useLayoutEffect, useRef, useState } from 'react';\nimport {\n type DetailProps,\n type PopoverProps,\n PopoverStatic,\n type PopoverType,\n Detail as StaticDetail,\n} from './static';\n\n/**\n * Popover Component (Client-side)\n *\n * Client-side wrapper around the static Popover component.\n * Reuses the server-side compatible implementation.\n *\n * @param props - Popover component props\n * @returns Trigger container with popover functionality\n */\nconst PopoverComponent: FC<PopoverProps> = (props) => {\n return <PopoverStatic {...props} />;\n};\n\n/**\n * Popover Detail Component (Client-side)\n *\n * Client-side wrapper around the static Detail component that adds automatic\n * positioning logic based on viewport constraints.\n *\n * Features:\n * - Reuses server-side compatible static Detail component\n * - Adds automatic positioning adjustment based on viewport\n * - Calculates optimal X/Y alignment to prevent overflow\n * - Dynamically adjusts max-width based on available space\n * - Listens to window resize and scroll events\n *\n * @param props - Popover Detail component props\n * @returns Positioned popover content with animations and accessibility\n */\nconst Detail: FC<DetailProps> = ({\n xAlign = 'start',\n yAlign = 'below',\n ...props\n}) => {\n const popoverRef = useRef<HTMLDivElement>(null);\n const [computedXAlign, setComputedXAlign] = useState(xAlign);\n const [computedYAlign, setComputedYAlign] = useState(yAlign);\n const [maxWidth, setMaxWidth] = useState<number | undefined>(undefined);\n\n useLayoutEffect(() => {\n const adjustPosition = () => {\n if (!popoverRef.current) return;\n\n const popoverElement = popoverRef.current;\n const triggerElement = document.getElementById(\n `unrollable-panel-button-${props.identifier}`\n );\n\n if (!triggerElement) return;\n\n const triggerRect = triggerElement.getBoundingClientRect();\n const viewportWidth = window.innerWidth;\n const viewportHeight = window.innerHeight;\n const gap = 16; // 1rem gap\n const padding = 16; // Additional padding from viewport edges\n\n // Calculate maximum width based on viewport and trigger position\n const maxWidthFromLeft = viewportWidth - triggerRect.left - padding;\n const maxWidthFromRight = triggerRect.right - padding;\n\n // Use the larger space to ensure popover can fit\n const absoluteMaxWidth = Math.max(maxWidthFromLeft, maxWidthFromRight);\n\n setMaxWidth(absoluteMaxWidth);\n\n // Force a layout calculation by temporarily making visible if needed\n const wasInvisible = popoverElement.classList.contains('invisible');\n if (wasInvisible) {\n popoverElement.style.visibility = 'hidden';\n popoverElement.classList.remove('invisible');\n }\n\n // Small delay to ensure max-width is applied and content reflows\n requestAnimationFrame(() => {\n const popoverRect = popoverElement.getBoundingClientRect();\n\n // Restore invisible state if it was invisible\n if (wasInvisible) {\n popoverElement.style.visibility = '';\n popoverElement.classList.add('invisible');\n }\n\n // Determine optimal Y alignment\n let newYAlign = yAlign;\n const spaceBelow = viewportHeight - triggerRect.bottom - gap;\n const spaceAbove = triggerRect.top - gap;\n\n if (yAlign === 'below' && spaceBelow < popoverRect.height) {\n // Not enough space below, try above\n if (spaceAbove >= popoverRect.height) {\n newYAlign = 'above';\n }\n } else if (yAlign === 'above' && spaceAbove < popoverRect.height) {\n // Not enough space above, try below\n if (spaceBelow >= popoverRect.height) {\n newYAlign = 'below';\n }\n }\n\n // Determine optimal X alignment\n let newXAlign = xAlign;\n const spaceRight = viewportWidth - triggerRect.left - padding;\n const spaceLeft = triggerRect.right - padding;\n\n if (xAlign === 'start' && spaceRight < popoverRect.width) {\n // Not enough space on the right, try left\n if (spaceLeft >= popoverRect.width) {\n newXAlign = 'end';\n }\n } else if (xAlign === 'end' && spaceLeft < popoverRect.width) {\n // Not enough space on the left, try right\n if (spaceRight >= popoverRect.width) {\n newXAlign = 'start';\n }\n }\n\n setComputedYAlign(newYAlign);\n setComputedXAlign(newXAlign);\n });\n };\n\n // Adjust position with a slight delay to ensure DOM is ready\n const timeoutId = setTimeout(adjustPosition, 0);\n\n // Listen to mouse enter on the trigger to recalculate\n const triggerElement = document.getElementById(\n `unrollable-panel-button-${props.identifier}`\n );\n\n if (triggerElement) {\n triggerElement.addEventListener('mouseenter', adjustPosition);\n triggerElement.addEventListener('focusin', adjustPosition);\n }\n\n // Use ResizeObserver to detect popover content size changes\n const resizeObserver = new ResizeObserver(() => {\n adjustPosition();\n });\n\n if (popoverRef.current) {\n resizeObserver.observe(popoverRef.current);\n }\n\n window.addEventListener('resize', adjustPosition);\n window.addEventListener('scroll', adjustPosition, true);\n\n return () => {\n clearTimeout(timeoutId);\n if (triggerElement) {\n triggerElement.removeEventListener('mouseenter', adjustPosition);\n triggerElement.removeEventListener('focusin', adjustPosition);\n }\n resizeObserver.disconnect();\n window.removeEventListener('resize', adjustPosition);\n window.removeEventListener('scroll', adjustPosition, true);\n };\n }, [props.identifier, xAlign, yAlign]);\n\n // Use the static Detail component with computed alignment values\n return (\n <StaticDetail\n {...props}\n xAlign={computedXAlign}\n yAlign={computedYAlign}\n ref={popoverRef}\n style={{\n ...props.style,\n maxWidth: maxWidth ? `${maxWidth}px` : undefined,\n }}\n />\n );\n};\n\n// Create Popover with Detail attached\nexport const Popover: PopoverType = PopoverComponent as PopoverType;\n\nPopover.Detail = Detail;\n"],"mappings":";;;;;;;;;;;;;;;;AAqBA,MAAM,oBAAsC,UAAU;CACpD,OAAO,oBAAC,eAAD,EAAe,GAAI,MAAQ;AACpC;;;;;;;;;;;;;;;;;AAkBA,MAAM,UAA2B,EAC/B,SAAS,SACT,SAAS,SACT,GAAG,YACC;CACJ,MAAM,aAAa,OAAuB,IAAI;CAC9C,MAAM,CAAC,gBAAgB,qBAAqB,SAAS,MAAM;CAC3D,MAAM,CAAC,gBAAgB,qBAAqB,SAAS,MAAM;CAC3D,MAAM,CAAC,UAAU,eAAe,SAA6B,MAAS;CAEtE,sBAAsB;EACpB,MAAM,uBAAuB;GAC3B,IAAI,CAAC,WAAW,SAAS;GAEzB,MAAM,iBAAiB,WAAW;GAClC,MAAM,iBAAiB,SAAS,eAC9B,2BAA2B,MAAM,YACnC;GAEA,IAAI,CAAC,gBAAgB;GAErB,MAAM,cAAc,eAAe,sBAAsB;GACzD,MAAM,gBAAgB,OAAO;GAC7B,MAAM,iBAAiB,OAAO;GAC9B,MAAM,MAAM;GACZ,MAAM,UAAU;GAGhB,MAAM,mBAAmB,gBAAgB,YAAY,OAAO;GAC5D,MAAM,oBAAoB,YAAY,QAAQ;GAK9C,YAFyB,KAAK,IAAI,kBAAkB,iBAEzB,CAAC;GAG5B,MAAM,eAAe,eAAe,UAAU,SAAS,WAAW;GAClE,IAAI,cAAc;IAChB,eAAe,MAAM,aAAa;IAClC,eAAe,UAAU,OAAO,WAAW;GAC7C;GAGA,4BAA4B;IAC1B,MAAM,cAAc,eAAe,sBAAsB;IAGzD,IAAI,cAAc;KAChB,eAAe,MAAM,aAAa;KAClC,eAAe,UAAU,IAAI,WAAW;IAC1C;IAGA,IAAI,YAAY;IAChB,MAAM,aAAa,iBAAiB,YAAY,SAAS;IACzD,MAAM,aAAa,YAAY,MAAM;IAErC,IAAI,WAAW,WAAW,aAAa,YAAY,QAEjD;SAAI,cAAc,YAAY,QAC5B,YAAY;IACd,OACK,IAAI,WAAW,WAAW,aAAa,YAAY,QAExD;SAAI,cAAc,YAAY,QAC5B,YAAY;IACd;IAIF,IAAI,YAAY;IAChB,MAAM,aAAa,gBAAgB,YAAY,OAAO;IACtD,MAAM,YAAY,YAAY,QAAQ;IAEtC,IAAI,WAAW,WAAW,aAAa,YAAY,OAEjD;SAAI,aAAa,YAAY,OAC3B,YAAY;IACd,OACK,IAAI,WAAW,SAAS,YAAY,YAAY,OAErD;SAAI,cAAc,YAAY,OAC5B,YAAY;IACd;IAGF,kBAAkB,SAAS;IAC3B,kBAAkB,SAAS;GAC7B,CAAC;EACH;EAGA,MAAM,YAAY,WAAW,gBAAgB,CAAC;EAG9C,MAAM,iBAAiB,SAAS,eAC9B,2BAA2B,MAAM,YACnC;EAEA,IAAI,gBAAgB;GAClB,eAAe,iBAAiB,cAAc,cAAc;GAC5D,eAAe,iBAAiB,WAAW,cAAc;EAC3D;EAGA,MAAM,iBAAiB,IAAI,qBAAqB;GAC9C,eAAe;EACjB,CAAC;EAED,IAAI,WAAW,SACb,eAAe,QAAQ,WAAW,OAAO;EAG3C,OAAO,iBAAiB,UAAU,cAAc;EAChD,OAAO,iBAAiB,UAAU,gBAAgB,IAAI;EAEtD,aAAa;GACX,aAAa,SAAS;GACtB,IAAI,gBAAgB;IAClB,eAAe,oBAAoB,cAAc,cAAc;IAC/D,eAAe,oBAAoB,WAAW,cAAc;GAC9D;GACA,eAAe,WAAW;GAC1B,OAAO,oBAAoB,UAAU,cAAc;GACnD,OAAO,oBAAoB,UAAU,gBAAgB,IAAI;EAC3D;CACF,GAAG;EAAC,MAAM;EAAY;EAAQ;CAAM,CAAC;CAGrC,OACE,oBAACA,UAAD;EACE,GAAI;EACJ,QAAQ;EACR,QAAQ;EACR,KAAK;EACL,OAAO;GACL,GAAG,MAAM;GACT,UAAU,WAAW,GAAG,SAAS,MAAM;EACzC;CACD;AAEL;AAGA,MAAa,UAAuB;AAEpC,QAAQ,SAAS"}
|
|
1
|
+
{"version":3,"file":"dynamic.mjs","names":["StaticDetail"],"sources":["../../../../src/components/Popover/dynamic.tsx"],"sourcesContent":["'use client';\n\nimport { scheduleFrameTask } from '@utils/scheduleFrameTask';\nimport type { FC } from 'react';\nimport { useLayoutEffect, useRef, useState } from 'react';\nimport {\n type DetailProps,\n type PopoverProps,\n PopoverStatic,\n type PopoverType,\n Detail as StaticDetail,\n usePopoverIds,\n} from './static';\n\n/** Gap kept between the trigger and the panel, mirroring the `1rem` offset of the static styles. */\nconst TRIGGER_GAP = 16;\n\n/** Space kept between the panel and the edges of the viewport. */\nconst VIEWPORT_PADDING = 16;\n\n/**\n * Popover Component (Client-side)\n *\n * Client-side wrapper around the static Popover component.\n * Reuses the server-side compatible implementation.\n *\n * @param props - Popover component props\n * @returns Trigger container with popover functionality\n */\nconst PopoverComponent: FC<PopoverProps> = (props) => {\n return <PopoverStatic {...props} />;\n};\n\n/**\n * Popover Detail Component (Client-side)\n *\n * Client-side wrapper around the static Detail component that adds automatic\n * positioning logic based on viewport constraints.\n *\n * Features:\n * - Reuses server-side compatible static Detail component\n * - Adds automatic positioning adjustment based on viewport\n * - Calculates optimal X/Y alignment to prevent overflow\n * - Dynamically adjusts max-width based on available space\n * - Measures only while the panel is open, on a frame shared with the rest of\n * the page\n *\n * @param props - Popover Detail component props\n * @returns Positioned popover content with animations and accessibility\n */\nconst Detail: FC<DetailProps> = ({\n xAlign = 'start',\n yAlign = 'below',\n ...props\n}) => {\n const popoverRef = useRef<HTMLDivElement>(null);\n const { triggerId } = usePopoverIds(props.identifier);\n const [computedXAlign, setComputedXAlign] = useState(xAlign);\n const [computedYAlign, setComputedYAlign] = useState(yAlign);\n const [maxWidth, setMaxWidth] = useState<number | undefined>(undefined);\n\n useLayoutEffect(() => {\n /** Cancels the measurement queued for the next frame, if any. */\n let cancelScheduledMeasurement: (() => void) | null = null;\n /** Cancels the read that follows the max-width write, if any. */\n let cancelScheduledRead: (() => void) | null = null;\n /** Whether the pointer or the focus is currently inside the trigger. */\n let isOpen = false;\n\n const measurePosition = () => {\n const popoverElement = popoverRef.current;\n const triggerElement = document.getElementById(triggerId);\n\n if (!popoverElement || !triggerElement) return;\n\n const triggerRect = triggerElement.getBoundingClientRect();\n const viewportWidth = window.innerWidth;\n const viewportHeight = window.innerHeight;\n\n // Calculate maximum width based on viewport and trigger position, using\n // the larger of the two sides so the panel can always fit somewhere.\n const maxWidthFromLeft =\n viewportWidth - triggerRect.left - VIEWPORT_PADDING;\n const maxWidthFromRight = triggerRect.right - VIEWPORT_PADDING;\n\n setMaxWidth(Math.max(maxWidthFromLeft, maxWidthFromRight));\n\n // A panel that is still hidden has no box to measure, so it is revealed\n // to layout only — `visibility: hidden` keeps it off the screen.\n const wasInvisible = popoverElement.classList.contains('invisible');\n if (wasInvisible) {\n popoverElement.style.visibility = 'hidden';\n popoverElement.classList.remove('invisible');\n }\n\n // The max-width above still has to be committed and laid out, so the\n // panel is measured on the next frame rather than right away.\n cancelScheduledRead = scheduleFrameTask(() => {\n cancelScheduledRead = null;\n\n const popoverRect = popoverElement.getBoundingClientRect();\n\n if (wasInvisible) {\n popoverElement.style.visibility = '';\n popoverElement.classList.add('invisible');\n }\n\n // Determine optimal Y alignment\n let newYAlign = yAlign;\n const spaceBelow = viewportHeight - triggerRect.bottom - TRIGGER_GAP;\n const spaceAbove = triggerRect.top - TRIGGER_GAP;\n\n if (yAlign === 'below' && spaceBelow < popoverRect.height) {\n // Not enough space below, try above\n if (spaceAbove >= popoverRect.height) {\n newYAlign = 'above';\n }\n } else if (yAlign === 'above' && spaceAbove < popoverRect.height) {\n // Not enough space above, try below\n if (spaceBelow >= popoverRect.height) {\n newYAlign = 'below';\n }\n }\n\n // Determine optimal X alignment\n let newXAlign = xAlign;\n const spaceRight = viewportWidth - triggerRect.left - VIEWPORT_PADDING;\n const spaceLeft = triggerRect.right - VIEWPORT_PADDING;\n\n if (xAlign === 'start' && spaceRight < popoverRect.width) {\n // Not enough space on the right, try left\n if (spaceLeft >= popoverRect.width) {\n newXAlign = 'end';\n }\n } else if (xAlign === 'end' && spaceLeft < popoverRect.width) {\n // Not enough space on the left, try right\n if (spaceRight >= popoverRect.width) {\n newXAlign = 'start';\n }\n }\n\n setComputedYAlign(newYAlign);\n setComputedXAlign(newXAlign);\n });\n };\n\n /**\n * Defers the measurement to the next frame, and to a single one however\n * many events fire.\n *\n * Reading `getBoundingClientRect()` straight from a scroll, resize or\n * observer callback forces a synchronous reflow. Sharing one frame with\n * every other measurement the page schedules keeps the reads in a single\n * batch, so React commits them all after the last one.\n */\n const scheduleMeasurement = () => {\n if (cancelScheduledMeasurement) return;\n\n cancelScheduledMeasurement = scheduleFrameTask(() => {\n cancelScheduledMeasurement = null;\n measurePosition();\n });\n };\n\n /*\n * Scrolling only matters while the panel is on screen, and a closed\n * popover is re-measured when it opens — behind an 800ms CSS delay, so the\n * measurement always lands before anything is painted. Subscribing on open\n * rather than on mount keeps a page holding several popovers from running\n * one capture-phase handler per popover on every scroll event, and leaves\n * a page whose popovers are never opened with no layout work at all.\n */\n const handleOpen = () => {\n if (!isOpen) {\n isOpen = true;\n window.addEventListener('scroll', scheduleMeasurement, {\n passive: true,\n capture: true,\n });\n }\n\n scheduleMeasurement();\n };\n\n const handleClose = () => {\n if (!isOpen) return;\n\n isOpen = false;\n window.removeEventListener('scroll', scheduleMeasurement, true);\n };\n\n const triggerElement = document.getElementById(triggerId);\n\n if (triggerElement) {\n triggerElement.addEventListener('mouseenter', handleOpen, {\n passive: true,\n });\n triggerElement.addEventListener('focusin', handleOpen, { passive: true });\n triggerElement.addEventListener('mouseleave', handleClose, {\n passive: true,\n });\n triggerElement.addEventListener('focusout', handleClose, {\n passive: true,\n });\n }\n\n // The panel keeps its own size in check: content growing past the viewport\n // has to flip the alignment even when the user did nothing.\n const resizeObserver = new ResizeObserver(() => {\n if (isOpen) scheduleMeasurement();\n });\n\n if (popoverRef.current) {\n resizeObserver.observe(popoverRef.current);\n }\n\n window.addEventListener('resize', scheduleMeasurement, { passive: true });\n\n return () => {\n cancelScheduledMeasurement?.();\n cancelScheduledRead?.();\n\n if (triggerElement) {\n triggerElement.removeEventListener('mouseenter', handleOpen);\n triggerElement.removeEventListener('focusin', handleOpen);\n triggerElement.removeEventListener('mouseleave', handleClose);\n triggerElement.removeEventListener('focusout', handleClose);\n }\n\n resizeObserver.disconnect();\n window.removeEventListener('resize', scheduleMeasurement);\n window.removeEventListener('scroll', scheduleMeasurement, true);\n };\n }, [triggerId, xAlign, yAlign]);\n\n // Use the static Detail component with computed alignment values\n return (\n <StaticDetail\n {...props}\n xAlign={computedXAlign}\n yAlign={computedYAlign}\n ref={popoverRef}\n style={{\n ...props.style,\n maxWidth: maxWidth ? `${maxWidth}px` : undefined,\n }}\n />\n );\n};\n\n// Create Popover with Detail attached\nexport const Popover: PopoverType = PopoverComponent as PopoverType;\n\nPopover.Detail = Detail;\n"],"mappings":";;;;;;;;;AAeA,MAAM,cAAc;;AAGpB,MAAM,mBAAmB;;;;;;;;;;AAWzB,MAAM,oBAAsC,UAAU;CACpD,OAAO,oBAAC,eAAD,EAAe,GAAI,MAAQ;AACpC;;;;;;;;;;;;;;;;;;AAmBA,MAAM,UAA2B,EAC/B,SAAS,SACT,SAAS,SACT,GAAG,YACC;CACJ,MAAM,aAAa,OAAuB,IAAI;CAC9C,MAAM,EAAE,cAAc,cAAc,MAAM,UAAU;CACpD,MAAM,CAAC,gBAAgB,qBAAqB,SAAS,MAAM;CAC3D,MAAM,CAAC,gBAAgB,qBAAqB,SAAS,MAAM;CAC3D,MAAM,CAAC,UAAU,eAAe,SAA6B,MAAS;CAEtE,sBAAsB;;EAEpB,IAAI,6BAAkD;;EAEtD,IAAI,sBAA2C;;EAE/C,IAAI,SAAS;EAEb,MAAM,wBAAwB;GAC5B,MAAM,iBAAiB,WAAW;GAClC,MAAM,iBAAiB,SAAS,eAAe,SAAS;GAExD,IAAI,CAAC,kBAAkB,CAAC,gBAAgB;GAExC,MAAM,cAAc,eAAe,sBAAsB;GACzD,MAAM,gBAAgB,OAAO;GAC7B,MAAM,iBAAiB,OAAO;GAI9B,MAAM,mBACJ,gBAAgB,YAAY,OAAO;GACrC,MAAM,oBAAoB,YAAY,QAAQ;GAE9C,YAAY,KAAK,IAAI,kBAAkB,iBAAiB,CAAC;GAIzD,MAAM,eAAe,eAAe,UAAU,SAAS,WAAW;GAClE,IAAI,cAAc;IAChB,eAAe,MAAM,aAAa;IAClC,eAAe,UAAU,OAAO,WAAW;GAC7C;GAIA,sBAAsB,wBAAwB;IAC5C,sBAAsB;IAEtB,MAAM,cAAc,eAAe,sBAAsB;IAEzD,IAAI,cAAc;KAChB,eAAe,MAAM,aAAa;KAClC,eAAe,UAAU,IAAI,WAAW;IAC1C;IAGA,IAAI,YAAY;IAChB,MAAM,aAAa,iBAAiB,YAAY,SAAS;IACzD,MAAM,aAAa,YAAY,MAAM;IAErC,IAAI,WAAW,WAAW,aAAa,YAAY,QAEjD;SAAI,cAAc,YAAY,QAC5B,YAAY;IACd,OACK,IAAI,WAAW,WAAW,aAAa,YAAY,QAExD;SAAI,cAAc,YAAY,QAC5B,YAAY;IACd;IAIF,IAAI,YAAY;IAChB,MAAM,aAAa,gBAAgB,YAAY,OAAO;IACtD,MAAM,YAAY,YAAY,QAAQ;IAEtC,IAAI,WAAW,WAAW,aAAa,YAAY,OAEjD;SAAI,aAAa,YAAY,OAC3B,YAAY;IACd,OACK,IAAI,WAAW,SAAS,YAAY,YAAY,OAErD;SAAI,cAAc,YAAY,OAC5B,YAAY;IACd;IAGF,kBAAkB,SAAS;IAC3B,kBAAkB,SAAS;GAC7B,CAAC;EACH;;;;;;;;;;EAWA,MAAM,4BAA4B;GAChC,IAAI,4BAA4B;GAEhC,6BAA6B,wBAAwB;IACnD,6BAA6B;IAC7B,gBAAgB;GAClB,CAAC;EACH;EAUA,MAAM,mBAAmB;GACvB,IAAI,CAAC,QAAQ;IACX,SAAS;IACT,OAAO,iBAAiB,UAAU,qBAAqB;KACrD,SAAS;KACT,SAAS;IACX,CAAC;GACH;GAEA,oBAAoB;EACtB;EAEA,MAAM,oBAAoB;GACxB,IAAI,CAAC,QAAQ;GAEb,SAAS;GACT,OAAO,oBAAoB,UAAU,qBAAqB,IAAI;EAChE;EAEA,MAAM,iBAAiB,SAAS,eAAe,SAAS;EAExD,IAAI,gBAAgB;GAClB,eAAe,iBAAiB,cAAc,YAAY,EACxD,SAAS,KACX,CAAC;GACD,eAAe,iBAAiB,WAAW,YAAY,EAAE,SAAS,KAAK,CAAC;GACxE,eAAe,iBAAiB,cAAc,aAAa,EACzD,SAAS,KACX,CAAC;GACD,eAAe,iBAAiB,YAAY,aAAa,EACvD,SAAS,KACX,CAAC;EACH;EAIA,MAAM,iBAAiB,IAAI,qBAAqB;GAC9C,IAAI,QAAQ,oBAAoB;EAClC,CAAC;EAED,IAAI,WAAW,SACb,eAAe,QAAQ,WAAW,OAAO;EAG3C,OAAO,iBAAiB,UAAU,qBAAqB,EAAE,SAAS,KAAK,CAAC;EAExE,aAAa;GACX,6BAA6B;GAC7B,sBAAsB;GAEtB,IAAI,gBAAgB;IAClB,eAAe,oBAAoB,cAAc,UAAU;IAC3D,eAAe,oBAAoB,WAAW,UAAU;IACxD,eAAe,oBAAoB,cAAc,WAAW;IAC5D,eAAe,oBAAoB,YAAY,WAAW;GAC5D;GAEA,eAAe,WAAW;GAC1B,OAAO,oBAAoB,UAAU,mBAAmB;GACxD,OAAO,oBAAoB,UAAU,qBAAqB,IAAI;EAChE;CACF,GAAG;EAAC;EAAW;EAAQ;CAAM,CAAC;CAG9B,OACE,oBAACA,UAAD;EACE,GAAI;EACJ,QAAQ;EACR,QAAQ;EACR,KAAK;EACL,OAAO;GACL,GAAG,MAAM;GACT,UAAU,WAAW,GAAG,SAAS,MAAM;EACzC;CACD;AAEL;AAGA,MAAa,UAAuB;AAEpC,QAAQ,SAAS"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Detail, PopoverStatic } from "./static.mjs";
|
|
1
|
+
import { Detail, PopoverStatic, usePopoverIds } from "./static.mjs";
|
|
2
2
|
import { Popover } from "./dynamic.mjs";
|
|
3
3
|
|
|
4
|
-
export { Detail, Popover, PopoverStatic };
|
|
4
|
+
export { Detail, Popover, PopoverStatic, usePopoverIds };
|
|
@@ -1,9 +1,28 @@
|
|
|
1
1
|
import { cn } from "../../utils/cn.mjs";
|
|
2
2
|
import { Container } from "../Container/index.mjs";
|
|
3
|
+
import { createContext, useContext, useId } from "react";
|
|
3
4
|
import { jsx } from "react/jsx-runtime";
|
|
4
5
|
|
|
5
6
|
//#region src/components/Popover/static.tsx
|
|
6
7
|
/**
|
|
8
|
+
* Name the trigger shares with its own `Detail` so both derive the same ids.
|
|
9
|
+
*
|
|
10
|
+
* The `identifier` prop names a *kind* of popover ("copy", "history"), and the
|
|
11
|
+
* same kind legitimately appears many times on one page — a documentation page
|
|
12
|
+
* renders one copy popover per code block. Deriving the ids from the identifier
|
|
13
|
+
* alone made every one of them share them, which is invalid HTML and leaves
|
|
14
|
+
* `aria-labelledby` pointing at whichever element the browser matched first.
|
|
15
|
+
*/
|
|
16
|
+
const PopoverNameContext = createContext(null);
|
|
17
|
+
/** Ids of a popover pair, unique to the instance that rendered it. */
|
|
18
|
+
const usePopoverIds = (identifier) => {
|
|
19
|
+
const name = useContext(PopoverNameContext) ?? identifier;
|
|
20
|
+
return {
|
|
21
|
+
triggerId: `unrollable-panel-button-${name}`,
|
|
22
|
+
panelId: `unrollable-panel-${name}`
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
7
26
|
* Popover Component
|
|
8
27
|
*
|
|
9
28
|
* A versatile popover container that displays contextual content when triggered by hover
|
|
@@ -84,13 +103,20 @@ import { jsx } from "react/jsx-runtime";
|
|
|
84
103
|
* @param props - Popover component props
|
|
85
104
|
* @returns Trigger container with popover functionality
|
|
86
105
|
*/
|
|
87
|
-
const PopoverStatic = ({ children, className, identifier, ...props }) =>
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
106
|
+
const PopoverStatic = ({ children, className, identifier, ...props }) => {
|
|
107
|
+
const name = `${identifier}${useId()}`;
|
|
108
|
+
const { triggerId } = usePopoverIds(name);
|
|
109
|
+
return /* @__PURE__ */ jsx(PopoverNameContext, {
|
|
110
|
+
value: name,
|
|
111
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
112
|
+
className: cn(`group/popover relative flex cursor-pointer`, className),
|
|
113
|
+
id: triggerId,
|
|
114
|
+
"aria-haspopup": true,
|
|
115
|
+
...props,
|
|
116
|
+
children
|
|
117
|
+
})
|
|
118
|
+
});
|
|
119
|
+
};
|
|
94
120
|
/**
|
|
95
121
|
* Popover Detail Component
|
|
96
122
|
*
|
|
@@ -154,18 +180,21 @@ const PopoverStatic = ({ children, className, identifier, ...props }) => /* @__P
|
|
|
154
180
|
* @param props - Popover Detail component props
|
|
155
181
|
* @returns Positioned popover content with animations and accessibility
|
|
156
182
|
*/
|
|
157
|
-
const Detail = ({ children, isHidden = void 0, isOverable = true, isFocusable = false, xAlign = "start", yAlign = "below", identifier, className, displayArrow = true, ...props }) =>
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
183
|
+
const Detail = ({ children, isHidden = void 0, isOverable = true, isFocusable = false, xAlign = "start", yAlign = "below", identifier, className, displayArrow = true, ...props }) => {
|
|
184
|
+
const { triggerId, panelId } = usePopoverIds(identifier);
|
|
185
|
+
return /* @__PURE__ */ jsx(Container, {
|
|
186
|
+
transparency: "xs",
|
|
187
|
+
role: "group",
|
|
188
|
+
"aria-hidden": isHidden,
|
|
189
|
+
"aria-labelledby": triggerId,
|
|
190
|
+
id: panelId,
|
|
191
|
+
className: cn("absolute z-60 min-w-full rounded-md ring-1 ring-neutral", xAlign === "start" && "left-0", xAlign === "center" && "left-1/2 -translate-x-1/2", xAlign === "end" && "right-0", yAlign === "below" && "top-[calc(100%+1rem)]", yAlign === "above" && "bottom-[calc(100%+1rem)]", displayArrow && "before:absolute before:z-[999] before:h-0 before:w-0 before:content-[\"\"]", displayArrow && xAlign === "start" && "before:left-2", displayArrow && xAlign === "center" && "before:left-1/2 before:-translate-x-1/2", displayArrow && xAlign === "end" && "before:right-2", displayArrow && yAlign === "below" && "before:-top-2.5 before:border-r-[10px] before:border-r-transparent before:border-b-[10px] before:border-b-neutral before:border-l-[10px] before:border-l-transparent", displayArrow && yAlign === "above" && "before:-bottom-2.5 before:border-t-[10px] before:border-t-neutral before:border-r-[10px] before:border-r-transparent before:border-l-[10px] before:border-l-transparent", "overflow-x-visible opacity-0 transition-all duration-400 ease-in-out", isHidden !== false ? "invisible" : "visible opacity-100 delay-800", isOverable && `group-hover/popover:visible group-hover/popover:opacity-100 group-hover/popover:delay-800`, isFocusable && `group-focus-within/popover:visible group-focus-within/popover:opacity-100 group-focus-within/popover:delay-800`, className),
|
|
192
|
+
...props,
|
|
193
|
+
children
|
|
194
|
+
});
|
|
195
|
+
};
|
|
167
196
|
PopoverStatic.Detail = Detail;
|
|
168
197
|
|
|
169
198
|
//#endregion
|
|
170
|
-
export { Detail, PopoverStatic };
|
|
199
|
+
export { Detail, PopoverStatic, usePopoverIds };
|
|
171
200
|
//# sourceMappingURL=static.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"static.mjs","names":[],"sources":["../../../../src/components/Popover/static.tsx"],"sourcesContent":["import { cn } from '@utils/cn';\nimport type {\n ComponentProps,\n DetailedHTMLProps,\n FC,\n HTMLAttributes,\n} from 'react';\nimport { Container } from '../Container';\n\n/**\n * Props for the main Popover component\n * Extends HTMLDivElement attributes for full DOM compatibility\n */\nexport type PopoverProps = DetailedHTMLProps<\n HTMLAttributes<HTMLDivElement>,\n HTMLDivElement\n> & {\n /** Unique identifier linking the trigger to its popover content for accessibility */\n identifier: string;\n};\n\n/**\n * Composite type for the Popover component with Detail subcomponent\n * Allows for Popover.Detail usage pattern\n */\nexport type PopoverType = FC<PopoverProps> & {\n Detail: FC<DetailProps>;\n};\n\n/**\n * Horizontal alignment options for popover positioning\n */\nexport type PopoverXAlign = 'start' | 'center' | 'end';\n\n/**\n * Vertical alignment options for popover positioning\n */\nexport type PopoverYAlign = 'below' | 'above';\n\n/**\n * Popover Component\n *\n * A versatile popover container that displays contextual content when triggered by hover\n * or focus interactions. Built with accessibility in mind and supports multiple positioning\n * options with smooth animations.\n *\n * Features:\n * - Hover and focus-based triggering\n * - Multiple positioning options (above/below, start/center/end)\n * - Accessibility compliant with ARIA attributes\n * - Smooth animations with configurable delays\n * - Optional directional arrows\n * - Automatic z-index management\n * - Responsive design support\n *\n * Architecture:\n * - Main Popover acts as trigger container\n * - Popover.Detail renders the actual popover content\n * - Uses CSS groups for coordinated hover/focus states\n * - Unique identifier system prevents conflicts\n *\n * @example\n * Basic hover popover:\n * ```jsx\n * <Popover identifier=\"help-tooltip\">\n * <button>Need Help?</button>\n *\n * <Popover.Detail identifier=\"help-tooltip\">\n * <div>This is helpful information!</div>\n * </Popover.Detail>\n * </Popover>\n * ```\n *\n * @example\n * Focus-triggered popover:\n * ```jsx\n * <Popover identifier=\"focus-menu\">\n * <input placeholder=\"Focus me\" />\n *\n * <Popover.Detail\n * identifier=\"focus-menu\"\n * isFocusable\n * isOverable={false}\n * >\n * <div>Focus-only menu content</div>\n * </Popover.Detail>\n * </Popover>\n * ```\n *\n * @example\n * Positioned popover with custom alignment:\n * ```jsx\n * <Popover identifier=\"positioned\">\n * <span>Hover me</span>\n *\n * <Popover.Detail\n * identifier=\"positioned\"\n * xAlign=\"end\"\n * yAlign=\"above\"\n * displayArrow={false}\n * >\n * <div>Above and right-aligned</div>\n * </Popover.Detail>\n * </Popover>\n * ```\n *\n * Accessibility Features:\n * - Proper ARIA labeling and relationships\n * - Keyboard navigation support\n * - Screen reader compatibility\n * - Focus management\n *\n * Performance Considerations:\n * - CSS-only animations for smooth transitions\n * - Efficient group-based state management\n * - Minimal DOM updates during interactions\n *\n * @param props - Popover component props\n * @returns Trigger container with popover functionality\n */\nexport const PopoverStatic: PopoverType = ({\n children,\n className,\n identifier,\n ...props\n}) => (\n <div\n className={cn(`group/popover relative flex cursor-pointer`, className)}\n id={`unrollable-panel-button-${identifier}`}\n aria-haspopup\n {...props}\n >\n {children}\n </div>\n);\n\n/**\n * Props for the Popover.Detail component\n * Extends HTMLDivElement attributes for styling flexibility\n */\nexport type DetailProps = ComponentProps<typeof Container> & {\n /** Whether the popover responds to focus events on the trigger */\n isFocusable?: boolean;\n /** Controls visibility state - undefined allows automatic hover/focus control */\n isHidden?: boolean;\n /** Whether the popover responds to hover events on the trigger */\n isOverable?: boolean;\n /** Unique identifier matching the trigger's identifier for accessibility */\n identifier: string;\n /** Horizontal positioning relative to trigger */\n xAlign?: PopoverXAlign | `${PopoverXAlign}`;\n /** Vertical positioning relative to trigger */\n yAlign?: PopoverYAlign | `${PopoverYAlign}`;\n /** Whether to display the directional arrow indicator */\n displayArrow?: boolean;\n};\n\n/**\n * Popover Detail Component\n *\n * The actual popover content container with advanced positioning, animation, and\n * accessibility features. Automatically manages visibility based on trigger interactions.\n *\n * Features:\n * - Precise positioning with alignment options\n * - Smooth fade and slide animations\n * - Configurable directional arrows\n * - Hover and focus interaction support\n * - Accessibility-compliant ARIA attributes\n * - High z-index for overlay behavior\n * - Automatic visibility management\n *\n * Positioning System:\n * - X-axis: START (left-aligned), CENTER (centered), or END (right-aligned)\n * - Y-axis: BELOW (underneath) or ABOVE (on top)\n * - Automatic spacing with 1rem gap from trigger\n * - Responsive minimum width matching trigger\n *\n * Arrow Indicators:\n * - CSS-generated triangular arrows\n * - Positioned based on alignment settings\n * - Points toward trigger for visual connection\n * - Can be disabled for clean, minimal appearance\n *\n * Animation Behavior:\n * - Starts invisible with opacity: 0\n * - Smooth 400ms transitions with easing\n * - 800ms delay for hover states (prevents flicker)\n * - Immediate hiding when trigger loses focus/hover\n *\n * @example\n * Rich content popover:\n * ```jsx\n * <Popover.Detail identifier=\"rich-content\">\n * <div className=\"p-4\">\n * <h3>Popover Title</h3>\n * <p>Detailed information with multiple paragraphs.</p>\n * <button>Action Button</button>\n * </div>\n * </Popover.Detail>\n * ```\n *\n * @example\n * Menu-style popover:\n * ```jsx\n * <Popover.Detail\n * identifier=\"context-menu\"\n * displayArrow={false}\n * xAlign=\"end\"\n * >\n * <ul className=\"py-2\">\n * <li><button className=\"w-full px-4 py-2\">Edit</button></li>\n * <li><button className=\"w-full px-4 py-2\">Delete</button></li>\n * </ul>\n * </Popover.Detail>\n * ```\n *\n * @param props - Popover Detail component props\n * @returns Positioned popover content with animations and accessibility\n */\nconst Detail: FC<DetailProps> = ({\n children,\n isHidden = undefined,\n isOverable = true,\n isFocusable = false,\n xAlign = 'start',\n yAlign = 'below',\n identifier,\n className,\n displayArrow = true,\n ...props\n}) => (\n <Container\n transparency=\"xs\"\n role=\"group\"\n aria-hidden={isHidden}\n aria-labelledby={`unrollable-panel-button-${identifier}`}\n id={`unrollable-panel-${identifier}`}\n className={cn(\n 'absolute z-60 min-w-full rounded-md ring-1 ring-neutral',\n\n /* Positioning */\n xAlign === 'start' && 'left-0',\n xAlign === 'center' && 'left-1/2 -translate-x-1/2',\n xAlign === 'end' && 'right-0',\n yAlign === 'below' && 'top-[calc(100%+1rem)]',\n yAlign === 'above' && 'bottom-[calc(100%+1rem)]',\n\n /* Arrow indicator */\n displayArrow &&\n 'before:absolute before:z-[999] before:h-0 before:w-0 before:content-[\"\"]',\n\n /* Horizontal positioning */\n displayArrow && xAlign === 'start' && 'before:left-2',\n displayArrow &&\n xAlign === 'center' &&\n 'before:left-1/2 before:-translate-x-1/2',\n displayArrow && xAlign === 'end' && 'before:right-2',\n\n /* Arrow pointing up (when popover is below trigger) */\n displayArrow &&\n yAlign === 'below' &&\n 'before:-top-2.5 before:border-r-[10px] before:border-r-transparent before:border-b-[10px] before:border-b-neutral before:border-l-[10px] before:border-l-transparent',\n\n /* Arrow pointing down (when popover is above trigger) */\n displayArrow &&\n yAlign === 'above' &&\n 'before:-bottom-2.5 before:border-t-[10px] before:border-t-neutral before:border-r-[10px] before:border-r-transparent before:border-l-[10px] before:border-l-transparent',\n\n /* Visibility management */\n 'overflow-x-visible opacity-0 transition-all duration-400 ease-in-out',\n isHidden !== false ? 'invisible' : 'visible opacity-100 delay-800',\n isOverable &&\n `group-hover/popover:visible group-hover/popover:opacity-100 group-hover/popover:delay-800`,\n isFocusable &&\n `group-focus-within/popover:visible group-focus-within/popover:opacity-100 group-focus-within/popover:delay-800`,\n className\n )}\n {...props}\n >\n {children}\n </Container>\n);\n\nPopoverStatic.Detail = Detail;\n\n// Export Detail for use in dynamic version\nexport { Detail };\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAwHA,MAAa,iBAA8B,EACzC,UACA,WACA,YACA,GAAG,YAEH,oBAAC,OAAD;CACE,WAAW,GAAG,8CAA8C,SAAS;CACrE,IAAI,2BAA2B;CAC/B;CACA,GAAI;CAEH;AACE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAuFP,MAAM,UAA2B,EAC/B,UACA,WAAW,QACX,aAAa,MACb,cAAc,OACd,SAAS,SACT,SAAS,SACT,YACA,WACA,eAAe,MACf,GAAG,YAEH,oBAAC,WAAD;CACE,cAAa;CACb,MAAK;CACL,eAAa;CACb,mBAAiB,2BAA2B;CAC5C,IAAI,oBAAoB;CACxB,WAAW,GACT,2DAGA,WAAW,WAAW,UACtB,WAAW,YAAY,6BACvB,WAAW,SAAS,WACpB,WAAW,WAAW,yBACtB,WAAW,WAAW,4BAGtB,gBACE,8EAGF,gBAAgB,WAAW,WAAW,iBACtC,gBACE,WAAW,YACX,2CACF,gBAAgB,WAAW,SAAS,kBAGpC,gBACE,WAAW,WACX,wKAGF,gBACE,WAAW,WACX,2KAGF,wEACA,aAAa,QAAQ,cAAc,iCACnC,cACE,6FACF,eACE,kHACF,SACF;CACA,GAAI;CAEH;AACQ;AAGb,cAAc,SAAS"}
|
|
1
|
+
{"version":3,"file":"static.mjs","names":[],"sources":["../../../../src/components/Popover/static.tsx"],"sourcesContent":["import { cn } from '@utils/cn';\nimport {\n type ComponentProps,\n createContext,\n type DetailedHTMLProps,\n type FC,\n type HTMLAttributes,\n useContext,\n useId,\n} from 'react';\nimport { Container } from '../Container';\n\n/**\n * Name the trigger shares with its own `Detail` so both derive the same ids.\n *\n * The `identifier` prop names a *kind* of popover (\"copy\", \"history\"), and the\n * same kind legitimately appears many times on one page — a documentation page\n * renders one copy popover per code block. Deriving the ids from the identifier\n * alone made every one of them share them, which is invalid HTML and leaves\n * `aria-labelledby` pointing at whichever element the browser matched first.\n */\nconst PopoverNameContext = createContext<string | null>(null);\n\n/** Ids of a popover pair, unique to the instance that rendered it. */\nexport const usePopoverIds = (identifier: string) => {\n const name = useContext(PopoverNameContext) ?? identifier;\n\n return {\n triggerId: `unrollable-panel-button-${name}`,\n panelId: `unrollable-panel-${name}`,\n };\n};\n\n/**\n * Props for the main Popover component\n * Extends HTMLDivElement attributes for full DOM compatibility\n */\nexport type PopoverProps = DetailedHTMLProps<\n HTMLAttributes<HTMLDivElement>,\n HTMLDivElement\n> & {\n /** Unique identifier linking the trigger to its popover content for accessibility */\n identifier: string;\n};\n\n/**\n * Composite type for the Popover component with Detail subcomponent\n * Allows for Popover.Detail usage pattern\n */\nexport type PopoverType = FC<PopoverProps> & {\n Detail: FC<DetailProps>;\n};\n\n/**\n * Horizontal alignment options for popover positioning\n */\nexport type PopoverXAlign = 'start' | 'center' | 'end';\n\n/**\n * Vertical alignment options for popover positioning\n */\nexport type PopoverYAlign = 'below' | 'above';\n\n/**\n * Popover Component\n *\n * A versatile popover container that displays contextual content when triggered by hover\n * or focus interactions. Built with accessibility in mind and supports multiple positioning\n * options with smooth animations.\n *\n * Features:\n * - Hover and focus-based triggering\n * - Multiple positioning options (above/below, start/center/end)\n * - Accessibility compliant with ARIA attributes\n * - Smooth animations with configurable delays\n * - Optional directional arrows\n * - Automatic z-index management\n * - Responsive design support\n *\n * Architecture:\n * - Main Popover acts as trigger container\n * - Popover.Detail renders the actual popover content\n * - Uses CSS groups for coordinated hover/focus states\n * - Unique identifier system prevents conflicts\n *\n * @example\n * Basic hover popover:\n * ```jsx\n * <Popover identifier=\"help-tooltip\">\n * <button>Need Help?</button>\n *\n * <Popover.Detail identifier=\"help-tooltip\">\n * <div>This is helpful information!</div>\n * </Popover.Detail>\n * </Popover>\n * ```\n *\n * @example\n * Focus-triggered popover:\n * ```jsx\n * <Popover identifier=\"focus-menu\">\n * <input placeholder=\"Focus me\" />\n *\n * <Popover.Detail\n * identifier=\"focus-menu\"\n * isFocusable\n * isOverable={false}\n * >\n * <div>Focus-only menu content</div>\n * </Popover.Detail>\n * </Popover>\n * ```\n *\n * @example\n * Positioned popover with custom alignment:\n * ```jsx\n * <Popover identifier=\"positioned\">\n * <span>Hover me</span>\n *\n * <Popover.Detail\n * identifier=\"positioned\"\n * xAlign=\"end\"\n * yAlign=\"above\"\n * displayArrow={false}\n * >\n * <div>Above and right-aligned</div>\n * </Popover.Detail>\n * </Popover>\n * ```\n *\n * Accessibility Features:\n * - Proper ARIA labeling and relationships\n * - Keyboard navigation support\n * - Screen reader compatibility\n * - Focus management\n *\n * Performance Considerations:\n * - CSS-only animations for smooth transitions\n * - Efficient group-based state management\n * - Minimal DOM updates during interactions\n *\n * @param props - Popover component props\n * @returns Trigger container with popover functionality\n */\nexport const PopoverStatic: PopoverType = ({\n children,\n className,\n identifier,\n ...props\n}) => {\n const name = `${identifier}${useId()}`;\n const { triggerId } = usePopoverIds(name);\n\n return (\n <PopoverNameContext value={name}>\n <div\n className={cn(`group/popover relative flex cursor-pointer`, className)}\n id={triggerId}\n aria-haspopup\n {...props}\n >\n {children}\n </div>\n </PopoverNameContext>\n );\n};\n\n/**\n * Props for the Popover.Detail component\n * Extends HTMLDivElement attributes for styling flexibility\n */\nexport type DetailProps = ComponentProps<typeof Container> & {\n /** Whether the popover responds to focus events on the trigger */\n isFocusable?: boolean;\n /** Controls visibility state - undefined allows automatic hover/focus control */\n isHidden?: boolean;\n /** Whether the popover responds to hover events on the trigger */\n isOverable?: boolean;\n /** Unique identifier matching the trigger's identifier for accessibility */\n identifier: string;\n /** Horizontal positioning relative to trigger */\n xAlign?: PopoverXAlign | `${PopoverXAlign}`;\n /** Vertical positioning relative to trigger */\n yAlign?: PopoverYAlign | `${PopoverYAlign}`;\n /** Whether to display the directional arrow indicator */\n displayArrow?: boolean;\n};\n\n/**\n * Popover Detail Component\n *\n * The actual popover content container with advanced positioning, animation, and\n * accessibility features. Automatically manages visibility based on trigger interactions.\n *\n * Features:\n * - Precise positioning with alignment options\n * - Smooth fade and slide animations\n * - Configurable directional arrows\n * - Hover and focus interaction support\n * - Accessibility-compliant ARIA attributes\n * - High z-index for overlay behavior\n * - Automatic visibility management\n *\n * Positioning System:\n * - X-axis: START (left-aligned), CENTER (centered), or END (right-aligned)\n * - Y-axis: BELOW (underneath) or ABOVE (on top)\n * - Automatic spacing with 1rem gap from trigger\n * - Responsive minimum width matching trigger\n *\n * Arrow Indicators:\n * - CSS-generated triangular arrows\n * - Positioned based on alignment settings\n * - Points toward trigger for visual connection\n * - Can be disabled for clean, minimal appearance\n *\n * Animation Behavior:\n * - Starts invisible with opacity: 0\n * - Smooth 400ms transitions with easing\n * - 800ms delay for hover states (prevents flicker)\n * - Immediate hiding when trigger loses focus/hover\n *\n * @example\n * Rich content popover:\n * ```jsx\n * <Popover.Detail identifier=\"rich-content\">\n * <div className=\"p-4\">\n * <h3>Popover Title</h3>\n * <p>Detailed information with multiple paragraphs.</p>\n * <button>Action Button</button>\n * </div>\n * </Popover.Detail>\n * ```\n *\n * @example\n * Menu-style popover:\n * ```jsx\n * <Popover.Detail\n * identifier=\"context-menu\"\n * displayArrow={false}\n * xAlign=\"end\"\n * >\n * <ul className=\"py-2\">\n * <li><button className=\"w-full px-4 py-2\">Edit</button></li>\n * <li><button className=\"w-full px-4 py-2\">Delete</button></li>\n * </ul>\n * </Popover.Detail>\n * ```\n *\n * @param props - Popover Detail component props\n * @returns Positioned popover content with animations and accessibility\n */\nconst Detail: FC<DetailProps> = ({\n children,\n isHidden = undefined,\n isOverable = true,\n isFocusable = false,\n xAlign = 'start',\n yAlign = 'below',\n identifier,\n className,\n displayArrow = true,\n ...props\n}) => {\n const { triggerId, panelId } = usePopoverIds(identifier);\n\n return (\n <Container\n transparency=\"xs\"\n role=\"group\"\n aria-hidden={isHidden}\n aria-labelledby={triggerId}\n id={panelId}\n className={cn(\n 'absolute z-60 min-w-full rounded-md ring-1 ring-neutral',\n\n /* Positioning */\n xAlign === 'start' && 'left-0',\n xAlign === 'center' && 'left-1/2 -translate-x-1/2',\n xAlign === 'end' && 'right-0',\n yAlign === 'below' && 'top-[calc(100%+1rem)]',\n yAlign === 'above' && 'bottom-[calc(100%+1rem)]',\n\n /* Arrow indicator */\n displayArrow &&\n 'before:absolute before:z-[999] before:h-0 before:w-0 before:content-[\"\"]',\n\n /* Horizontal positioning */\n displayArrow && xAlign === 'start' && 'before:left-2',\n displayArrow &&\n xAlign === 'center' &&\n 'before:left-1/2 before:-translate-x-1/2',\n displayArrow && xAlign === 'end' && 'before:right-2',\n\n /* Arrow pointing up (when popover is below trigger) */\n displayArrow &&\n yAlign === 'below' &&\n 'before:-top-2.5 before:border-r-[10px] before:border-r-transparent before:border-b-[10px] before:border-b-neutral before:border-l-[10px] before:border-l-transparent',\n\n /* Arrow pointing down (when popover is above trigger) */\n displayArrow &&\n yAlign === 'above' &&\n 'before:-bottom-2.5 before:border-t-[10px] before:border-t-neutral before:border-r-[10px] before:border-r-transparent before:border-l-[10px] before:border-l-transparent',\n\n /* Visibility management */\n 'overflow-x-visible opacity-0 transition-all duration-400 ease-in-out',\n isHidden !== false ? 'invisible' : 'visible opacity-100 delay-800',\n isOverable &&\n `group-hover/popover:visible group-hover/popover:opacity-100 group-hover/popover:delay-800`,\n isFocusable &&\n `group-focus-within/popover:visible group-focus-within/popover:opacity-100 group-focus-within/popover:delay-800`,\n className\n )}\n {...props}\n >\n {children}\n </Container>\n );\n};\n\nPopoverStatic.Detail = Detail;\n\n// Export Detail for use in dynamic version\nexport { Detail };\n"],"mappings":";;;;;;;;;;;;;;;AAqBA,MAAM,qBAAqB,cAA6B,IAAI;;AAG5D,MAAa,iBAAiB,eAAuB;CACnD,MAAM,OAAO,WAAW,kBAAkB,KAAK;CAE/C,OAAO;EACL,WAAW,2BAA2B;EACtC,SAAS,oBAAoB;CAC/B;AACF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiHA,MAAa,iBAA8B,EACzC,UACA,WACA,YACA,GAAG,YACC;CACJ,MAAM,OAAO,GAAG,aAAa,MAAM;CACnC,MAAM,EAAE,cAAc,cAAc,IAAI;CAExC,OACE,oBAAC,oBAAD;EAAoB,OAAO;YACzB,oBAAC,OAAD;GACE,WAAW,GAAG,8CAA8C,SAAS;GACrE,IAAI;GACJ;GACA,GAAI;GAEH;EACE;CACa;AAExB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsFA,MAAM,UAA2B,EAC/B,UACA,WAAW,QACX,aAAa,MACb,cAAc,OACd,SAAS,SACT,SAAS,SACT,YACA,WACA,eAAe,MACf,GAAG,YACC;CACJ,MAAM,EAAE,WAAW,YAAY,cAAc,UAAU;CAEvD,OACE,oBAAC,WAAD;EACE,cAAa;EACb,MAAK;EACL,eAAa;EACb,mBAAiB;EACjB,IAAI;EACJ,WAAW,GACT,2DAGA,WAAW,WAAW,UACtB,WAAW,YAAY,6BACvB,WAAW,SAAS,WACpB,WAAW,WAAW,yBACtB,WAAW,WAAW,4BAGtB,gBACE,8EAGF,gBAAgB,WAAW,WAAW,iBACtC,gBACE,WAAW,YACX,2CACF,gBAAgB,WAAW,SAAS,kBAGpC,gBACE,WAAW,WACX,wKAGF,gBACE,WAAW,WACX,2KAGF,wEACA,aAAa,QAAQ,cAAc,iCACnC,cACE,6FACF,eACE,kHACF,SACF;EACA,GAAI;EAEH;CACQ;AAEf;AAEA,cAAc,SAAS"}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import { useGetElementOrWindow } from "../../hooks/useGetElementOrWindow.mjs";
|
|
4
|
-
import { useDevice } from "../../hooks/useDevice.mjs";
|
|
5
|
-
import { useScrollBlockage } from "../../hooks/useScrollBlockage/index.mjs";
|
|
6
3
|
import { Button } from "../Button/Button.mjs";
|
|
7
4
|
import { Container } from "../Container/index.mjs";
|
|
5
|
+
import { useDevice } from "../../hooks/useDevice.mjs";
|
|
8
6
|
import { KeyboardShortcut } from "../KeyboardShortcut/KeyboardShortcut.mjs";
|
|
9
7
|
import { Popover } from "../Popover/dynamic.mjs";
|
|
8
|
+
import { useGetElementOrWindow } from "../../hooks/useGetElementOrWindow.mjs";
|
|
9
|
+
import { useScrollBlockage } from "../../hooks/useScrollBlockage/index.mjs";
|
|
10
10
|
import { MaxWidthSmoother } from "../MaxWidthSmoother/index.mjs";
|
|
11
11
|
import { isElementAtTopAndNotCovered } from "./isElementAtTopAndNotCovered.mjs";
|
|
12
12
|
import { useRightDrawer } from "./useRightDrawer.mjs";
|
|
@@ -98,9 +98,10 @@ const RightDrawer = ({ title, identifier, children, header, footer, closeOnOutsi
|
|
|
98
98
|
isHidden: !isOpen,
|
|
99
99
|
align: "right",
|
|
100
100
|
children: /* @__PURE__ */ jsxs(Container, {
|
|
101
|
-
className: "relative flex h-screen w-screen flex-col text-text md:w-100",
|
|
101
|
+
className: "relative flex h-screen w-screen flex-col border-l text-text md:w-100",
|
|
102
102
|
ref: panelRef,
|
|
103
103
|
roundedSize: "none",
|
|
104
|
+
borderColor: "neutral",
|
|
104
105
|
children: [
|
|
105
106
|
/* @__PURE__ */ jsxs("div", {
|
|
106
107
|
className: "flex shrink-0 flex-col gap-3 px-6 pt-6",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RightDrawer.mjs","names":[],"sources":["../../../../src/components/RightDrawer/RightDrawer.tsx"],"sourcesContent":["'use client';\n\nimport { useGetElementOrWindow } from '@hooks/index';\nimport { useDevice } from '@hooks/useDevice';\nimport { useScrollBlockage } from '@hooks/useScrollBlockage';\nimport { ChevronLeft, X } from 'lucide-react';\nimport {\n type FC,\n type KeyboardEventHandler,\n type MouseEventHandler,\n type ReactNode,\n useEffect,\n useRef,\n} from 'react';\nimport { createPortal } from 'react-dom';\nimport { useIntlayer } from 'react-intlayer';\nimport { Button } from '../Button';\nimport { Container } from '../Container';\nimport { KeyboardShortcut } from '../KeyboardShortcut';\nimport { MaxWidthSmoother } from '../MaxWidthSmoother/index';\nimport { Popover } from '../Popover';\nimport { isElementAtTopAndNotCovered } from './isElementAtTopAndNotCovered';\nimport { useRightDrawer } from './useRightDrawer';\n\n/**\n * Configuration for the back button functionality in the RightDrawer\n *\n * @interface BackButtonProps\n */\ntype BackButtonProps = {\n /** Callback function triggered when the back button is clicked */\n onBack: () => void;\n /** Optional custom text for the back buttoDefaults to \"Go back\" if not provided */\n text?: string;\n};\n\n/**\n * Props configuration for the RightDrawer component\n *\n * @interface RightDrawerProps\n */\ntype RightDrawerProps = {\n /**\n * Title displayed in the drawer header\n */\n title?: ReactNode;\n\n /**\n * Unique identifier for the drawer instancRequired for store management\n */\n identifier: string;\n\n /** The content to be displayed inside the drawer */\n children?: ReactNode;\n\n /**\n * Optional header content displayed below the title\n */\n header?: ReactNode;\n\n /**\n * Optional footer content pinned to the bottom of the drawer\n */\n footer?: ReactNode;\n\n /**\n * Whether the drawer should close when clicking outside of it\n * @default true\n */\n closeOnOutsideClick?: boolean;\n\n /**\n * Configuration for an optional back button in the drawer header\n */\n backButton?: BackButtonProps;\n\n /**\n * External control for the open statWhen provided, overrides internal store state\n */\n isOpen?: boolean;\n\n /**\n * Callback function triggered when the drawer is closed\n */\n onClose?: () => void;\n\n /**\n * Optional container to render the drawer into.\n * If not provided, it will be rendered into the body.\n */\n container?: HTMLElement;\n};\n\nexport const RightDrawer: FC<RightDrawerProps> = ({\n title,\n identifier,\n children,\n header,\n footer,\n closeOnOutsideClick = true,\n backButton,\n isOpen: isOpenProp,\n onClose,\n container,\n}) => {\n const content = useIntlayer('right-drawer');\n const { isMobile } = useDevice('md');\n const panelRef = useRef<HTMLDivElement>(null);\n const childrenContainerRef = useRef<HTMLDivElement>(null);\n const containerElement = useGetElementOrWindow(container);\n\n const {\n open: openDrawer,\n close: closeDrawer,\n isOpen: checkIsOpen,\n } = useRightDrawer();\n const storeIsOpen = checkIsOpen(identifier);\n const isOpen = storeIsOpen;\n\n useScrollBlockage({\n disableScroll: isOpen,\n key: identifier ? `right_drawer_${identifier}` : 'right_drawer',\n });\n\n // Handle Click Outside\n useEffect(() => {\n const handleClickOutside = (event: MouseEvent) => {\n try {\n if (!panelRef.current) return;\n\n const isClickAble = isOpen && closeOnOutsideClick;\n const isClickOutside =\n event.target && !panelRef.current.contains(event.target as Node);\n const isAtTopAndVisible = isElementAtTopAndNotCovered(panelRef.current);\n\n if (\n (isClickAble && isClickOutside && isAtTopAndVisible) ||\n !event.target\n ) {\n closeDrawer(identifier);\n onClose?.();\n }\n } catch (_e) {\n closeDrawer(identifier);\n onClose?.();\n }\n };\n\n window.addEventListener('mousedown', handleClickOutside);\n return () => window.removeEventListener('mousedown', handleClickOutside);\n }, [isOpen, closeDrawer, onClose, closeOnOutsideClick, identifier]);\n\n const onCloseRef = useRef(onClose);\n useEffect(() => {\n onCloseRef.current = onClose;\n }, [onClose]);\n\n useEffect(() => {\n if (isOpenProp === undefined) return;\n if (isOpenProp === storeIsOpen) return;\n\n if (isOpenProp) {\n openDrawer(identifier);\n } else {\n closeDrawer(identifier);\n onCloseRef.current?.();\n }\n }, [isOpenProp, storeIsOpen, identifier, openDrawer, closeDrawer]);\n\n const handleSpareSpaceClick: MouseEventHandler<HTMLDivElement> = (e) => {\n if (e.target !== e.currentTarget) {\n return;\n }\n if (isMobile) {\n closeDrawer(identifier);\n onClose?.();\n }\n };\n\n // Handle Keyboard on Spare Space (Linter Fix)\n const handleSpareSpaceKeyDown: KeyboardEventHandler<HTMLDivElement> = (e) => {\n if (e.target !== e.currentTarget) return;\n\n // Allow closing via Enter or Space if focused on the spare area\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n if (isMobile) {\n closeDrawer(identifier);\n onClose?.();\n }\n }\n };\n\n if (!containerElement) return <></>;\n\n return createPortal(\n <div className=\"fixed top-0 right-0 z-50 flex h-full justify-end\">\n <MaxWidthSmoother isHidden={!isOpen} align=\"right\">\n <Container\n className=\"relative flex h-screen w-screen flex-col text-text md:w-100\"\n ref={panelRef}\n roundedSize=\"none\"\n >\n {/* Header */}\n <div className=\"flex shrink-0 flex-col gap-3 px-6 pt-6\">\n <div className=\"flex justify-between gap-3\">\n <div>\n {backButton && (\n <Button\n variant=\"hoverable\"\n color=\"text\"\n label={backButton.text ?? content.goBack.value}\n onClick={backButton.onBack}\n Icon={ChevronLeft}\n >\n {backButton?.text}\n </Button>\n )}\n </div>\n <div>\n <Popover identifier=\"close-drawer\">\n <Button\n variant=\"hoverable\"\n color=\"text\"\n label=\"Close\"\n className=\"ml-auto\"\n onClick={() => {\n closeDrawer(identifier);\n onClose?.();\n }}\n Icon={X}\n size=\"icon-md\"\n />\n\n <Popover.Detail identifier=\"close-drawer\">\n <div className=\"flex items-center gap-2 p-2\">\n <span className=\"whitespace-nowrap text-neutral text-xs\">\n {content.closeDrawer}\n </span>\n <KeyboardShortcut\n shortcut=\"Escape\"\n size=\"sm\"\n onTriggered={() => {\n closeDrawer(identifier);\n onClose?.();\n }}\n />\n </div>\n </Popover.Detail>\n </Popover>\n </div>\n </div>\n {title && (\n <h2 className=\"flex items-center justify-center font-bold text-lg\">\n {title}\n </h2>\n )}\n {header}\n </div>\n\n {/* Body */}\n <div className=\"flex min-h-0 flex-1 flex-col overflow-y-auto p-2\">\n {/** biome-ignore lint/a11y/useSemanticElements: This div is used to handle the spare space click and keydown events */}\n <div\n className=\"flex flex-1 flex-col outline-none\"\n onClick={handleSpareSpaceClick}\n onKeyDown={handleSpareSpaceKeyDown}\n ref={childrenContainerRef}\n role=\"button\" // Semantically acts as a button area\n tabIndex={0} // Makes it focusable to receive key events\n >\n {children}\n </div>\n </div>\n\n {/* Footer */}\n {footer && <div className=\"shrink-0\">{footer}</div>}\n </Container>\n </MaxWidthSmoother>\n </div>,\n containerElement\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"RightDrawer.mjs","names":[],"sources":["../../../../src/components/RightDrawer/RightDrawer.tsx"],"sourcesContent":["'use client';\n\nimport { useGetElementOrWindow } from '@hooks/index';\nimport { useDevice } from '@hooks/useDevice';\nimport { useScrollBlockage } from '@hooks/useScrollBlockage';\nimport { ChevronLeft, X } from 'lucide-react';\nimport {\n type FC,\n type KeyboardEventHandler,\n type MouseEventHandler,\n type ReactNode,\n useEffect,\n useRef,\n} from 'react';\nimport { createPortal } from 'react-dom';\nimport { useIntlayer } from 'react-intlayer';\nimport { Button } from '../Button';\nimport { Container } from '../Container';\nimport { KeyboardShortcut } from '../KeyboardShortcut';\nimport { MaxWidthSmoother } from '../MaxWidthSmoother/index';\nimport { Popover } from '../Popover';\nimport { isElementAtTopAndNotCovered } from './isElementAtTopAndNotCovered';\nimport { useRightDrawer } from './useRightDrawer';\n\n/**\n * Configuration for the back button functionality in the RightDrawer\n *\n * @interface BackButtonProps\n */\ntype BackButtonProps = {\n /** Callback function triggered when the back button is clicked */\n onBack: () => void;\n /** Optional custom text for the back buttoDefaults to \"Go back\" if not provided */\n text?: string;\n};\n\n/**\n * Props configuration for the RightDrawer component\n *\n * @interface RightDrawerProps\n */\ntype RightDrawerProps = {\n /**\n * Title displayed in the drawer header\n */\n title?: ReactNode;\n\n /**\n * Unique identifier for the drawer instancRequired for store management\n */\n identifier: string;\n\n /** The content to be displayed inside the drawer */\n children?: ReactNode;\n\n /**\n * Optional header content displayed below the title\n */\n header?: ReactNode;\n\n /**\n * Optional footer content pinned to the bottom of the drawer\n */\n footer?: ReactNode;\n\n /**\n * Whether the drawer should close when clicking outside of it\n * @default true\n */\n closeOnOutsideClick?: boolean;\n\n /**\n * Configuration for an optional back button in the drawer header\n */\n backButton?: BackButtonProps;\n\n /**\n * External control for the open statWhen provided, overrides internal store state\n */\n isOpen?: boolean;\n\n /**\n * Callback function triggered when the drawer is closed\n */\n onClose?: () => void;\n\n /**\n * Optional container to render the drawer into.\n * If not provided, it will be rendered into the body.\n */\n container?: HTMLElement;\n};\n\nexport const RightDrawer: FC<RightDrawerProps> = ({\n title,\n identifier,\n children,\n header,\n footer,\n closeOnOutsideClick = true,\n backButton,\n isOpen: isOpenProp,\n onClose,\n container,\n}) => {\n const content = useIntlayer('right-drawer');\n const { isMobile } = useDevice('md');\n const panelRef = useRef<HTMLDivElement>(null);\n const childrenContainerRef = useRef<HTMLDivElement>(null);\n const containerElement = useGetElementOrWindow(container);\n\n const {\n open: openDrawer,\n close: closeDrawer,\n isOpen: checkIsOpen,\n } = useRightDrawer();\n const storeIsOpen = checkIsOpen(identifier);\n const isOpen = storeIsOpen;\n\n useScrollBlockage({\n disableScroll: isOpen,\n key: identifier ? `right_drawer_${identifier}` : 'right_drawer',\n });\n\n // Handle Click Outside\n useEffect(() => {\n const handleClickOutside = (event: MouseEvent) => {\n try {\n if (!panelRef.current) return;\n\n const isClickAble = isOpen && closeOnOutsideClick;\n const isClickOutside =\n event.target && !panelRef.current.contains(event.target as Node);\n const isAtTopAndVisible = isElementAtTopAndNotCovered(panelRef.current);\n\n if (\n (isClickAble && isClickOutside && isAtTopAndVisible) ||\n !event.target\n ) {\n closeDrawer(identifier);\n onClose?.();\n }\n } catch (_e) {\n closeDrawer(identifier);\n onClose?.();\n }\n };\n\n window.addEventListener('mousedown', handleClickOutside);\n return () => window.removeEventListener('mousedown', handleClickOutside);\n }, [isOpen, closeDrawer, onClose, closeOnOutsideClick, identifier]);\n\n const onCloseRef = useRef(onClose);\n useEffect(() => {\n onCloseRef.current = onClose;\n }, [onClose]);\n\n useEffect(() => {\n if (isOpenProp === undefined) return;\n if (isOpenProp === storeIsOpen) return;\n\n if (isOpenProp) {\n openDrawer(identifier);\n } else {\n closeDrawer(identifier);\n onCloseRef.current?.();\n }\n }, [isOpenProp, storeIsOpen, identifier, openDrawer, closeDrawer]);\n\n const handleSpareSpaceClick: MouseEventHandler<HTMLDivElement> = (e) => {\n if (e.target !== e.currentTarget) {\n return;\n }\n if (isMobile) {\n closeDrawer(identifier);\n onClose?.();\n }\n };\n\n // Handle Keyboard on Spare Space (Linter Fix)\n const handleSpareSpaceKeyDown: KeyboardEventHandler<HTMLDivElement> = (e) => {\n if (e.target !== e.currentTarget) return;\n\n // Allow closing via Enter or Space if focused on the spare area\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n if (isMobile) {\n closeDrawer(identifier);\n onClose?.();\n }\n }\n };\n\n if (!containerElement) return <></>;\n\n return createPortal(\n <div className=\"fixed top-0 right-0 z-50 flex h-full justify-end\">\n <MaxWidthSmoother isHidden={!isOpen} align=\"right\">\n <Container\n className=\"relative flex h-screen w-screen flex-col border-l text-text md:w-100\"\n ref={panelRef}\n roundedSize=\"none\"\n borderColor=\"neutral\"\n >\n {/* Header */}\n <div className=\"flex shrink-0 flex-col gap-3 px-6 pt-6\">\n <div className=\"flex justify-between gap-3\">\n <div>\n {backButton && (\n <Button\n variant=\"hoverable\"\n color=\"text\"\n label={backButton.text ?? content.goBack.value}\n onClick={backButton.onBack}\n Icon={ChevronLeft}\n >\n {backButton?.text}\n </Button>\n )}\n </div>\n <div>\n <Popover identifier=\"close-drawer\">\n <Button\n variant=\"hoverable\"\n color=\"text\"\n label=\"Close\"\n className=\"ml-auto\"\n onClick={() => {\n closeDrawer(identifier);\n onClose?.();\n }}\n Icon={X}\n size=\"icon-md\"\n />\n\n <Popover.Detail identifier=\"close-drawer\">\n <div className=\"flex items-center gap-2 p-2\">\n <span className=\"whitespace-nowrap text-neutral text-xs\">\n {content.closeDrawer}\n </span>\n <KeyboardShortcut\n shortcut=\"Escape\"\n size=\"sm\"\n onTriggered={() => {\n closeDrawer(identifier);\n onClose?.();\n }}\n />\n </div>\n </Popover.Detail>\n </Popover>\n </div>\n </div>\n {title && (\n <h2 className=\"flex items-center justify-center font-bold text-lg\">\n {title}\n </h2>\n )}\n {header}\n </div>\n\n {/* Body */}\n <div className=\"flex min-h-0 flex-1 flex-col overflow-y-auto p-2\">\n {/** biome-ignore lint/a11y/useSemanticElements: This div is used to handle the spare space click and keydown events */}\n <div\n className=\"flex flex-1 flex-col outline-none\"\n onClick={handleSpareSpaceClick}\n onKeyDown={handleSpareSpaceKeyDown}\n ref={childrenContainerRef}\n role=\"button\" // Semantically acts as a button area\n tabIndex={0} // Makes it focusable to receive key events\n >\n {children}\n </div>\n </div>\n\n {/* Footer */}\n {footer && <div className=\"shrink-0\">{footer}</div>}\n </Container>\n </MaxWidthSmoother>\n </div>,\n containerElement\n );\n};\n"],"mappings":"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"}
|
|
@@ -5,12 +5,12 @@ import { useEffect, useRef, useState } from "react";
|
|
|
5
5
|
|
|
6
6
|
//#region src/components/SwitchSelector/useSwitchSelector.ts
|
|
7
7
|
const useSwitchSelector = ({ choices = [], value, defaultValue, onChange, hoverable = true, disabled = false }, orientation) => {
|
|
8
|
-
const [valueState, setValue] = useState(value ?? defaultValue ?? choices[0]
|
|
8
|
+
const [valueState, setValue] = useState(value ?? defaultValue ?? choices[0].value);
|
|
9
9
|
const [hoveredIndex, setHoveredIndex] = useState(null);
|
|
10
10
|
const optionsRefs = useRef([]);
|
|
11
11
|
const indicatorRef = useRef(null);
|
|
12
12
|
const { choiceIndicatorPosition } = useItemSelector(optionsRefs, {
|
|
13
|
-
selector: (el) => el
|
|
13
|
+
selector: (el) => el?.getAttribute("data-indicator") === "true",
|
|
14
14
|
isHoverable: false,
|
|
15
15
|
orientation
|
|
16
16
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useSwitchSelector.mjs","names":[],"sources":["../../../../src/components/SwitchSelector/useSwitchSelector.ts"],"sourcesContent":["'use client';\n\nimport {\n type ItemSelectorOrientation,\n useItemSelector,\n} from '@hooks/useItemSelector';\nimport { useEffect, useRef, useState } from 'react';\nimport type { SwitchSelectorBaseProps } from './SwitchSelector';\n\nexport const useSwitchSelector = <T>(\n {\n choices = [],\n value,\n defaultValue,\n onChange,\n hoverable = true,\n disabled = false,\n }: SwitchSelectorBaseProps<T>,\n orientation: ItemSelectorOrientation\n) => {\n const [valueState, setValue] = useState<T>(\n value ?? defaultValue ?? choices[0]
|
|
1
|
+
{"version":3,"file":"useSwitchSelector.mjs","names":[],"sources":["../../../../src/components/SwitchSelector/useSwitchSelector.ts"],"sourcesContent":["'use client';\n\nimport {\n type ItemSelectorOrientation,\n useItemSelector,\n} from '@hooks/useItemSelector';\nimport { useEffect, useRef, useState } from 'react';\nimport type { SwitchSelectorBaseProps } from './SwitchSelector';\n\nexport const useSwitchSelector = <T>(\n {\n choices = [],\n value,\n defaultValue,\n onChange,\n hoverable = true,\n disabled = false,\n }: SwitchSelectorBaseProps<T>,\n orientation: ItemSelectorOrientation\n) => {\n const [valueState, setValue] = useState<T>(\n value ?? defaultValue ?? choices[0]!.value\n );\n const [hoveredIndex, setHoveredIndex] = useState<number | null>(null);\n\n const optionsRefs = useRef<HTMLButtonElement[]>([]);\n const indicatorRef = useRef<HTMLDivElement | null>(null);\n const { choiceIndicatorPosition } = useItemSelector(optionsRefs, {\n selector: (el) => el?.getAttribute('data-indicator') === 'true',\n isHoverable: false,\n orientation,\n });\n\n const selectedIndex = choices.findIndex(\n (choice) => choice.value === valueState\n );\n\n // The indicator follows hover if hoverable, otherwise the selected option\n const indicatorIndex =\n hoverable && hoveredIndex !== null ? hoveredIndex : selectedIndex;\n\n const handleChange = (newValue: T) => {\n setValue(newValue);\n onChange?.(newValue);\n };\n\n useEffect(() => {\n if (value === undefined) return;\n setValue(value);\n }, [value]);\n\n return {\n valueState,\n hoveredIndex,\n setHoveredIndex,\n optionsRefs,\n indicatorRef,\n choiceIndicatorPosition,\n selectedIndex,\n indicatorIndex,\n handleChange,\n disabled,\n };\n};\n"],"mappings":";;;;;;AASA,MAAa,qBACX,EACE,UAAU,CAAC,GACX,OACA,cACA,UACA,YAAY,MACZ,WAAW,SAEb,gBACG;CACH,MAAM,CAAC,YAAY,YAAY,SAC7B,SAAS,gBAAgB,QAAQ,EAAE,CAAE,KACvC;CACA,MAAM,CAAC,cAAc,mBAAmB,SAAwB,IAAI;CAEpE,MAAM,cAAc,OAA4B,CAAC,CAAC;CAClD,MAAM,eAAe,OAA8B,IAAI;CACvD,MAAM,EAAE,4BAA4B,gBAAgB,aAAa;EAC/D,WAAW,OAAO,IAAI,aAAa,gBAAgB,MAAM;EACzD,aAAa;EACb;CACF,CAAC;CAED,MAAM,gBAAgB,QAAQ,WAC3B,WAAW,OAAO,UAAU,UAC/B;CAGA,MAAM,iBACJ,aAAa,iBAAiB,OAAO,eAAe;CAEtD,MAAM,gBAAgB,aAAgB;EACpC,SAAS,QAAQ;EACjB,WAAW,QAAQ;CACrB;CAEA,gBAAgB;EACd,IAAI,UAAU,QAAW;EACzB,SAAS,KAAK;CAChB,GAAG,CAAC,KAAK,CAAC;CAEV,OAAO;EACL;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;CACF;AACF"}
|
|
@@ -1,14 +1,22 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import { cn } from "../../utils/cn.mjs";
|
|
4
|
-
import { useHorizontalSwipe } from "../../hooks/useHorizontalSwipe.mjs";
|
|
5
4
|
import { TabSelector } from "../TabSelector/TabSelector.mjs";
|
|
5
|
+
import { useHorizontalSwipe } from "../../hooks/useHorizontalSwipe.mjs";
|
|
6
6
|
import { useTabContext } from "./TabContext.mjs";
|
|
7
7
|
import { Children, createContext, isValidElement, useState } from "react";
|
|
8
8
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
9
9
|
import { cva } from "class-variance-authority";
|
|
10
10
|
|
|
11
11
|
//#region src/components/Tab/Tab.tsx
|
|
12
|
+
/**
|
|
13
|
+
* Builds an id fragment usable in `id` / `aria-controls`.
|
|
14
|
+
*
|
|
15
|
+
* Tab values come from markdown (`<Tab value="Extract command">`), so they can
|
|
16
|
+
* hold spaces — which makes `aria-controls` reference a list of ids rather than
|
|
17
|
+
* the one panel, and silently breaks the association for screen readers.
|
|
18
|
+
*/
|
|
19
|
+
const toIdFragment = (value) => value.trim().replace(/\s+/g, "-").replace(/[^\w-]/g, "");
|
|
12
20
|
const TabContext = createContext(void 0);
|
|
13
21
|
const tabContainerVariant = cva("relative w-full rounded-lg", {
|
|
14
22
|
variants: {
|
|
@@ -25,6 +33,18 @@ const tabContainerVariant = cva("relative w-full rounded-lg", {
|
|
|
25
33
|
defaultVariants: { variant: "default" }
|
|
26
34
|
});
|
|
27
35
|
/**
|
|
36
|
+
* Resolves the identifier of a tab.
|
|
37
|
+
*
|
|
38
|
+
* Both `label` and `value` are optional for markdown-authored tabs
|
|
39
|
+
* (`<Tab label="next-intl">`), so the identifier falls back to the label, then
|
|
40
|
+
* to the position, keeping every tab addressable instead of rendering ids from
|
|
41
|
+
* `undefined`.
|
|
42
|
+
*/
|
|
43
|
+
const resolveTabValue = ({ value, label }, index) => {
|
|
44
|
+
const identifier = value ?? label;
|
|
45
|
+
return typeof identifier === "string" ? identifier : String(index);
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
28
48
|
* TabItem component that represents a single tab
|
|
29
49
|
* Must be used as a child of the Tab component
|
|
30
50
|
*/
|
|
@@ -51,24 +71,27 @@ TabItem.displayName = "TabItem";
|
|
|
51
71
|
const TabComponent = ({ defaultTab, group, variant, children, className, headerClassName, fullHeight, ...props }) => {
|
|
52
72
|
const tabItems = Children.toArray(children).filter((child) => {
|
|
53
73
|
return isValidElement(child) && child.type === TabItem;
|
|
54
|
-
})
|
|
55
|
-
|
|
74
|
+
}).map((child, index) => ({
|
|
75
|
+
...child.props,
|
|
76
|
+
value: resolveTabValue(child.props, index)
|
|
77
|
+
}));
|
|
78
|
+
const firstTabValue = tabItems[0]?.value;
|
|
56
79
|
const { tabsValues, setTabsValues } = useTabContext();
|
|
57
80
|
const [activeTab, setActiveTab] = useState(defaultTab ?? firstTabValue ?? "");
|
|
58
81
|
const currentTabValue = (group && typeof tabsValues === "object" ? tabsValues?.[group] : activeTab) ?? defaultTab ?? firstTabValue;
|
|
59
|
-
const activeTabIndex = tabItems.findIndex((tab) => tab.
|
|
82
|
+
const activeTabIndex = tabItems.findIndex((tab) => tab.value === currentTabValue);
|
|
60
83
|
const tabsCount = tabItems.length;
|
|
61
84
|
const { containerProps, dragDeltaPct, isDragging } = useHorizontalSwipe({
|
|
62
85
|
itemIndex: activeTabIndex,
|
|
63
86
|
itemCount: tabsCount,
|
|
64
87
|
onSwipeLeft: () => {
|
|
65
88
|
const targetIndex = Math.min(tabsCount - 1, activeTabIndex + 1);
|
|
66
|
-
const nextValue = tabItems[targetIndex]?.
|
|
89
|
+
const nextValue = tabItems[targetIndex]?.value;
|
|
67
90
|
if (nextValue) handleSetActiveTab(nextValue);
|
|
68
91
|
},
|
|
69
92
|
onSwipeRight: () => {
|
|
70
93
|
const targetIndex = Math.max(0, activeTabIndex - 1);
|
|
71
|
-
const nextValue = tabItems[targetIndex]?.
|
|
94
|
+
const nextValue = tabItems[targetIndex]?.value;
|
|
72
95
|
if (nextValue) handleSetActiveTab(nextValue);
|
|
73
96
|
}
|
|
74
97
|
});
|
|
@@ -92,9 +115,9 @@ const TabComponent = ({ defaultTab, group, variant, children, className, headerC
|
|
|
92
115
|
className: cn("flex shrink-0 gap-3 p-3", headerClassName),
|
|
93
116
|
children: /* @__PURE__ */ jsx(TabSelector, {
|
|
94
117
|
selectedChoice: currentTabValue,
|
|
95
|
-
tabs: tabItems.map((
|
|
96
|
-
const { label, value, disabled } = child.props;
|
|
118
|
+
tabs: tabItems.map(({ label, value, disabled }) => {
|
|
97
119
|
const isActive = currentTabValue === value;
|
|
120
|
+
const idFragment = toIdFragment(value);
|
|
98
121
|
return /* @__PURE__ */ jsx("button", {
|
|
99
122
|
className: cn("cursor-pointer whitespace-nowrap rounded-md px-4 py-1 font-medium text-sm transition-colors focus:outline-none", !isActive && "text-neutral/70"),
|
|
100
123
|
"data-active": isActive,
|
|
@@ -102,8 +125,8 @@ const TabComponent = ({ defaultTab, group, variant, children, className, headerC
|
|
|
102
125
|
onClick: () => !disabled && handleSetActiveTab(value),
|
|
103
126
|
role: "tab",
|
|
104
127
|
"aria-selected": isActive,
|
|
105
|
-
"aria-controls": `tabpanel-${
|
|
106
|
-
id: `tab-${
|
|
128
|
+
"aria-controls": `tabpanel-${idFragment}`,
|
|
129
|
+
id: `tab-${idFragment}`,
|
|
107
130
|
type: "button",
|
|
108
131
|
children: label ?? value
|
|
109
132
|
}, value);
|
|
@@ -115,21 +138,19 @@ const TabComponent = ({ defaultTab, group, variant, children, className, headerC
|
|
|
115
138
|
className: cn("relative w-full min-w-0 overflow-x-clip [-webkit-clip-path:inset(0)] [clip-path:inset(0)]", fullHeight && "min-h-0 flex-1"),
|
|
116
139
|
...containerProps,
|
|
117
140
|
children: /* @__PURE__ */ jsx("div", {
|
|
118
|
-
role: "tablist",
|
|
119
|
-
"aria-orientation": "horizontal",
|
|
120
141
|
className: cn("grid w-full min-w-0", fullHeight && "h-full", isDragging ? "transition-none" : "transition-transform duration-300 ease-in-out"),
|
|
121
142
|
style: {
|
|
122
143
|
gridTemplateColumns: `repeat(${tabItems.length}, 100%)`,
|
|
123
144
|
transform: `translateX(-${activeTabIndex * 100 - (isDragging ? dragDeltaPct : 0)}%)`
|
|
124
145
|
},
|
|
125
|
-
children: tabItems.map(({
|
|
126
|
-
const { value, children, className: itemClassName } = props;
|
|
146
|
+
children: tabItems.map(({ value, children, className: itemClassName }, index) => {
|
|
127
147
|
const isActive = index === activeTabIndex;
|
|
148
|
+
const idFragment = toIdFragment(value);
|
|
128
149
|
return /* @__PURE__ */ jsx("div", {
|
|
129
150
|
role: "tabpanel",
|
|
130
|
-
"aria-labelledby": `tab-${
|
|
131
|
-
id: `tabpanel-${
|
|
132
|
-
|
|
151
|
+
"aria-labelledby": `tab-${idFragment}`,
|
|
152
|
+
id: `tabpanel-${idFragment}`,
|
|
153
|
+
inert: !isActive,
|
|
133
154
|
tabIndex: isActive ? 0 : -1,
|
|
134
155
|
"data-active": isActive,
|
|
135
156
|
className: cn("w-full min-w-0 p-3 opacity-100 transition-opacity duration-300 ease-in-out", fullHeight && "h-full overflow-y-auto", !isActive && "pointer-events-none opacity-0", itemClassName),
|