[READ-ONLY] Mirror of https://github.com/flo-bit/ui-kit. 🦊 fox ui, svelte 5 and tailwind 4 flo-bit.dev/ui-kit/
svelte tailwindcss ui-components
0

Configure Feed

Select the types of activity you want to include in your feed.

animated text component

Florian (Apr 15, 2026, 10:56 PM +0200) fc2c7071 72796cc9

+223 -7
+24
pnpm-lock.yaml
··· 868 868 cheerio: 869 869 specifier: ^1.2.0 870 870 version: 1.2.0 871 + tegaki: 872 + specifier: ^0.11.1 873 + version: 0.11.1(svelte@5.53.2) 871 874 devDependencies: 872 875 '@eslint/compat': 873 876 specifier: ^2.0.2 ··· 3921 3924 tapable@2.3.0: 3922 3925 resolution: {integrity: sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==} 3923 3926 engines: {node: '>=6'} 3927 + 3928 + tegaki@0.11.1: 3929 + resolution: {integrity: sha512-Ie4SLKcOTNFoRznLLEsAUzRU+xnOkKt/82qbsFdbbMMoVHDwJnpeynrw3b3mu4XsZFSy6G4YiLYF3zlZ8Ot4BA==} 3930 + peerDependencies: 3931 + react: '>=18' 3932 + solid-js: '>=1' 3933 + svelte: '>=5' 3934 + vue: '>=3' 3935 + peerDependenciesMeta: 3936 + react: 3937 + optional: true 3938 + solid-js: 3939 + optional: true 3940 + svelte: 3941 + optional: true 3942 + vue: 3943 + optional: true 3924 3944 3925 3945 term-size@2.2.1: 3926 3946 resolution: {integrity: sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg==} ··· 7557 7577 tailwindcss@4.2.1: {} 7558 7578 7559 7579 tapable@2.3.0: {} 7580 + 7581 + tegaki@0.11.1(svelte@5.53.2): 7582 + optionalDependencies: 7583 + svelte: 5.53.2 7560 7584 7561 7585 term-size@2.2.1: {} 7562 7586
+5
.changeset/humble-adults-refuse.md
··· 1 + --- 2 + '@foxui/visual': patch 3 + --- 4 + 5 + add animated text
+2 -1
.claude/settings.local.json
··· 2 2 "permissions": { 3 3 "allow": [ 4 4 "mcp__plugin_svelte_svelte__svelte-autofixer", 5 - "Bash(npx svelte-check:*)" 5 + "Bash(npx svelte-check:*)", 6 + "Bash(pnpm check *)" 6 7 ] 7 8 } 8 9 }
+2 -1
apps/docs/tsconfig.json
··· 9 9 "skipLibCheck": true, 10 10 "sourceMap": true, 11 11 "strict": true, 12 - "moduleResolution": "bundler" 12 + "moduleResolution": "bundler", 13 + "allowImportingTsExtensions": true 13 14 } 14 15 // Path aliases are handled by https://svelte.dev/docs/kit/configuration#alias 15 16 // except $lib which is handled by https://svelte.dev/docs/kit/configuration#files
+4 -1
apps/docs/vite.config.ts
··· 4 4 import glsl from 'vite-plugin-glsl'; 5 5 6 6 export default defineConfig({ 7 - plugins: [tailwindcss(), sveltekit(), glsl()] 7 + plugins: [tailwindcss(), sveltekit(), glsl()], 8 + optimizeDeps: { 9 + exclude: ['tegaki'] 10 + } 8 11 });
+3 -2
packages/visual/package.json
··· 71 71 "dependencies": { 72 72 "@foxui/colors": "workspace:*", 73 73 "@foxui/core": "workspace:*", 74 - "canvas-confetti": "^1.9.4", 75 74 "bits-ui": "^2.16.2", 76 - "cheerio": "^1.2.0" 75 + "canvas-confetti": "^1.9.4", 76 + "cheerio": "^1.2.0", 77 + "tegaki": "^0.11.1" 77 78 }, 78 79 "peerDependencies": { 79 80 "svelte": ">=5",
+2 -1
packages/visual/tsconfig.json
··· 9 9 "skipLibCheck": true, 10 10 "sourceMap": true, 11 11 "strict": true, 12 - "moduleResolution": "bundler" 12 + "moduleResolution": "bundler", 13 + "allowImportingTsExtensions": true 13 14 } 14 15 // Path aliases are handled by https://svelte.dev/docs/kit/configuration#alias 15 16 // except $lib which is handled by https://svelte.dev/docs/kit/configuration#files
+4 -1
packages/visual/vite.config.ts
··· 3 3 import { defineConfig } from 'vite'; 4 4 5 5 export default defineConfig({ 6 - plugins: [tailwindcss(), sveltekit()] 6 + plugins: [tailwindcss(), sveltekit()], 7 + optimizeDeps: { 8 + exclude: ['tegaki'] 9 + } 7 10 });
+1
packages/visual/src/lib/components/index.ts
··· 5 5 export * from './undraw'; 6 6 export * from './image-masonry'; 7 7 export * from './confetti'; 8 + export * from './animated-text';
+64
packages/visual/src/lib/components/animated-text/AnimatedText.svelte
··· 1 + <script lang="ts"> 2 + import { TegakiRenderer } from 'tegaki/svelte'; 3 + import type { TegakiBundle } from 'tegaki'; 4 + import { cn } from '@foxui/core'; 5 + import type { HTMLAttributes } from 'svelte/elements'; 6 + 7 + type FontName = 'caveat' | 'italianno' | 'tangerine' | 'parisienne'; 8 + 9 + const fontLoaders: Record<FontName, () => Promise<{ default: TegakiBundle }>> = { 10 + caveat: () => import('tegaki/fonts/caveat'), 11 + italianno: () => import('tegaki/fonts/italianno'), 12 + tangerine: () => import('tegaki/fonts/tangerine'), 13 + parisienne: () => import('tegaki/fonts/parisienne') 14 + }; 15 + 16 + let { 17 + text, 18 + font = 'caveat', 19 + speed = 1, 20 + loop = true, 21 + mode = 'uncontrolled', 22 + progress = 0, 23 + class: className, 24 + style, 25 + ...rest 26 + }: HTMLAttributes<HTMLDivElement> & { 27 + text: string; 28 + font?: FontName | TegakiBundle; 29 + speed?: number; 30 + loop?: boolean; 31 + mode?: 'uncontrolled' | 'controlled'; 32 + progress?: number; 33 + } = $props(); 34 + 35 + let loadedFonts = $state<Partial<Record<FontName, TegakiBundle>>>({}); 36 + 37 + const resolvedFont = $derived.by(() => { 38 + if (typeof font !== 'string') return font; 39 + const cached = loadedFonts[font]; 40 + if (cached) return cached; 41 + const name = font; 42 + fontLoaders[name]().then((m) => { 43 + loadedFonts = { ...loadedFonts, [name]: m.default }; 44 + }); 45 + return undefined; 46 + }); 47 + 48 + const time = $derived( 49 + mode === 'controlled' 50 + ? { mode: 'controlled' as const, value: progress, unit: 'progress' as const } 51 + : { mode: 'uncontrolled' as const, speed, loop } 52 + ); 53 + </script> 54 + 55 + {#if resolvedFont} 56 + <TegakiRenderer 57 + font={resolvedFont} 58 + {text} 59 + {time} 60 + class={cn('text-base-900 dark:text-base-100', className)} 61 + {style} 62 + {...rest} 63 + /> 64 + {/if}
+1
packages/visual/src/lib/components/animated-text/index.ts
··· 1 + export { default as AnimatedText } from './AnimatedText.svelte';
+7
apps/docs/src/lib/docs/visual/animated-text/Card.svelte
··· 1 + <script lang="ts"> 2 + import { AnimatedText } from '@foxui/all'; 3 + </script> 4 + 5 + <div class="flex h-full w-full items-center justify-center"> 6 + <AnimatedText text="hello" font="caveat" style="font-size: 72px" /> 7 + </div>
+23
apps/docs/src/lib/docs/visual/animated-text/Documentation.md
··· 1 + ## Usage 2 + 3 + Animated handwriting text powered by [tegaki](https://github.com/KurtGokhan/tegaki). Text draws itself stroke by stroke. 4 + 5 + ```svelte 6 + <AnimatedText text="Hello world" font="caveat" style="font-size: 64px" /> 7 + ``` 8 + 9 + ### Built-in fonts 10 + 11 + Four handwriting fonts are bundled: `caveat`, `italianno`, `tangerine`, `parisienne`. 12 + 13 + ```svelte 14 + <AnimatedText text="Elegant script" font="parisienne" speed={1.5} /> 15 + ``` 16 + 17 + ### Controlled progress 18 + 19 + Drive the animation yourself by binding progress (0 → 1): 20 + 21 + ```svelte 22 + <AnimatedText text="Scrub me" mode="controlled" progress={0.5} /> 23 + ```
+27
apps/docs/src/lib/docs/visual/animated-text/Example.svelte
··· 1 + <script lang="ts"> 2 + import { AnimatedText, Box, Button } from '@foxui/all'; 3 + 4 + const fonts = ['caveat', 'italianno', 'tangerine', 'parisienne'] as const; 5 + const texts = ['Hello world', 'Fancy handwriting', 'Sweet & Tangy', 'Elegant script']; 6 + 7 + let index = $state(0); 8 + let runId = $state(0); 9 + 10 + const font = $derived(fonts[index]); 11 + const text = $derived(texts[index]); 12 + 13 + function next() { 14 + index = (index + 1) % fonts.length; 15 + runId++; 16 + } 17 + </script> 18 + 19 + <Box class="not-prose flex flex-col items-center gap-8 py-8"> 20 + <div class="flex h-24 w-full items-center justify-center"> 21 + {#key runId} 22 + <AnimatedText {text} {font} loop={false} speed={3} style="font-size: 64px" /> 23 + {/key} 24 + </div> 25 + 26 + <Button onclick={next}>Next font ({font})</Button> 27 + </Box>
+41
apps/docs/src/lib/docs/visual/animated-text/api.ts
··· 1 + import type { APISchema } from '$lib/types/schema'; 2 + 3 + export default [ 4 + { 5 + title: 'AnimatedText', 6 + description: 7 + 'Animated handwriting text. Any font draws itself stroke by stroke, powered by tegaki.', 8 + props: { 9 + text: { 10 + type: 'string', 11 + description: 'The text to animate.' 12 + }, 13 + font: { 14 + type: { type: 'enum', definition: "'caveat' | 'italianno' | 'tangerine' | 'parisienne'" }, 15 + description: 'Built-in handwriting font to use. You can also pass a custom tegaki font bundle.', 16 + default: "'caveat'" 17 + }, 18 + speed: { 19 + type: 'number', 20 + description: 'Animation speed multiplier (uncontrolled mode).', 21 + default: '1' 22 + }, 23 + loop: { 24 + type: 'boolean', 25 + description: 'Whether to loop the animation (uncontrolled mode).', 26 + default: 'true' 27 + }, 28 + mode: { 29 + type: { type: 'enum', definition: "'uncontrolled' | 'controlled'" }, 30 + description: 31 + "Use 'controlled' to drive the animation progress yourself via the progress prop.", 32 + default: "'uncontrolled'" 33 + }, 34 + progress: { 35 + type: 'number', 36 + description: 'Progress from 0 to 1. Only used when mode is "controlled".', 37 + default: '0' 38 + } 39 + } 40 + } 41 + ] satisfies APISchema[];
+13
apps/docs/src/lib/docs/visual/animated-text/index.ts
··· 1 + import Docs from './Documentation.md'; 2 + import Example from './Example.svelte'; 3 + import Card from './Card.svelte'; 4 + import api from './api'; 5 + 6 + export default { 7 + slug: 'animated-text', 8 + title: 'Animated Text', 9 + docs: Docs, 10 + example: Example, 11 + card: Card, 12 + api, 13 + };