srcdev-nuxt-components 6.1.2 → 6.1.3

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.
@@ -0,0 +1,37 @@
1
+ .srcdev-components-extended {
2
+ .expanding-panel {
3
+ .expanding-panel-details {
4
+ .expanding-panel-summary {
5
+ &::-webkit-details-marker,
6
+ &::marker {
7
+ display: none;
8
+ }
9
+
10
+ .label-wrapper {
11
+ }
12
+ .icon-wrapper {
13
+ .icon {
14
+ }
15
+ }
16
+ }
17
+
18
+ &[open] {
19
+ .expanding-panel-summary {
20
+ .icon-wrapper {
21
+ .icon {
22
+ }
23
+ }
24
+ }
25
+ + .expanding-panel-content {
26
+ .inner {
27
+ }
28
+ }
29
+ }
30
+ }
31
+
32
+ .expanding-panel-content {
33
+ .inner {
34
+ }
35
+ }
36
+ }
37
+ }
@@ -1 +1,2 @@
1
- @import './_display-prompt-core';
1
+ @import "./_display-prompt-core";
2
+ @import "./_expanding-panel";
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <details @click.prevent="handleClick()" :name="name" class="display-details" :class="[elementClasses]" ref="detailsRef">
2
+ <details :name="name" class="display-details" :class="[elementClasses]" ref="detailsRef">
3
3
  <summary class="display-details-summary" :id="triggerId" :aria-controls="contentId" ref="summaryRef">
4
4
  <span class="label">
5
5
  <slot name="summary"></slot>
@@ -9,175 +9,13 @@
9
9
  </slot>
10
10
  </summary>
11
11
  <div class="display-details-content" :aria-labelledby="triggerId" :id="contentId" role="region" ref="contentRef">
12
- <slot name="content"></slot>
12
+ <div class="inner">
13
+ <slot name="details-content"></slot>
14
+ </div>
13
15
  </div>
14
16
  </details>
15
17
  </template>
16
18
 
