@rezcom/rez-components 0.0.2
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 +58 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +6 -0
- package/dist/ui/Button.svelte +93 -0
- package/dist/ui/Button.svelte.d.ts +20 -0
- package/dist/ui/Carousel.svelte +311 -0
- package/dist/ui/Carousel.svelte.d.ts +25 -0
- package/dist/ui/Dropdown.svelte +126 -0
- package/dist/ui/Dropdown.svelte.d.ts +18 -0
- package/dist/ui/Hero.svelte +27 -0
- package/dist/ui/Hero.svelte.d.ts +9 -0
- package/dist/ui/Modal.svelte +59 -0
- package/dist/ui/Modal.svelte.d.ts +13 -0
- package/dist/util/type.d.ts +4 -0
- package/dist/util/type.js +1 -0
- package/dist/util/util.d.ts +7 -0
- package/dist/util/util.js +47 -0
- package/package.json +73 -0
package/README.md
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# Svelte library
|
|
2
|
+
|
|
3
|
+
Everything you need to build a Svelte library, powered by [`sv`](https://npmjs.com/package/sv).
|
|
4
|
+
|
|
5
|
+
Read more about creating a library [in the docs](https://svelte.dev/docs/kit/packaging).
|
|
6
|
+
|
|
7
|
+
## Creating a project
|
|
8
|
+
|
|
9
|
+
If you're seeing this, you've probably already done this step. Congrats!
|
|
10
|
+
|
|
11
|
+
```sh
|
|
12
|
+
# create a new project in the current directory
|
|
13
|
+
npx sv create
|
|
14
|
+
|
|
15
|
+
# create a new project in my-app
|
|
16
|
+
npx sv create my-app
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Developing
|
|
20
|
+
|
|
21
|
+
Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server:
|
|
22
|
+
|
|
23
|
+
```sh
|
|
24
|
+
npm run dev
|
|
25
|
+
|
|
26
|
+
# or start the server and open the app in a new browser tab
|
|
27
|
+
npm run dev -- --open
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Everything inside `src/lib` is part of your library, everything inside `src/routes` can be used as a showcase or preview app.
|
|
31
|
+
|
|
32
|
+
## Building
|
|
33
|
+
|
|
34
|
+
To build your library:
|
|
35
|
+
|
|
36
|
+
```sh
|
|
37
|
+
npm pack
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
To create a production version of your showcase app:
|
|
41
|
+
|
|
42
|
+
```sh
|
|
43
|
+
npm run build
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
You can preview the production build with `npm run preview`.
|
|
47
|
+
|
|
48
|
+
> To deploy your app, you may need to install an [adapter](https://svelte.dev/docs/kit/adapters) for your target environment.
|
|
49
|
+
|
|
50
|
+
## Publishing
|
|
51
|
+
|
|
52
|
+
Go into the `package.json` and give your package the desired name through the `"name"` option. Also consider adding a `"license"` field and point it to a `LICENSE` file which you can create from a template (one popular option is the [MIT license](https://opensource.org/license/mit/)).
|
|
53
|
+
|
|
54
|
+
To publish your library to [npm](https://www.npmjs.com):
|
|
55
|
+
|
|
56
|
+
```sh
|
|
57
|
+
npm publish
|
|
58
|
+
```
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
|
|
4
|
+
interface ButtonProps {
|
|
5
|
+
text?: null | string;
|
|
6
|
+
active?: boolean; // Condition as to whether or not it's clickable
|
|
7
|
+
|
|
8
|
+
// TailwindCSS
|
|
9
|
+
color?: string;
|
|
10
|
+
textColor?: string;
|
|
11
|
+
inactiveColor?: string;
|
|
12
|
+
inactiveTextColor?: string;
|
|
13
|
+
ringColor?: string;
|
|
14
|
+
class?: string;
|
|
15
|
+
|
|
16
|
+
// Add ring and shine effects?
|
|
17
|
+
ring?: boolean;
|
|
18
|
+
shine?: boolean;
|
|
19
|
+
|
|
20
|
+
// href
|
|
21
|
+
href?: null | string;
|
|
22
|
+
target?: null | string;
|
|
23
|
+
|
|
24
|
+
// Interactivity - Use if the button should have behavior rather than redirecting to a link
|
|
25
|
+
onclick?: (() => void) | null;
|
|
26
|
+
|
|
27
|
+
// Child props
|
|
28
|
+
children?: null | Snippet;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
let {
|
|
32
|
+
text = null,
|
|
33
|
+
active = true,
|
|
34
|
+
|
|
35
|
+
color = 'bg-indigo-700 hover:bg-indigo-800 active:bg-blue-900',
|
|
36
|
+
textColor = 'text-white ',
|
|
37
|
+
inactiveColor = 'bg-gray-700',
|
|
38
|
+
inactiveTextColor = 'text-white',
|
|
39
|
+
|
|
40
|
+
ringColor = 'hover:ring-indigo-800 focus-visible:ring-indigo-800',
|
|
41
|
+
class: otherClasses = '',
|
|
42
|
+
|
|
43
|
+
ring = true,
|
|
44
|
+
shine = true,
|
|
45
|
+
|
|
46
|
+
target = null,
|
|
47
|
+
href = null,
|
|
48
|
+
|
|
49
|
+
onclick = null,
|
|
50
|
+
children = null
|
|
51
|
+
}: ButtonProps = $props();
|
|
52
|
+
|
|
53
|
+
const baseClass =
|
|
54
|
+
'group relative inline-flex overflow-hidden items-center justify-center rounded-md px-2 py-2 text-center uppercase tracking-wide focus:outline-hidden focus:ring-3 focus:ring-offset-white transition duration-200 ease-in-out'.concat(
|
|
55
|
+
active ? ' hover:cursor-pointer' : ''
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
const classes = [
|
|
59
|
+
otherClasses,
|
|
60
|
+
baseClass,
|
|
61
|
+
active ? color : inactiveColor,
|
|
62
|
+
active ? textColor : inactiveTextColor,
|
|
63
|
+
ring ? `hover:ring-2 hover:ring-offset-2 ${ringColor}` : ''
|
|
64
|
+
].join(' ');
|
|
65
|
+
</script>
|
|
66
|
+
|
|
67
|
+
{#snippet inside()}
|
|
68
|
+
{#if shine}
|
|
69
|
+
<span
|
|
70
|
+
class="absolute left-0 -mt-12 h-32 w-1/2 translate-x-[250%] rotate-12 bg-white/20 transition-all duration-250 ease-out group-hover:translate-x-[2%]"
|
|
71
|
+
></span>
|
|
72
|
+
{/if}
|
|
73
|
+
{@render children?.()}
|
|
74
|
+
{#if text}
|
|
75
|
+
{text}
|
|
76
|
+
{/if}
|
|
77
|
+
{/snippet}
|
|
78
|
+
|
|
79
|
+
{#if active}
|
|
80
|
+
{#if href}
|
|
81
|
+
<a {href} class={classes} {target}>
|
|
82
|
+
{@render inside()}
|
|
83
|
+
</a>
|
|
84
|
+
{:else if onclick}
|
|
85
|
+
<button {onclick} class={classes}>
|
|
86
|
+
{@render inside()}
|
|
87
|
+
</button>
|
|
88
|
+
{/if}
|
|
89
|
+
{:else}
|
|
90
|
+
<div class={classes}>
|
|
91
|
+
{@render inside()}
|
|
92
|
+
</div>
|
|
93
|
+
{/if}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
interface ButtonProps {
|
|
3
|
+
text?: null | string;
|
|
4
|
+
active?: boolean;
|
|
5
|
+
color?: string;
|
|
6
|
+
textColor?: string;
|
|
7
|
+
inactiveColor?: string;
|
|
8
|
+
inactiveTextColor?: string;
|
|
9
|
+
ringColor?: string;
|
|
10
|
+
class?: string;
|
|
11
|
+
ring?: boolean;
|
|
12
|
+
shine?: boolean;
|
|
13
|
+
href?: null | string;
|
|
14
|
+
target?: null | string;
|
|
15
|
+
onclick?: (() => void) | null;
|
|
16
|
+
children?: null | Snippet;
|
|
17
|
+
}
|
|
18
|
+
declare const Button: import("svelte").Component<ButtonProps, {}, "">;
|
|
19
|
+
type Button = ReturnType<typeof Button>;
|
|
20
|
+
export default Button;
|
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { ImageMetadata } from 'astro';
|
|
3
|
+
import { Carousel } from 'flowbite';
|
|
4
|
+
import { onDestroy, onMount } from 'svelte';
|
|
5
|
+
import { measureImageHeights, preloadImages } from '../util/util.js';
|
|
6
|
+
|
|
7
|
+
let carousel: Carousel | null = $state(null);
|
|
8
|
+
|
|
9
|
+
let maxHeight = $state(0);
|
|
10
|
+
let aspectRatio = $state(1);
|
|
11
|
+
|
|
12
|
+
interface CarouselProps {
|
|
13
|
+
// List of image metadata's OR strings
|
|
14
|
+
images: (ImageMetadata | string)[];
|
|
15
|
+
|
|
16
|
+
// Carousel ID to make it unique
|
|
17
|
+
carouselID?: number;
|
|
18
|
+
|
|
19
|
+
// Whether or not the user can click to move the carousel pages.
|
|
20
|
+
clickablePages?: boolean;
|
|
21
|
+
|
|
22
|
+
// Duration for auto-slide, if any
|
|
23
|
+
// 0 Indicates none
|
|
24
|
+
duration?: number;
|
|
25
|
+
|
|
26
|
+
// How fast should the page switch? (as a TailwindCSS class)
|
|
27
|
+
animationSpeed?: string;
|
|
28
|
+
|
|
29
|
+
// Pause on mouse hover
|
|
30
|
+
pauseOnHover?: boolean;
|
|
31
|
+
|
|
32
|
+
// Override the height with a TailwindCSS class
|
|
33
|
+
heightOverride?: string;
|
|
34
|
+
|
|
35
|
+
// Default Page index
|
|
36
|
+
defaultPage?: number;
|
|
37
|
+
|
|
38
|
+
// Show Slider Indicators
|
|
39
|
+
showSliderIndicators?: boolean;
|
|
40
|
+
|
|
41
|
+
// Image Options (see interface below)
|
|
42
|
+
imageOptions?: ImageOptions;
|
|
43
|
+
|
|
44
|
+
// Carousel Bg Color (as a TailwindCSS class)
|
|
45
|
+
bgColor?: string;
|
|
46
|
+
|
|
47
|
+
// Object property (as a TailwindCSS class such as "object-cover")
|
|
48
|
+
object?: string;
|
|
49
|
+
|
|
50
|
+
// Other classes TailwindCSS
|
|
51
|
+
class?: string;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
interface ImageOptions {
|
|
55
|
+
// Should the images be rounded?
|
|
56
|
+
rounded?: boolean;
|
|
57
|
+
|
|
58
|
+
// Image's height as a TailwindCSS class
|
|
59
|
+
height?: string;
|
|
60
|
+
|
|
61
|
+
// Move the image slightly up within the wrapping div? (Stupid Flowbite bug)
|
|
62
|
+
translateUp?: boolean;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const {
|
|
66
|
+
images = [],
|
|
67
|
+
carouselID = 0,
|
|
68
|
+
clickablePages = true,
|
|
69
|
+
defaultPage = 0,
|
|
70
|
+
showSliderIndicators = true,
|
|
71
|
+
animationSpeed = 'duration-700',
|
|
72
|
+
object = 'object-cover',
|
|
73
|
+
heightOverride,
|
|
74
|
+
imageOptions = {
|
|
75
|
+
rounded: true,
|
|
76
|
+
height: 'h-full',
|
|
77
|
+
translateUp: true
|
|
78
|
+
},
|
|
79
|
+
bgColor = '',
|
|
80
|
+
duration = 0,
|
|
81
|
+
pauseOnHover = true,
|
|
82
|
+
class: otherClasses = ''
|
|
83
|
+
}: CarouselProps = $props();
|
|
84
|
+
|
|
85
|
+
let isMouseHovering = $state(false);
|
|
86
|
+
let interval: ReturnType<typeof setInterval> | undefined = undefined;
|
|
87
|
+
let preloadedImages: HTMLImageElement[] = $state([]);
|
|
88
|
+
|
|
89
|
+
function nextPage() {
|
|
90
|
+
if (carousel) {
|
|
91
|
+
carousel.next();
|
|
92
|
+
} else {
|
|
93
|
+
console.error(
|
|
94
|
+
'The carousel has not been initialized correctly in the mount and therefore the nextPage() function failed.'
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function previousPage() {
|
|
100
|
+
if (carousel) {
|
|
101
|
+
carousel.prev();
|
|
102
|
+
} else {
|
|
103
|
+
console.error(
|
|
104
|
+
'The carousel has not been initialized correctly in the mount and therefore the previousPage() function failed.'
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function startAutoPlay(carousel: Carousel) {
|
|
110
|
+
if (duration) {
|
|
111
|
+
interval = setInterval(() => {
|
|
112
|
+
if (!isMouseHovering) {
|
|
113
|
+
carousel.next();
|
|
114
|
+
}
|
|
115
|
+
}, duration);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function stopAutoPlay() {
|
|
120
|
+
clearInterval(interval);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
onMount(() => {
|
|
124
|
+
async function setup() {
|
|
125
|
+
// Preload images
|
|
126
|
+
preloadedImages = preloadImages(images);
|
|
127
|
+
|
|
128
|
+
// Measure and set max height after images load
|
|
129
|
+
const { height, ratio } = await measureImageHeights(preloadedImages);
|
|
130
|
+
maxHeight = height;
|
|
131
|
+
aspectRatio = ratio;
|
|
132
|
+
|
|
133
|
+
// Indicate carousel-item-ids
|
|
134
|
+
const items = preloadedImages
|
|
135
|
+
.map((item, i) => {
|
|
136
|
+
const el = document.getElementById(`carousel-item-${carouselID}-${i}`);
|
|
137
|
+
|
|
138
|
+
if (el) {
|
|
139
|
+
return { position: i, el };
|
|
140
|
+
}
|
|
141
|
+
console.warn(`Element with ID carousel-item-${carouselID}-${i} not found.`);
|
|
142
|
+
return null;
|
|
143
|
+
})
|
|
144
|
+
.filter((item): item is { position: number; el: HTMLElement } => item !== null);
|
|
145
|
+
|
|
146
|
+
// Indicate the options which outline the indicator ids
|
|
147
|
+
const options = {
|
|
148
|
+
defaultPosition: defaultPage,
|
|
149
|
+
indicators: {
|
|
150
|
+
activeClasses: 'bg-white',
|
|
151
|
+
items: preloadedImages
|
|
152
|
+
.map((item, i) => {
|
|
153
|
+
const el = document.getElementById(`carousel-indicator-${carouselID}-${i}`);
|
|
154
|
+
if (el) {
|
|
155
|
+
return { position: i, el };
|
|
156
|
+
}
|
|
157
|
+
console.warn(`Element with ID carousel-indicator-${carouselID}-${i} not found.`);
|
|
158
|
+
return null;
|
|
159
|
+
})
|
|
160
|
+
.filter((item): item is { position: number; el: HTMLElement } => item !== null)
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
// Handle resizes
|
|
165
|
+
const resizeHandler = async () => {
|
|
166
|
+
const { height, ratio } = await measureImageHeights(preloadedImages);
|
|
167
|
+
maxHeight = height;
|
|
168
|
+
aspectRatio = ratio;
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
window.addEventListener('resize', resizeHandler);
|
|
172
|
+
|
|
173
|
+
// Initialize the carousel
|
|
174
|
+
carousel = new Carousel(carouselElement, items, options);
|
|
175
|
+
|
|
176
|
+
startAutoPlay(carousel);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const carouselElement = document.getElementById(`default-carousel-${carouselID}`);
|
|
180
|
+
|
|
181
|
+
setup();
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
onDestroy(() => {
|
|
185
|
+
stopAutoPlay();
|
|
186
|
+
});
|
|
187
|
+
</script>
|
|
188
|
+
|
|
189
|
+
<menu
|
|
190
|
+
id="default-carousel-{carouselID}"
|
|
191
|
+
class={`${otherClasses} relative top-0 ${bgColor} overflow-hidden ${heightOverride ? heightOverride : ''}`}
|
|
192
|
+
style={!heightOverride && maxHeight ? `aspect-ratio: ${aspectRatio}` : ''}
|
|
193
|
+
onmouseenter={() => {
|
|
194
|
+
if (pauseOnHover) {
|
|
195
|
+
isMouseHovering = true;
|
|
196
|
+
}
|
|
197
|
+
}}
|
|
198
|
+
onmouseleave={() => {
|
|
199
|
+
isMouseHovering = false;
|
|
200
|
+
}}
|
|
201
|
+
>
|
|
202
|
+
<!-- Carousel wrapper -->
|
|
203
|
+
<div
|
|
204
|
+
class={`relative h-full w-full overflow-hidden ${bgColor} ${imageOptions.rounded ? 'rounded-md' : ''}`}
|
|
205
|
+
>
|
|
206
|
+
<!-- Items -->
|
|
207
|
+
{#if preloadedImages}
|
|
208
|
+
{#each preloadedImages as srcObject, i}
|
|
209
|
+
<div
|
|
210
|
+
class="{animationSpeed} flex h-full w-full items-center justify-center ease-in-out"
|
|
211
|
+
id="carousel-item-{carouselID}-{i}"
|
|
212
|
+
>
|
|
213
|
+
<img
|
|
214
|
+
alt=""
|
|
215
|
+
src={typeof srcObject === 'object' && srcObject !== null && 'src' in srcObject
|
|
216
|
+
? srcObject.src
|
|
217
|
+
: srcObject}
|
|
218
|
+
class={`h-full w-full ${imageOptions.rounded ? 'rounded-md' : ''} ${object ?? 'object-contain'}`}
|
|
219
|
+
style={heightOverride ? '' : `aspect-ratio: ${aspectRatio};`}
|
|
220
|
+
/>
|
|
221
|
+
</div>
|
|
222
|
+
{/each}
|
|
223
|
+
{:else}
|
|
224
|
+
<span class="font-bold italic">Loading Images Carousel...</span>
|
|
225
|
+
{/if}
|
|
226
|
+
</div>
|
|
227
|
+
<!-- Slider indicators -->
|
|
228
|
+
{#if showSliderIndicators}
|
|
229
|
+
<div
|
|
230
|
+
class="absolute bottom-5 left-1/2 z-30 flex -translate-x-1/2 space-x-3 rtl:space-x-reverse"
|
|
231
|
+
>
|
|
232
|
+
{#each images as item, index}
|
|
233
|
+
<button
|
|
234
|
+
id="carousel-indicator-{carouselID}-{index}"
|
|
235
|
+
type="button"
|
|
236
|
+
class="h-3 w-3 rounded-full"
|
|
237
|
+
aria-current="true"
|
|
238
|
+
aria-label="Slide {index}"
|
|
239
|
+
data-carousel-slide-to={index}
|
|
240
|
+
></button>
|
|
241
|
+
{/each}
|
|
242
|
+
</div>
|
|
243
|
+
{/if}
|
|
244
|
+
<!-- Slider controls -->
|
|
245
|
+
{#if clickablePages}
|
|
246
|
+
<button
|
|
247
|
+
type="button"
|
|
248
|
+
class="group absolute start-0 top-0 z-30 flex h-full cursor-pointer items-center justify-center px-4 focus:outline-hidden"
|
|
249
|
+
onclick={previousPage}
|
|
250
|
+
>
|
|
251
|
+
<div
|
|
252
|
+
class={'absolute inset-0 bg-gradient-to-r from-black/50 to-transparent opacity-0 transition-opacity duration-300 group-hover:opacity-100'.concat(
|
|
253
|
+
' ',
|
|
254
|
+
imageOptions.rounded ? 'rounded-l-md' : ''
|
|
255
|
+
)}
|
|
256
|
+
></div>
|
|
257
|
+
<span
|
|
258
|
+
class="relative inline-flex h-10 w-10 items-center justify-center rounded-full bg-white/30 opacity-0 transition-opacity duration-300 group-hover:opacity-100 group-focus:ring-4 group-focus:ring-white group-focus:outline-hidden"
|
|
259
|
+
>
|
|
260
|
+
<svg
|
|
261
|
+
class="h-4 w-4 text-white"
|
|
262
|
+
aria-hidden="true"
|
|
263
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
264
|
+
fill="none"
|
|
265
|
+
viewBox="0 0 6 10"
|
|
266
|
+
>
|
|
267
|
+
<path
|
|
268
|
+
stroke="currentColor"
|
|
269
|
+
stroke-linecap="round"
|
|
270
|
+
stroke-linejoin="round"
|
|
271
|
+
stroke-width="2"
|
|
272
|
+
d="M5 1 1 5l4 4"
|
|
273
|
+
/>
|
|
274
|
+
</svg>
|
|
275
|
+
<span class="sr-only">Previous</span>
|
|
276
|
+
</span>
|
|
277
|
+
</button>
|
|
278
|
+
<button
|
|
279
|
+
type="button"
|
|
280
|
+
class="group absolute end-0 top-0 z-30 flex h-full cursor-pointer items-center justify-center px-4 focus:outline-hidden"
|
|
281
|
+
onclick={nextPage}
|
|
282
|
+
>
|
|
283
|
+
<div
|
|
284
|
+
class={'absolute inset-0 bg-gradient-to-l from-black/50 to-transparent opacity-0 transition-opacity duration-300 group-hover:opacity-100'.concat(
|
|
285
|
+
' ',
|
|
286
|
+
imageOptions.rounded ? 'rounded-r-md' : ''
|
|
287
|
+
)}
|
|
288
|
+
></div>
|
|
289
|
+
<span
|
|
290
|
+
class="relative inline-flex h-10 w-10 items-center justify-center rounded-full bg-white/30 opacity-0 transition-opacity duration-300 group-hover:opacity-100 group-focus:ring-4 group-focus:ring-white group-focus:outline-hidden"
|
|
291
|
+
>
|
|
292
|
+
<svg
|
|
293
|
+
class="h-4 w-4 text-white"
|
|
294
|
+
aria-hidden="true"
|
|
295
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
296
|
+
fill="none"
|
|
297
|
+
viewBox="0 0 6 10"
|
|
298
|
+
>
|
|
299
|
+
<path
|
|
300
|
+
stroke="currentColor"
|
|
301
|
+
stroke-linecap="round"
|
|
302
|
+
stroke-linejoin="round"
|
|
303
|
+
stroke-width="2"
|
|
304
|
+
d="m1 9 4-4-4-4"
|
|
305
|
+
/>
|
|
306
|
+
</svg>
|
|
307
|
+
<span class="sr-only">Next</span>
|
|
308
|
+
</span>
|
|
309
|
+
</button>
|
|
310
|
+
{/if}
|
|
311
|
+
</menu>
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { ImageMetadata } from 'astro';
|
|
2
|
+
import { Carousel } from 'flowbite';
|
|
3
|
+
interface ImageOptions {
|
|
4
|
+
rounded?: boolean;
|
|
5
|
+
height?: string;
|
|
6
|
+
translateUp?: boolean;
|
|
7
|
+
}
|
|
8
|
+
interface CarouselProps {
|
|
9
|
+
images: (ImageMetadata | string)[];
|
|
10
|
+
carouselID?: number;
|
|
11
|
+
clickablePages?: boolean;
|
|
12
|
+
duration?: number;
|
|
13
|
+
animationSpeed?: string;
|
|
14
|
+
pauseOnHover?: boolean;
|
|
15
|
+
heightOverride?: string;
|
|
16
|
+
defaultPage?: number;
|
|
17
|
+
showSliderIndicators?: boolean;
|
|
18
|
+
imageOptions?: ImageOptions;
|
|
19
|
+
bgColor?: string;
|
|
20
|
+
object?: string;
|
|
21
|
+
class?: string;
|
|
22
|
+
}
|
|
23
|
+
declare const Carousel: import("svelte").Component<CarouselProps, {}, "">;
|
|
24
|
+
type Carousel = ReturnType<typeof Carousel>;
|
|
25
|
+
export default Carousel;
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { DropdownItem } from '../util/type.js';
|
|
3
|
+
import { onMount } from 'svelte';
|
|
4
|
+
import { fly } from 'svelte/transition';
|
|
5
|
+
|
|
6
|
+
interface DropdownProps {
|
|
7
|
+
// Options
|
|
8
|
+
// Label before options chosen
|
|
9
|
+
defaultLabel?: string;
|
|
10
|
+
// First item is loaded by default?
|
|
11
|
+
firstItemDefault?: boolean;
|
|
12
|
+
|
|
13
|
+
// Function that fires when the user selects something
|
|
14
|
+
onselect: (selection: string) => void;
|
|
15
|
+
|
|
16
|
+
// List of selections
|
|
17
|
+
items: DropdownItem[];
|
|
18
|
+
|
|
19
|
+
// TailwindCSS
|
|
20
|
+
|
|
21
|
+
itemColor?: string; // Styling for individual items
|
|
22
|
+
textColor?: string;
|
|
23
|
+
color?: string;
|
|
24
|
+
hoverColor?: string;
|
|
25
|
+
ringColor?: string;
|
|
26
|
+
|
|
27
|
+
minWidth?: string;
|
|
28
|
+
textSize?: string;
|
|
29
|
+
class?: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Props
|
|
33
|
+
let {
|
|
34
|
+
defaultLabel = '',
|
|
35
|
+
firstItemDefault = true,
|
|
36
|
+
onselect,
|
|
37
|
+
items,
|
|
38
|
+
itemColor = 'hover:bg-indigo-900',
|
|
39
|
+
|
|
40
|
+
textColor = 'text-white',
|
|
41
|
+
color = 'bg-indigo-700',
|
|
42
|
+
hoverColor = 'bg-indigo-800',
|
|
43
|
+
ringColor = 'hover:ring-indigo-800 focus-visible:ring-indigo-800',
|
|
44
|
+
minWidth = 'min-w-full',
|
|
45
|
+
textSize = 'text-md',
|
|
46
|
+
class: otherClasses = ''
|
|
47
|
+
}: DropdownProps = $props();
|
|
48
|
+
|
|
49
|
+
let dropdownOpen = $state(false);
|
|
50
|
+
let currItem: DropdownItem | null = $state(null);
|
|
51
|
+
|
|
52
|
+
onMount(() => {
|
|
53
|
+
// Initially set the current option to the first one
|
|
54
|
+
if (firstItemDefault) {
|
|
55
|
+
currItem = items[0];
|
|
56
|
+
onselect(currItem.value);
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
</script>
|
|
60
|
+
|
|
61
|
+
<div class={`relative ${otherClasses}`}>
|
|
62
|
+
<form
|
|
63
|
+
onmouseleave={() => {
|
|
64
|
+
dropdownOpen = false;
|
|
65
|
+
}}
|
|
66
|
+
>
|
|
67
|
+
<button
|
|
68
|
+
onmouseenter={() => {
|
|
69
|
+
dropdownOpen = true;
|
|
70
|
+
}}
|
|
71
|
+
class={`flex ${textSize} ${color} ${textColor} ${ringColor} ${minWidth} items-center rounded-t-lg px-5 py-2.5 text-center transition duration-200 `.concat(
|
|
72
|
+
dropdownOpen ? `rounded-br-lg ${hoverColor}` : 'rounded-b-lg'
|
|
73
|
+
)}
|
|
74
|
+
type="button"
|
|
75
|
+
>
|
|
76
|
+
{#if items && currItem !== null}
|
|
77
|
+
{currItem.label}
|
|
78
|
+
{:else}
|
|
79
|
+
{defaultLabel}
|
|
80
|
+
{/if}
|
|
81
|
+
|
|
82
|
+
<!-- Down Arrow -->
|
|
83
|
+
<div class="ml-auto">
|
|
84
|
+
<svg
|
|
85
|
+
class="ms-3 h-2.5 w-2.5"
|
|
86
|
+
aria-hidden="true"
|
|
87
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
88
|
+
fill="none"
|
|
89
|
+
viewBox="0 0 10 6"
|
|
90
|
+
>
|
|
91
|
+
<path
|
|
92
|
+
stroke="currentColor"
|
|
93
|
+
stroke-linecap="round"
|
|
94
|
+
stroke-linejoin="round"
|
|
95
|
+
stroke-width="2"
|
|
96
|
+
d="m1 1 4 4 4-4"
|
|
97
|
+
/>
|
|
98
|
+
</svg>
|
|
99
|
+
</div>
|
|
100
|
+
</button>
|
|
101
|
+
<!-- Dropdown menu -->
|
|
102
|
+
{#if dropdownOpen}
|
|
103
|
+
<div
|
|
104
|
+
transition:fly={{ duration: 150, y: -10 }}
|
|
105
|
+
class={`${hoverColor} ${minWidth} absolute z-10 rounded-tr-lg rounded-b-lg shadow-2xl`}
|
|
106
|
+
>
|
|
107
|
+
<ul class={`py-2 ${textSize} ${textColor}`}>
|
|
108
|
+
{#each items as dropdownOption}
|
|
109
|
+
<li>
|
|
110
|
+
<button
|
|
111
|
+
onclick={() => {
|
|
112
|
+
onselect(dropdownOption.value);
|
|
113
|
+
currItem = dropdownOption;
|
|
114
|
+
dropdownOpen = false;
|
|
115
|
+
}}
|
|
116
|
+
class={`${itemColor} w-full cursor-pointer px-4 py-2 transition duration-200 ease-in-out`}
|
|
117
|
+
>
|
|
118
|
+
{dropdownOption.label}
|
|
119
|
+
</button>
|
|
120
|
+
</li>
|
|
121
|
+
{/each}
|
|
122
|
+
</ul>
|
|
123
|
+
</div>
|
|
124
|
+
{/if}
|
|
125
|
+
</form>
|
|
126
|
+
</div>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { DropdownItem } from '../util/type.js';
|
|
2
|
+
interface DropdownProps {
|
|
3
|
+
defaultLabel?: string;
|
|
4
|
+
firstItemDefault?: boolean;
|
|
5
|
+
onselect: (selection: string) => void;
|
|
6
|
+
items: DropdownItem[];
|
|
7
|
+
itemColor?: string;
|
|
8
|
+
textColor?: string;
|
|
9
|
+
color?: string;
|
|
10
|
+
hoverColor?: string;
|
|
11
|
+
ringColor?: string;
|
|
12
|
+
minWidth?: string;
|
|
13
|
+
textSize?: string;
|
|
14
|
+
class?: string;
|
|
15
|
+
}
|
|
16
|
+
declare const Dropdown: import("svelte").Component<DropdownProps, {}, "">;
|
|
17
|
+
type Dropdown = ReturnType<typeof Dropdown>;
|
|
18
|
+
export default Dropdown;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
|
|
4
|
+
interface HeroProps {
|
|
5
|
+
// Background of the hero image
|
|
6
|
+
background: Snippet;
|
|
7
|
+
|
|
8
|
+
// Content
|
|
9
|
+
children: Snippet;
|
|
10
|
+
|
|
11
|
+
// Height of the child div
|
|
12
|
+
childHeight?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const { background, children, childHeight = 'h-full' }: HeroProps = $props();
|
|
16
|
+
</script>
|
|
17
|
+
|
|
18
|
+
<main class="relative h-max overflow-hidden">
|
|
19
|
+
<div class="absolute inset-0 top-0 -z-50">
|
|
20
|
+
{@render background()}
|
|
21
|
+
</div>
|
|
22
|
+
<div
|
|
23
|
+
class={`${childHeight} relative z-0 flex flex-col items-center justify-center p-4 text-center`}
|
|
24
|
+
>
|
|
25
|
+
{@render children()}
|
|
26
|
+
</div>
|
|
27
|
+
</main>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
interface HeroProps {
|
|
3
|
+
background: Snippet;
|
|
4
|
+
children: Snippet;
|
|
5
|
+
childHeight?: string;
|
|
6
|
+
}
|
|
7
|
+
declare const Hero: import("svelte").Component<HeroProps, {}, "">;
|
|
8
|
+
type Hero = ReturnType<typeof Hero>;
|
|
9
|
+
export default Hero;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
|
|
4
|
+
interface ModalProps {
|
|
5
|
+
// Function that fires when the modal is closed
|
|
6
|
+
onclose: () => void;
|
|
7
|
+
// Whether or not to show the modal
|
|
8
|
+
showModal: boolean;
|
|
9
|
+
|
|
10
|
+
styling?: string; // Styles in TailwindCSS
|
|
11
|
+
blurStyle?: string;
|
|
12
|
+
|
|
13
|
+
// Clicking Closes?
|
|
14
|
+
clickingBackdropCloses?: boolean;
|
|
15
|
+
clickingModalCloses?: boolean;
|
|
16
|
+
|
|
17
|
+
children: Snippet;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const {
|
|
21
|
+
onclose,
|
|
22
|
+
showModal = false,
|
|
23
|
+
styling = 'bg-gradient-to-b from-slate-700/20 to-gray-700/20 border border-slate-500/60',
|
|
24
|
+
blurStyle = 'backdrop-blur-md',
|
|
25
|
+
clickingBackdropCloses = true,
|
|
26
|
+
clickingModalCloses = false,
|
|
27
|
+
children
|
|
28
|
+
}: ModalProps = $props();
|
|
29
|
+
</script>
|
|
30
|
+
|
|
31
|
+
{#if showModal}
|
|
32
|
+
<!-- Backdrop -->
|
|
33
|
+
<button
|
|
34
|
+
class="fixed inset-0 z-50 flex items-center justify-center bg-black/60 hover:cursor-default"
|
|
35
|
+
onclick={() => {
|
|
36
|
+
if (clickingBackdropCloses) {
|
|
37
|
+
onclose();
|
|
38
|
+
}
|
|
39
|
+
}}
|
|
40
|
+
>
|
|
41
|
+
<!-- Modal container -->
|
|
42
|
+
<!-- svelte-ignore a11y_click_events_have_key_events, a11y_no_noninteractive_element_interactions -->
|
|
43
|
+
<div
|
|
44
|
+
role="alertdialog"
|
|
45
|
+
tabindex={-1}
|
|
46
|
+
class={`${styling} ${blurStyle} max-h-[90%] max-w-[90%] overflow-x-hidden overflow-y-scroll rounded-xl p-6 shadow-lg`}
|
|
47
|
+
onclick={(e) => {
|
|
48
|
+
if (clickingModalCloses) {
|
|
49
|
+
onclose();
|
|
50
|
+
} else {
|
|
51
|
+
e.stopPropagation();
|
|
52
|
+
}
|
|
53
|
+
}}
|
|
54
|
+
style="-ms-overflow-style: none; scrollbar-width: none;"
|
|
55
|
+
>
|
|
56
|
+
{@render children()}
|
|
57
|
+
</div>
|
|
58
|
+
</button>
|
|
59
|
+
{/if}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
interface ModalProps {
|
|
3
|
+
onclose: () => void;
|
|
4
|
+
showModal: boolean;
|
|
5
|
+
styling?: string;
|
|
6
|
+
blurStyle?: string;
|
|
7
|
+
clickingBackdropCloses?: boolean;
|
|
8
|
+
clickingModalCloses?: boolean;
|
|
9
|
+
children: Snippet;
|
|
10
|
+
}
|
|
11
|
+
declare const Modal: import("svelte").Component<ModalProps, {}, "">;
|
|
12
|
+
type Modal = ReturnType<typeof Modal>;
|
|
13
|
+
export default Modal;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { ImageMetadata } from 'astro';
|
|
2
|
+
export declare function getImageSource(image: HTMLImageElement | ImageMetadata | string): string;
|
|
3
|
+
export declare function measureImageHeights(imageElements: HTMLImageElement[], imageStyle?: string): Promise<{
|
|
4
|
+
height: number;
|
|
5
|
+
ratio: number;
|
|
6
|
+
}>;
|
|
7
|
+
export declare function preloadImages(images: (ImageMetadata | string)[], imageClass?: string): HTMLImageElement[];
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// Given an ImageMetadata or a string representing an image, returns the source
|
|
2
|
+
export function getImageSource(image) {
|
|
3
|
+
return typeof image === 'object' && 'src' in image ? image.src : image;
|
|
4
|
+
}
|
|
5
|
+
// Measure the height and aspect ratio of each image as it would be rendered
|
|
6
|
+
export async function measureImageHeights(imageElements, imageStyle) {
|
|
7
|
+
const hiddenContainer = document.createElement('div');
|
|
8
|
+
hiddenContainer.style.visibility = 'hidden';
|
|
9
|
+
hiddenContainer.style.position = 'absolute';
|
|
10
|
+
hiddenContainer.style.top = '-9999px';
|
|
11
|
+
hiddenContainer.style.left = '-9999px';
|
|
12
|
+
hiddenContainer.style.width = '1000px'; // simulate real layout width
|
|
13
|
+
document.body.appendChild(hiddenContainer);
|
|
14
|
+
const imageObjects = imageElements.map((img) => {
|
|
15
|
+
const image = new Image();
|
|
16
|
+
image.src = getImageSource(img);
|
|
17
|
+
image.className = imageStyle ?? '';
|
|
18
|
+
hiddenContainer.appendChild(image);
|
|
19
|
+
return image;
|
|
20
|
+
});
|
|
21
|
+
// Wait until all images load and can be measured
|
|
22
|
+
await Promise.all(imageObjects.map((img) => new Promise((resolve) => {
|
|
23
|
+
img.onload = () => resolve();
|
|
24
|
+
})));
|
|
25
|
+
let maxHeight = 0;
|
|
26
|
+
let bestRatio = 1;
|
|
27
|
+
for (const img of imageObjects) {
|
|
28
|
+
const height = img.offsetHeight;
|
|
29
|
+
const width = img.offsetWidth;
|
|
30
|
+
const ratio = width / height;
|
|
31
|
+
if (height > maxHeight) {
|
|
32
|
+
maxHeight = height;
|
|
33
|
+
bestRatio = ratio;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
document.body.removeChild(hiddenContainer);
|
|
37
|
+
return { height: maxHeight, ratio: bestRatio };
|
|
38
|
+
}
|
|
39
|
+
// Returns a list of image objects given a list of ImageMetadatas / image sources
|
|
40
|
+
export function preloadImages(images, imageClass) {
|
|
41
|
+
return images.map((img) => {
|
|
42
|
+
const image = new Image();
|
|
43
|
+
image.src = getImageSource(img);
|
|
44
|
+
image.className = imageClass ?? '';
|
|
45
|
+
return image;
|
|
46
|
+
});
|
|
47
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@rezcom/rez-components",
|
|
3
|
+
"version": "0.0.2",
|
|
4
|
+
"scripts": {
|
|
5
|
+
"dev": "vite dev",
|
|
6
|
+
"build": "vite build && npm run prepack",
|
|
7
|
+
"preview": "vite preview",
|
|
8
|
+
"prepare": "svelte-kit sync || echo ''",
|
|
9
|
+
"prepack": "svelte-kit sync && svelte-package && publint",
|
|
10
|
+
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
|
11
|
+
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
|
12
|
+
"format": "prettier --write .",
|
|
13
|
+
"lint": "prettier --check . && eslint ."
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist",
|
|
17
|
+
"!dist/**/*.test.*",
|
|
18
|
+
"!dist/**/*.spec.*"
|
|
19
|
+
],
|
|
20
|
+
"sideEffects": [
|
|
21
|
+
"**/*.css"
|
|
22
|
+
],
|
|
23
|
+
"svelte": "./dist/index.js",
|
|
24
|
+
"types": "./dist/index.d.ts",
|
|
25
|
+
"type": "module",
|
|
26
|
+
"exports": {
|
|
27
|
+
".": {
|
|
28
|
+
"types": "./dist/index.d.ts",
|
|
29
|
+
"svelte": "./dist/index.js"
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
"peerDependencies": {
|
|
33
|
+
"svelte": "^5.0.0"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@eslint/compat": "^1.3.2",
|
|
37
|
+
"@eslint/js": "^9.36.0",
|
|
38
|
+
"@sveltejs/adapter-auto": "^6.1.0",
|
|
39
|
+
"@sveltejs/kit": "^2.42.2",
|
|
40
|
+
"@sveltejs/package": "^2.5.2",
|
|
41
|
+
"@sveltejs/vite-plugin-svelte": "^6.2.0",
|
|
42
|
+
"@tailwindcss/forms": "^0.5.10",
|
|
43
|
+
"@tailwindcss/typography": "^0.5.18",
|
|
44
|
+
"@tailwindcss/vite": "^4.1.13",
|
|
45
|
+
"@types/node": "^24.5.2",
|
|
46
|
+
"astro": "^5.13.9",
|
|
47
|
+
"eslint": "^9.36.0",
|
|
48
|
+
"eslint-config-prettier": "^10.1.8",
|
|
49
|
+
"eslint-plugin-svelte": "^3.12.3",
|
|
50
|
+
"flowbite": "^3.1.2",
|
|
51
|
+
"globals": "^16.4.0",
|
|
52
|
+
"prettier": "^3.6.2",
|
|
53
|
+
"prettier-plugin-svelte": "^3.4.0",
|
|
54
|
+
"prettier-plugin-tailwindcss": "^0.6.14",
|
|
55
|
+
"publint": "^0.3.13",
|
|
56
|
+
"svelte": "^5.39.2",
|
|
57
|
+
"svelte-check": "^4.3.1",
|
|
58
|
+
"tailwindcss": "^4.1.13",
|
|
59
|
+
"typescript": "^5.9.2",
|
|
60
|
+
"typescript-eslint": "^8.44.0",
|
|
61
|
+
"vite": "^7.1.6"
|
|
62
|
+
},
|
|
63
|
+
"keywords": [
|
|
64
|
+
"svelte"
|
|
65
|
+
],
|
|
66
|
+
"pnpm": {
|
|
67
|
+
"onlyBuiltDependencies": [
|
|
68
|
+
"@tailwindcss/oxide",
|
|
69
|
+
"esbuild",
|
|
70
|
+
"sharp"
|
|
71
|
+
]
|
|
72
|
+
}
|
|
73
|
+
}
|