Skip to content

Commit

Permalink
feat(ui): add new animation components and keyframes utility
Browse files Browse the repository at this point in the history
  • Loading branch information
HuakunShen committed Mar 10, 2025
1 parent e065e8f commit a3a9f65
Show file tree
Hide file tree
Showing 18 changed files with 467 additions and 5 deletions.
34 changes: 34 additions & 0 deletions packages/svelte-animation/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# dependencies (bun install)
node_modules

# output
out
dist
*.tgz

# code coverage
coverage
*.lcov

# logs
logs
_.log
report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json

# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local

# caches
.eslintcache
.cache
*.tsbuildinfo

# IntelliJ based IDEs
.idea

# Finder (MacOS) folder config
.DS_Store
Empty file.
1 change: 1 addition & 0 deletions packages/svelte-animation/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * as keyframes from "./src/keyframes"
12 changes: 12 additions & 0 deletions packages/svelte-animation/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"name": "@kksh/svelte-animation",
"module": "index.ts",
"type": "module",
"private": true,
"devDependencies": {
"@types/bun": "latest"
},
"peerDependencies": {
"typescript": "^5"
}
}
63 changes: 63 additions & 0 deletions packages/svelte-animation/src/keyframes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import type { Config } from "tailwindcss"

export const shinePulse: NonNullable<NonNullable<Config["theme"]>["extend"]>["keyframes"] = {
"border-beam": {
"100%": {
"offset-distance": "100%"
}
},
"text-gradient": {
to: {
backgroundPosition: "200% center"
}
},
meteor: {
"0%": { transform: "rotate(215deg) translateX(0)", opacity: "1" },
"70%": { opacity: "1" },
"100%": {
transform: "rotate(215deg) translateX(-500px)",
opacity: "0"
}
},
grid: {
"0%": { transform: "translateY(-50%)" },
"100%": { transform: "translateY(0)" }
},
"aurora-border": {
"0%, 100%": { borderRadius: "37% 29% 27% 27% / 28% 25% 41% 37%" },
"25%": { borderRadius: "47% 29% 39% 49% / 61% 19% 66% 26%" },
"50%": { borderRadius: "57% 23% 47% 72% / 63% 17% 66% 33%" },
"75%": { borderRadius: "28% 49% 29% 100% / 93% 20% 64% 25%" }
},
"aurora-1": {
"0%, 100%": { top: "0", right: "0" },
"50%": { top: "50%", right: "25%" },
"75%": { top: "25%", right: "50%" }
},
"aurora-2": {
"0%, 100%": { top: "0", left: "0" },
"60%": { top: "75%", left: "25%" },
"85%": { top: "50%", left: "50%" }
},
"aurora-3": {
"0%, 100%": { bottom: "0", left: "0" },
"40%": { bottom: "50%", left: "25%" },
"65%": { bottom: "25%", left: "50%" }
},
"aurora-4": {
"0%, 100%": { bottom: "0", right: "0" },
"50%": { bottom: "25%", right: "40%" },
"90%": { bottom: "50%", right: "25%" }
},
"shine-pulse": {
"0%": {
"background-position": "0% 0%"
},
"50%": {
"background-position": "100% 100%"
},
to: {
"background-position": "0% 0%"
}
}
}
6 changes: 6 additions & 0 deletions packages/svelte-animation/src/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { clsx, type ClassValue } from "clsx"
import { twMerge } from "tailwind-merge"