17
- <script lang="ts">
18
- // Create a global store to track open details elements by name
19
- const openDetailsByName = reactive(new Map<string, HTMLDetailsElement>());
20
-
21
- export const useDetailsTransition = (
22
- detailsRef: Ref<HTMLDetailsElement | null>,
23
- summaryRef: Ref<HTMLElement | null>,
24
- contentRef: Ref<HTMLDivElement | null>,
25
- name: string,
26
- animationDuration: number
27
- ) => {
28
- // State
29
- const animation = ref<Animation | null>(null);
30
- const isClosing = ref(false);
31
- const isExpanding = ref(false);
32
-
33
- // Check if refs are available
34
- if (!detailsRef.value || !summaryRef.value || !contentRef.value) {
35
- console.warn('Details, summary, or content ref is null');
36
- return {
37
- clickAction: () => console.warn('Component not fully initialized'),
38
- };
39
- }
40
-
41
- const closeOtherDetailsWithSameName = () => {
42
- const currentDetails = detailsRef.value;
43
- if (!currentDetails || !name) return;
44
-
45
- // Get the currently open details with the same name
46
- const openDetails = openDetailsByName.get(name);
47
-
48
- // If there's an open details with the same name and it's not the current one, close it
49
- if (openDetails && openDetails !== currentDetails && openDetails.open) {
50
- // Simulate a click on the other details to close it with animation
51
- const otherSummary = openDetails.querySelector('summary');
52
- if (otherSummary) {
53
- otherSummary.click();
54
- } else {
55
- // Fallback: close directly without animation
56
- openDetails.open = false;
57
- }
58
- }
59
-
60
- // Update the map with the current details if it's open
61
- if (currentDetails.open) {
62
- openDetailsByName.set(name, currentDetails);
63
- } else {
64
- // If it's closed and was the one in the map, remove it
65
- if (openDetailsByName.get(name) === currentDetails) {
66
- openDetailsByName.delete(name);
67
- }
68
- }
69
- };
70
-
71
- const clickAction = () => {
72
- const details = detailsRef.value;
73
- const summary = summaryRef.value;
74
- const content = contentRef.value;
75
-
76
- if (!details || !summary || !content) return;
77
-
78
- // Add overflow hidden to avoid content jumping
79
- details.style.overflow = 'hidden';
80
-
81
- if (isClosing.value || !details.open) {
82
- // Close other details with the same name first
83
- closeOtherDetailsWithSameName();
84
-
85
- // Open the details
86
- details.open = true;
87
- isExpanding.value = true;
88
- isClosing.value = false;
89
-
90
- // Get the height of the content
91
- const detailsHeight = details.offsetHeight;
92
- const contentHeight = content.offsetHeight;
93
- const summaryHeight = summary.offsetHeight;
94
-
95
- const startHeight = `${detailsHeight - contentHeight}px`;
96
- const endHeight = `${summaryHeight + contentHeight}px`;
97
-
98
- // If there's an animation running, cancel it
99
- if (animation.value) {
100
- animation.value.cancel();
101
- }
102
-
103
- // Start animation
104
- animation.value = details.animate(
105
- {
106
- height: [startHeight, endHeight],
107
- },
108
- {
109
- duration: animationDuration,
110
- easing: 'linear',
111
- }
112
- );
113
-
114
- animation.value.onfinish = () => {
115
- // Animation finished - reset everything
116
- details.style.height = 'auto';
117
- details.style.overflow = '';
118
- isExpanding.value = false;
119
- animation.value = null;
120
-
121
- // Register this as the open details for this name
122
- openDetailsByName.set(name, details);
123
- };
124
-
125
- animation.value.oncancel = () => {
126
- isExpanding.value = false;
127
- };
128
- } else if (isExpanding.value || details.open) {
129
- // Close the details
130
- isClosing.value = true;
131
- isExpanding.value = false;
132
-
133
- // Get the height of the content
134
- const startHeight = `${details.offsetHeight}px`;
135
- const endHeight = `${details.offsetHeight - content.offsetHeight}px`;
136
-
137
- // If there's an animation running, cancel it
138
- if (animation.value) {
139
- animation.value.cancel();
140
- }
141
-
142
- // Start animation
143
- animation.value = details.animate(
144
- {
145
- height: [startHeight, endHeight],
146
- },
147
- {
148
- duration: animationDuration,
149
- easing: 'linear',
150
- }
151
- );
152
-
153
- animation.value.onfinish = () => {
154
- // Animation finished - reset everything
155
- details.open = false;
156
- details.style.height = 'auto';
157
- details.style.overflow = '';
158
- isClosing.value = false;
159
- animation.value = null;
160
-
161
- // Remove this from the open details map if it's there
162
- if (openDetailsByName.get(name) === details) {
163
- openDetailsByName.delete(name);
164
- }
165
- };
166
-
167
- animation.value.oncancel = () => {
168
- isClosing.value = false;
169
- };
170
- }
171
- };
172
-
173
- return {
174
- clickAction,
175
- isClosing,
176
- isExpanding,
177
- };
178
- };
179
- </script>
180
-
181
19
  <script setup lang="ts">
182
20
  const props = defineProps({
183
21
  name: {
@@ -188,58 +26,31 @@ const props = defineProps({
188
26
  type: String,
189
27
  required: true,
190
28
  },
191
- animationDuration: {
192
- type: Number,
193
- default: 400,
194
- },
195
29
  iconSize: {
196
30
  type: String,
197
- default: 'small',
31
+ default: "small",
198
32
  validator(value: string) {
199
- return ['small', 'medium', 'large'].includes(value);
33
+ return ["small", "medium", "large"].includes(value)
200
34
  },
201
35
  },
202
36
  styleClassPassthrough: {
203
37
  type: Array as PropType<string[]>,
204
38
  default: () => [],
205
39
  },
206
- });
207
-
208
- const triggerId = computed(() => `${props.id}-trigger`);
209
- const contentId = computed(() => `${props.id}-content`);
40
+ })
210
41
 
