srcdev-nuxt-components 1.3.0 → 2.1.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.
@@ -9,15 +9,7 @@ html {
9
9
 
10
10
  font-size: 62.5%;
11
11
  }
12
- body,
13
- p,
14
- pre,
15
- code,
16
- div,
17
- fieldset,
18
- legend,
19
- li,
20
- a {
12
+ body {
21
13
  color: var(--grayscale-text-body);
22
14
  font-family: var(--font-family);
23
15
  font-size: var(--step-1);
@@ -0,0 +1,61 @@
1
+ <template>
2
+ <component :is="tag" class="clipped-panel" :class="[elementClasses]">
3
+ <slot name="default"></slot>
4
+ </component>
5
+ </template>
6
+
7
+ <script lang="ts">
8
+ const TAGS_ALLOWED = <string[]>['div', 'p', 'span', 'section', 'article', 'aside', 'header', 'footer', 'main', 'nav', 'ul', 'ol'];
9
+ </script>
10
+
11
+ <script setup lang="ts">
12
+ const props = defineProps({
13
+ tag: {
14
+ type: String,
15
+ default: 'div',
16
+ validator(value: string) {
17
+ return TAGS_ALLOWED.includes(value);
18
+ },
19
+ },
20
+ styleClassPassthrough: {
21
+ type: Array as PropType<string[]>,
22
+ default: () => [],
23
+ },
24
+ });
25
+
26
+ const { elementClasses, resetElementClasses } = useStyleClassPassthrough(props.styleClassPassthrough);
27
+
28
+ watch(
29
+ () => props.styleClassPassthrough,
30
+ () => {
31
+ resetElementClasses(props.styleClassPassthrough);
32
+ }
33
+ );
34
+ </script>
35
+
36
+ <style lang="css">
37
+ .clipped-panel {
38
+ --_foreground-color: light-dark(hsl(0, 29%, 3%), hsl(0, 0%, 92%));
39
+
40
+ /* Component styles */
41
+
42
+ background-color: red;
43
+ /* color: var(--_foreground-color); */
44
+ outline: 1px solid var(--_foreground-color);
45
+ /* box-shadow: 5px 5px 5px 5px white; */
46
+
47
+ aspect-ratio: 1;
48
+ width: 200px;
49
+
50
+ &.square {
51
+ /* clip-path: path('M 10, 50 L 90, 50 A 10, 10, 0, 0, 0 100, 40 L 100, 10 L 110, 0 L 190, 0 L 200, 10 L 200, 190 L 190, 200 L 10, 200 L 0, 190 L 0, 60 L 10, 50 Z'); */
52
+ clip-path: path(
53
+ 'M 10, 50 L 90, 50 A 10, 10, 0, 0, 0 100, 40 L 100, 10 A 10, 10, 0, 0, 1 110, 0 L 190, 0 A 10, 10, 0, 0, 1 200, 10 L 200, 190 A 10, 10, 0, 0, 1 190, 200 L 10, 200 A 10, 10, 0, 0, 1 0, 190 L 0, 60 A 10, 10, 0, 0, 1 10, 50 Z'
54
+ );
55
+ }
56
+
57
+ &.circle-cutout {
58
+ clip-path: path('M Z');
59
+ }
60
+ }
61
+ </style>
@@ -0,0 +1,222 @@
1
+ <template>
2
+ <component :is="tag" class="deep-expanding-menu" :class="[elementClasses]">
3
+ <div class="inner">
4
+ <template v-for="(link, key) in navLinks" :key="key">
5
+ <NuxtLink v-if="link.path" :to="link.path" class="navigation-link">{{ link.name }}</NuxtLink>
6
+
7
+ <div v-else class="navigation-group" :style="`--_anchor-name: --anchor-nav-1-${key};`" ref="detailsRef">
8
+ <button :popovertarget="`popovertarget-nav-1-${key}`" class="navigation-group-toggle">
9
+ <span>{{ link.name }}</span>
10
+ <Icon name="bi:caret-down-fill" class="icon" />
11
+ </button>
12
+
13
+ <div class="navigation-group-panel" popover role="tooltip" :id="`popovertarget-nav-1-${key}`" :class="[elementClasses]">
14
+ <h4 class="heading-4 mb-6">{{ link.childLinksTitle }}</h4>
15
+ <ul class="navigation-group-list">
16
+ <li class="navigation-group-item" v-for="childLink in link.childLinks" :key="childLink.name">
17
+ <NuxtLink :to="childLink.path" class="navigation-group-link">{{ childLink.name }}</NuxtLink>
18
+ </li>
19
+ </ul>
20
+ </div>
21
+ </div>
22
+ </template>
23
+ </div>
24
+ </component>
25
+ </template>
26
+
27
+ <script lang="ts">
28
+ const TAGS_ALLOWED = <string[]>['div', 'section', 'nav', 'ul', 'ol'];
29
+
30
+ interface INavLink {
31
+ name: string;
32
+ path?: string;
33
+ isExternal?: boolean;
34
+ childLinks?: INavLink[];
35
+ childLinksTitle?: string;
36
+ }
37
+ </script>
38
+
39
+ <script setup lang="ts">
40
+ const props = defineProps({
41
+ tag: {
42
+ type: String,
43
+ default: 'nav',
44
+ validator(value: string) {
45
+ return TAGS_ALLOWED.includes(value);
46
+ },
47
+ },
48
+ navLinks: {
49
+ type: Array as PropType<INavLink[]>,
50
+ default: () => [],
51
+ },
52
+ styleClassPassthrough: {
53
+ type: Array as PropType<string[]>,
54
+ default: () => [],
55
+ },
56
+ });
57
+
58
+ const { elementClasses, resetElementClasses } = useStyleClassPassthrough(props.styleClassPassthrough);
59
+ // const detailsRef = useTemplateRef('detailsRef');
60
+
61
+ watch(
62
+ () => props.styleClassPassthrough,
63
+ () => {
64
+ resetElementClasses(props.styleClassPassthrough);
65
+ }
66
+ );
67
+
68
+ // onMounted(() => {
69
+ // console.log(detailsRef.value);
70
+ // });
71
+ </script>
72
+
73
+ <style lang="css">
74
+ @layer deep-expanding-menu-setup {
75
+ @position-try --anchor-left {
76
+ inset: auto;
77
+ top: calc(anchor(bottom) + 10px);
78
+ left: calc(anchor(left) + 10px);
79
+ }
80
+
81
+ @position-try-fallbacks --anchor-right {
82
+ inset: auto;
83
+ top: calc(anchor(bottom) + 10px);
84
+ right: calc(anchor(right) + 10px);
85
+ }
86
+
87
+ .deep-expanding-menu {
88
+ --_gap-between-top-level-items: 24px;
89
+
90
+ container-type: inline-size;
91
+ display: grid;
92
+ grid-template-areas: 'element-stack';
93
+
94
+ .inner {
95
+ grid-area: element-stack;
96
+ display: flex;
97
+ gap: var(--_gap-between-top-level-items);
98
+ align-items: center;
99
+
100
+ .navigation-link,
101
+ .navigation-group-toggle {
102
+ all: unset;
103
+ border-bottom: 2px solid transparent;
104
+ padding-block: 8px;
105
+
106
+ transition: border-color 200ms;
107
+
108
+ &:hover {
109
+ cursor: pointer;
110
+ border-color: light-dark(var(--blue-12), var(--gray-0));
111
+ }
112
+
113
+ &:focus {
114
+ border-color: light-dark(var(--blue-12), var(--gray-0));
115
+ }
116
+
117
+ &:focus-visible {
118
+ border-color: light-dark(var(--blue-12), var(--gray-0));
119
+ }
120
+ }
121
+
122
+ .navigation-group {
123
+ --_icon-transform: scaleY(1);
124
+
125
+ .navigation-group-toggle {
126
+ anchor-name: var(--_anchor-name);
127
+
128
+ display: flex;
129
+ align-items: center;
130
+ gap: 12px;
131
+
132
+ .icon {
133
+ display: block;
134
+ font-size: 1.2rem;
135
+
136
+ transform: var(--_icon-transform);
137
+ transition: transform 200ms;
138
+ }
139
+ }
140
+
141
+ .navigation-group-panel {
142
+ display: none;
143
+ position: absolute;
144
+ position-anchor: var(--_anchor-name);
145
+ margin: 0;
146
+ /* inset: auto; */
147
+ top: calc(anchor(bottom) + 10px);
148
+ left: calc(anchor(left) + 0px);
149
+ opacity: 0;
150
+ transition: opacity 200ms, display 200ms, overlay 200ms;
151
+ transition-behavior: allow-discrete;
152
+
153
+ width: min(100%, 50vw);
154
+
155
+ background-color: white;
156
+ border: 1px solid black;
157
+ border-radius: 12px;
158
+ box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
159
+ padding: 12px;
160
+ overflow: clip;
161
+
162
+ /* position-try: flip-inline, flip-block, flip-block flip-inline; */
163
+ /* position-try-fallbacks: flip-inline, flip-block, flip-block flip-inline; */
164
+
165
+ /* position-try: ----anchor-left; */
166
+ /* position-try-fallbacks: --anchor-right; */
167
+
168
+ &:popover-open {
169
+ display: block;
170
+ opacity: 1;
171
+
172
+ @starting-style {
173
+ display: block;
174
+ opacity: 0;
175
+ }
176
+ }
177
+
178
+ h4 {
179
+ color: var(--gray-12);
180
+ }
181
+
182
+ .navigation-group-list {
183
+ display: grid;
184
+ grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
185
+ gap: 12px;
186
+ padding-inline-start: 0;
187
+ margin-block-end: 8px;
188
+
189
+ .navigation-group-item {
190
+ display: block;
191
+
192
+ a.navigation-group-link {
193
+ display: inline-block;
194
+ color: var(--gray-12);
195
+ text-decoration: none;
196
+ padding-block: 8px;
197
+
198
+ border-bottom: 2px solid transparent;
199
+
200
+ transition: border-color 200ms;
201
+
202
+ &:hover {
203
+ cursor: pointer;
204
+ border-color: var(--gray-12);
205
+ }
206
+
207
+ &:focus-visible {
208
+ border-color: var(--gray-12);
209
+ }
210
+ }
211
+ }
212
+ }
213
+ }
214
+
215
+ &:has(.navigation-group-panel:popover-open) {
216
+ --_icon-transform: scaleY(-1);
217
+ }
218
+ }
219
+ }
220
+ }
221
+ }
222
+ </style>
@@ -0,0 +1,180 @@
1
+ <template>
2
+ <component :is="tag" class="deep-expanding-menu" :class="[elementClasses]">
3
+ <div class="inner">
4
+ <template v-for="(link, key) in navLinks" :key="key">
5
+ <NuxtLink v-if="link.path" :to="link.path" class="">{{ link.name }}</NuxtLink>
6
+ <details v-else name="top-level-nav" :style="`--_position-anchor: --anchor-nav-1-${key};, --_anchor-name: --anchor-nav-1-${key};`" ref="detailsRef">
7
+ <summary>{{ link.name }}</summary>
8
+ <div popver>
9
+ <NuxtLink v-for="childLink in link.childLinks" :key="childLink.name" :to="childLink.path" class="">{{ childLink.name }}</NuxtLink>
10
+ </div>
11
+ </details>
12
+ </template>
13
+ </div>
14
+ </component>
15
+ </template>
16
+
17
+ <script setup lang="ts">
18
+ const props = defineProps({
19
+ tag: {
20
+ type: String,
21
+ default: 'nav',
22
+ validator(value: string) {
23
+ return TAGS_ALLOWED.includes(value);
24
+ },
25
+ },
26
+ styleClassPassthrough: {
27
+ type: Array as PropType<string[]>,
28
+ default: () => [],
29
+ },
30
+ });
31
+
32
+ const { elementClasses, resetElementClasses } = useStyleClassPassthrough(props.styleClassPassthrough);
33
+ const detailsRef = useTemplateRef('detailsRef');
34
+
35
+ watch(
36
+ () => props.styleClassPassthrough,
37
+ () => {
38
+ resetElementClasses(props.styleClassPassthrough);
39
+ }
40
+ );
41
+
42
+ onMounted(() => {
43
+ console.log(detailsRef.value);
44
+ });
45
+ </script>
46
+
47
+ <script lang="ts">
48
+ const TAGS_ALLOWED = <string[]>['div', 'section', 'nav', 'ul', 'ol'];
49
+
50
+ interface INavLink {
51
+ name: string;
52
+ path?: string;
53
+ isExternal?: boolean;
54
+ childLinks?: INavLink[];
55
+ }
56
+
57
+ const navLinks = <INavLink[]>[
58
+ { name: 'Home', path: '/' },
59
+ {
60
+ name: 'Components',
61
+ childLinks: [
62
+ { name: 'Container Glow', path: '/ui/container-glow' },
63
+ { name: 'Accordian', path: '/ui/accordian' },
64
+ { name: 'Dialogs', path: '/ui/dialog' },
65
+ { name: 'Tabs X', path: '/ui/tabs' },
66
+ { name: 'Tabs Y', path: '/ui/tabs-y' },
67
+ { name: 'Prompts', path: '/ui/display-prompt' },
68
+ { name: 'Rotating Carousel', path: '/ui/rotating-carousel' },
69
+ { name: 'Clipped Panels', path: '/ui/clipped-panels' },
70
+ ],
71
+ },
72
+ {
73
+ name: 'Layouts',
74
+ childLinks: [
75
+ { name: 'Layout Row', path: '/ui/layout-row' },
76
+ { name: 'Layout Grid A', path: '/ui/layout-grid-a' },
77
+ { name: 'Layout Grid B', path: '/ui/layout-grid-b' },
78
+ { name: 'Simple Grid', path: '/ui/simple-grid' },
79
+ { name: 'Masonry Grid Simple', path: '/ui/masonry-grid' },
80
+ { name: 'Masonry Grid Sorted', path: '/ui/masonry-grid-sorted' },
81
+ { name: 'Masonry Grid Ordered', path: '/ui/masonry-grid-ordered' },
82
+ { name: 'Masonry Columns', path: '/ui/masonry-columns' },
83
+ ],
84
+ },
85
+ { name: 'About', path: '/' },
86
+ ];
87
+ </script>
88
+
89
+ <style lang="css">
90
+ @layer popover-setup {
91
+ @position-try --anchor-left {
92
+ inset: auto;
93
+ top: anchor(top);
94
+ right: calc(anchor(left) + 10px);
95
+ }
96
+
97
+ @position-try-fallbacks --anchor-right {
98
+ inset: auto;
99
+ top: anchor(top);
100
+ left: calc(anchor(right) + 10px);
101
+ }
102
+
103
+ .deep-expanding-menu {
104
+ container-type: inline-size;
105
+ display: grid;
106
+ grid-template-areas: 'element-stack';
107
+ align-items: center;
108
+ gap: 12px;
109
+
110
+ .inner {
111
+ grid-area: element-stack;
112
+ display: flex;
113
+ gap: 12px;
114
+ align-items: center;
115
+ z-index: 1;
116
+
117
+ a,
118
+ summary {
119
+ &:hover {
120
+ cursor: pointer;
121
+ }
122
+ }
123
+
124
+ a {
125
+ all: unset;
126
+ border: 1px solid red;
127
+ padding: 6px 12px;
128
+
129
+ &:focus-visible {
130
+ outline: 2px solid red;
131
+ }
132
+ }
133
+
134
+ details {
135
+ all: unset;
136
+ border: 1px solid red;
137
+ padding: 6px 12px;
138
+ }
139
+
140
+ details {
141
+ display: grid;
142
+ grid-template-areas: 'details-stack';
143
+ z-index: 1;
144
+
145
+ summary {
146
+ grid-area: details-stack;
147
+ /* position: relative; */
148
+ anchor-name: var(--_anchor-name);
149
+
150
+ &:focus {
151
+ outline: 2px solid green;
152
+ }
153
+
154
+ &:focus-visible {
155
+ outline: 2px solid red;
156
+ }
157
+ }
158
+
159
+ div {
160
+ position-anchor: var(--_position-anchor);
161
+ background-color: black;
162
+ display: grid;
163
+ grid-area: details-stack;
164
+ z-index: 2;
165
+ position: absolute;
166
+ inset: auto;
167
+ top: calc(anchor(bottom) + 20px);
168
+ left: calc(anchor(left) + 0px);
169
+ /* translate: 0 20px; */
170
+ padding: 12px;
171
+ gap: 12px;
172
+
173
+ /* position-try: --anchor-left;
174
+ position-try-fallbacks: --anchor-right; */
175
+ }
176
+ }
177
+ }
178
+ }
179
+ }
180
+ </style>
@@ -4,10 +4,10 @@
4
4
  <slot name="trigger"></slot>
5
5
  </button>
6
6
 
7
- <dialog popover role="tooltip" :id="popovertarget" :class="[elementClasses]">
7
+ <div class="dialog" popover role="tooltip" :id="popovertarget" :class="[elementClasses]">
8
8
  <button :popovertarget popovertargetaction="hide">x</button>
9
9
  <slot name="popoverCotent"></slot>
10
- </dialog>
10
+ </div>
11
11
  </ClientOnly>
12
12
  </template>
13
13
 
@@ -46,7 +46,7 @@ const { elementClasses } = useStyleClassPassthrough(props.styleClassPassthrough)
46
46
  anchor-name: v-bind(anchorName);
47
47
  }
48
48
 
49
- dialog {
49
+ .dialog {
50
50
  display: none;
51
51
  position: absolute;
52
52
  position-anchor: v-bind(anchorName);
@@ -3,11 +3,10 @@
3
3
  :is="tag"
4
4
  class="rotating-carousel"
5
5
  :class="[elementClasses]"
6
- :style="`--quantity: ${Object.keys(data).length}; --_rotate-x: ${rotateXProp}; --_perspective: ${perspectiveProp}; --_translateZ: ${translateZProp}; --_animation-play-state: ${
7
- pauseOnHover ? 'paused' : 'running'
8
- }`"
6
+ :style="`--_rotate-x: ${rotateXProp}deg; --_perspective: ${perspectiveProp}; --_translateZ: ${translateZProp}; --_animation-play-state: ${pauseOnHover ? 'paused' : 'running'}`"
7
+ ref="carouselRef"
9
8
  >
10
- <div class="slider" style="--quantity: 10">
9
+ <div class="slider" :style="`--quantity: ${Object.keys(data).length}`">
11
10
  <div v-for="(item, key) in data" :key="key" class="item" :style="`--_position: ${key}`"><NuxtImg :src="item.src" :alt="item.alt" /></div>
12
11
  </div>
13
12
  </component>
@@ -50,16 +49,24 @@ const props = defineProps({
50
49
  type: Boolean,
51
50
  default: false,
52
51
  },
52
+ useParallaxEffect: {
53
+ type: Boolean,
54
+ default: true,
55
+ },
53
56
  styleClassPassthrough: {
54
57
  type: Array as PropType<string[]>,
55
58
  default: () => [],
56
59
  },
57
60
  });
58
61
 
59
- const rotateXProp = computed(() => `${props.rotateX.toString()}deg`);
60
62
  const perspectiveProp = computed(() => `${props.perspective.toString()}px`);
61
63
  const translateZProp = computed(() => `${props.translateZ.toString()}px`);
62
64
 
65
+ const carouselRef = ref<HTMLElement | null>(null);
66
+ const rotateXProp = ref(props.rotateX);
67
+ const minRotateX = -32;
68
+ const maxRotateX = 32;
69
+
63
70
  const { elementClasses, resetElementClasses } = useStyleClassPassthrough(props.styleClassPassthrough);
64
71
 
65
72
  watch(
@@ -68,9 +75,77 @@ watch(
68
75
  resetElementClasses(props.styleClassPassthrough);
69
76
  }
70
77
  );
78
+
79
+ watch(
80
+ () => props.rotateX,
81
+ () => {
82
+ if (!props.useParallaxEffect) {
83
+ console.log('rotateXProp changed: ', rotateXProp.value);
84
+
85
+ rotateXProp.value = props.rotateX;
86
+ }
87
+ }
88
+ );
89
+
90
+ watch(
91
+ () => props.useParallaxEffect,
92
+ (currentValue) => {
93
+ if (currentValue) {
94
+ handleScroll();
95
+ window.addEventListener('scroll', handleScroll);
96
+ } else {
97
+ window.removeEventListener('scroll', handleScroll);
98
+ }
99
+ }
100
+ );
101
+
102
+ const handleScroll = () => {
103
+ if (!carouselRef.value) return;
104
+ if ('IntersectionObserver' in window) {
105
+ const rect = carouselRef.value.getBoundingClientRect();
106
+ const viewportHeight = window.innerHeight;
107
+
108
+ const elementCenter = rect.top + rect.height / 2;
109
+ const viewportCenter = viewportHeight / 2;
110
+ const distanceFromCenter = viewportCenter - elementCenter;
111
+ const maxDistance = viewportHeight / 2 + rect.height / 2;
112
+
113
+ const progress = (distanceFromCenter + maxDistance) / (maxDistance * 2);
114
+ const clampedProgress = Math.max(0, Math.min(1, progress));
115
+
116
+ rotateXProp.value = minRotateX + (maxRotateX - minRotateX) * clampedProgress;
117
+ }
118
+ };
119
+
120
+ onMounted(async () => {
121
+ if (props.useParallaxEffect) {
122
+ handleScroll();
123
+ await nextTick();
124
+ window.addEventListener('scroll', handleScroll);
125
+ }
126
+ });
127
+
128
+ onUnmounted(() => {
129
+ window.removeEventListener('scroll', handleScroll);
130
+ });
71
131
  </script>
72
132
 
73
133
  <style lang="css">
134
+ /* @property --_rotate-x {
135
+ syntax: '<angle>';
136
+ inherits: false;
137
+ initial-value: 0deg;
138
+ }
139
+
140
+ @keyframes autoRotateAnimation {
141
+ from {
142
+ --_rotate-x: -16deg;
143
+ }
144
+ to {
145
+ --_rotate-x: 16deg;
146
+ }
147
+ } */
148
+
74
149
  @keyframes autoRun {
75
150
  from {
76
151
  transform: perspective(var(--_perspective)) rotateX(var(--_rotate-x)) rotateY(0deg);
@@ -82,16 +157,23 @@ watch(
82
157
 
83
158
  .rotating-carousel {
84
159
  width: 100%;
85
- height: 100vh;
160
+ height: 70svh;
86
161
  text-align: center;
87
162
  overflow: hidden;
88
163
  position: relative;
89
164
 
165
+ /* padding-block: 800px; */
166
+
167
+ /* &.scroll-effect {
168
+ animation: autoRotateAnimation;
169
+ animation-timeline: view();
170
+ } */
171
+
90
172
  .slider {
91
173
  position: absolute;
92
174
  width: 200px;
93
175
  height: 250px;
94
- top: 10%;
176
+ bottom: 35%;
95
177
  left: calc(50% - 100px);
96
178
  transform-style: preserve-3d;
97
179
  transform: perspective(var(--_perspective));
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "srcdev-nuxt-components",
3
3
  "type": "module",
4
- "version": "1.3.0",
4
+ "version": "2.1.0",
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",