export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}
27 changes: 27 additions & 0 deletions packages/svelte-animation/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"compilerOptions": {
// Enable latest features
"lib": ["ESNext", "DOM"],
"target": "ESNext",
"module": "ESNext",
"moduleDetection": "force",
"jsx": "react-jsx",
"allowJs": true,

// Bundler mode
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"verbatimModuleSyntax": true,
"noEmit": true,

// Best practices
"strict": true,
"skipLibCheck": true,
"noFallthroughCasesInSwitch": true,

// Some stricter flags (disabled by default)
"noUnusedLocals": false,
"noUnusedParameters": false,
"noPropertyAccessFromIndexSignature": false
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

## Permission Table

<table>
Expand All @@ -7,7 +6,6 @@
<th>Description</th>
</tr>


<tr>
<td>

Expand Down
4 changes: 4 additions & 0 deletions packages/ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@
"./extension": {
"types": "./src/components/extension/index.ts",
"svelte": "./src/components/extension/index.ts"
},
"./animation": {
"types": "./src/components/animation/index.ts",
"svelte": "./src/components/animation/index.ts"
}
},
"scripts": {
Expand Down
24 changes: 24 additions & 0 deletions packages/ui/src/components/animation/AuroraText.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<script lang="ts">
import type { Snippet } from "svelte"
import { cn } from "../../utils"
let { class: className, children }: { class?: string; children: Snippet } = $props()
</script>

<span class={cn("relative inline-flex overflow-hidden", className)}>
{@render children()}
<span class="pointer-events-none absolute inset-0 mix-blend-lighten dark:mix-blend-darken">
<span
class="pointer-events-none absolute -top-1/2 h-[30vw] w-[30vw] animate-[aurora-border_6s_ease-in-out_infinite,aurora-1_12s_ease-in-out_infinite_alternate] bg-[hsl(var(--color-1))] mix-blend-overlay blur-[1rem]"
></span>
<span
class="pointer-events-none absolute right-0 top-0 h-[30vw] w-[30vw] animate-[aurora-border_6s_ease-in-out_infinite,aurora-2_12s_ease-in-out_infinite_alternate] bg-[hsl(var(--color-2))] mix-blend-overlay blur-[1rem]"
></span>
<span
class="pointer-events-none absolute bottom-0 left-0 h-[30vw] w-[30vw] animate-[aurora-border_6s_ease-in-out_infinite,aurora-3_12s_ease-in-out_infinite_alternate] bg-[hsl(var(--color-3))] mix-blend-overlay blur-[1rem]"
></span>
<span
class="pointer-events-none absolute -bottom-1/2 right-0 h-[30vw] w-[30vw] animate-[aurora-border_6s_ease-in-out_infinite,aurora-4_12s_ease-in-out_infinite_alternate] bg-[hsl(var(--color-4))] mix-blend-overlay blur-[1rem]"
></span>
</span>
</span>
72 changes: 72 additions & 0 deletions packages/ui/src/components/animation/MagicCard.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
<script lang="ts">
import { onMount } from "svelte"
import { Motion, useMotionTemplate, useMotionValue } from "svelte-motion"
import { cn } from "../../utils"
export let gradientSize: number = 200
export let gradientColor: string = "#262626"
export let gradientOpacity: number = 0.8
let className: string = ""
export { className as class }
let gradSize = useMotionValue(gradientSize)
let gradColor = useMotionValue(gradientColor)
let mouseX = useMotionValue(-gradientSize)
let mouseY = useMotionValue(-gradientSize)
function handleMouseMove(e: MouseEvent) {
const rect = (e.currentTarget as HTMLDivElement).getBoundingClientRect()
mouseX.set(e.clientX - rect.left)
mouseY.set(e.clientY - rect.top)
}
function handleMouseLeave() {
mouseX.set(-gradientSize)
mouseY.set(-gradientSize)
}
onMount(() => {
mouseX.set(-gradientSize)
mouseY.set(-gradientSize)
})
let bg = useMotionTemplate`radial-gradient(${gradSize}px circle at ${mouseX}px ${mouseY}px, ${gradColor}, transparent 100%)`
</script>

<!-- svelte-ignore a11y-no-static-element-interactions -->
<!-- I have added py-4 in below code, you can customize the component as per needs -->
<div
on:mousemove={handleMouseMove}
on:mouseleave={handleMouseLeave}
class={cn(
"group relative flex size-full justify-center overflow-hidden rounded-xl border bg-neutral-100 py-4 text-black dark:bg-neutral-900 dark:text-white",
className
)}
>
<div class="relative z-10">
<!-- Default -->
<slot>
<div class="flex h-full items-center justify-center text-center">
<p class="text-2xl">Magic Card</p>
</div>
</slot>
</div>
<Motion
style={{
background: bg,
opacity: gradientOpacity
}}
let:motion
>
<div
use:motion
class="pointer-events-none absolute -inset-px rounded-xl opacity-0 transition-opacity duration-300 group-hover:opacity-100"
/>
</Motion>
</div>

<style>
.size-full {
width: 100%;
height: 100%;
}
</style>
35 changes: 35 additions & 0 deletions packages/ui/src/components/animation/RetroGrid.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<script lang="ts">
import { cn } from "../../utils"
let className: any = ""
export { className as class }
</script>

<div
class={cn(
"pointer-events-none absolute h-full w-full overflow-hidden opacity-50 [perspective:200px]",
className
)}
>
<!-- Grid -->
<div class="absolute inset-0 [transform:rotateX(35deg)]">
<div
class={cn(
"animate-grid",

"[background-repeat:repeat] [background-size:60px_60px] [height:300vh] [inset:0%_0px] [margin-left:-50%] [transform-origin:100%_0_0] [width:600vw]",

// Light Styles
"[background-image:linear-gradient(to_right,rgba(0,0,0,0.3)_1px,transparent_0),linear-gradient(to_bottom,rgba(0,0,0,0.3)_1px,transparent_0)]",

// Dark styles
"dark:[background-image:linear-gradient(to_right,rgba(255,255,255,0.2)_1px,transparent_0),linear-gradient(to_bottom,rgba(255,255,255,0.2)_1px,transparent_0)]"
)}
></div>
</div>

<!-- Background Gradient -->
<div
class="absolute inset-0 bg-gradient-to-t from-white to-transparent to-90% dark:from-black"
></div>
</div>
39 changes: 39 additions & 0 deletions packages/ui/src/components/animation/ShineBorder.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<script lang="ts">
import { cn } from "../../utils"
type TColorProp = string | string[]
export let borderRadius: number = 8
export let borderWidth: number = 1
export let duration: number = 14
export let color: TColorProp = ["#4FF9FF"]
let className: string = ""
export { className as class }
</script>

<div
style="
--border-radius: {borderRadius}px;
"
class={cn(
"relative grid min-h-[60px] w-fit min-w-[300px] place-items-center rounded-[var(--border-radius)] bg-white p-3 text-black dark:bg-black dark:text-white",
className
)}
>
<div
style="
--border-width: {borderWidth}px;
--border-radius: {borderRadius}px;
--shine-pulse-duration: {duration}s;
--mask-linear-gradient: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
--background-radial-gradient: radial-gradient(transparent, transparent, {Array.isArray(
color
)
? color.join(',')
: color}, transparent, transparent);
"
class="before:bg-shine-size before:absolute before:inset-0 before:aspect-square before:size-full before:rounded-[var(--border-radius)] before:p-[var(--border-width)] before:will-change-[background-position] before:content-[''] before:![-webkit-mask-composite:xor] before:[background-image:var(--background-radial-gradient)] before:[background-size:300%_300%] before:![mask-composite:exclude] before:[mask:var(--mask-linear-gradient)] motion-safe:before:animate-[shine-pulse_var(--shine-pulse-duration)_infinite_linear]"
></div>
<!-- This is Default Slot -->
<slot>Default</slot>
</div>
35 changes: 35 additions & 0 deletions packages/ui/src/components/animation/WordRotate.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<script lang="ts">
import { onMount } from "svelte"
import { fly } from "svelte/transition"
import { cn } from "../../utils"
export let words: string[] = ["Hello", "Svelte", "Coders"]
export let duration: number = 2100
let className: string = ""
export { className as class }
let index = 0
let chnageIndex = () => {
index = (index + 1) % words.length
}
onMount(() => {
let interval = setInterval(chnageIndex, duration)
return () => clearInterval(interval)
})
// I tried with Svelte Motion but there was a slight delay in the animation
// so I used the built-in svelte transition
</script>

<div class="overflow-hidden py-2">
{#key index}
<!-- Change In, Out Animation for Rotate Effects -->
<h1
in:fly={{ y: -50, delay: 200 }}
out:fly={{ y: 40, duration: 200 }}
class={cn(className, "text-center")}
>
{words[index]}
</h1>
{/key}
</div>
7 changes: 7 additions & 0 deletions packages/ui/src/components/animation/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export { default as GridAnimation } from "./grid-animation.svelte"
export { default as BorderBeam } from "./BorderBeam.svelte"
export { default as Meteors } from "./meteros.svelte"
export { default as RetroGrid } from "./RetroGrid.svelte"
export { default as AuroraText } from "./AuroraText.svelte"
export { default as WordRotate } from "./WordRotate.svelte"
export { default as MagicCard } from "./MagicCard.svelte"
Loading

0 comments on commit a3a9f65

Please sign in to comment.