@umbra.ui/core 0.1.18 → 0.1.19

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.
Files changed (146) hide show
  1. package/dist/components/controls/Button/Button.vue +417 -0
  2. package/dist/components/controls/Button/README.md +348 -0
  3. package/dist/components/controls/Button/theme.css +200 -0
  4. package/dist/components/controls/Checkbox/Checkbox.vue +164 -0
  5. package/dist/components/controls/Checkbox/README.md +441 -0
  6. package/dist/components/controls/Checkbox/theme.css +36 -0
  7. package/dist/components/controls/Dropdown/Dropdown.vue +476 -0
  8. package/dist/components/controls/Dropdown/README.md +370 -0
  9. package/dist/components/controls/Dropdown/theme.css +50 -0
  10. package/dist/components/controls/Dropdown/types.ts +6 -0
  11. package/dist/components/controls/IconButton/IconButton.vue +267 -0
  12. package/dist/components/controls/IconButton/README.md +502 -0
  13. package/dist/components/controls/IconButton/theme.css +89 -0
  14. package/dist/components/controls/Radio/README.md +591 -0
  15. package/dist/components/controls/Radio/Radio.vue +89 -0
  16. package/dist/components/controls/Radio/theme.css +14 -0
  17. package/dist/components/controls/RangeSlider/README.md +608 -0
  18. package/dist/components/controls/RangeSlider/RangeSlider.vue +535 -0
  19. package/dist/components/controls/RangeSlider/theme.css +80 -0
  20. package/dist/components/controls/SegmentedControl/README.md +587 -0
  21. package/dist/components/controls/SegmentedControl/SegmentedControl.vue +284 -0
  22. package/dist/components/controls/SegmentedControl/theme.css +60 -0
  23. package/dist/components/controls/SegmentedControl/types.ts +5 -0
  24. package/dist/components/controls/Slider/README.md +627 -0
  25. package/dist/components/controls/Slider/Slider.vue +260 -0
  26. package/dist/components/controls/Slider/theme.css +74 -0
  27. package/dist/components/controls/Stepper/README.md +601 -0
  28. package/dist/components/controls/Stepper/Stepper.vue +103 -0
  29. package/dist/components/controls/Stepper/theme.css +53 -0
  30. package/dist/components/controls/Switch/README.md +667 -0
  31. package/dist/components/controls/Switch/Switch.vue +127 -0
  32. package/dist/components/controls/Switch/theme.css +42 -0
  33. package/dist/components/dialogs/Alert/Alert.vue +218 -0
  34. package/dist/components/dialogs/Alert/README.md +450 -0
  35. package/dist/components/dialogs/Alert/theme.css +44 -0
  36. package/dist/components/dialogs/Alert/types.ts +11 -0
  37. package/dist/components/dialogs/Toast/README.md +522 -0
  38. package/dist/components/dialogs/Toast/Toast.vue +296 -0
  39. package/dist/components/dialogs/Toast/ToastContainer.vue +330 -0
  40. package/dist/components/dialogs/Toast/theme.css +44 -0
  41. package/dist/components/dialogs/Toast/types.ts +46 -0
  42. package/dist/components/dialogs/Toast/useToast.ts +127 -0
  43. package/dist/components/indicators/ProgressBar/ProgressBar.vue +98 -0
  44. package/dist/components/indicators/ProgressBar/README.md +744 -0
  45. package/dist/components/indicators/ProgressBar/theme.css +36 -0
  46. package/dist/components/indicators/Tooltip/README.md +723 -0
  47. package/dist/components/indicators/Tooltip/TooltipProvider.vue +142 -0
  48. package/dist/components/indicators/Tooltip/theme.css +18 -0
  49. package/dist/components/indicators/Tooltip/tooltip.ts +48 -0
  50. package/dist/components/indicators/Tooltip/types.ts +15 -0
  51. package/dist/components/indicators/Tooltip/useTooltip.ts +71 -0
  52. package/dist/components/inputs/AutogrowTextView/AutogrowTextView.vue +110 -0
  53. package/dist/components/inputs/AutogrowTextView/README.md +643 -0
  54. package/dist/components/inputs/AutogrowTextView/theme.css +28 -0
  55. package/dist/components/inputs/InputCard/InputCard.vue +600 -0
  56. package/dist/components/inputs/InputCard/README.md +636 -0
  57. package/dist/components/inputs/InputEmail/InputEmail.vue +698 -0
  58. package/dist/components/inputs/InputEmail/README.md +764 -0
  59. package/dist/components/inputs/InputNumber/InputNumber.vue +300 -0
  60. package/dist/components/inputs/InputNumber/README.md +749 -0
  61. package/dist/components/inputs/InputPhone/InputPhone.vue +645 -0
  62. package/dist/components/inputs/InputPhone/README.md +636 -0
  63. package/dist/components/inputs/InputSecure/InputSecure.vue +646 -0
  64. package/dist/components/inputs/InputSecure/README.md +771 -0
  65. package/dist/components/inputs/InputText/InputText.vue +225 -0
  66. package/dist/components/inputs/InputText/README.md +844 -0
  67. package/dist/components/inputs/OTP/OTP.vue +349 -0
  68. package/dist/components/inputs/OTP/README.md +736 -0
  69. package/dist/components/inputs/OTP/theme.css +50 -0
  70. package/dist/components/inputs/StringCapture/README.md +718 -0
  71. package/dist/components/inputs/StringCapture/StringCapture.vue +315 -0
  72. package/dist/components/inputs/StringCapture/theme.css +86 -0
  73. package/dist/components/inputs/Tags/README.md +897 -0
  74. package/dist/components/inputs/Tags/TagBar.vue +793 -0
  75. package/dist/components/inputs/Tags/TagCreation.vue +219 -0
  76. package/dist/components/inputs/Tags/TagPicker.vue +380 -0
  77. package/dist/components/inputs/Tags/tag-bar-styles.ts +354 -0
  78. package/dist/components/inputs/Tags/theme.css +121 -0
  79. package/dist/components/inputs/Tags/types.ts +346 -0
  80. package/dist/components/inputs/search/README.md +759 -0
  81. package/dist/components/inputs/search/SearchBar.vue +394 -0
  82. package/dist/components/inputs/search/SearchResults.vue +310 -0
  83. package/dist/components/inputs/search/theme.css +187 -0
  84. package/dist/components/inputs/search/types.ts +8 -0
  85. package/dist/components/inputs/theme.css +102 -0
  86. package/dist/components/menus/ActionMenu/ActionMenu.vue +383 -0
  87. package/dist/components/menus/ActionMenu/README.md +825 -0
  88. package/dist/components/menus/ActionMenu/theme.css +93 -0
  89. package/dist/components/models/Popover/Popover.vue +551 -0
  90. package/dist/components/models/Popover/README.md +885 -0
  91. package/dist/components/models/Popover/theme.css +52 -0
  92. package/dist/components/models/Sheet/README.md +1159 -0
  93. package/dist/components/models/Sheet/Sheet.vue +465 -0
  94. package/dist/components/models/Sheet/theme.css +72 -0
  95. package/dist/components/models/Sidebar/README.md +1228 -0
  96. package/dist/components/models/Sidebar/Sidebar.vue +480 -0
  97. package/dist/components/models/Sidebar/theme.css +90 -0
  98. package/dist/components/navigation/adaptive/AdaptiveLayout.vue +779 -0
  99. package/dist/components/navigation/adaptive/AdaptiveLayoutBreadcrumbs.vue +192 -0
  100. package/dist/components/navigation/adaptive/AdaptiveLayoutMenuButton.vue +149 -0
  101. package/dist/components/navigation/adaptive/README.md +768 -0
  102. package/dist/components/navigation/adaptive/types.ts +19 -0
  103. package/dist/components/navigation/adaptive/useAdaptiveLayout.ts +89 -0
  104. package/dist/components/navigation/adaptive/useBreakpoints.ts +41 -0
  105. package/dist/components/navigation/adaptive/useContainerMonitor.ts +214 -0
  106. package/dist/components/navigation/adaptive/useViewAnimation.ts +721 -0
  107. package/dist/components/navigation/adaptive/useViewResize.ts +211 -0
  108. package/dist/components/navigation/navstack/NavigationStack.vue +180 -0
  109. package/dist/components/navigation/navstack/README.md +994 -0
  110. package/dist/components/navigation/navstack/useNavigationStack.ts +164 -0
  111. package/dist/components/navigation/slideover/README.md +1275 -0
  112. package/dist/components/navigation/slideover/SlideoverController.vue +287 -0
  113. package/dist/components/navigation/slideover/useSlideoverController.ts +320 -0
  114. package/dist/components/navigation/splitview/README.md +1115 -0
  115. package/dist/components/navigation/splitview/SplitViewController.vue +176 -0
  116. package/dist/components/navigation/splitview/useSplitViewController.ts +388 -0
  117. package/dist/components/navigation/tabcontroller/README.md +919 -0
  118. package/dist/components/navigation/tabcontroller/TabController.vue +307 -0
  119. package/dist/components/navigation/tabcontroller/TabItem.vue +57 -0
  120. package/dist/components/navigation/tabcontroller/types.ts +24 -0
  121. package/dist/components/navigation/tabcontroller/useTabController.ts +18 -0
  122. package/dist/components/navigation/theme.css +91 -0
  123. package/dist/components/navigation/types.ts +7 -0
  124. package/dist/components/pickers/CollectionPicker/CollectionPicker.vue +398 -0
  125. package/dist/components/pickers/CollectionPicker/README.md +1115 -0
  126. package/dist/components/pickers/CollectionPicker/theme.css +14 -0
  127. package/dist/components/pickers/CollectionPicker/types.ts +11 -0
  128. package/dist/components/pickers/ColorPicker/ColorPicker.vue +376 -0
  129. package/dist/components/pickers/ColorPicker/README.md +1439 -0
  130. package/dist/components/pickers/ColorPicker/colors.ts +299 -0
  131. package/dist/components/pickers/ColorPicker/theme.css +32 -0
  132. package/dist/components/pickers/DatePicker/DatePicker.vue +660 -0
  133. package/dist/components/pickers/DatePicker/README.md +1195 -0
  134. package/dist/components/pickers/DatePicker/theme.css +22 -0
  135. package/dist/components/pickers/FilePicker/FilePicker.vue +534 -0
  136. package/dist/components/pickers/FilePicker/README.md +1542 -0
  137. package/dist/components/pickers/FilePicker/theme.css +48 -0
  138. package/dist/components/pickers/FilePicker/types.ts +10 -0
  139. package/dist/components/pickers/IconPicker/IconPicker.vue +327 -0
  140. package/dist/components/pickers/IconPicker/README.md +1161 -0
  141. package/dist/components/pickers/IconPicker/theme.css +28 -0
  142. package/dist/components/pickers/theme.css +82 -0
  143. package/dist/components/views/MarkdownViewer/MarkdownViewer.vue +442 -0
  144. package/dist/components/views/MarkdownViewer/README.md +833 -0
  145. package/dist/components/views/MarkdownViewer/theme.css +130 -0
  146. package/package.json +3 -2
