mirror of
https://github.com/nestriness/nestri.git
synced 2025-12-16 18:55:37 +02:00
✨ feat: Add qwik-react (#103)
This adds the following pages: The landing page (/) The pricing page (/pricing) The contact page (/contact) The changelog page (/changelog) Terms Of Service page (/terms) Privacy Policy (/privacy)
This commit is contained in:
105
packages/ui/src/image/basic-image-loader.tsx
Normal file
105
packages/ui/src/image/basic-image-loader.tsx
Normal file
@@ -0,0 +1,105 @@
|
||||
/* eslint-disable qwik/no-use-visible-task */
|
||||
import { cn } from '@/design';
|
||||
import { component$, useSignal, useTask$, useStyles$, useVisibleTask$, $ } from '@builder.io/qwik';
|
||||
|
||||
interface ImageLoaderProps {
|
||||
src: string;
|
||||
alt: string;
|
||||
width?: number;
|
||||
height?: number;
|
||||
class?: string;
|
||||
}
|
||||
|
||||
export const BasicImageLoader = component$((props: ImageLoaderProps) => {
|
||||
const imageLoaded = useSignal(false);
|
||||
const hasError = useSignal(false);
|
||||
const imgRef = useSignal<HTMLImageElement>();
|
||||
|
||||
useStyles$(`
|
||||
@keyframes gradientShift {
|
||||
0% { background-position: 200% 0; }
|
||||
100% { background-position: -200% 0; }
|
||||
}
|
||||
.loading-animation {
|
||||
animation: gradientShift 1.5s infinite linear;
|
||||
background-size: 200% 100%;
|
||||
}
|
||||
`);
|
||||
|
||||
useTask$(({ track }) => {
|
||||
track(() => props.src);
|
||||
imageLoaded.value = false;
|
||||
hasError.value = false;
|
||||
});
|
||||
|
||||
|
||||
useVisibleTask$(async ({ cleanup }) => {
|
||||
const img = imgRef.value;
|
||||
if (!img) return;
|
||||
// const imageData = await imageGetter();
|
||||
|
||||
const checkImageLoaded = async () => {
|
||||
if (img.complete && img.naturalHeight !== 0) {
|
||||
imageLoaded.value = true;
|
||||
}
|
||||
};
|
||||
|
||||
// Check immediately in case the image is already loaded
|
||||
await checkImageLoaded();
|
||||
|
||||
// Set up event listeners
|
||||
const loadHandler = async () => {
|
||||
imageLoaded.value = true;
|
||||
};
|
||||
const errorHandler = () => {
|
||||
hasError.value = true;
|
||||
};
|
||||
|
||||
img.addEventListener('load', loadHandler);
|
||||
img.addEventListener('error', errorHandler);
|
||||
|
||||
// Use MutationObserver to detect src changes
|
||||
const observer = new MutationObserver(checkImageLoaded);
|
||||
observer.observe(img, { attributes: true, attributeFilter: ['src'] });
|
||||
|
||||
cleanup(() => {
|
||||
img.removeEventListener('load', loadHandler);
|
||||
img.removeEventListener('error', errorHandler);
|
||||
observer.disconnect();
|
||||
});
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
{!imageLoaded.value && !hasError.value && (
|
||||
<div
|
||||
class={cn("relative x-[20] inset-0 h-full loading-animation bg-gradient-to-r from-gray-200 via-gray-300 to-gray-200 dark:from-gray-800 dark:via-gray-900 dark:to-gray-800", props.class)}
|
||||
style={{
|
||||
height: props.height,
|
||||
aspectRatio: props.width && props.height ? `${props.width} / ${props.height}` : 'auto'
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<img
|
||||
src={props.src}
|
||||
draggable={false}
|
||||
alt={props.alt}
|
||||
width={props.width}
|
||||
height={props.height}
|
||||
ref={imgRef}
|
||||
class={{
|
||||
'z-[5] relative': imageLoaded.value,
|
||||
'hidden': !imageLoaded.value && !hasError.value,
|
||||
'w-full h-full': imageLoaded.value,
|
||||
'w-16 h-16 text-red-500': hasError.value,
|
||||
[props.class || '']: !!props.class,
|
||||
}}
|
||||
/>
|
||||
{hasError.value && (
|
||||
<p class="text-red-500 text-sm" >
|
||||
Error loading image
|
||||
</p>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
});
|
||||
Reference in New Issue
Block a user