migrate web to svelte 5
This commit is contained in:
@@ -2,8 +2,14 @@
|
||||
import { Badge } from '@cio/ui/base/badge';
|
||||
import { formatDate } from '$lib/utils/format-date';
|
||||
|
||||
export let post;
|
||||
export let isRecommended = false;
|
||||
/**
|
||||
* @typedef {Object} Props
|
||||
* @property {any} post
|
||||
* @property {boolean} [isRecommended]
|
||||
*/
|
||||
|
||||
/** @type {Props} */
|
||||
let { post, isRecommended = false } = $props();
|
||||
</script>
|
||||
|
||||
{#if !isRecommended}
|
||||
|
||||
@@ -1,13 +1,29 @@
|
||||
<script>
|
||||
import { flexible, leftFist, rightFist, robotArm, thumbsUp } from '$lib/emojis';
|
||||
|
||||
export let id = '';
|
||||
export let rightToLeft = false;
|
||||
export let tagline = '';
|
||||
export let title = '';
|
||||
export let description = '';
|
||||
export let video = '';
|
||||
export let taglineIcon = '';
|
||||
/**
|
||||
* @typedef {Object} Props
|
||||
* @property {string} [id]
|
||||
* @property {boolean} [rightToLeft]
|
||||
* @property {string} [tagline]
|
||||
* @property {string} [title]
|
||||
* @property {string} [description]
|
||||
* @property {string} [video]
|
||||
* @property {string} [taglineIcon]
|
||||
* @property {import('svelte').Snippet} [more]
|
||||
*/
|
||||
|
||||
/** @type {Props} */
|
||||
let {
|
||||
id = '',
|
||||
rightToLeft = false,
|
||||
tagline = '',
|
||||
title = '',
|
||||
description = '',
|
||||
video = '',
|
||||
taglineIcon = '',
|
||||
more
|
||||
} = $props();
|
||||
</script>
|
||||
|
||||
<section {id} class="w-full py-32 border-b border-x-0 border-t-0 border-gray-200">
|
||||
@@ -32,15 +48,15 @@
|
||||
{/if}
|
||||
<p class="font-medium text-base">{tagline}</p>
|
||||
</div>
|
||||
<h2 class="font-bold text-4xl mb-3 lg:mb-6">{title}</h2>
|
||||
<h2 class="text-3xl mb-3 lg:mb-6">{title}</h2>
|
||||
<p class="leading-8 text-lg text-gray-500">
|
||||
{description}
|
||||
</p>
|
||||
{#if $$slots.more}
|
||||
{#if more}
|
||||
<br />
|
||||
|
||||
<p class="leading-8 text-lg text-gray-500">
|
||||
<slot name="more" />
|
||||
{@render more?.()}
|
||||
</p>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
<script lang="ts">
|
||||
export let src: string;
|
||||
export let alt: string;
|
||||
interface Props {
|
||||
src: string;
|
||||
alt: string;
|
||||
}
|
||||
|
||||
let { src, alt }: Props = $props();
|
||||
</script>
|
||||
|
||||
<figure>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
import Youtube from '@lucide/svelte/icons/youtube';
|
||||
</script>
|
||||
|
||||
<div class="border-t-[1px] px-[2%] lg:px-[14%]">
|
||||
<div class="border-t px-[2%] lg:px-[14%]">
|
||||
<div class="flex flex-col items-center justify-between py-[10%] pl-5 lg:flex-row lg:pl-0">
|
||||
<div class="w-full min-w-[200px] lg:w-3/12">
|
||||
<img width="32" height="32" loading="lazy" src="/logo-192.png" alt="classroomio logo" class="mb-5" />
|
||||
|
||||
@@ -11,8 +11,8 @@
|
||||
{ label: 'Customers', color: '#0233BD' },
|
||||
{ label: 'Students', color: '#9747FF' }
|
||||
];
|
||||
let animate = false;
|
||||
let titleIndex = 0;
|
||||
let animate = $state(false);
|
||||
let titleIndex = $state(0);
|
||||
let timeout: NodeJS.Timeout;
|
||||
|
||||
onMount(() => {
|
||||
|
||||
@@ -19,4 +19,3 @@ export { default as Testimonial } from './testimonial.svelte';
|
||||
export { default as Timeline } from './timeline.svelte';
|
||||
export { default as ToolsHeader } from './tools-header.svelte';
|
||||
export { default as UploadWidget } from './upload-widget.svelte';
|
||||
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
<script>
|
||||
export let className = '';
|
||||
export let open = false;
|
||||
/**
|
||||
* @typedef {Object} Props
|
||||
* @property {string} [className]
|
||||
* @property {boolean} [open]
|
||||
* @property {import('svelte').Snippet} [children]
|
||||
*/
|
||||
|
||||
/** @type {Props} */
|
||||
let { className = '', open = false, children } = $props();
|
||||
</script>
|
||||
|
||||
{#if open}
|
||||
@@ -10,7 +17,7 @@
|
||||
<div
|
||||
class="fixed z-[3001] {className} w-full md:w-[30%] md:min-w-[500px] bg-white rounded-md mx-auto py-6 px-7"
|
||||
>
|
||||
<slot />
|
||||
{@render children?.()}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
import { folder, globe, team, transcription } from '$lib/emojis';
|
||||
import { tools } from '$lib/utils/constants/tools';
|
||||
|
||||
let selected = 0;
|
||||
let interval: NodeJS.Timer;
|
||||
let selected = $state(0);
|
||||
let interval: NodeJS.Timer = $state();
|
||||
|
||||
const moreFeatures = [
|
||||
{
|
||||
@@ -22,13 +22,13 @@
|
||||
'Extend invitations and manage your teaching institution seamlessly through ClassroomIO.',
|
||||
image: '/team-management.png'
|
||||
},
|
||||
{
|
||||
icon: transcription,
|
||||
title: 'Auto Video Transcription',
|
||||
description:
|
||||
'AI automatically transcribes videos, making content searchable for seamless student access.',
|
||||
image: '/video-transcription.png'
|
||||
},
|
||||
// {
|
||||
// icon: transcription,
|
||||
// title: 'Auto Video Transcription',
|
||||
// description:
|
||||
// 'AI automatically transcribes videos, making content searchable for seamless student access.',
|
||||
// image: '/video-transcription.png'
|
||||
// },
|
||||
{
|
||||
icon: folder,
|
||||
title: 'Downloadable Lesson PDF',
|
||||
@@ -52,7 +52,7 @@
|
||||
<section id="morefeatures" class="py-[10%] px-3 lg:px-0 bg-gray-50">
|
||||
<div class="mx-0 lg:mx-[12%]">
|
||||
<div class="w-full lg:w-4/5 mx-4 lg:mx-0 mb-14">
|
||||
<h2 class="mt-2 text-3xl font-bold font-display tracking-tight lg:text-4xl">
|
||||
<h2 class="mt-2 text-3xl tracking-tight">
|
||||
There is More...
|
||||
</h2>
|
||||
<p class="mt-4 text-lg text-gray-500 w-[94%] lg:w-4/5">
|
||||
@@ -67,8 +67,8 @@
|
||||
{#each moreFeatures as moreFeature, i}
|
||||
<button
|
||||
class="p-4 rounded-md {i === selected &&
|
||||
'selected'} mb-3 text-start min-w-[75vw] lg:min-w-[unset]"
|
||||
on:mouseenter={() => {
|
||||
'selected'} mb-3 text-start min-w-[75vw] lg:min-w-[unset] cursor-pointer"
|
||||
onclick={() => {
|
||||
selected = i;
|
||||
clearInterval(interval);
|
||||
}}
|
||||
@@ -82,11 +82,11 @@
|
||||
alt=""
|
||||
class="w-8"
|
||||
/>
|
||||
<p class="text-lg font-semibold">
|
||||
<p class="text-lg">
|
||||
{moreFeature.title}
|
||||
</p>
|
||||
</div>
|
||||
<p>{moreFeature.description}</p>
|
||||
<p class="text-sm">{moreFeature.description}</p>
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
@@ -99,7 +99,7 @@
|
||||
height="100%"
|
||||
loading="lazy"
|
||||
id="image"
|
||||
in:fly={{ y: 100, duration: 1000 }}
|
||||
in:fly={{ x: 100, duration: 800 }}
|
||||
class="w-full flex flex-col items-center"
|
||||
src={moreFeatures[selected].image}
|
||||
alt={moreFeatures[selected].title}
|
||||
|
||||
@@ -14,13 +14,28 @@
|
||||
import type { HTMLAttributes } from 'svelte/elements';
|
||||
import * as NavigationMenu from '@cio/ui/base/navigation-menu';
|
||||
import LibraryBigIcon from '@lucide/svelte/icons/library-big';
|
||||
import { cn } from '@cio/ui/tools';
|
||||
|
||||
|
||||
export let stars: number;
|
||||
let { stars }: { stars: number } = $props();
|
||||
|
||||
type ListItemProps = HTMLAttributes<HTMLAnchorElement> & {
|
||||
title: string;
|
||||
href: string;
|
||||
subtitle: string;
|
||||
key: string;
|
||||
};
|
||||
|
||||
let showDrawer = false;
|
||||
let showSolutions = false;
|
||||
let activeLink = '';
|
||||
let activeLink = $derived(page.url.pathname);
|
||||
let activeHash = $derived(page.url.hash);
|
||||
|
||||
$effect(() => {
|
||||
console.log('activeLink', activeLink);
|
||||
console.log('activeHash', activeHash);
|
||||
console.log('isSolutionsActive', isSolutionsActive);
|
||||
});
|
||||
|
||||
function handleShowDrawer() {
|
||||
showDrawer = !showDrawer;
|
||||
@@ -76,16 +91,7 @@
|
||||
}
|
||||
];
|
||||
|
||||
type ListItemProps = HTMLAttributes<HTMLAnchorElement> & {
|
||||
title: string;
|
||||
href: string;
|
||||
subtitle: string;
|
||||
key: string;
|
||||
};
|
||||
|
||||
$: activeLink = page.url.pathname;
|
||||
$: activeHash = page.url.hash;
|
||||
$: isSolutionsActive = solutions.some((s) => activeHash.includes(s.key));
|
||||
let isSolutionsActive = $derived(!!solutions.some((s) => activeLink.includes(s.key)));
|
||||
</script>
|
||||
|
||||
{#snippet list_item({ title, subtitle, href, key, class: className, ...restProps }: ListItemProps)}
|
||||
@@ -140,11 +146,14 @@
|
||||
</a>
|
||||
|
||||
<section class="hidden lg:block">
|
||||
<NavigationMenu.Root class="rounded-lg bg-white p-1">
|
||||
<NavigationMenu.Root class="p-1">
|
||||
<NavigationMenu.List class="flex w-full items-center justify-center gap-1">
|
||||
<NavigationMenu.Item>
|
||||
<NavigationMenu.Trigger
|
||||
class="flex cursor-pointer items-center rounded-md px-4 py-2 text-sm font-medium text-gray-800 transition-all duration-200 hover:bg-gray-100 data-[state=open]:bg-gray-100"
|
||||
class={cn(
|
||||
"flex cursor-pointer items-center rounded-md px-4 py-2 text-sm font-medium text-gray-800 transition-all duration-200 hover:bg-gray-100 data-[state=open]:bg-gray-100",
|
||||
isSolutionsActive && 'bg-gray-100!'
|
||||
)}
|
||||
>
|
||||
<a href="/" class="no-underline"> Solutions </a>
|
||||
</NavigationMenu.Trigger>
|
||||
@@ -164,7 +173,7 @@
|
||||
|
||||
<NavigationMenu.Item>
|
||||
<NavigationMenu.Trigger
|
||||
class="flex cursor-pointer items-center rounded-md px-4 py-2 text-sm font-medium text-gray-800 transition-all duration-200 hover:bg-gray-100 data-[state=open]:bg-gray-100"
|
||||
class={cn("flex cursor-pointer items-center rounded-md px-4 py-2 text-sm font-medium text-gray-800 transition-all duration-200 hover:bg-gray-100 data-[state=open]:bg-gray-100", activeLink.startsWith('/tools') && 'bg-gray-100!')}
|
||||
>
|
||||
<a href="/tools" class="no-underline"> Free Tools </a>
|
||||
</NavigationMenu.Trigger>
|
||||
@@ -187,11 +196,10 @@
|
||||
{#snippet child()}
|
||||
<a
|
||||
href="/blog"
|
||||
class="cursor-pointer rounded-md px-4 py-2 text-sm font-medium text-gray-800 no-underline transition-all duration-200 hover:bg-gray-100 {activeLink.startsWith(
|
||||
'/blog'
|
||||
)
|
||||
? 'bg-gray-100'
|
||||
: ''}"
|
||||
class={cn(
|
||||
'cursor-pointer rounded-md px-4 py-2 text-sm font-medium text-gray-800 no-underline transition-all duration-200 hover:bg-gray-100',
|
||||
activeLink.startsWith('/blog') && 'bg-gray-100'
|
||||
)}
|
||||
>
|
||||
Blog
|
||||
</a>
|
||||
@@ -204,11 +212,10 @@
|
||||
{#snippet child()}
|
||||
<a
|
||||
href="/pricing"
|
||||
class="cursor-pointer rounded-md px-4 py-2 text-sm font-medium text-gray-800 no-underline transition-all duration-200 hover:bg-gray-100 {activeLink.startsWith(
|
||||
'/pricing'
|
||||
)
|
||||
? 'bg-gray-100'
|
||||
: ''}"
|
||||
class={cn(
|
||||
'cursor-pointer rounded-md px-4 py-2 text-sm font-medium text-gray-800 no-underline transition-all duration-200 hover:bg-gray-100',
|
||||
activeLink.startsWith('/pricing') && 'bg-gray-100'
|
||||
)}
|
||||
>
|
||||
Pricing
|
||||
</a>
|
||||
@@ -250,7 +257,7 @@
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<button type="button" aria-label="Hamburger Menu" class="block md:block lg:hidden" on:click={handleShowSolutions}>
|
||||
<button type="button" aria-label="Hamburger Menu" class="block md:block lg:hidden" onclick={handleShowSolutions}>
|
||||
<Menu size={24} />
|
||||
</button>
|
||||
|
||||
@@ -262,7 +269,7 @@
|
||||
>
|
||||
<div class="mb-5 flex justify-between py-2">
|
||||
<img loading="lazy" width="20" height="20" src="/logo-512.png" alt="classroomio logo" class="w-[15%]" />
|
||||
<button class="mr-5" on:click={handleShowSolutions}>
|
||||
<button class="mr-5" onclick={handleShowSolutions}>
|
||||
<X size={24} />
|
||||
</button>
|
||||
</div>
|
||||
@@ -270,9 +277,11 @@
|
||||
<ul class="flex w-full flex-col items-center justify-between lg:flex-row">
|
||||
<li class="w-full cursor-pointer text-sm font-semibold text-gray-800 md:text-lg">
|
||||
<button
|
||||
class="flex w-full items-center justify-between rounded-lg px-4 py-3 transition-all duration-200 hover:bg-gray-100"
|
||||
on:click={handleShowDrawer}
|
||||
class:active={isSolutionsActive}
|
||||
class={cn(
|
||||
"flex w-full items-center justify-between rounded-lg px-4 py-3 transition-all duration-200 hover:bg-gray-100",
|
||||
isSolutionsActive && 'bg-gray-100'
|
||||
)}
|
||||
onclick={handleShowDrawer}
|
||||
>
|
||||
Our Superpowers <ChevronDown />
|
||||
</button>
|
||||
@@ -281,7 +290,7 @@
|
||||
{#each solutions as solution}
|
||||
<a
|
||||
href="/{solution.key}"
|
||||
on:click={() => {
|
||||
onclick={() => {
|
||||
handleShowSolutions();
|
||||
}}
|
||||
>
|
||||
@@ -297,37 +306,43 @@
|
||||
</li>
|
||||
<a
|
||||
class="w-full cursor-pointer rounded-xl px-4 py-3 text-sm font-semibold text-gray-800 transition-all duration-200 hover:bg-gray-100 md:text-lg"
|
||||
on:click={handleShowSolutions}
|
||||
onclick={handleShowSolutions}
|
||||
href="/tools"
|
||||
>
|
||||
<li>Free Tools</li>
|
||||
</a>
|
||||
<a
|
||||
class="w-full cursor-pointer rounded-md px-4 py-3 text-sm font-semibold text-gray-800 transition-all duration-200 hover:bg-gray-100 md:text-lg"
|
||||
on:click={() => {
|
||||
class={cn(
|
||||
"w-full cursor-pointer rounded-md px-4 py-3 text-sm font-semibold text-gray-800 transition-all duration-200 hover:bg-gray-100 md:text-lg",
|
||||
activeLink.startsWith('/morefeatures') && 'bg-gray-100'
|
||||
)}
|
||||
onclick={() => {
|
||||
handleShowSolutions();
|
||||
}}
|
||||
class:active={activeLink.startsWith('/blog')}
|
||||
href="/blog"
|
||||
>
|
||||
<li>Blog</li>
|
||||
</a>
|
||||
<a
|
||||
class="w-full cursor-pointer rounded-xl px-4 py-3 text-sm font-semibold text-gray-800 transition-all duration-200 hover:bg-gray-100 md:text-lg"
|
||||
on:click={() => {
|
||||
class={cn(
|
||||
"w-full cursor-pointer rounded-xl px-4 py-3 text-sm font-semibold text-gray-800 transition-all duration-200 hover:bg-gray-100 md:text-lg",
|
||||
activeLink.startsWith('/pricing') && 'bg-gray-100'
|
||||
)}
|
||||
onclick={() => {
|
||||
handleShowSolutions();
|
||||
}}
|
||||
href="/pricing"
|
||||
class:active={activeLink.startsWith('/pricing')}
|
||||
>
|
||||
<li>Pricing</li>
|
||||
</a>
|
||||
<a
|
||||
class="w-full cursor-pointer rounded-md px-4 py-3 text-sm font-semibold text-gray-800 transition-all duration-200 hover:bg-gray-100 md:text-lg"
|
||||
on:click={() => {
|
||||
class={cn(
|
||||
"w-full cursor-pointer rounded-md px-4 py-3 text-sm font-semibold text-gray-800 transition-all duration-200 hover:bg-gray-100 md:text-lg",
|
||||
activeHash.includes('morefeatures') && 'bg-gray-100'
|
||||
)}
|
||||
onclick={() => {
|
||||
handleShowSolutions();
|
||||
}}
|
||||
class:active={activeHash.includes('morefeatures')}
|
||||
href="/#morefeatures"
|
||||
>
|
||||
<li>More features</li>
|
||||
|
||||
@@ -4,8 +4,14 @@
|
||||
import SearchXIcon from '@lucide/svelte/icons/search-x';
|
||||
import { Button } from '@cio/ui/base/button';
|
||||
|
||||
export let isOrg = false;
|
||||
export let className = '';
|
||||
/**
|
||||
* @typedef {Object} Props
|
||||
* @property {boolean} [isOrg]
|
||||
* @property {string} [className]
|
||||
*/
|
||||
|
||||
/** @type {Props} */
|
||||
let { isOrg = false, className = '' } = $props();
|
||||
|
||||
function handleClick() {
|
||||
if (!isOrg) {
|
||||
|
||||
@@ -1,7 +1,14 @@
|
||||
<script>
|
||||
export let className = '';
|
||||
/**
|
||||
* @typedef {Object} Props
|
||||
* @property {string} [className]
|
||||
* @property {import('svelte').Snippet} [children]
|
||||
*/
|
||||
|
||||
/** @type {Props} */
|
||||
let { className = '', children } = $props();
|
||||
</script>
|
||||
|
||||
<header class="hero-bg w-full border-b border-slate-100 pb-16 pt-32 lg:min-h-[20rem] {className}">
|
||||
<slot />
|
||||
{@render children?.()}
|
||||
</header>
|
||||
|
||||
@@ -17,11 +17,11 @@
|
||||
>
|
||||
<div>
|
||||
<h3
|
||||
class="font-display mt-3 text-xl font-semibold tracking-tight text-white lg:mt-2 lg:text-4xl max-w-2xl"
|
||||
class="font-display mt-3 text-xl tracking-tight text-white lg:mt-2 lg:text-4xl max-w-2xl"
|
||||
>
|
||||
{header}
|
||||
</h3>
|
||||
<p class="mt-6 text-xl text-gray-300 lg:text-lg">
|
||||
<p class="mt-6 text-md text-gray-300">
|
||||
{subText}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -2,7 +2,11 @@
|
||||
import { embedSenjaWidget } from '@cio/utils/senja';
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
export let id = '';
|
||||
interface Props {
|
||||
id?: string;
|
||||
}
|
||||
|
||||
let { id = '' }: Props = $props();
|
||||
|
||||
onMount(() => {
|
||||
embedSenjaWidget(id);
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<div
|
||||
class="relative overflow-hidden mt-10 flex space-y-3 flex-col text-center items-center border border-[#D9E0F5] justify-center w-full rounded-md min-h-[300px] p-4 bg-[#F5F8FE]"
|
||||
>
|
||||
<div class="absolute w-20 h-20 bg-[#0233BD] -top-14 -left-9 rotate-[50deg]" />
|
||||
<div class="absolute w-20 h-20 bg-[#0233BD] -top-14 -left-9 rotate-[50deg]"></div>
|
||||
<h1 class="font-bold text-3xl">Your message has been sent 🎉</h1>
|
||||
<p class="w-[70%] font-normal text-sm">
|
||||
We'll get back to you as soon as possible. In the meantime, Explore and enjoy features on our
|
||||
|
||||
@@ -1,25 +1,29 @@
|
||||
<script lang="ts">
|
||||
export let items: {
|
||||
interface Props {
|
||||
items?: {
|
||||
title: string;
|
||||
desc: string;
|
||||
}[] = [];
|
||||
}[];
|
||||
}
|
||||
|
||||
let { items = [] }: Props = $props();
|
||||
</script>
|
||||
|
||||
<div class="relative w-fit flex justify-center space-x-8 md:space-x-10">
|
||||
<div
|
||||
class="w-[3px] md:w-[2px] bg-blue-600 mt-10 before:bg-gradient-to-t before:from-white before:to-transparent before:w-[4px] before:h-[9rem] before:absolute before:bottom-0"
|
||||
/>
|
||||
class="w-[3px] md:w-[2px] bg-blue-600 mt-10 before:bg-linear-to-t before:from-white before:to-transparent before:w-[4px] before:h-36 before:absolute before:bottom-0"
|
||||
></div>
|
||||
<div class="space-y-8">
|
||||
{#each items as card, index}
|
||||
<div class="md:w-[35rem] w-full border-2 rounded-lg px-4 py-8 space-y-4 bg-white">
|
||||
<div class="md:w-140 w-full border-2 rounded-lg px-4 py-8 space-y-4 bg-white">
|
||||
<div class="flex items-center">
|
||||
<div
|
||||
class={`z-50 absolute -left-3 ${
|
||||
index > 0 &&
|
||||
'before:bg-gradient-to-t before:from-white before:to-transparent before:w-[4px] before:h-[9rem] before:absolute before:-top-36'
|
||||
'before:bg-linear-to-t before:from-white before:to-transparent before:w-[4px] before:h-36 before:absolute before:-top-36'
|
||||
} flex items-center justify-center w-7 h-7 bg-blue-100 rounded-full`}
|
||||
>
|
||||
<div class="w-4 h-4 bg-blue-800 rounded-full z-50" />
|
||||
<div class="w-4 h-4 bg-blue-800 rounded-full z-50"></div>
|
||||
</div>
|
||||
<h1 class="text-2xl font-bold text-blue-800">{card.title}</h1>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,14 @@
|
||||
<script>
|
||||
export let className = '';
|
||||
/**
|
||||
* @typedef {Object} Props
|
||||
* @property {string} [className]
|
||||
* @property {import('svelte').Snippet} [children]
|
||||
*/
|
||||
|
||||
/** @type {Props} */
|
||||
let { className = '', children } = $props();
|
||||
</script>
|
||||
|
||||
<header class="tools-hero-bg py-24 text-center {className} border-b border-slate-100 bg-white">
|
||||
<slot />
|
||||
{@render children?.()}
|
||||
</header>
|
||||
|
||||
@@ -3,12 +3,21 @@
|
||||
import Trash2 from '@lucide/svelte/icons/trash-2';
|
||||
import { ALLOWED_IMAGE_TYPES, validateImageUpload } from '@cio/utils/functions/fileValidation';
|
||||
|
||||
export let imageURL = '';
|
||||
export let label = '';
|
||||
export let isRequired = false;
|
||||
export let labelClassName = '';
|
||||
interface Props {
|
||||
imageURL?: string;
|
||||
label?: string;
|
||||
isRequired?: boolean;
|
||||
labelClassName?: string;
|
||||
}
|
||||
|
||||
let {
|
||||
imageURL = $bindable(''),
|
||||
label = '',
|
||||
isRequired = false,
|
||||
labelClassName = ''
|
||||
}: Props = $props();
|
||||
let isUploading = false;
|
||||
let fileInput: HTMLInputElement;
|
||||
let fileInput: HTMLInputElement = $state();
|
||||
|
||||
const onFileSelected = () => {
|
||||
const file = fileInput?.files?.[0];
|
||||
@@ -52,8 +61,8 @@
|
||||
};
|
||||
</script>
|
||||
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
||||
<div>
|
||||
{#if label}
|
||||
<p class="flex w-full items-center justify-between text-left dark:text-white {labelClassName}">
|
||||
@@ -65,7 +74,7 @@
|
||||
</span>
|
||||
|
||||
{#if imageURL}
|
||||
<span title="clear image" on:click={deleteImage}>
|
||||
<span title="clear image" onclick={deleteImage}>
|
||||
<Trash2 class="fill-red-500 " />
|
||||
</span>
|
||||
{/if}
|
||||
@@ -73,14 +82,14 @@
|
||||
{/if}
|
||||
<div
|
||||
class="flex min-h-[200px] w-full items-center justify-center rounded-md border p-4"
|
||||
on:click={() => fileInput.click()}
|
||||
onclick={() => fileInput.click()}
|
||||
>
|
||||
<input
|
||||
type="file"
|
||||
accept=".jpg,.jpeg,.png,.gif,.webp,image/jpeg,image/png,image/gif,image/webp"
|
||||
style="display: none;"
|
||||
bind:this={fileInput}
|
||||
on:change={onFileSelected}
|
||||
onchange={onFileSelected}
|
||||
disabled={isUploading}
|
||||
required={isRequired}
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user