211
- const { elementClasses, resetElementClasses, updateElementClasses } = useStyleClassPassthrough(props.styleClassPassthrough);
42
+ const triggerId = computed(() => `${props.id}-trigger`)
43
+ const contentId = computed(() => `${props.id}-content`)
212
44
 
213
- updateElementClasses([props.iconSize]);
45
+ const { elementClasses, resetElementClasses, updateElementClasses } = useStyleClassPassthrough(
46
+ props.styleClassPassthrough
47
+ )
214
48
 
215
- const detailsRef = ref<HTMLDetailsElement | null>(null);
216
- const summaryRef = ref<HTMLElement | null>(null);
217
- const contentRef = ref<HTMLDivElement | null>(null);
49
+ updateElementClasses([props.iconSize])
218
50
 
219
- // Initialize with dummy function that will be replaced when refs are available
220
- let clickAction = () => console.warn('Component not fully initialized');
221
-
222
- // Handle click with the current clickAction function
223
- const handleClick = () => {
224
- clickAction();
225
- };
226
-
227
- watch(
228
- () => props.styleClassPassthrough,
229
- () => {
230
- resetElementClasses(props.styleClassPassthrough);
231
- }
232
- );
233
-
234
- onMounted(() => {
235
- // Initialize the composable once the component is mounted and refs are available
236
- if (detailsRef.value && contentRef.value && summaryRef.value) {
237
- const details = useDetailsTransition(detailsRef, summaryRef, contentRef, props.name, props.animationDuration);
238
- clickAction = details.clickAction; // Assign the real click handler
239
- } else {
240
- console.error('Refs not available after mounting');
241
- }
242
- });
51
+ const detailsRef = useTemplateRef<HTMLDetailsElement>("detailsRef")
52
+ const summaryRef = useTemplateRef<HTMLElement | null>("summaryRef")
53
+ const contentRef = useTemplateRef<HTMLDivElement | null>("contentRef")
243
54
  </script>
244
55
 
245
56
  <style lang="css">
@@ -255,6 +66,10 @@ onMounted(() => {
255
66
  transform: scaleY(-1);
256
67
  }
257
68
  }
69
+ .display-details-content {
70
+ grid-template-rows: 1fr;
71
+ overflow: hidden;
72
+ }
258
73
  }
259
74
 
