@sugarat/theme 0.5.10 → 0.5.12-beta.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.
@@ -0,0 +1,254 @@
1
+ <script lang="ts" setup>
2
+ import { computed, onMounted, onUnmounted, provide, ref, watch } from 'vue'
3
+
4
+ const props = defineProps({
5
+ height: { type: String, default: '300px' },
6
+ initialIndex: { type: Number, default: 0 },
7
+ trigger: { type: String, default: 'hover' },
8
+ autoplay: { type: Boolean, default: true },
9
+ interval: { type: Number, default: 3000 },
10
+ indicatorPosition: { type: String, default: '' },
11
+ arrow: { type: String, default: 'hover' },
12
+ type: { type: String, default: '' },
13
+ loop: { type: Boolean, default: true },
14
+ direction: { type: String, default: 'horizontal' },
15
+ pauseOnHover: { type: Boolean, default: true },
16
+ })
17
+
18
+ const activeIndex = ref(props.initialIndex)
19
+ const items = ref<any[]>([])
20
+ const timer = ref<any>(null)
21
+
22
+ function addItem(item: any) {
23
+ items.value.push(item)
24
+ }
25
+
26
+ function removeItem(uid: number) {
27
+ const index = items.value.findIndex(item => item.uid === uid)
28
+ if (index > -1)
29
+ items.value.splice(index, 1)
30
+ }
31
+
32
+ provide('carousel', {
33
+ addItem,
34
+ removeItem,
35
+ activeIndex,
36
+ items,
37
+ type: computed(() => props.type),
38
+ direction: computed(() => props.direction),
39
+ })
40
+
41
+ function startTimer() {
42
+ if (typeof window === 'undefined')
43
+ return
44
+ if (props.interval <= 0 || !props.autoplay || timer.value)
45
+ return
46
+ timer.value = setInterval(playSlides, props.interval)
47
+ }
48
+
49
+ function pauseTimer() {
50
+ if (timer.value) {
51
+ clearInterval(timer.value)
52
+ timer.value = null
53
+ }
54
+ }
55
+
56
+ function playSlides() {
57
+ if (activeIndex.value < items.value.length - 1) {
58
+ activeIndex.value++
59
+ }
60
+ else if (props.loop) {
61
+ activeIndex.value = 0
62
+ }
63
+ }
64
+
65
+ function setActiveItem(index: number) {
66
+ activeIndex.value = index
67
+ }
68
+
69
+ function prev() {
70
+ setActiveItem(activeIndex.value > 0 ? activeIndex.value - 1 : items.value.length - 1)
71
+ }
72
+
73
+ function next() {
74
+ setActiveItem(activeIndex.value < items.value.length - 1 ? activeIndex.value + 1 : 0)
75
+ }
76
+
77
+ function handleIndicatorClick(index: number) {
78
+ activeIndex.value = index
79
+ }
80
+
81
+ const hover = ref(false)
82
+
83
+ function handleMouseEnter() {
84
+ hover.value = true
85
+ if (props.pauseOnHover)
86
+ pauseTimer()
87
+ }
88
+
89
+ function handleMouseLeave() {
90
+ hover.value = false
91
+ if (props.pauseOnHover)
92
+ startTimer()
93
+ }
94
+
95
+ watch(() => props.autoplay, (val) => {
96
+ val ? startTimer() : pauseTimer()
97
+ })
98
+
99
+ onMounted(() => {
100
+ startTimer()
101
+ })
102
+
103
+ onUnmounted(() => {
104
+ pauseTimer()
105
+ })
106
+ </script>
107
+
108
+ <template>
109
+ <div
110
+ class="sugar-carousel"
111
+ :class="{ 'sugar-carousel--card': type === 'card' }"
112
+ @mouseenter="handleMouseEnter"
113
+ @mouseleave="handleMouseLeave"
114
+ >
115
+ <div class="sugar-carousel__container" :style="{ height }">
116
+ <slot />
117
+ <transition name="carousel-arrow-left">
118
+ <button
119
+ v-if="arrow !== 'never' && items.length > 1"
120
+ v-show="(arrow === 'always' || hover) && (loop || activeIndex > 0)"
121
+ type="button"
122
+ class="sugar-carousel__arrow sugar-carousel__arrow--left"
123
+ @click.stop="prev"
124
+ >
125
+ <svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="1em" height="1em"><path fill="currentColor" d="M609.408 149.376 277.76 489.6a32 32 0 0 0 0 44.672l331.648 340.352a29.12 29.12 0 0 0 41.728 0 30.592 30.592 0 0 0 0-42.752L339.264 511.936l311.872-319.872a30.592 30.592 0 0 0 0-42.688 29.12 29.12 0 0 0-41.728 0z" /></svg>
126
+ </button>
127
+ </transition>
128
+ <transition name="carousel-arrow-right">
129
+ <button
130
+ v-if="arrow !== 'never' && items.length > 1"
131
+ v-show="(arrow === 'always' || hover) && (loop || activeIndex < items.length - 1)"
132
+ type="button"
133
+ class="sugar-carousel__arrow sugar-carousel__arrow--right"
134
+ @click.stop="next"
135
+ >
136
+ <svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="1em" height="1em"><path fill="currentColor" d="M340.864 149.312a30.592 30.592 0 0 0 0 42.752L652.736 512 340.864 831.872a30.592 30.592 0 0 0 0 42.752 29.12 29.12 0 0 0 41.728 0L714.24 534.336a32 32 0 0 0 0-44.672L382.592 149.376a29.12 29.12 0 0 0-41.728 0z" /></svg>
137
+ </button>
138
+ </transition>
139
+ </div>
140
+
141
+ <ul v-if="indicatorPosition !== 'none' && items.length > 1" class="sugar-carousel__indicators">
142
+ <li
143
+ v-for="(item, index) in items"
144
+ :key="index"
145
+ class="sugar-carousel__indicator"
146
+ :class="{ 'is-active': index === activeIndex }"
147
+ @mouseenter="trigger === 'hover' && setActiveItem(index)"
148
+ @click.stop="handleIndicatorClick(index)"
149
+ >
150
+ <button class="sugar-carousel__button" />
151
+ </li>
152
+ </ul>
153
+ </div>
154
+ </template>
155
+
156
+ <style lang="scss" scoped>
157
+ .sugar-carousel {
158
+ position: relative;
159
+ overflow: hidden; // simplified
160
+ &:hover {
161
+ .sugar-carousel__arrow {
162
+ display: inline-flex;
163
+ }
164
+ }
165
+ }
166
+
167
+ .sugar-carousel__container {
168
+ position: relative;
169
+ height: 300px;
170
+ overflow: hidden;
171
+ }
172
+
173
+ .sugar-carousel__indicators {
174
+ position: absolute;
175
+ list-style: none;
176
+ bottom: 0;
177
+ left: 50%;
178
+ transform: translateX(-50%);
179
+ margin: 0;
180
+ padding: 0;
181
+ z-index: 20;
182
+ }
183
+
184
+ .sugar-carousel__indicator {
185
+ display: inline-block;
186
+ background-color: transparent;
187
+ padding: 12px 4px;
188
+ cursor: pointer;
189
+
190
+ &.is-active button {
191
+ opacity: 1;
192
+ }
193
+ }
194
+
195
+ .sugar-carousel__button {
196
+ display: block;
197
+ opacity: 0.48;
198
+ width: 30px;
199
+ height: 2px;
200
+ background-color: #d3dce6; // element-plus default like
201
+ border: none;
202
+ outline: none;
203
+ padding: 0;
204
+ margin: 0;
205
+ cursor: pointer;
206
+ transition: .3s;
207
+ }
208
+
209
+ .sugar-carousel__arrow {
210
+ border: none;
211
+ outline: none;
212
+ padding: 0;
213
+ margin: 0;
214
+ height: 36px;
215
+ width: 36px;
216
+ cursor: pointer;
217
+ transition: .3s;
218
+ border-radius: 50%;
219
+ background-color: rgba(31, 45, 61, .11);
220
+ color: #fff;
221
+ position: absolute;
222
+ top: 50%;
223
+ z-index: 20;
224
+ transform: translateY(-50%);
225
+ text-align: center;
226
+ font-size: 12px;
227
+ display: inline-flex;
228
+ justify-content: center;
229
+ align-items: center;
230
+
231
+ &:hover {
232
+ background-color: rgba(31, 45, 61, .23);
233
+ }
234
+
235
+ &--left {
236
+ left: 16px;
237
+ }
238
+
239
+ &--right {
240
+ right: 16px;
241
+ }
242
+ }
243
+ .carousel-arrow-left-enter-from,
244
+ .carousel-arrow-left-leave-to {
245
+ opacity: 0;
246
+ transform: translateY(-50%) translateX(-10px);
247
+ }
248
+
249
+ .carousel-arrow-right-enter-from,
250
+ .carousel-arrow-right-leave-to {
251
+ opacity: 0;
252
+ transform: translateY(-50%) translateX(10px);
253
+ }
254
+ </style>
@@ -0,0 +1,154 @@
1
+ <script lang="ts" setup>
2
+ import { computed, getCurrentInstance, inject, onMounted, onUnmounted } from 'vue'
3
+
4
+ const instance = getCurrentInstance()
5
+ const uid = instance?.uid
6
+
7
+ const carousel = inject('carousel') as any
8
+ const { activeIndex, items, type, addItem, removeItem } = carousel
9
+
10
+ const index = computed(() => {
11
+ return items.value.findIndex((item: any) => item.uid === uid)
12
+ })
13
+
14
+ const isActive = computed(() => index.value === activeIndex.value)
15
+
16
+ // Card logic
17
+ const CARD_SCALE = 0.83
18
+
19
+ const cardStyle = computed(() => {
20
+ if (type.value !== 'card') {
21
+ // Normal mode: simplified translate
22
+ // const isPrev = index.value === activeIndex.value - 1 || (activeIndex.value === 0 && index.value === items.value.length - 1)
23
+ // const isNext = index.value === activeIndex.value + 1 || (activeIndex.value === items.value.length - 1 && index.value === 0)
24
+
25
+ // Simple show/hide for normal mode or use CSS transition
26
+ // Better: use transform
27
+ // const offset = index.value - activeIndex.value
28
+ // Handle loop wrap
29
+ // ... too complex to implement full logic in one go.
30
+ // Let's rely on simple absolute positioning and z-index for normal fade/slide
31
+
32
+ return {
33
+ transform: `translateX(${(index.value - activeIndex.value) * 100}%)`,
34
+ zIndex: isActive.value ? 2 : 1,
35
+ }
36
+ }
37
+
38
+ // Card Mode
39
+ // const parentWidth = instance?.parent?.vnode?.el?.offsetWidth || 0 // Need parent width
40
+ // Actually simpler:
41
+ // Active: translate(0) scale(1)
42
+ // Prev: translate(-50%) scale(0.83)
43
+ // Next: translate(50%) scale(0.83)
44
+ // Others: hidden or further
45
+
46
+ const active = activeIndex.value
47
+ const count = items.value.length
48
+ const idx = index.value
49
+
50
+ // Logic from Element Plus (simplified)
51
+ const processIndex = (index: number, activeIndex: number, length: number) => {
52
+ if (activeIndex === 0 && index === length - 1)
53
+ return -1
54
+ if (activeIndex === length - 1 && index === 0)
55
+ return length
56
+ if (index < activeIndex - 1 && activeIndex - index >= length / 2)
57
+ return length + 1
58
+ if (index > activeIndex + 1 && index - activeIndex >= length / 2)
59
+ return -2
60
+ return index
61
+ }
62
+
63
+ const calculatedIndex = processIndex(idx, active, count)
64
+
65
+ const inStage = Math.round(Math.abs(calculatedIndex - active)) <= 1
66
+
67
+ const translate = (() => {
68
+ if (calculatedIndex === active)
69
+ return 0
70
+ if (Math.abs(calculatedIndex - active) > 1)
71
+ return 0 // Should be hidden really
72
+ return calculatedIndex > active ? '50%' : '-50%'
73
+ })()
74
+
75
+ const scale = calculatedIndex === active ? 1 : CARD_SCALE
76
+ const zIndex = calculatedIndex === active ? 10 : 0 // Simplified
77
+
78
+ return {
79
+ transform: `translateX(${translate}) scale(${scale})`,
80
+ zIndex,
81
+ opacity: inStage ? 1 : 0, // Hide others
82
+ display: inStage ? 'block' : 'none', // optimize
83
+ }
84
+ })
85
+
86
+ onMounted(() => {
87
+ addItem({ uid })
88
+ })
89
+ onUnmounted(() => {
90
+ removeItem(uid)
91
+ })
92
+ </script>
93
+
94
+ <template>
95
+ <div
96
+ v-if="type !== 'card' || cardStyle.display !== 'none'"
97
+ class="sugar-carousel__item"
98
+ :class="{
99
+ 'is-active': isActive,
100
+ 'is-in-stage': type === 'card' && cardStyle.opacity === 1,
101
+ 'sugar-carousel__item--card': type === 'card',
102
+ }"
103
+ :style="cardStyle"
104
+ >
105
+ <div v-if="type === 'card'" v-show="!isActive" class="sugar-carousel__mask" />
106
+ <slot />
107
+ </div>
108
+ </template>
109
+
110
+ <style lang="scss" scoped>
111
+ .sugar-carousel__item {
112
+ position: absolute;
113
+ top: 0;
114
+ left: 0;
115
+ width: 100%;
116
+ height: 100%;
117
+ display: inline-block;
118
+ overflow: hidden;
119
+ z-index: 0;
120
+ transition: transform 0.4s ease-in-out;
121
+ background-color: transparent; // Default
122
+ }
123
+
124
+ .sugar-carousel__item--card {
125
+ width: 50%;
126
+ left: 25%; // Center it initially?
127
+ // Actually if we use translateX 50% or -50%, it's relative to item width.
128
+ // Element Plus Card:
129
+ // Item width is 50%.
130
+ // Active item is at translateX(containerWidth/4).
131
+ // Prev item at translateX(0).
132
+ // Next item at translateX(containerWidth/2).
133
+
134
+ // My simplified CSS logic:
135
+ // left: 25% (centers the 50% width item in container)
136
+ // active: translate(0) -> centered
137
+ // prev: translate(-50% of item) -> -25% of container? No, -50% of 50% is -25% width.
138
+ // left 25% - 25% = 0% (Left aligned)
139
+ // next: translate(50% of item) -> +25% of container.
140
+ // left 25% + 25% = 50% (Right aligned)
141
+ // This works!
142
+ }
143
+
144
+ .sugar-carousel__mask {
145
+ position: absolute;
146
+ width: 100%;
147
+ height: 100%;
148
+ top: 0;
149
+ left: 0;
150
+ background-color: #fff;
151
+ opacity: 0.24;
152
+ transition: .2s;
153
+ }
154
+ </style>
@@ -0,0 +1,34 @@
1
+ <script lang="ts" setup>
2
+ defineProps({
3
+ src: { type: String, default: '' },
4
+ alt: { type: String, default: '' },
5
+ loading: { type: String, default: 'eager' },
6
+ })
7
+ </script>
8
+
9
+ <template>
10
+ <div class="sugar-image">
11
+ <img
12
+ :src="src"
13
+ :alt="alt"
14
+ :loading="loading as any"
15
+ class="sugar-image__inner"
16
+ >
17
+ </div>
18
+ </template>
19
+
20
+ <style lang="scss" scoped>
21
+ .sugar-image {
22
+ position: relative;
23
+ display: inline-block;
24
+ overflow: hidden;
25
+
26
+ &__inner {
27
+ width: 100%;
28
+ height: 100%;
29
+ vertical-align: top;
30
+ object-fit: inherit;
31
+ cursor: pointer;
32
+ }
33
+ }
34
+ </style>