Open dillionverma opened 2 months ago
linking #74 since it has some ports
Border Beam for Svelte
Thank you @gursheyss
<script lang="ts">
import { cn } from '$lib/utils';
interface Props {
class?: string;
size?: number;
duration?: number;
borderWidth?: number;
anchor?: number;
colorFrom?: string;
colorTo?: string;
delay?: number;
}
let {
class: className,
size = 200,
duration = 15,
anchor = 90,
borderWidth = 1.5,
colorFrom = '#ffaa40',
colorTo = '#9c40ff',
delay = 0
}: Props = $props();
let styleVars = $derived.by(() => {
return `--size: ${size};
--duration: ${duration};
--anchor: ${anchor};
--border-width: ${borderWidth};
--color-from: ${colorFrom};
--color-to: ${colorTo};
--delay: -${delay}s;`;
});
</script>
<div
style={styleVars}
class={cn(
'absolute inset-[0] rounded-[inherit] [border:calc(var(--border-width)*1px)_solid_transparent]',
// mask styles
'![mask-clip:padding-box,border-box] ![mask-composite:intersect] [mask:linear-gradient(transparent,transparent),linear-gradient(white,white)]',
// pseudo styles
'after:absolute after:aspect-square after:w-[calc(var(--size)*1px)] after:animate-border-beam after:[animation-delay:var(--delay)] after:[background:linear-gradient(to_left,var(--color-from),var(--color-to),transparent)] after:[offset-anchor:calc(var(--anchor)*1%)_50%] after:[offset-path:rect(0_auto_auto_0_round_calc(var(--size)*1px))]',
className
)}
></div>
Shimmer Button for Svelte
Thank you @gursheyss 🙏
Shimmer Button
<script lang="ts">
import { cn } from '$lib/utils';
import type { Snippet } from 'svelte';
import type { HTMLButtonAttributes } from 'svelte/elements';
interface ShimmerButtonProps extends HTMLButtonAttributes {
shimmerColor?: string;
shimmerSize?: string;
borderRadius?: string;
shimmerDuration?: string;
background?: string;
class?: string;
children?: Snippet;
}
let {
shimmerColor = '#ffffff',
shimmerSize = '0.05em',
shimmerDuration = '3s',
borderRadius = '100px',
background = 'rgba(0, 0, 0, 1)',
class: className,
children,
...restProps
}: ShimmerButtonProps = $props();
let styleVars = $derived.by(() => {
return `--spread: 90deg;
--shimmer-color:${shimmerColor};
--radius: ${borderRadius};
--speed: ${shimmerDuration};
--cut: ${shimmerSize};
--bg: ${background};`;
});
</script>
<button
style={styleVars}
class={cn(
'group relative z-0 flex cursor-pointer items-center justify-center overflow-hidden whitespace-nowrap border border-white/10 px-6 py-3 text-white [background:var(--bg)] [border-radius:var(--radius)] dark:text-black',
'transform-gpu transition-transform duration-300 ease-in-out active:translate-y-[1px]',
className
)}
{...restProps}
>
<!-- spark container -->
<div class={cn('-z-30 blur-[2px]', 'absolute inset-0 overflow-visible [container-type:size]')}>
<!-- spark -->
<div
class="animate-slide absolute inset-0 h-[100cqh] [aspect-ratio:1] [border-radius:0] [mask:none]"
>
<!-- spark before -->
<div
class="animate-spin-around absolute inset-[-100%] w-auto rotate-0 [background:conic-gradient(from_calc(270deg-(var(--spread)*0.5)),transparent_0,var(--shimmer-color)_var(--spread),transparent_var(--spread))] [translate:0_0]"
></div>
</div>
</div>
{#if children}
{@render children()}
{/if}
<!-- Highlight -->
<div
class={cn(
'insert-0 absolute h-full w-full',
'rounded-2xl px-4 py-1.5 text-sm font-medium shadow-[inset_0_-8px_10px_#ffffff1f]',
// transition
'transform-gpu transition-all duration-300 ease-in-out',
// on hover
'group-hover:shadow-[inset_0_-6px_10px_#ffffff3f]',
// on click
'group-active:shadow-[inset_0_-10px_10px_#ffffff3f]'
)}
></div>
<!-- backdrop -->
<div
class={cn(
'absolute -z-20 [background:var(--bg)] [border-radius:var(--radius)] [inset:var(--cut)]'
)}
></div>
</button>
Animated Shiny Text
<script lang="ts">
import { cn } from '$lib/utils';
import type { Snippet } from 'svelte';
interface AnimatedShinyTextProps {
children: Snippet;
class?: string;
shimmerWidth?: number;
}
let { children, class: className, shimmerWidth = 100 }: AnimatedShinyTextProps = $props();
let styleVars = $derived.by(() => {
return `--shimmer-width: ${shimmerWidth}px;`;
});
</script>
<p
style={styleVars}
class={cn(
'mx-auto max-w-md text-neutral-600/50 dark:text-neutral-400/50 ',
// Shimmer effect
'animate-shimmer bg-clip-text bg-no-repeat [background-position:0_0] [background-size:var(--shimmer-width)_100%] [transition:background-position_1s_cubic-bezier(.6,.6,0,1)_infinite]',
// Shimmer gradient
'bg-gradient-to-r from-transparent via-black/80 via-50% to-transparent dark:via-white/80',
className
)}
>
{@render children()}
</p>
Will try integrating into the docs as well
Number ticker, had to modify it a bit to use svelte's built in motion stuff, also introduced a new prop duration
for how long the animation lasts
<script lang="ts">
import { cn } from '$lib/utils';
import { cubicOut } from 'svelte/easing';
import { tweened } from 'svelte/motion';
interface NumberTickerProps {
value: number;
direction?: 'up' | 'down';
class?: string;
delay?: number;
duration?: number;
}
let {
value,
direction = 'up',
class: className = '',
delay = 0,
duration = 4000
}: NumberTickerProps = $props();
const count = tweened(direction === 'down' ? value : 0, {
duration: duration,
easing: cubicOut,
delay: delay
});
$effect.pre(() => {
if (direction === 'down') {
count.set(0);
}
});
$effect(() => {
if (value !== $count) {
if (direction === 'down') {
count.set(0);
} else {
count.set(value);
}
}
});
let formattedCount = $derived(Intl.NumberFormat('en-US').format(Math.round($count)));
</script>
<span class={cn('inline-block tabular-nums text-black dark:text-white', className)}>
{formattedCount}
</span>
love it 🫶
Marquee
<script lang="ts">
import { cn } from '$lib/utils';
import type { Snippet } from 'svelte';
interface MarqueeProps {
class?: string;
reverse?: boolean;
pauseOnHover?: boolean;
children: Snippet;
vertical?: boolean;
repeat?: number;
[key: string]: any;
}
let {
class: className,
reverse,
pauseOnHover = false,
children,
vertical = false,
repeat = 4,
...restProps
}: MarqueeProps = $props();
</script>
<div
{...restProps}
class={cn(
'group flex overflow-hidden p-2 [--duration:40s] [--gap:1rem] [gap:var(--gap)]',
{
'flex-row': !vertical,
'flex-col': vertical
},
className
)}
>
{#each Array(repeat).fill(0) as _}
<div
class={cn('flex shrink-0 justify-around [gap:var(--gap)]', {
'animate-marquee flex-row': !vertical,
'animate-marquee-vertical flex-col': vertical,
'group-hover:[animation-play-state:paused]': pauseOnHover,
'[animation-direction:reverse]': reverse
})}
>
{@render children()}
</div>
{/each}
</div>
Bentocard, had to change it a little bit
Bentocard
<script lang="ts">
import { cn } from '$lib/utils';
import { ArrowRightIcon } from 'lucide-svelte';
import type { ComponentType, Snippet } from 'svelte';
import { Button } from '$lib/components/ui/button';
interface BentoCardProps {
name: string;
class?: string;
children: Snippet;
Icon: ComponentType;
description: string;
href: string;
cta: string;
}
let { name, class: className, children, Icon, description, href, cta }: BentoCardProps = $props();
</script>
<div
class={cn(
'group relative col-span-3 flex flex-col justify-between overflow-hidden rounded-xl',
// light styles
'bg-white [box-shadow:0_0_0_1px_rgba(0,0,0,.03),0_2px_4px_rgba(0,0,0,.05),0_12px_24px_rgba(0,0,0,.05)]',
// dark styles
'transform-gpu dark:bg-black dark:[border:1px_solid_rgba(255,255,255,.1)] dark:[box-shadow:0_-20px_80px_-20px_#ffffff1f_inset]',
className
)}
>
<div>{@render children()}</div>
<div
class="pointer-events-none z-10 flex transform-gpu flex-col gap-1 p-6 transition-all duration-300 group-hover:-translate-y-10"
>
<Icon
class="h-12 w-12 origin-left transform-gpu text-neutral-700 transition-all duration-300 ease-in-out group-hover:scale-75"
/>
<h3 class="text-xl font-semibold text-neutral-700 dark:text-neutral-300">
{name}
</h3>
<p class="max-w-lg text-neutral-400">{description}</p>
</div>
<div
class="absolute bottom-0 flex w-full translate-y-10 transform-gpu flex-row items-center p-4 opacity-0 transition-all duration-300 group-hover:translate-y-0 group-hover:opacity-100"
>
<Button variant="ghost" {href} class="flex justify-between">
{cta}
<ArrowRightIcon class="ml-1 mt-1 h-4 w-4" />
</Button>
</div>
<div
class="pointer-events-none absolute inset-0 transform-gpu transition-all duration-300 group-hover:bg-black/[.03] group-hover:dark:bg-neutral-800/10"
></div>
</div>
Bentogrid
<script lang="ts">
import { cn } from '$lib/utils';
import type { Snippet } from 'svelte';
let { children, class: className }: { children: Snippet; class?: string } = $props();
</script>
<div class={cn('grid w-full auto-rows-[22rem] grid-cols-3 gap-4', className)}>
{@render children()}
</div>
Usage
<script lang="ts">
import BentoCard from '$lib/components/magicui/BentoCard.svelte';
import BentoGrid from '$lib/components/magicui/BentoGrid.svelte';
import { Bell, Calendar, FileText, Globe, Input } from 'svelte-radix';
import TestImage from './TestImage.svelte';
const features = [
{
Icon: FileText,
name: 'Save your files',
description: 'We automatically save your files as you type.',
href: '/',
cta: 'Learn more',
background: TestImage,
class: 'lg:row-start-1 lg:row-end-4 lg:col-start-2 lg:col-end-3'
},
{
Icon: Input,
name: 'Full text search',
description: 'Search through all your files in one place.',
href: '/',
cta: 'Learn more',
background: TestImage,
class: 'lg:col-start-1 lg:col-end-2 lg:row-start-1 lg:row-end-3'
},
{
Icon: Globe,
name: 'Multilingual',
description: 'Supports 100+ languages and counting.',
href: '/',
cta: 'Learn more',
background: TestImage,
class: 'lg:col-start-1 lg:col-end-2 lg:row-start-3 lg:row-end-4'
},
{
Icon: Calendar,
name: 'Calendar',
description: 'Use the calendar to filter your files by date.',
href: '/',
cta: 'Learn more',
background: TestImage,
class: 'lg:col-start-3 lg:col-end-3 lg:row-start-1 lg:row-end-2'
},
{
Icon: Bell,
name: 'Notifications',
description: 'Get notified when someone shares a file or mentions you in a comment.',
href: '/',
cta: 'Learn more',
background: TestImage,
class: 'lg:col-start-3 lg:col-end-3 lg:row-start-2 lg:row-end-4'
}
];
</script>
<BentoGrid class="lg:grid-rows-3">
{#each features as feature}
<BentoCard {...feature}>
<svelte:component this={feature.background}></svelte:component>
</BentoCard>
{/each}
</BentoGrid>
I'm making this issue to track any ports of Magic UI components to Svelte
If you have any ports, feel free to comment them below to share with the community!
We will eventually try and merge everything together in one repo if possible down the line.
However our current priority will be on react first.
If someone from the community wants to take initiative to integrate Svelte support into this repo please feel free to comment below and get started :) No permission needed