260
75
  .display-details-summary {
@@ -282,7 +97,6 @@ onMounted(() => {
282
97
  display: block;
283
98
 
284
99
  transform: scaleY(1);
285
- transition: transform 200ms;
286
100
 
287
101
  font-size: 1.2rem;
288
102
  &.medium {
@@ -295,7 +109,12 @@ onMounted(() => {
295
109
  }
296
110
 
297
111
  .display-details-content {
298
- /* Use an inner element for styling */
112
+ display: grid;
113
+ grid-template-rows: 0;
114
+
115
+ .inner {
116
+ overflow: hidden;
117
+ }
299
118
  }
300
119
  }
301
120
  </style>
@@ -57,17 +57,17 @@ const { elementClasses } = useStyleClassPassthrough(props.styleClassPassthrough)
57
57
  align-items: center;
58
58
  justify-content: space-between;
59
59
  flex-direction: row;
60
- gap: 0;
60
+ gap: 1rem;
61
61
  list-style: none;
62
62
 
63
- padding-inline: 0.5rem;
63
+ padding-block: 0.5rem;
64
64
 
65
65
  &::-webkit-details-marker,
66
66
  &::marker {
67
67
  display: none;
68
68
  }
69
69
 
70
- overflow: clip;
70
+ overflow: hidden;
71
71
 
72
72
  .label-wrapper {
73
73
  }
@@ -77,8 +77,6 @@ const { elementClasses } = useStyleClassPassthrough(props.styleClassPassthrough)
77
77
  justify-content: space-between;
78
78
 
79
79
  aspect-ratio: 1;
80
- outline: 1px solid var(--gray-3);
81
- padding: 1rem;
82
80
  overflow: hidden;
83
81
 
84
82
  .icon {
@@ -101,7 +99,7 @@ const { elementClasses } = useStyleClassPassthrough(props.styleClassPassthrough)
101
99
  + .expanding-panel-content {
102
100
  grid-template-rows: 1fr;
103
101
  .inner {
104
- /* transform: scaleY(1); */
102
+ transition: all v-bind(animationDurationStr) ease-in-out;
105
103
  }
106
104
  }
107
105
  }
@@ -114,10 +112,7 @@ const { elementClasses } = useStyleClassPassthrough(props.styleClassPassthrough)
114
112
 
115
113
  .inner {
116
114
  overflow: hidden;
117
- padding: 0.5rem;
118
-
119
- /* transform: scaleY(0); */
120
- /* transition: transform v-bind(animationDurationStr) ease-in-out; */
115
+ margin-top: 0;
121
116
  }
122
117
  }
123
118
  }
@@ -13,7 +13,10 @@
13
13
  v-if="link.path"
14
14
  class="overflow-navigation-item"
15
15
  :class="{ visible: !mainNavigationState.clonedNavLinks?.[groupKey]?.[localIndex]?.config?.visible }"
16
- :style="{ '--_main-navigation-item-width': mainNavigationState.clonedNavLinks?.[groupKey]?.[localIndex]?.config?.width + 'px' }"
16
+ :style="{
17
+ '--_main-navigation-item-width':
18
+ mainNavigationState.clonedNavLinks?.[groupKey]?.[localIndex]?.config?.width + 'px',
19
+ }"
17
20
  :data-group-key="groupKey"
18
21
  :data-local-index="localIndex"
19
22
  role="none"
@@ -26,28 +29,41 @@
26
29
  v-else
27
30
  class="overflow-navigation-item"
28
31
  :class="{ visible: !mainNavigationState.clonedNavLinks?.[groupKey]?.[localIndex]?.config?.visible }"
29
- :style="{ '--_main-navigation-item-width': mainNavigationState.clonedNavLinks?.[groupKey]?.[localIndex]?.config?.width + 'px' }"
32
+ :style="{
33
+ '--_main-navigation-item-width':
34
+ mainNavigationState.clonedNavLinks?.[groupKey]?.[localIndex]?.config?.width + 'px',
35
+ }"
30
36
  :data-group-key="groupKey"
31
37
  :data-local-index="localIndex"
32
38
  role="none"
33
39
  >
34
- <DisplayDetailsCore
35
- :id="useId()"
40
+ <ExpandingPanel
36
41
  name="overflow-navigation-group"
37
42
  :animation-duration="detailsAanimationDuration"
38
43
  icon-size="medium"
39
44
  :style-class-passthrough="['overflow-navigation-details']"
40
45
  >
41
46
  <template #summary>
42
- <span class="overflow-navigation-text" :aria-label="`${link.childLinksTitle} submenu`" role="menuitem" :aria-haspopup="true">{{ link.childLinksTitle }}</span>
47
+ <span
48
+ class="overflow-navigation-text"
49
+ :aria-label="`${link.childLinksTitle} submenu`"
50
+ role="menuitem"
51
+ :aria-haspopup="true"
52
+ >
53
+ {{ link.childLinksTitle }}
54
+ </span>
43
55
  </template>
44
- <template #summaryIcon>
56
+ <template #icon>
45
57
  <Icon name="mdi:chevron-down" class="icon" :aria-hidden="true" />
46
58
  </template>
47
- <template #layout-content>
59
+ <template #content>
48
60
  <div class="overflow-navigation-sub-nav-inner">
49
61
  <ul class="overflow-navigation-sub-nav-list">
