[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.

add phone, timer, stopwatch

Florian (Mar 20, 2025, 5:34 AM +0100) c2d6702d 4229f450

+487 -76
+6
src/lib/index.ts
··· 6 6 export { Avatar, AvatarGroup } from '$lib/components/base/avatar'; 7 7 export { Badge } from '$lib/components/base/badge'; 8 8 export { Button } from '$lib/components/base/button'; 9 + export { default as Box } from '$lib/components/base/box/Box.svelte'; 9 10 export { default as ImageCard } from '$lib/components/base/card/ImageCard.svelte'; 10 11 export { default as ChatBubble } from '$lib/components/base/chat-bubble/ChatBubble.svelte'; 11 12 export { Checkbox } from '$lib/components/base/checkbox'; ··· 34 35 export { default as Excalidraw } from '$lib/components/extra/excalidraw/Excalidraw.svelte'; 35 36 export { default as Undraw } from '$lib/components/extra/undraw/Undraw.svelte'; 36 37 export { ColorSelect } from '$lib/components/extra/color-select'; 38 + export { default as Stopwatch } from '$lib/components/extra/stopwatch/Stopwatch.svelte'; 39 + export { default as Timer } from '$lib/components/extra/timer/Timer.svelte'; 40 + export { StopwatchState } from '$lib/components/extra/stopwatch/StopwatchState.svelte'; 41 + export { TimerState } from '$lib/components/extra/timer/TimerState.svelte'; 42 + export { default as Phone } from '$lib/components/extra/phone/Phone.svelte'; 37 43 38 44 // 3d components 39 45 export { default as ModelPicker } from '$lib/components/3d/model-picker/ModelPicker.svelte';
+21
src/docs/site-components/components_extra.ts
··· 2 2 import CardUndraw from '$docs/cards/extras/CardUndraw.svelte'; 3 3 import CardColorPicker from '$docs/cards/extras/CardColorPicker.svelte'; 4 4 import CardColorSelect from '$docs/cards/extras/CardColorSelect.svelte'; 5 + import CardStopwatch from '$docs/cards/extras/CardStopwatch.svelte'; 6 + import CardTimer from '$docs/cards/extras/CardTimer.svelte'; 7 + import CardPhone from '$docs/cards/extras/CardPhone.svelte'; 5 8 6 9 export const extraComponents = [ 7 10 { ··· 27 30 className: '', 28 31 label: 'Color Select', 29 32 href: 'color-select' 33 + }, 34 + { 35 + component: CardStopwatch, 36 + className: '', 37 + label: 'Stopwatch', 38 + href: 'stopwatch' 39 + }, 40 + { 41 + component: CardTimer, 42 + className: '', 43 + label: 'Timer', 44 + href: 'timer' 45 + }, 46 + { 47 + component: CardPhone, 48 + className: '', 49 + label: 'Phone', 50 + href: 'phone' 30 51 } 31 52 ].sort((a, b) => a.label.localeCompare(b.label));
+11
src/docs/cards/extras/CardPhone.svelte
··· 1 + <script> 2 + import Phone from '$lib/components/extra/phone/Phone.svelte'; 3 + </script> 4 + 5 + <div class="-mt-20 h-full w-full scale-50"> 6 + <Phone class=""> 7 + <div class="bg-accent-200 flex h-full w-full justify-center p-8 bg-gradient-to-b from-accent-200 via-accent-300"> 8 + <div class="text-3xl font-bold text-black mt-20 text-center">Hello there</div> 9 + </div> 10 + </Phone> 11 + </div>
+16
src/docs/cards/extras/CardStopwatch.svelte
··· 1 + <script lang="ts"> 2 + import Box from '$lib/components/base/box/Box.svelte'; 3 + import Stopwatch from '$lib/components/extra/stopwatch/Stopwatch.svelte'; 4 + import { StopwatchState } from '$lib/components/extra/stopwatch/StopwatchState.svelte'; 5 + import { onMount } from 'svelte'; 6 + 7 + let stopwatch: StopwatchState | undefined = $state(undefined); 8 + 9 + onMount(() => { 10 + stopwatch?.start(); 11 + }); 12 + </script> 13 + 14 + <Box class="min-h-0"> 15 + <Stopwatch bind:stopwatch class="text-4xl lg:text-5xl" /> 16 + </Box>
+16
src/docs/cards/extras/CardTimer.svelte
··· 1 + <script lang="ts"> 2 + import Box from '$lib/components/base/box/Box.svelte'; 3 + import Timer from '$lib/components/extra/timer/Timer.svelte'; 4 + import { TimerState } from '$lib/components/extra/timer/TimerState.svelte'; 5 + import { onMount } from 'svelte'; 6 + 7 + let timer: TimerState | undefined = $state(); 8 + 9 + onMount(() => { 10 + timer?.start(); 11 + }); 12 + </script> 13 + 14 + <Box class="min-h-0"> 15 + <Timer bind:timer class="text-4xl lg:text-5xl" /> 16 + </Box>
+3 -43
src/lib/components/base/scroll-area/BodyScrollArea.svelte
··· 1 - <style> 2 - :global(.body::-webkit-scrollbar-track) { 3 - background-color: transparent; 4 - } 5 - 6 - @supports (scrollbar-width: auto) { 7 - :global(body) { 8 - scrollbar-color: var(--color-base-400) transparent; 9 - scrollbar-width: thin; 10 - } 11 - 12 - :global(.dark body) { 13 - scrollbar-color: var(--color-base-800) transparent; 14 - } 15 - } 16 - 17 - @supports not (scrollbar-width: auto) { 18 - :global(body::-webkit-scrollbar) { 19 - width: 14px; 20 - height: 14px; 21 - } 22 - } 23 - 24 - :global(body::-webkit-scrollbar-thumb) { 25 - background-color: var(--color-base-400); 26 - border-radius: 20px; 27 - border: 4px solid transparent; 28 - background-clip: content-box; 29 - } 30 - 31 - :global(body::-webkit-scrollbar-thumb:hover) { 32 - background-color: var(--color-base-500); 33 - } 34 - 35 - /* Dark mode rules */ 36 - :global(.dark body::-webkit-scrollbar-thumb) { 37 - background-color: var(--color-base-800); 38 - } 39 - 40 - :global(.dark body::-webkit-scrollbar-thumb:hover) { 41 - background-color: var(--color-base-700); 42 - } 43 - </style> 1 + <script lang="ts"> 2 + import './style.css'; 3 + </script>
+41
src/lib/components/base/scroll-area/style.css
··· 1 + .body::-webkit-scrollbar-track { 2 + background-color: transparent; 3 + } 4 + 5 + @supports (scrollbar-width: auto) { 6 + body { 7 + scrollbar-color: var(--color-base-400) transparent; 8 + scrollbar-width: thin; 9 + } 10 + 11 + .dark body { 12 + scrollbar-color: var(--color-base-800) transparent; 13 + } 14 + } 15 + 16 + @supports not (scrollbar-width: auto) { 17 + body::-webkit-scrollbar { 18 + width: 14px; 19 + height: 14px; 20 + } 21 + } 22 + 23 + body::-webkit-scrollbar-thumb { 24 + background-color: var(--color-base-400); 25 + border-radius: 20px; 26 + border: 4px solid transparent; 27 + background-clip: content-box; 28 + } 29 + 30 + body::-webkit-scrollbar-thumb:hover { 31 + background-color: var(--color-base-500); 32 + } 33 + 34 + /* Dark mode rules */ 35 + .dark body::-webkit-scrollbar-thumb { 36 + background-color: var(--color-base-800); 37 + } 38 + 39 + .dark body::-webkit-scrollbar-thumb:hover { 40 + background-color: var(--color-base-700); 41 + }
+10 -2
src/lib/components/extra/phone/Phone.svelte
··· 1 1 <script lang="ts"> 2 - const { children } = $props(); 2 + import { cn } from '$lib'; 3 + import type { Snippet } from 'svelte'; 4 + 5 + const { children, class: className }: { children: Snippet; class?: string } = $props(); 3 6 </script> 4 7 5 - <div class="bg-accent-500 relative mx-auto h-[712px] w-[350px] max-w-full rounded-[60px]"> 8 + <div 9 + class={cn( 10 + 'isolate bg-accent-500 relative h-[712px] w-[350px] max-w-full rounded-[60px] aspect-[350/712]', 11 + className 12 + )} 13 + > 6 14 <div class="bg-accent-500 absolute top-20 -left-0.5 h-8 w-3 rounded-xs"></div> 7 15 <div class="bg-accent-500 absolute top-[140px] -left-[3px] h-14 w-3 rounded-xs"></div> 8 16 <div class="bg-accent-500 absolute top-[210px] -left-[3px] h-14 w-3 rounded-xs"></div>
+66
src/lib/components/extra/stopwatch/Stopwatch.svelte
··· 1 + <script lang="ts"> 2 + import NumberFlow, { NumberFlowGroup } from '@number-flow/svelte'; 3 + import { StopwatchState } from './StopwatchState.svelte'; 4 + import type { WithElementRef, WithoutChildrenOrChild } from 'bits-ui'; 5 + import type { HTMLAttributes } from 'svelte/elements'; 6 + import { cn } from '$lib/utils.js'; 7 + 8 + let { 9 + stopwatch = $bindable(), 10 + class: className, 11 + ref = $bindable(null), 12 + showHours = false, 13 + showMinutes = true, 14 + showSeconds = true, 15 + ...restProps 16 + }: WithElementRef<WithoutChildrenOrChild<HTMLAttributes<HTMLDivElement>>> & { 17 + stopwatch?: StopwatchState; 18 + showHours?: boolean; 19 + showMinutes?: boolean; 20 + showSeconds?: boolean; 21 + } = $props(); 22 + 23 + if (!stopwatch) { 24 + stopwatch = new StopwatchState(); 25 + } 26 + 27 + const hh = $derived(Math.floor(stopwatch.elapsed / 3600000)); 28 + const mm = $derived(Math.floor((stopwatch.elapsed % 3600000) / 60000)); 29 + const ss = $derived(Math.floor((stopwatch.elapsed % 60000) / 1000)); 30 + </script> 31 + 32 + <NumberFlowGroup> 33 + <div 34 + bind:this={ref} 35 + class={cn( 36 + 'text-base-900 dark:text-base-100 flex w-full justify-center text-5xl font-bold sm:text-7xl', 37 + className 38 + )} 39 + style="font-variant-numeric: tabular-nums;" 40 + {...restProps} 41 + > 42 + {#if showHours} 43 + <NumberFlow value={hh} trend={1} format={{ minimumIntegerDigits: 2 }} /> 44 + {/if} 45 + 46 + {#if showMinutes} 47 + <NumberFlow 48 + value={mm} 49 + trend={1} 50 + format={{ minimumIntegerDigits: 2 }} 51 + prefix={showHours ? ':' : ''} 52 + digits={{ 1: { max: 5 } }} 53 + /> 54 + {/if} 55 + 56 + {#if showSeconds} 57 + <NumberFlow 58 + value={ss} 59 + format={{ minimumIntegerDigits: 2 }} 60 + trend={1} 61 + prefix={showHours || showMinutes ? ':' : ''} 62 + digits={{ 1: { max: 5 } }} 63 + /> 64 + {/if} 65 + </div> 66 + </NumberFlowGroup>
+90
src/lib/components/extra/stopwatch/StopwatchState.svelte.ts
··· 1 + // adopted from https://github.com/joshnuss/svelte-reactive-timer 2 + 3 + type Status = 'running' | 'paused' | 'stopped'; 4 + type Options = { 5 + precision?: number; 6 + }; 7 + 8 + export class StopwatchState { 9 + status = $state<Status>('stopped'); 10 + duration = $state<number>(0); 11 + elapsed = $state<number>(0); 12 + startAt = $state<Date | null>(null); 13 + 14 + time = $state<Date | null>(null); 15 + precision: number; 16 + #interval: number | null = null; 17 + 18 + constructor(options: Options = {}) { 19 + this.precision = options.precision ?? 1000 / 60; 20 + } 21 + 22 + get isRunning() { 23 + return this.status == 'running'; 24 + } 25 + 26 + get isStopped() { 27 + return this.status == 'stopped'; 28 + } 29 + 30 + get isPaused() { 31 + return this.status == 'paused'; 32 + } 33 + 34 + start() { 35 + if (this.isRunning) { 36 + this.stop(); 37 + } 38 + 39 + this.time = new Date(); 40 + this.startAt = this.time; 41 + this.status = 'running'; 42 + this.elapsed = 0; 43 + this.#schedule(); 44 + } 45 + 46 + stop() { 47 + this.#dispose(); 48 + this.status = 'stopped'; 49 + } 50 + 51 + pause() { 52 + this.#dispose(); 53 + this.status = 'paused'; 54 + } 55 + 56 + resume() { 57 + this.time = new Date(); 58 + this.status = 'running'; 59 + this.#schedule(); 60 + } 61 + 62 + reset(duration?: number) { 63 + if (duration) { 64 + this.duration = duration; 65 + } 66 + this.#dispose(); 67 + 68 + this.time = new Date(); 69 + this.startAt = this.time; 70 + this.elapsed = 0; 71 + } 72 + 73 + #schedule() { 74 + this.time = new Date(); 75 + this.#interval = setInterval(() => this.#onInterval(), this.precision); 76 + } 77 + 78 + #dispose() { 79 + if (this.#interval) { 80 + clearInterval(this.#interval); 81 + } 82 + } 83 + 84 + #onInterval() { 85 + const now = new Date(); 86 + 87 + this.elapsed += now.getTime() - this.time!.getTime(); 88 + this.time = now; 89 + } 90 + }
+33 -16
src/lib/components/extra/timer/Timer.svelte
··· 6 6 import { cn } from '$lib/utils.js'; 7 7 8 8 let { 9 - timer = $bindable(new TimerState(1000 * 60 * 60)), 9 + timer = $bindable(), 10 10 class: className, 11 11 ref = $bindable(null), 12 + showHours = false, 13 + showMinutes = true, 14 + showSeconds = true, 12 15 ...restProps 13 16 }: WithElementRef<WithoutChildrenOrChild<HTMLAttributes<HTMLDivElement>>> & { 14 17 timer?: TimerState; 18 + showHours?: boolean; 19 + showMinutes?: boolean; 20 + showSeconds?: boolean; 15 21 } = $props(); 22 + 23 + if (!timer) { 24 + timer = new TimerState(1000 * 60 * 10); 25 + } 16 26 17 27 const hh = $derived(Math.floor(timer.remaining / 3600000)); 18 28 const mm = $derived(Math.floor((timer.remaining % 3600000) / 60000)); ··· 29 39 style="font-variant-numeric: tabular-nums;" 30 40 {...restProps} 31 41 > 32 - <NumberFlow value={hh} trend={-1} format={{ minimumIntegerDigits: 2 }} /> 42 + {#if showHours} 43 + <NumberFlow value={hh} trend={-1} format={{ minimumIntegerDigits: 2 }} /> 44 + {/if} 33 45 34 - <NumberFlow 35 - value={mm} 36 - trend={-1} 37 - format={{ minimumIntegerDigits: 2 }} 38 - prefix=":" 39 - digits={{ 1: { max: 5 } }} 40 - /> 41 - <NumberFlow 42 - value={ss} 43 - format={{ minimumIntegerDigits: 2 }} 44 - trend={-1} 45 - prefix=":" 46 - digits={{ 1: { max: 5 } }} 47 - /> 46 + {#if showMinutes} 47 + <NumberFlow 48 + value={mm} 49 + trend={-1} 50 + format={{ minimumIntegerDigits: 2 }} 51 + prefix={showHours ? ':' : ''} 52 + digits={{ 1: { max: 5 } }} 53 + /> 54 + {/if} 55 + 56 + {#if showSeconds} 57 + <NumberFlow 58 + value={ss} 59 + format={{ minimumIntegerDigits: 2 }} 60 + trend={-1} 61 + prefix={showHours || showMinutes ? ':' : ''} 62 + digits={{ 1: { max: 5 } }} 63 + /> 64 + {/if} 48 65 </div> 49 66 </NumberFlowGroup>
+5 -8
src/lib/components/extra/timer/TimerState.svelte.ts
··· 69 69 } 70 70 this.#dispose(); 71 71 72 - if (!this.isStopped) { 73 - this.start(); 74 - } else { 75 - this.time = new Date(); 76 - this.startAt = this.time; 77 - this.endAt = new Date(this.time.getTime() + this.duration); 78 - this.elapsed = 0; 79 - } 72 + this.status = 'stopped'; 73 + this.time = new Date(); 74 + this.startAt = this.time; 75 + this.endAt = new Date(this.time.getTime() + this.duration); 76 + this.elapsed = 0; 80 77 } 81 78 82 79 #schedule() {
+4
src/routes/components/base/number-input/NumberInput.md
··· 20 20 21 21 <NumberInput min={0} max={100} bind:value /> 22 22 ``` 23 + 24 + ## Credits 25 + 26 + - Adapted from an example of [number-flow](https://number-flow.barvian.me/)
+1 -1
src/routes/components/extras/color-picker/ColorPicker.md
··· 30 30 31 31 ### Credits 32 32 33 - Adapted from [svelte-color-select](https://github.com/CaptainCodeman/svelte-color-select). 33 + - Adapted from [svelte-color-select](https://github.com/CaptainCodeman/svelte-color-select).
+6 -3
src/routes/components/extras/phone/+page.svelte
··· 1 - <script> 2 - import PhonePreview from '$docs/preview/PhonePreview.svelte'; 1 + <script lang="ts"> 2 + import PhoneDocs from './Phone.md'; 3 + import Prose from '$lib/components/base/prose/Prose.svelte'; 3 4 </script> 4 5 5 - <PhonePreview /> 6 + <Prose> 7 + <PhoneDocs /> 8 + </Prose>
+11
src/routes/components/extras/phone/Example.svelte
··· 1 + <script> 2 + import { Phone } from '$lib'; 3 + </script> 4 + 5 + <Phone> 6 + <div 7 + class="from-accent-200 to-accent-300 flex h-full w-full items-center justify-center bg-gradient-to-b p-8" 8 + > 9 + <div class="text-3xl font-bold text-black">Hello there</div> 10 + </div> 11 + </Phone>
+23
src/routes/components/extras/phone/Phone.md
··· 1 + <script lang="ts"> 2 + import Example from './Example.svelte'; 3 + </script> 4 + 5 + # Phone 6 + 7 + ## Example 8 + 9 + <Example /> 10 + 11 + ## Usage 12 + 13 + ```svelte 14 + <script lang="ts"> 15 + import { Phone } from 'fuchs'; 16 + </script> 17 + 18 + <Phone> 19 + <div class="bg-accent-200 flex h-full w-full items-center justify-center p-8"> 20 + <div class="text-3xl font-bold text-black">Hello there</div> 21 + </div> 22 + </Phone> 23 + ```
+8
src/routes/components/extras/stopwatch/+page.svelte
··· 1 + <script lang="ts"> 2 + import StopwatchDocs from './Stopwatch.md'; 3 + import Prose from '$lib/components/base/prose/Prose.svelte'; 4 + </script> 5 + 6 + <Prose> 7 + <StopwatchDocs /> 8 + </Prose>
+27
src/routes/components/extras/stopwatch/Example.svelte
··· 1 + <script lang="ts"> 2 + import { Box, Button, Stopwatch, StopwatchState } from '$lib'; 3 + import { onMount } from 'svelte'; 4 + 5 + let stopwatch: StopwatchState | undefined = $state(undefined); 6 + 7 + onMount(() => { 8 + stopwatch?.start(); 9 + }); 10 + </script> 11 + 12 + <Box class="min-h-0"> 13 + <Stopwatch bind:stopwatch /> 14 + </Box> 15 + 16 + <div class="flex w-full justify-center gap-2 mt-2"> 17 + <Button 18 + onclick={() => { 19 + if (stopwatch?.isRunning) { 20 + stopwatch?.pause(); 21 + } else { 22 + stopwatch?.resume(); 23 + } 24 + }}>{stopwatch?.isRunning ? 'Pause' : 'Start'}</Button 25 + > 26 + <Button onclick={() => stopwatch?.reset()}>Reset</Button> 27 + </div>
+28
src/routes/components/extras/stopwatch/Stopwatch.md
··· 1 + <script lang="ts"> 2 + import Example from './Example.svelte'; 3 + </script> 4 + 5 + # Stopwatch 6 + 7 + ## Example 8 + 9 + <Example /> 10 + 11 + ## Usage 12 + 13 + ```svelte 14 + <script lang="ts"> 15 + import { Button, Stopwatch, StopwatchState } from 'fuchs'; 16 + 17 + let stopwatch: StopwatchState | undefined = $state(); 18 + </script> 19 + 20 + <Stopwatch bind:stopwatch /> 21 + 22 + <Button onclick={() => stopwatch?.start()}>Start</Button> 23 + ``` 24 + 25 + ## Credits 26 + 27 + - Stopwatch state based on [svelte-reactive-timer](https://github.com/joshnuss/svelte-reactive-timer) 28 + - Moving numbers component from [number-flow](https://number-flow.barvian.me/)
+6 -3
src/routes/components/extras/timer/+page.svelte
··· 1 - <script> 2 - import TimerPreview from '$docs/preview/TimerPreview.svelte'; 1 + <script lang="ts"> 2 + import TimerDocs from './Timer.md'; 3 + import Prose from '$lib/components/base/prose/Prose.svelte'; 3 4 </script> 4 5 5 - <TimerPreview /> 6 + <Prose> 7 + <TimerDocs /> 8 + </Prose>
+27
src/routes/components/extras/timer/Example.svelte
··· 1 + <script lang="ts"> 2 + import { Box, Button, Timer, TimerState } from '$lib'; 3 + import { onMount } from 'svelte'; 4 + 5 + let timer: TimerState | undefined = $state(undefined); 6 + 7 + onMount(() => { 8 + timer?.start(); 9 + }); 10 + </script> 11 + 12 + <Box class="min-h-0"> 13 + <Timer bind:timer /> 14 + </Box> 15 + 16 + <div class="flex w-full justify-center gap-2 mt-2"> 17 + <Button 18 + onclick={() => { 19 + if (timer?.isRunning) { 20 + timer?.pause(); 21 + } else { 22 + timer?.resume(); 23 + } 24 + }}>{timer?.isRunning ? 'Pause' : 'Start'}</Button 25 + > 26 + <Button onclick={() => timer?.reset()}>Reset</Button> 27 + </div>
+28
src/routes/components/extras/timer/Timer.md
··· 1 + <script lang="ts"> 2 + import Example from './Example.svelte'; 3 + </script> 4 + 5 + # Timer 6 + 7 + ## Example 8 + 9 + <Example /> 10 + 11 + ## Usage 12 + 13 + ```svelte 14 + <script lang="ts"> 15 + import { Button, Timer, TimerState } from 'fuchs'; 16 + 17 + let timer = $state(new TimerState(1000 * 60 * 60)); 18 + </script> 19 + 20 + <Timer bind:timer /> 21 + 22 + <Button onclick={() => timer.start()}>Start</Button> 23 + ``` 24 + 25 + ## Credits 26 + 27 + - Timer state based on [svelte-reactive-timer](https://github.com/joshnuss/svelte-reactive-timer) 28 + - Moving numbers component from [number-flow](https://number-flow.barvian.me/)