fancy-ui-svelte 0.8.0 → 0.9.0
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 +107 -89
- package/dist/fancy-ui/animated-testimonials/README.md +44 -0
- package/dist/fancy-ui/animated-tooltip/README.md +40 -0
- package/dist/fancy-ui/apple-card-carousel/README.md +46 -0
- package/dist/fancy-ui/bento-grid/BentoGrid.test.d.ts +1 -0
- package/dist/fancy-ui/bento-grid/BentoGrid.test.js +109 -0
- package/dist/fancy-ui/border-beam/README.md +37 -0
- package/dist/fancy-ui/card-3d/CardContainer.test.d.ts +1 -0
- package/dist/fancy-ui/card-3d/CardContainer.test.js +118 -0
- package/dist/fancy-ui/card-spotlight/CardSpotlight.test.d.ts +1 -0
- package/dist/fancy-ui/card-spotlight/CardSpotlight.test.js +58 -0
- package/dist/fancy-ui/displacement-text/README.md +39 -0
- package/dist/fancy-ui/dock/README.md +1 -1
- package/dist/fancy-ui/editorial-engine/EditorialEngine.test.d.ts +1 -0
- package/dist/fancy-ui/editorial-engine/EditorialEngine.test.js +54 -0
- package/dist/fancy-ui/fluid-cursor/FluidCursor.svelte +130 -9
- package/dist/fancy-ui/fluid-cursor/FluidCursor.svelte.d.ts +9 -1
- package/dist/fancy-ui/fluid-cursor/FluidCursor.test.js +19 -0
- package/dist/fancy-ui/fluid-cursor/fluid-shared.d.ts +32 -0
- package/dist/fancy-ui/fluid-cursor/index.d.ts +1 -0
- package/dist/fancy-ui/fluid-cursor/webgpu-engine.d.ts +11 -4
- package/dist/fancy-ui/fluid-cursor/webgpu-engine.js +113 -34
- package/dist/fancy-ui/line-hover-link/README.md +37 -0
- package/dist/fancy-ui/line-reveal/LineReveal.test.d.ts +1 -0
- package/dist/fancy-ui/line-reveal/LineReveal.test.js +77 -0
- package/dist/fancy-ui/marquee/README.md +57 -0
- package/dist/fancy-ui/matrix-rain/MatrixRain.test.d.ts +1 -0
- package/dist/fancy-ui/matrix-rain/MatrixRain.test.js +30 -0
- package/dist/fancy-ui/matrix-rain/README.md +42 -0
- package/dist/fancy-ui/noise-reveal/README.md +52 -0
- package/dist/fancy-ui/rainbow-button/README.md +36 -0
- package/dist/fancy-ui/registry.js +20 -20
- package/dist/fancy-ui/ripple-button/README.md +36 -0
- package/dist/fancy-ui/smooth-cursor/SmoothCursor.test.d.ts +1 -0
- package/dist/fancy-ui/smooth-cursor/SmoothCursor.test.js +45 -0
- package/dist/fancy-ui/terminal-text/README.md +48 -0
- package/dist/fancy-ui/terminal-text/TerminalText.test.d.ts +1 -0
- package/dist/fancy-ui/terminal-text/TerminalText.test.js +88 -0
- package/package.json +15 -2
|
@@ -68,7 +68,7 @@ export const registry = {
|
|
|
68
68
|
"apple-card-carousel": {
|
|
69
69
|
name: "AppleCardCarousel",
|
|
70
70
|
slug: "apple-card-carousel",
|
|
71
|
-
description: "Horizontal card carousel
|
|
71
|
+
description: "Horizontal card carousel where the focused card expands into a spring-animated full-screen view",
|
|
72
72
|
category: "cards",
|
|
73
73
|
status: "done",
|
|
74
74
|
credits: [
|
|
@@ -246,7 +246,7 @@ export const registry = {
|
|
|
246
246
|
"blur-reveal": {
|
|
247
247
|
name: "BlurReveal",
|
|
248
248
|
slug: "blur-reveal",
|
|
249
|
-
description: "Scroll-triggered
|
|
249
|
+
description: "Scroll-triggered reveal that unblurs and slides each direct child into place as the container enters the viewport, staggering up to 10 children via CSS nth-child delays and respecting reduced motion",
|
|
250
250
|
category: "text",
|
|
251
251
|
status: "done",
|
|
252
252
|
credits: [{ source: "Magic UI", url: "https://magicui.design/docs/components/blur-fade" }],
|
|
@@ -284,7 +284,7 @@ export const registry = {
|
|
|
284
284
|
"border-beam": {
|
|
285
285
|
name: "BorderBeam",
|
|
286
286
|
slug: "border-beam",
|
|
287
|
-
description: "
|
|
287
|
+
description: "Gradient beam that races around a container's border using a CSS offset-path animation, masked so only the border ring is painted, with configurable size, speed, anchor position, and gradient colors",
|
|
288
288
|
category: "effects",
|
|
289
289
|
status: "done",
|
|
290
290
|
credits: [{ source: "Magic UI", url: "https://magicui.design/docs/components/border-beam" }],
|
|
@@ -405,7 +405,7 @@ export const registry = {
|
|
|
405
405
|
"image-trail-cursor": {
|
|
406
406
|
name: "ImageTrailCursor",
|
|
407
407
|
slug: "image-trail-cursor",
|
|
408
|
-
description: "
|
|
408
|
+
description: "Trail of images that spawn and animate along the cursor's path, powered by GSAP timelines, with 8 selectable variants spanning simple fades, momentum drift, rotation flings, and 3D perspective tilt",
|
|
409
409
|
category: "effects",
|
|
410
410
|
status: "done",
|
|
411
411
|
tags: ["cursor", "animation", "images", "trail", "interactive"],
|
|
@@ -643,7 +643,7 @@ export const registry = {
|
|
|
643
643
|
"ripple-button": {
|
|
644
644
|
name: "RippleButton",
|
|
645
645
|
slug: "ripple-button",
|
|
646
|
-
description: "Button
|
|
646
|
+
description: "Button that spawns an expanding, fading ripple circle centered on the click point, with configurable color and duration",
|
|
647
647
|
category: "buttons",
|
|
648
648
|
status: "done",
|
|
649
649
|
credits: [{ source: "Magic UI", url: "https://magicui.design/docs/components/ripple-button" }],
|
|
@@ -734,7 +734,7 @@ export const registry = {
|
|
|
734
734
|
"bg-stars": {
|
|
735
735
|
name: "StarsBackground",
|
|
736
736
|
slug: "bg-stars",
|
|
737
|
-
description: "
|
|
737
|
+
description: "Three-layer starfield background that scrolls continuously while parallax-shifting in response to cursor movement via spring physics, with stars rendered as box-shadow dots across near, mid, and far depth layers",
|
|
738
738
|
category: "backgrounds",
|
|
739
739
|
status: "done",
|
|
740
740
|
credits: [
|
|
@@ -773,11 +773,11 @@ export const registry = {
|
|
|
773
773
|
dock: {
|
|
774
774
|
name: "Dock",
|
|
775
775
|
slug: "dock",
|
|
776
|
-
description: "
|
|
776
|
+
description: "Icon dock where each item magnifies smoothly as the cursor approaches, sharing pointer position via Svelte context so every DockIcon scales by proximity within a configurable magnification and distance range",
|
|
777
777
|
category: "navigation",
|
|
778
778
|
status: "done",
|
|
779
779
|
credits: [{ source: "Magic UI", url: "https://magicui.design/docs/components/dock" }],
|
|
780
|
-
tags: ["dock", "navigation", "
|
|
780
|
+
tags: ["dock", "navigation", "launcher", "magnification", "hover"],
|
|
781
781
|
props: [
|
|
782
782
|
{
|
|
783
783
|
name: "magnification",
|
|
@@ -809,7 +809,7 @@ export const registry = {
|
|
|
809
809
|
"fluid-cursor": {
|
|
810
810
|
name: "FluidCursor",
|
|
811
811
|
slug: "fluid-cursor",
|
|
812
|
-
description: "
|
|
812
|
+
description: "Full Navier-Stokes fluid simulation that swirls color in response to cursor and touch movement, running on WebGL2 with a WebGL1 fallback and an optional WebGPU HDR path for wide-gamut, brighter-than-white glow",
|
|
813
813
|
category: "effects",
|
|
814
814
|
status: "done",
|
|
815
815
|
credits: [
|
|
@@ -952,7 +952,7 @@ export const registry = {
|
|
|
952
952
|
"glow-border": {
|
|
953
953
|
name: "GlowBorder",
|
|
954
954
|
slug: "glow-border",
|
|
955
|
-
description: "
|
|
955
|
+
description: "Glowing ring that animates around a container's edge by sweeping a radial gradient's position, masked with CSS mask-composite so only the border ring is painted, supporting single or multi-color gradients",
|
|
956
956
|
category: "effects",
|
|
957
957
|
status: "done",
|
|
958
958
|
credits: [
|
|
@@ -1100,7 +1100,7 @@ export const registry = {
|
|
|
1100
1100
|
"flickering-grid": {
|
|
1101
1101
|
name: "FlickeringGrid",
|
|
1102
1102
|
slug: "flickering-grid",
|
|
1103
|
-
description: "Canvas
|
|
1103
|
+
description: "Canvas grid of squares that flicker in and out of opacity at random each frame, pausing automatically via IntersectionObserver when scrolled off-screen and resizing to fit its container via ResizeObserver",
|
|
1104
1104
|
category: "backgrounds",
|
|
1105
1105
|
status: "done",
|
|
1106
1106
|
credits: [
|
|
@@ -1183,7 +1183,7 @@ export const registry = {
|
|
|
1183
1183
|
"colourful-text": {
|
|
1184
1184
|
name: "ColourfulText",
|
|
1185
1185
|
slug: "colourful-text",
|
|
1186
|
-
description: "Per-character
|
|
1186
|
+
description: "Per-character text animation where each glyph transitions to a new color from a shuffled palette, staggered across characters and reshuffling automatically every 5 seconds using pure CSS transitions",
|
|
1187
1187
|
category: "text",
|
|
1188
1188
|
status: "done",
|
|
1189
1189
|
credits: [
|
|
@@ -1238,7 +1238,7 @@ export const registry = {
|
|
|
1238
1238
|
"hyper-text": {
|
|
1239
1239
|
name: "HyperText",
|
|
1240
1240
|
slug: "hyper-text",
|
|
1241
|
-
description: "
|
|
1241
|
+
description: "Text that scrambles into random uppercase letters and resolves character-by-character back to the original on hover, with a staggered per-letter reveal and an optional animate-on-load mode",
|
|
1242
1242
|
category: "text",
|
|
1243
1243
|
status: "done",
|
|
1244
1244
|
credits: [{ source: "Magic UI", url: "https://magicui.design/docs/components/hyper-text" }],
|
|
@@ -1321,7 +1321,7 @@ export const registry = {
|
|
|
1321
1321
|
"sparkles-text": {
|
|
1322
1322
|
name: "SparklesText",
|
|
1323
1323
|
slug: "sparkles-text",
|
|
1324
|
-
description: "Text with
|
|
1324
|
+
description: "Text overlaid with small SVG star sparkles that scale, rotate, and fade in a continuous loop, each one regenerating at a random position once its lifespan expires, cycling between two configurable colors",
|
|
1325
1325
|
category: "text",
|
|
1326
1326
|
status: "done",
|
|
1327
1327
|
credits: [{ source: "Aceternity UI", url: "https://ui.aceternity.com/components/sparkles" }],
|
|
@@ -1345,7 +1345,7 @@ export const registry = {
|
|
|
1345
1345
|
"box-reveal": {
|
|
1346
1346
|
name: "BoxReveal",
|
|
1347
1347
|
slug: "box-reveal",
|
|
1348
|
-
description: "
|
|
1348
|
+
description: "Reveal animation where a solid color box slides left to right across content on viewport entry, unveiling it as it fades up into place underneath, triggered once via IntersectionObserver",
|
|
1349
1349
|
category: "text",
|
|
1350
1350
|
status: "done",
|
|
1351
1351
|
credits: [{ source: "Magic UI", url: "https://magicui.design/docs/components/box-reveal" }],
|
|
@@ -1395,7 +1395,7 @@ export const registry = {
|
|
|
1395
1395
|
"card-spotlight": {
|
|
1396
1396
|
name: "CardSpotlight",
|
|
1397
1397
|
slug: "card-spotlight",
|
|
1398
|
-
description: "Card
|
|
1398
|
+
description: "Card whose radial-gradient spotlight tracks the cursor as it moves inside, fading in on hover and parking itself off-canvas when the pointer leaves, with configurable gradient size, color, and opacity",
|
|
1399
1399
|
category: "cards",
|
|
1400
1400
|
status: "done",
|
|
1401
1401
|
credits: [
|
|
@@ -1967,7 +1967,7 @@ export const registry = {
|
|
|
1967
1967
|
ripple: {
|
|
1968
1968
|
name: "Ripple",
|
|
1969
1969
|
slug: "ripple",
|
|
1970
|
-
description: "
|
|
1970
|
+
description: "Set of concentric circles that pulse in a staggered wave, each ring larger, more transparent, and more delayed than the last, with the outermost ring rendered dashed to suggest a fading signal",
|
|
1971
1971
|
category: "effects",
|
|
1972
1972
|
status: "done",
|
|
1973
1973
|
credits: [{ source: "Magic UI", url: "https://magicui.design/docs/components/ripple" }],
|
|
@@ -2125,7 +2125,7 @@ export const registry = {
|
|
|
2125
2125
|
"matrix-rain": {
|
|
2126
2126
|
name: "MatrixRain",
|
|
2127
2127
|
slug: "matrix-rain",
|
|
2128
|
-
description: "Canvas-based
|
|
2128
|
+
description: "Canvas-based falling glyph rain with configurable color, speed, and density",
|
|
2129
2129
|
category: "backgrounds",
|
|
2130
2130
|
status: "done",
|
|
2131
2131
|
tags: ["background", "matrix", "canvas", "animation", "glyphs", "cyberpunk"],
|
|
@@ -2134,7 +2134,7 @@ export const registry = {
|
|
|
2134
2134
|
name: "color",
|
|
2135
2135
|
type: "string",
|
|
2136
2136
|
default: '"#00ff41"',
|
|
2137
|
-
description: "Glyph color (
|
|
2137
|
+
description: "Glyph color (classic terminal green)",
|
|
2138
2138
|
},
|
|
2139
2139
|
{
|
|
2140
2140
|
name: "speed",
|
|
@@ -2206,7 +2206,7 @@ export const registry = {
|
|
|
2206
2206
|
"noise-reveal": {
|
|
2207
2207
|
name: "NoiseReveal",
|
|
2208
2208
|
slug: "noise-reveal",
|
|
2209
|
-
description: "WebGL image reveal with a Perlin-noise dissolve mask, contracting radial gradient, and wave displacement
|
|
2209
|
+
description: "WebGL image reveal with a Perlin-noise dissolve mask, contracting radial gradient, and wave displacement",
|
|
2210
2210
|
category: "media",
|
|
2211
2211
|
status: "done",
|
|
2212
2212
|
credits: [
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# RippleButton
|
|
2
|
+
|
|
3
|
+
A button that spawns a Material-style expanding ripple circle from the exact click position on every click, layered behind the button's content.
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
```svelte
|
|
8
|
+
<script lang="ts">
|
|
9
|
+
import { RippleButton } from "fancy-ui-svelte";
|
|
10
|
+
</script>
|
|
11
|
+
|
|
12
|
+
<RippleButton onclick={() => console.log("clicked")}>Click me</RippleButton>
|
|
13
|
+
|
|
14
|
+
<RippleButton rippleColor="#f472b6" duration={800} class="bg-primary text-primary-foreground">
|
|
15
|
+
Custom ripple
|
|
16
|
+
</RippleButton>
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Props
|
|
20
|
+
|
|
21
|
+
| Prop | Type | Default | Description |
|
|
22
|
+
| ------------- | --------- | ----------- | ---------------------------------- |
|
|
23
|
+
| `rippleColor` | `string` | `"#ADD8E6"` | Color of the ripple effect |
|
|
24
|
+
| `duration` | `number` | `600` | Animation duration in milliseconds |
|
|
25
|
+
| `class` | `string` | — | Additional CSS classes |
|
|
26
|
+
| `children` | `Snippet` | — | Button content |
|
|
27
|
+
|
|
28
|
+
`RippleButtonProps` also extends `HTMLButtonAttributes` (minus `class`), and any attributes not explicitly listed above (e.g. `type`, `aria-*`, `data-*`) are spread onto the rendered `<button>` via `...restProps`.
|
|
29
|
+
|
|
30
|
+
## Implementation notes
|
|
31
|
+
|
|
32
|
+
- `onclick` is intercepted, not overwritten: the component's own `handleClick` always creates the ripple first, then calls the caller's `onclick` (if provided) with the original event — passing `onclick` works exactly as it would on a plain `<button>`.
|
|
33
|
+
- Ripple size is `Math.max(width, height)` of the button so a single circle always covers the whole button regardless of click position; position is computed from `event.clientX/Y` relative to the button's `getBoundingClientRect()`, centered on the click point.
|
|
34
|
+
- Each ripple is tracked in a `ripples` array keyed by `Date.now()` and removed via `setTimeout(duration)` after the animation finishes — rapid repeated clicks stack multiple concurrent ripple elements rather than restarting a single one.
|
|
35
|
+
- `duration` is threaded into the CSS animation via the `--ripple-duration` custom property, so the visual animation and the removal timeout always stay in sync even when `duration` changes between renders.
|
|
36
|
+
- No `prefers-reduced-motion` handling — the ripple scale/opacity animation plays on every click regardless of user preference.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { render, cleanup, fireEvent } from "@testing-library/svelte";
|
|
2
|
+
import { createRawSnippet } from "svelte";
|
|
3
|
+
import { afterEach, describe, it, expect } from "vitest";
|
|
4
|
+
import SmoothCursor from "./SmoothCursor.svelte";
|
|
5
|
+
function textSnippet(html) {
|
|
6
|
+
return createRawSnippet(() => ({ render: () => html }));
|
|
7
|
+
}
|
|
8
|
+
describe("SmoothCursor", () => {
|
|
9
|
+
afterEach(cleanup);
|
|
10
|
+
it("renders hidden (opacity-0) before any pointer movement", () => {
|
|
11
|
+
const { container } = render(SmoothCursor);
|
|
12
|
+
const cursor = container.firstElementChild;
|
|
13
|
+
expect(cursor.className).toContain("opacity-0");
|
|
14
|
+
expect(cursor.className).not.toContain("opacity-100");
|
|
15
|
+
});
|
|
16
|
+
it("applies custom class names", () => {
|
|
17
|
+
const { container } = render(SmoothCursor, { props: { class: "my-cursor" } });
|
|
18
|
+
const cursor = container.firstElementChild;
|
|
19
|
+
expect(cursor.className).toContain("my-cursor");
|
|
20
|
+
});
|
|
21
|
+
it("renders the default arrow svg when no cursor snippet is provided", () => {
|
|
22
|
+
const { container } = render(SmoothCursor);
|
|
23
|
+
expect(container.querySelector("svg")).toBeTruthy();
|
|
24
|
+
});
|
|
25
|
+
it("renders a custom cursor snippet instead of the default arrow", () => {
|
|
26
|
+
const { container } = render(SmoothCursor, {
|
|
27
|
+
props: { cursor: textSnippet("<div data-testid='custom-cursor'></div>") },
|
|
28
|
+
});
|
|
29
|
+
expect(container.querySelector("svg")).toBeFalsy();
|
|
30
|
+
expect(container.querySelector("[data-testid='custom-cursor']")).toBeTruthy();
|
|
31
|
+
});
|
|
32
|
+
it("hides the native cursor on mount and restores it on unmount", () => {
|
|
33
|
+
const { unmount } = render(SmoothCursor);
|
|
34
|
+
expect(document.body.style.cursor).toBe("none");
|
|
35
|
+
unmount();
|
|
36
|
+
expect(document.body.style.cursor).toBe("");
|
|
37
|
+
});
|
|
38
|
+
it("becomes visible once the pointer moves, and unmount does not throw once the animation loop is running", async () => {
|
|
39
|
+
const { container, unmount } = render(SmoothCursor);
|
|
40
|
+
const cursor = container.firstElementChild;
|
|
41
|
+
await fireEvent.mouseMove(document, { clientX: 120, clientY: 80 });
|
|
42
|
+
expect(cursor.className).toContain("opacity-100");
|
|
43
|
+
expect(() => unmount()).not.toThrow();
|
|
44
|
+
});
|
|
45
|
+
});
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# TerminalText
|
|
2
|
+
|
|
3
|
+
Streams an array of lines onto the screen character by character, terminal-style, with a blinking block cursor and an optional post-stream "glitch" effect that randomly swaps a visible character for a glyph for a moment.
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
```svelte
|
|
8
|
+
<script lang="ts">
|
|
9
|
+
import { TerminalText } from "fancy-ui-svelte";
|
|
10
|
+
</script>
|
|
11
|
+
|
|
12
|
+
<div class="rounded-lg border bg-black p-5 text-green-400">
|
|
13
|
+
<TerminalText
|
|
14
|
+
lines={[
|
|
15
|
+
"[INFO] boot sequence complete",
|
|
16
|
+
"[WARN] memory fragmentation at sector 0x4A2F",
|
|
17
|
+
"[OK] all systems nominal",
|
|
18
|
+
]}
|
|
19
|
+
speed={25}
|
|
20
|
+
/>
|
|
21
|
+
</div>
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
```svelte
|
|
25
|
+
<!-- Glitch effect after streaming finishes, and a completion callback -->
|
|
26
|
+
<TerminalText lines={["Connection established."]} glitch onComplete={() => console.log("done")} />
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Props
|
|
30
|
+
|
|
31
|
+
| Prop | Type | Default | Description |
|
|
32
|
+
| ------------ | ------------ | ------- | ---------------------------------------------------------- |
|
|
33
|
+
| `lines` | `string[]` | — | Array of lines to stream character by character (required) |
|
|
34
|
+
| `speed` | `number` | `40` | Delay between each character in ms |
|
|
35
|
+
| `delay` | `number` | `0` | Initial delay before streaming starts, in ms |
|
|
36
|
+
| `cursor` | `boolean` | `true` | Show the blinking cursor |
|
|
37
|
+
| `cursorChar` | `string` | `"█"` | Character used as the cursor |
|
|
38
|
+
| `glitch` | `boolean` | `false` | Enable random character glitching after streaming |
|
|
39
|
+
| `class` | `string` | — | Additional CSS classes |
|
|
40
|
+
| `onComplete` | `() => void` | — | Called once, after the last line finishes streaming |
|
|
41
|
+
|
|
42
|
+
## Implementation notes
|
|
43
|
+
|
|
44
|
+
- Streaming is scheduled with a flat list of `setTimeout` calls computed up front from a running `totalDelay` counter (one timeout per character, plus a `speed * 3` pause between lines), not a single interval — every timeout is tracked and cleared together (`clearAllTimeouts`) so restarting the stream never leaves stray callbacks running.
|
|
45
|
+
- Streaming and glitching are two independent `$effect`s. The streaming effect depends on `lines`, `speed`, and `delay` only (`void`-read to declare the dependency without using the value), so toggling `glitch` mid-stream does not restart the text. The glitch effect depends only on `glitch` and starts/stops its own self-rescheduling loop (`scheduleGlitch`, random 2–4s interval).
|
|
46
|
+
- The glitch loop picks a random visible character from a random non-empty line, swaps in a random glyph from a fixed set, and restores the original character after 100ms — it checks that the same `(lineIdx, charIdx)` is still the active glitch before restoring, so it can't clobber a different glitch that started in between.
|
|
47
|
+
- The blinking cursor is CSS-only (`animation: blink 1s step-end infinite`) and is rendered after whichever line is currently last — while streaming that's the in-progress line, once `done` it moves to its own trailing line.
|
|
48
|
+
- No `prefers-reduced-motion` handling — lines always stream character-by-character and the cursor always blinks, regardless of user preference.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { render, cleanup } from "@testing-library/svelte";
|
|
2
|
+
import { afterEach, describe, it, expect, vi } from "vitest";
|
|
3
|
+
import TerminalText from "./TerminalText.svelte";
|
|
4
|
+
// The template has static whitespace between its {#each} and {#if} blocks
|
|
5
|
+
// that shows up as stray spaces in textContent depending on which branch is
|
|
6
|
+
// active. Stripping all whitespace isolates the actual streamed characters.
|
|
7
|
+
function text(el) {
|
|
8
|
+
return (el.textContent ?? "").replace(/\s+/g, "");
|
|
9
|
+
}
|
|
10
|
+
describe("TerminalText", () => {
|
|
11
|
+
afterEach(() => {
|
|
12
|
+
cleanup();
|
|
13
|
+
vi.useRealTimers();
|
|
14
|
+
});
|
|
15
|
+
it("renders nothing until the first scheduled tick, then streams characters over time and completes", async () => {
|
|
16
|
+
vi.useFakeTimers();
|
|
17
|
+
const onComplete = vi.fn();
|
|
18
|
+
const { container } = render(TerminalText, {
|
|
19
|
+
props: { lines: ["hi"], speed: 10, onComplete },
|
|
20
|
+
});
|
|
21
|
+
const wrapper = container.firstElementChild;
|
|
22
|
+
// The effect only schedules timeouts synchronously; none have fired yet.
|
|
23
|
+
expect(text(wrapper)).toBe("");
|
|
24
|
+
// t=0: empty line placeholder + blinking cursor appear.
|
|
25
|
+
await vi.advanceTimersByTimeAsync(1);
|
|
26
|
+
expect(text(wrapper)).toBe("█");
|
|
27
|
+
expect(onComplete).not.toHaveBeenCalled();
|
|
28
|
+
// t=10: first character streamed in.
|
|
29
|
+
await vi.advanceTimersByTimeAsync(9);
|
|
30
|
+
expect(text(wrapper)).toBe("h█");
|
|
31
|
+
// t=20: second character streamed in.
|
|
32
|
+
await vi.advanceTimersByTimeAsync(10);
|
|
33
|
+
expect(text(wrapper)).toBe("hi█");
|
|
34
|
+
expect(onComplete).not.toHaveBeenCalled();
|
|
35
|
+
// t=50: stream marked done, onComplete fires exactly once.
|
|
36
|
+
await vi.advanceTimersByTimeAsync(30);
|
|
37
|
+
expect(text(wrapper)).toBe("hi█");
|
|
38
|
+
expect(onComplete).toHaveBeenCalledTimes(1);
|
|
39
|
+
});
|
|
40
|
+
it("renders no cursor glyph anywhere when cursor is false", async () => {
|
|
41
|
+
vi.useFakeTimers();
|
|
42
|
+
const { container } = render(TerminalText, {
|
|
43
|
+
props: { lines: ["ok"], speed: 5, cursor: false },
|
|
44
|
+
});
|
|
45
|
+
await vi.advanceTimersByTimeAsync(25);
|
|
46
|
+
const wrapper = container.firstElementChild;
|
|
47
|
+
expect(text(wrapper)).toBe("ok");
|
|
48
|
+
expect(container.querySelector(".cursor-blink")).toBeFalsy();
|
|
49
|
+
});
|
|
50
|
+
it("streams multiple lines in order, one div per line", async () => {
|
|
51
|
+
vi.useFakeTimers();
|
|
52
|
+
const { container } = render(TerminalText, {
|
|
53
|
+
props: { lines: ["a", "bb"], speed: 10, cursor: false },
|
|
54
|
+
});
|
|
55
|
+
await vi.advanceTimersByTimeAsync(90);
|
|
56
|
+
const wrapper = container.firstElementChild;
|
|
57
|
+
const lineDivs = Array.from(wrapper.children);
|
|
58
|
+
expect(lineDivs).toHaveLength(2);
|
|
59
|
+
expect(lineDivs[0].textContent).toBe("a");
|
|
60
|
+
expect(lineDivs[1].textContent).toBe("bb");
|
|
61
|
+
});
|
|
62
|
+
it("applies custom class names alongside the base classes", () => {
|
|
63
|
+
const { container } = render(TerminalText, {
|
|
64
|
+
props: { lines: ["x"], class: "my-terminal" },
|
|
65
|
+
});
|
|
66
|
+
const wrapper = container.firstElementChild;
|
|
67
|
+
expect(wrapper.className).toContain("my-terminal");
|
|
68
|
+
expect(wrapper.className).toContain("font-mono");
|
|
69
|
+
});
|
|
70
|
+
it("uses a custom cursor character once the stream is done", async () => {
|
|
71
|
+
vi.useFakeTimers();
|
|
72
|
+
const { container } = render(TerminalText, {
|
|
73
|
+
props: { lines: ["a"], speed: 5, cursorChar: "_" },
|
|
74
|
+
});
|
|
75
|
+
// push@0, char@5, done@20 (5 + speed*3=15)
|
|
76
|
+
await vi.advanceTimersByTimeAsync(20);
|
|
77
|
+
const wrapper = container.firstElementChild;
|
|
78
|
+
expect(text(wrapper)).toBe("a_");
|
|
79
|
+
});
|
|
80
|
+
it("does not throw or leave dangling timers when glitch is enabled", async () => {
|
|
81
|
+
vi.useFakeTimers();
|
|
82
|
+
const { unmount } = render(TerminalText, {
|
|
83
|
+
props: { lines: ["glitchy line"], speed: 5, glitch: true },
|
|
84
|
+
});
|
|
85
|
+
await vi.advanceTimersByTimeAsync(5000);
|
|
86
|
+
expect(() => unmount()).not.toThrow();
|
|
87
|
+
});
|
|
88
|
+
});
|
package/package.json
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fancy-ui-svelte",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.9.0",
|
|
5
|
+
"description": "61 animated UI components for Svelte 5 — WebGL, GSAP and CSS motion, styled with Tailwind CSS v4.",
|
|
6
|
+
"author": "Rama Herbin",
|
|
7
|
+
"funding": "https://github.com/sponsors/RamaHerbin",
|
|
5
8
|
"type": "module",
|
|
6
9
|
"license": "MIT",
|
|
7
10
|
"repository": {
|
|
@@ -15,10 +18,18 @@
|
|
|
15
18
|
"keywords": [
|
|
16
19
|
"svelte",
|
|
17
20
|
"svelte5",
|
|
21
|
+
"sveltekit",
|
|
18
22
|
"components",
|
|
23
|
+
"component-library",
|
|
19
24
|
"animations",
|
|
25
|
+
"animated-components",
|
|
26
|
+
"motion",
|
|
20
27
|
"ui",
|
|
21
|
-
"
|
|
28
|
+
"ui-library",
|
|
29
|
+
"tailwind",
|
|
30
|
+
"tailwindcss",
|
|
31
|
+
"webgl",
|
|
32
|
+
"typescript"
|
|
22
33
|
],
|
|
23
34
|
"sideEffects": [
|
|
24
35
|
"**/*.css",
|
|
@@ -110,6 +121,8 @@
|
|
|
110
121
|
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
|
111
122
|
"check:registry": "node scripts/check-registry.mjs",
|
|
112
123
|
"check:i18n": "node scripts/check-i18n.mjs",
|
|
124
|
+
"build:assets": "node scripts/build-synthwave-assets.mjs",
|
|
125
|
+
"build:og": "node scripts/build-og-images.mjs",
|
|
113
126
|
"test": "vitest run",
|
|
114
127
|
"test:watch": "vitest",
|
|
115
128
|
"test:e2e": "playwright test",
|