50
- <li v-for="childLink in link.childLinks" :key="childLink.name" class="overflow-navigation-sub-nav-item">
62
+ <li
63
+ v-for="childLink in link.childLinks"
64
+ :key="childLink.name"
65
+ class="overflow-navigation-sub-nav-item"
66
+ >
51
67
  <NuxtLink :to="childLink.path" class="overflow-navigation-sub-nav-link" role="menuitem">
52
68
  <span class="overflow-navigation-sub-nav-text">{{ childLink.name }}</span>
53
69
  </NuxtLink>
@@ -55,7 +71,7 @@
55
71
  </ul>
56
72
  </div>
57
73
  </template>
58
- </DisplayDetailsCore>
74
+ </ExpandingPanel>
59
75
  </li>
60
76
  </template>
61
77
  </ul>
@@ -63,7 +79,7 @@
63
79
  </template>
64
80
 
65
81
  <script setup lang="ts">
66
- import type { ResponsiveHeaderState } from '@/types/responsiveHeader';
82
+ import type { ResponsiveHeaderState } from "@/types/responsiveHeader"
67
83
 
68
84
  const props = defineProps({
69
85
  mainNavigationState: {
@@ -74,32 +90,35 @@ const props = defineProps({
74
90
  type: Array as PropType<string[]>,
75
91
  default: () => [],
76
92
  },
77
- });
93
+ })
78
94
 
79
- const detailsAanimationDuration = 200;
80
- const detailsAanimationDurationString = `${detailsAanimationDuration}ms`;
95
+ const detailsAanimationDuration = 200
96
+ const detailsAanimationDurationString = `${detailsAanimationDuration}ms`
81
97
 
82
98
  const widestNavLinkWidthInMainNavigationState = computed(() => {
83
99
  return Object.values(props.mainNavigationState.clonedNavLinks || {}).reduce((maxWidth, group) => {
84
- return Math.max(maxWidth, ...group.map((link) => link.config?.width || 0));
85
- }, 0);
86
- });
100
+ return Math.max(maxWidth, ...group.map((link) => link.config?.width || 0))
101
+ }, 0)
102
+ })
87
103
 
88
- const { elementClasses, resetElementClasses } = useStyleClassPassthrough(props.styleClassPassthrough);
104
+ const { elementClasses, resetElementClasses } = useStyleClassPassthrough(props.styleClassPassthrough)
89
105
 
90
106
  watch(
91
107
  () => props.styleClassPassthrough,
92
108
  () => {
93
- resetElementClasses(props.styleClassPassthrough);
109
+ resetElementClasses(props.styleClassPassthrough)
94
110
  }
95
- );
111
+ )
96
112
  </script>
97
113
 
98
114
  <style lang="css">