@@ -0,0 +1,287 @@
1
+ <script setup lang="ts">
2
+ import { gsap } from "gsap";
3
+ import { ref, computed, onMounted } from "vue";
4
+ import { ChevronLeftIcon } from "@umbra.ui/icons";
5
+ import { useSlideoverController } from "./useSlideoverController";
6
+ import "../theme.css";
7
+
8
+ // Define props
9
+ const props = defineProps<{
10
+ panes: Array<{
11
+ name: string;
12
+ background: string;
13
+ foreground: string;
14
+ component: any;
15
+ props: object;
16
+ }>;
17
+ controller?: ReturnType<typeof useSlideoverController>;
18
+ componentId?: string;
19
+ showBreadcrumb?: boolean;
20
+ }>();
21
+
22
+ // Use provided componentId or generate a unique one
23
+ const componentId =
24
+ props.componentId ||
25
+ `panel-layout-${Math.random().toString(36).substr(2, 9)}`;
26
+
27
+ // Use provided controller or create internal one
28
+ const controller =
29
+ props.controller || useSlideoverController(props.panes, componentId);
30
+
31
+ // Computed properties
32
+ const mainPane = computed(() => {
33
+ if (props.panes.length > 0) {
34
+ return props.panes[props.panes.length - 1];
35
+ }
36
+ return null;
37
+ });
38
+
39
+ const secondaryPanes = computed(() => {
40
+ if (props.panes.length > 0) {
41
+ return props.panes.slice(0, -1);
42
+ }
43
+ return [];
44
+ });
45
+
46
+ // Helper functions to get unique element IDs
47
+ const getOffscreenId = () => `${componentId}-offscreen`;
48
+ const getOnscreenId = () => `${componentId}-onscreen`;
49
+ const getOverlayId = () => `${componentId}-overlay`;
50
+ const getDarkenId = () => `${componentId}-darken`;
51
+ const getPaneId = (index: number) => `${componentId}-pane-${index}`;
52
+
53
+ // Initialize panes in offscreen on mount
54
+ onMounted(() => {
55
+ const offscreen = document.getElementById(getOffscreenId());
56
+ if (offscreen) {
57
+ // Move all secondary panes to offscreen initially
58
+ for (let i = 0; i < secondaryPanes.value.length; i++) {
59
+ const pane = document.getElementById(getPaneId(i));
60
+ if (pane) {
61
+ pane.parentNode?.removeChild(pane);
62
+ offscreen.appendChild(pane);
63
+ }
64
+ }
65
+ }
66
+ });
67
+ </script>
68
+
69
+ <template>
70
+ <div :class="$style.container">
71
+ <!-- Move darken outside of the grid structure -->
72
+ <div
73
+ :id="getDarkenId()"
74
+ :class="$style.darken"
75
+ @click="controller.hideAll"
76
+ ></div>
77
+
78
+ <div :class="$style.content_wrapper">
79
+ <div :id="getOnscreenId()" :class="$style.onscreen">
80
+ <div
81
+ v-if="mainPane"
82
+ :id="getPaneId(props.panes.length - 1)"
83
+ :class="$style.pane_full"
84
+ :style="{
85
+ order: props.panes.length - 1,
86
+ backgroundColor: mainPane.background,
87
+ }"
88
+ >
89
+ <div
90
+ v-if="(props.showBreadcrumb ?? true) && secondaryPanes.length > 0"
91
+ :class="$style.navbar"
92
+ :style="{ backgroundColor: mainPane.background }"
93
+ >
94
+ <div :class="$style.breadcrumbs">
95
+ <div
96
+ v-for="(pane, index) in secondaryPanes"
97
+ :key="`breadcrumb-${index}`"
98
+ :class="$style.breadcrumb"
99
+ >
100
+ <p
101
+ :class="$style.breadcrumb_divider"
102
+ v-if="index !== 0"
103
+ :style="{ color: mainPane.foreground }"
104
+ >
105
+ /
106
+ </p>
107
+ <p
108
+ :class="$style.breadcrumb_title"
109
+ :style="{ color: mainPane.foreground }"
110
+ @click="() => controller.showPane(index)"
111
+ >
112
+ {{ pane.name }}
113
+ </p>
114
+ </div>
115
+ </div>
116
+ </div>
117
+ <component
118
+ :is="mainPane.component"
119
+ v-bind="mainPane.props"
120
+ ></component>
121
+ </div>
122
+ </div>
123
+ <div :id="getOffscreenId()" :class="$style.offscreen">
124
+ <div
125
+ v-for="(pane, index) in secondaryPanes"
126
+ :key="`pane-${index}`"
127
+ :id="getPaneId(index)"
128
+ :class="$style.pane"
129
+ :style="{ order: index, backgroundColor: pane.background }"
130
+ >
131
+ <div :class="$style.component_container">
132
+ <component :is="pane.component" v-bind="pane.props"></component>
133
+ </div>
134
+ <div
135
+ v-if="index > 0"
136
+ :class="$style.handle_container"
137
+ @click="() => controller.togglePane(index)"
138
+ :style="{ backgroundColor: pane.background }"
139
+ >
140
+ <div
141
+ :class="$style.handle"
142
+ :style="{ backgroundColor: pane.foreground }"
143
+ ></div>
144
+ </div>
145
+ </div>
146
+ </div>
147
+ <div :id="getOverlayId()" :class="$style.overlay"></div>
148
+ </div>
149
+ </div>
150
+ </template>
151
+
152
+ <style module>
153
+ /* Same styles as before */
154
+ .container {
155
+ position: relative;
156
+ height: 100%;
157
+ width: 100%;
158
+ overflow: hidden;
159
+ }
160
+
161
+ .darken {
162
+ position: absolute;
163
+ top: 0;
164
+ left: 0;
165
+ right: 0;
166
+ bottom: 0;
167
+ background-color: var(--slideover-darken-bg);
168
+ opacity: 0;
169
+ pointer-events: none;
170
+ z-index: 5; /* Lower than overlay */
171
+ }
172
+
173
+ .content_wrapper {
174
+ position: relative;
175
+ display: grid;
176
+ grid-template-areas: "content";
177
+ height: 100%;
178
+ width: 100%;
179
+ grid-template-rows: 1fr;
180
+ }
181
+
182
+ .offscreen {
183
+ position: absolute;
184
+ top: 0;
185
+ left: -20rem;
186
+ bottom: 0;
187
+ width: 20rem;
188
+ display: flex;
189
+ justify-content: end;
190
+ z-index: 20; /* Highest z-index */
191
+ }
192
+
193
+ .overlay {
194
+ display: flex;
195
+ position: absolute;
196
+ width: auto;
197
+ left: 0;
198
+ bottom: 0;
199
+ top: 0;
200
+ z-index: 10; /* Higher than darken */
201
+ }
202
+
203
+ .onscreen {
204
+ grid-area: content;
205
+ display: flex;
206
+ height: 100%;
207
+ }
208
+
209
+ .pane {
210
+ display: grid;
211
+ grid-template-columns: 1fr;
212
+ grid-template-areas: "content";
213
+ height: 100%;
214
+ }
215
+
216
+ .pane_full {
217
+ display: flex;
218
+ flex-direction: column;
219
+ flex-grow: 1;
220
+ height: 100%;
221
+ }
222
+
223
+ .navbar {
224
+ display: flex;
225
+ align-items: center;
226
+ justify-content: start;
227
+ }
228
+
229
+ .breadcrumbs {
230
+ display: flex;
231
+ align-items: center;
232
+ gap: 0.75rem;
233
+ padding: 0.471rem;
234
+ }
235
+
236
+ .breadcrumb {
237
+ display: flex;
238
+ gap: 0.75rem;
239
+ }
240
+
241
+ .breadcrumb_title {
242
+ font-weight: 600;
243
+ cursor: default;
244
+ opacity: var(--slideover-breadcrumb-default-opacity);
245
+ transition: opacity 0.5s ease, transform 0.5s ease;
246
+ }
247
+
248
+ .breadcrumb_title:hover {
249
+ opacity: var(--slideover-breadcrumb-hover-opacity);
250
+ transform: scale(1.05);
251
+ }
252
+
253
+ .breadcrumb_divider {
254
+ opacity: 0.25;
255
+ }
256
+
257
+ .component_container {
258
+ grid-area: content;
259
+ overflow: hidden;
260
+ height: 100%;
261
+ }
262
+
263
+ .handle_container {
264
+ grid-area: content;
265
+ width: 15px;
266
+ min-height: 30px;
267
+ display: flex;
268
+ align-items: center;
269
+ justify-content: center;
270
+ height: 100%;
271
+ }
272
+
273
+ .handle {
274
+ width: var(--slideover-handle-default-width);
275
+ height: var(--slideover-handle-default-height);
276
+ opacity: var(--slideover-handle-default-opacity);
277
+ background-color: var(--slideover-handle-bg);
278
+ border-radius: 999px;
279
+ transition: width 0.5s ease, height 0.5s ease, opacity 0.5s ease;
280
+ }
281
+
282
+ .handle_container:hover .handle {
283
+ width: var(--slideover-handle-hover-width);
284
+ height: var(--slideover-handle-hover-height);
285
+ opacity: var(--slideover-handle-hover-opacity);
286
+ }
287
+ </style>
@@ -0,0 +1,320 @@
1
+ import { ref, Ref, computed } from "vue";
2
+ import { gsap } from "gsap";
3
+ import { Flip } from "gsap/Flip";
4
+
5
+ gsap.registerPlugin(Flip);
6
+
7
+ interface Pane {
8
+ name: string;
9
+ background: string;
10
+ foreground: string;
11
+ component: any;
12
+ props: Record<string, any>;
13
+ }
14
+
15
+ export const useSlideoverController = (panes: Pane[], componentId: string) => {
16
+ // State to track visibility of panes
17
+ const visiblePanes: Ref<boolean[]> = ref(panes.map(() => false));
18
+
19
+ // Track the current active pane index
20
+ const currentPaneIndex = computed(() => {
21
+ // Find the highest index that's visible
22
+ for (let i = visiblePanes.value.length - 1; i >= 0; i--) {
23
+ if (visiblePanes.value[i]) {
24
+ return i;
25
+ }
26
+ }
27
+ return -1;
28
+ });
29
+
30
+ // Helper functions to get unique element IDs
31
+ const getOffscreenId = () => `${componentId}-offscreen`;
32
+ const getOnscreenId = () => `${componentId}-onscreen`;
33
+ const getOverlayId = () => `${componentId}-overlay`;
34
+ const getDarkenId = () => `${componentId}-darken`;
35
+ const getPaneId = (index: number) => `${componentId}-pane-${index}`;
36
+
37
+ // Animate darken overlay
38
+ const animateDarken = (show: boolean) => {
39
+ const darken = document.getElementById(getDarkenId());
40
+ if (!darken) return;
41
+
42
+ if (show) {
43
+ darken.style.pointerEvents = "all";
44
+ gsap.to(darken, { duration: 0.5, opacity: 1, ease: "circ.outOut" });
45
+ } else {
46
+ darken.style.pointerEvents = "none";
47
+ gsap.to(darken, { duration: 0.5, opacity: 0, ease: "circ.outOut" });
48
+ }
49
+ };
50
+
51
+ // Show a specific pane and all panes after it
52
+ const showPane = (index: number) => {
53
+ const offscreen = document.getElementById(getOffscreenId());
54
+ const overlay = document.getElementById(getOverlayId());
55
+
56
+ if (!offscreen || !overlay) {
57
+ console.error(
58
+ `Elements are null for key '${componentId}'. Unable to animate`
59
+ );
60
+ return;
61
+ }
62
+
63
+ // Don't show if it's the main pane (last one)
64
+ if (index >= panes.length - 1) return;
65
+
66
+ // Get state for animation
67
+ const paneElements = panes
68
+ .slice(0, -1) // Exclude main pane
69
+ .map((_, i) => document.getElementById(getPaneId(i)))
70
+ .filter((el) => el !== null);
71
+ const state = Flip.getState(paneElements);
72
+
73
+ // Show all panes from the selected index onward
74
+ for (let i = 0; i < panes.length - 1; i++) {
75
+ const pane = document.getElementById(getPaneId(i));
76
+ if (pane) {
77
+ if (i >= index) {
78
+ visiblePanes.value[i] = true;
79
+ pane.parentNode?.removeChild(pane);
80
+ overlay.appendChild(pane);
81
+ } else {
82
+ visiblePanes.value[i] = false;
83
+ pane.parentNode?.removeChild(pane);
84
+ offscreen.appendChild(pane);
85
+ }
86
+ }
87
+ }
88
+
89
+ // Animate
90
+ Flip.from(state, {
91
+ duration: 0.3,
92
+ ease: "power1.inOut",
93
+ absolute: true,
94
+ });
95
+
96
+ // Show darken overlay if any panes are visible
97
+ animateDarken(true);
98
+ };
99
+
100
+ // Hide a specific pane and all panes before it
101
+ const hidePane = (index: number) => {
102
+ const offscreen = document.getElementById(getOffscreenId());
103
+ const overlay = document.getElementById(getOverlayId());
104
+
105
+ if (!offscreen || !overlay) {
106
+ console.error(
107
+ `Elements are null for key '${componentId}'. Unable to animate`
108
+ );
109
+ return;
110
+ }
111
+
112
+ // Get state for animation
113
+ const paneElements = panes
114
+ .slice(0, -1)
115
+ .map((_, i) => document.getElementById(getPaneId(i)))
116
+ .filter((el) => el !== null);
117
+ const state = Flip.getState(paneElements);
118
+
119
+ // Hide all panes up to and including the specified index
120
+ for (let i = 0; i <= index && i < panes.length - 1; i++) {
121
+ const pane = document.getElementById(getPaneId(i));
122
+ if (pane) {
123
+ visiblePanes.value[i] = false;
124
+ pane.parentNode?.removeChild(pane);
125
+ offscreen.appendChild(pane);
126
+ }
127
+ }
128
+
129
+ // Animate
130
+ Flip.from(state, {
131
+ duration: 0.3,
132
+ ease: "power1.inOut",
133
+ absolute: true,
134
+ });
135
+
136
+ // Hide darken if no panes are visible
137
+ const anyVisible = visiblePanes.value.slice(0, -1).some((v) => v);
138
+ animateDarken(anyVisible);
139
+ };
140
+
141
+ // Toggle a pane at a specific index
142
+ const togglePane = (index: number) => {
143
+ if (index <= 0 || index >= panes.length - 1) return;
144
+
145
+ const offscreen = document.getElementById(getOffscreenId());
146
+ const overlay = document.getElementById(getOverlayId());
147
+
148
+ if (!offscreen || !overlay) {
149
+ console.error(
150
+ `Elements are null for key '${componentId}'. Unable to animate`
151
+ );
152
+ return;
153
+ }
154
+
155
+ // Get state for animation
156
+ const paneElements = panes
157
+ .slice(0, -1)
158
+ .map((_, i) => document.getElementById(getPaneId(i)))
159
+ .filter((el) => el !== null);
160
+ const state = Flip.getState(paneElements);
161
+
162
+ // If the previous pane is visible, hide all before this one
163
+ if (visiblePanes.value[index - 1]) {
164
+ for (let i = 0; i < index; i++) {
165
+ const pane = document.getElementById(getPaneId(i));
166
+ if (pane) {
167
+ visiblePanes.value[i] = false;
168
+ pane.parentNode?.removeChild(pane);
169
+ offscreen.appendChild(pane);
170
+ }
171
+ }
172
+ } else {
173
+ // Show only the pane immediately before the clicked pane
174
+ const pane = document.getElementById(getPaneId(index - 1));
175
+ if (pane) {
176
+ visiblePanes.value[index - 1] = true;
177
+ pane.parentNode?.removeChild(pane);
178
+ overlay.insertBefore(pane, overlay.children[index - 1] || null);
179
+ }
180
+ }
181
+
182
+ // Animate
183
+ Flip.from(state, {
184
+ duration: 0.3,
185
+ ease: "power1.inOut",
186
+ absolute: true,
187
+ });
188
+
189
+ animateDarken(true);
190
+ };
191
+
192
+ // Show all secondary panes
193
+ const showAll = () => {
194
+ const offscreen = document.getElementById(getOffscreenId());
195
+ const overlay = document.getElementById(getOverlayId());
196
+
197
+ if (!offscreen || !overlay) {
198
+ console.error(
199
+ `Elements are null for key '${componentId}'. Unable to animate`
200
+ );
201
+ return;
202
+ }
203
+
204
+ const paneElements = panes
205
+ .slice(0, -1)
206
+ .map((_, i) => document.getElementById(getPaneId(i)))
207
+ .filter((el) => el !== null);
208
+ const state = Flip.getState(paneElements);
209
+
210
+ // Move all secondary panes to overlay
211
+ for (let i = 0; i < panes.length - 1; i++) {
212
+ const pane = document.getElementById(getPaneId(i));
213
+ if (pane) {
214
+ visiblePanes.value[i] = true;
215
+ pane.parentNode?.removeChild(pane);
216
+ overlay.appendChild(pane);
217
+ }
218
+ }
219
+
220
+ Flip.from(state, {
221
+ duration: 0.3,
222
+ ease: "power1.inOut",
223
+ absolute: true,
224
+ });
225
+
226
+ animateDarken(true);
227
+ };
228
+
229
+ // Hide all secondary panes
230
+ const hideAll = () => {
231
+ const offscreen = document.getElementById(getOffscreenId());
232
+ const overlay = document.getElementById(getOverlayId());
233
+
234
+ if (!offscreen || !overlay) {
235
+ console.error(
236
+ `Elements are null for key '${componentId}'. Unable to animate`
237
+ );
238
+ return;
239
+ }
240
+
241
+ const paneElements = panes
242
+ .slice(0, -1)
243
+ .map((_, i) => document.getElementById(getPaneId(i)))
244
+ .filter((el) => el !== null);
245
+ const state = Flip.getState(paneElements);
246
+
247
+ // Move all panes from overlay to offscreen
248
+ for (let i = 0; i < panes.length - 1; i++) {
249
+ const pane = document.getElementById(getPaneId(i));
250
+ if (pane) {
251
+ visiblePanes.value[i] = false;
252
+ pane.parentNode?.removeChild(pane);
253
+ offscreen.appendChild(pane);
254
+ }
255
+ }
256
+
257
+ Flip.from(state, {
258
+ duration: 0.3,
259
+ ease: "power1.inOut",
260
+ absolute: true,
261
+ });
262
+
263
+ animateDarken(false);
264
+ };
265
+
266
+ // Navigate to a specific pane (show it and hide others)
267
+ const navigate = (index: number, animated: boolean = true) => {
268
+ if (index < 0 || index >= panes.length) return;
269
+
270
+ // If navigating to the main pane, hide all
271
+ if (index === panes.length - 1) {
272
+ hideAll();
273
+ return;
274
+ }
275
+
276
+ // If not animated, directly manipulate DOM without GSAP
277
+ if (!animated) {
278
+ const offscreen = document.getElementById(getOffscreenId());
279
+ const overlay = document.getElementById(getOverlayId());
280
+
281
+ if (!offscreen || !overlay) return;
282
+
283
+ // Reset all panes
284
+ for (let i = 0; i < panes.length - 1; i++) {
285
+ const pane = document.getElementById(getPaneId(i));
286
+ if (pane) {
287
+ pane.parentNode?.removeChild(pane);
288
+ if (i === index) {
289
+ visiblePanes.value[i] = true;
290
+ overlay.appendChild(pane);
291
+ } else {
292
+ visiblePanes.value[i] = false;
293
+ offscreen.appendChild(pane);
294
+ }
295
+ }
296
+ }
297
+
298
+ // Set darken state instantly
299
+ const darken = document.getElementById(getDarkenId());
300
+ if (darken) {
301
+ darken.style.opacity = "1";
302
+ darken.style.pointerEvents = "all";
303
+ }
304
+ } else {
305
+ // Otherwise show the requested pane with animation
306
+ showPane(index);
307
+ }
308
+ };
309
+
310
+ return {
311
+ visiblePanes,
312
+ currentPaneIndex,
313
+ showPane,
314
+ hidePane,
315
+ togglePane,
316
+ showAll,
317
+ hideAll,
318
+ navigate,
319
+ };
320
+ };