99
115
  .overflow-navigation-wrapper {
116
+ --overflow-nav-padding-inline: 0.8rem;
117
+ --overflow-nav-items-gap: 0px;
118
+ --overflow-nav-items-padding-block: 0.8rem;
100
119
  display: flex;
101
120
  flex-direction: column;
102
- gap: 12px;
121
+ gap: var(--overflow-nav-items-gap);
103
122
 
104
123
  .overflow-navigation-list {
105
124
  display: none;
@@ -107,7 +126,7 @@ watch(
107
126
  &.visible {
108
127
  display: flex;
109
128
  flex-direction: column;
110
- gap: 12px;
129
+ gap: var(--overflow-nav-items-gap);
111
130
  min-width: var(--_overflow-navigation-list-min-width, auto);
112
131
  }
113
132
 
@@ -121,41 +140,72 @@ watch(
121
140
  .overflow-navigation-link {
122
141
  text-decoration: none;
123
142
  color: inherit;
143
+ padding-block: var(--overflow-nav-items-padding-block);
144
+ padding-inline: var(--overflow-nav-padding-inline);
145
+ display: flex;
146
+ /* background-color: red; */
147
+ border-bottom: 0.1rem solid #efefef75;
124
148
  }
125
149
 
126
150
  .overflow-navigation-details {
127
- --_overflow-navigation-sub-nav-list-margin-block-start: 0;
128
-
129
- &[open] {
130
- --_overflow-navigation-sub-nav-list-margin-block-start: 12px;
131
- }
132
-
133
- &.display-details {
151
+ &.expanding-panel {
134
152
  margin-block-end: 0;
135
153
 
136
- .display-details-summary {
137
- .label {
138
- .overflow-navigation-text {
139
- text-wrap: nowrap;
154
+ .expanding-panel-details {
155
+ .expanding-panel-summary {
156
+ padding-block: var(--overflow-nav-items-padding-block);
157
+ padding-inline: var(--overflow-nav-padding-inline);
158
+ gap: 1rem;
159
+ /* background-color: red; */
160
+ border-bottom: 0.1rem solid #efefef75;
161
+
162
+ .label-wrapper {
163
+ .overflow-navigation-text {
164
+ text-wrap: nowrap;
165
+ }
166
+ }
167
+ .icon-wrapper {
168
+ padding: 0;
169
+ }
170
+ }
171
+
172
+ &[open] {
173
+ .expanding-panel-summary {
174
+ border-bottom: 0.1rem solid transparent;
175
+ }
176
+ + .expanding-panel-content {
177
+ border-bottom: 0.1rem solid #efefef75;
178
+ .inner {
179
+ .overflow-navigation-sub-nav-inner {
180
+ margin-top: var(--overflow-nav-items-gap);
181
+ }
182
+ }
140
183
  }
141
184
  }
142
- /* .icon {} */
143
185
  }
144
- .display-details-content {
145
- .overflow-navigation-sub-nav-inner {
146
- .overflow-navigation-sub-nav-list {
147
- display: flex;
148
- flex-direction: column;
149
- gap: 12px;
150
- margin-block-start: var(--_overflow-navigation-sub-nav-list-margin-block-start);
151
- transition: margin-block-start v-bind(detailsAanimationDurationString) ease;
152
-
153
- .overflow-navigation-sub-nav-item {
154
- .overflow-navigation-sub-nav-link {
155
- text-decoration: none;
156
- color: inherit;
157
-
158
- /* .overflow-navigation-sub-nav-text {} */
186
+
187
+ .expanding-panel-content {
188
+ border-bottom: 0.1rem solid transparent;
189
+
190
+ .inner {
191
+ margin-top: 0;
192
+
193
+ .overflow-navigation-sub-nav-inner {
194
+ margin-top: 0;
195
+
196
+ .overflow-navigation-sub-nav-list {
197
+ display: flex;
198
+ flex-direction: column;
199
+ gap: 2px;
200
+
201
+ .overflow-navigation-sub-nav-item {
202
+ padding-block: var(--overflow-nav-items-padding-block);
203
+ padding-inline: var(--overflow-nav-padding-inline);
204
+
205
+ .overflow-navigation-sub-nav-link {
206
+ text-decoration: none;
207
+ color: inherit;
208
+ }
159
209
  }
160
210
  }
161
211
  }
@@ -696,7 +696,7 @@ watch(
696
696
  background-color: #000;
697
697
  border: 1px solid #ffffff90;
698
698
  border-radius: 8px;
699
- padding: 12px;
699
+ padding-block: 12px;
700
700
  margin: 0;
701
701
  z-index: 999;
702
702
  min-width: var(--_overflow-drop-down-width, fit-content);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "srcdev-nuxt-components",
3
3
  "type": "module",
4
- "version": "6.1.2",
4
+ "version": "6.1.3",
5
5
  "main": "nuxt.config.ts",
6
6
  "scripts": {
7
7
  "clean": "rm -rf .nuxt && rm -rf .output && rm -rf .playground/.nuxt && rm -rf .playground/.output",