Skip to content

Commit

Permalink
Merge pull request #120 from ieedan/update-readme
Browse files Browse the repository at this point in the history
  • Loading branch information
ieedan authored Feb 21, 2025
2 parents 36e05fd + d61f3b5 commit c72f103
Show file tree
Hide file tree
Showing 10 changed files with 283 additions and 25 deletions.
35 changes: 32 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,40 @@
![image](https://github.com/user-attachments/assets/276803e5-8370-483d-acee-f8e1e6f3abd9)

# shadcn-svelte-extras

[![jsrepo](https://jsrepo.dev/badges/build/passing.svg)](https://jsrepo.dev)
[![jsrepo](https://jsrepo.dev/badges/build/passing.svg)](https://jsrepo.dev/registry?url=github/ieedan/shadcn-svelte-extras)

Turn key shadcn-svelte components to help finish you your app.

![image](./static/og.png)

## Components

| Name | |
| ---------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------- |
| [AvatarGroup](https://shadcn-svelte-extras.com/components/avatar-group) | A composable avatar grouping component |
| [Button](https://shadcn-svelte-extras.com/components/button) | An extended button component |
| [Chat](https://shadcn-svelte-extras.com/components/chat) | A component for creating live chats |
| [Code](https://shadcn-svelte-extras.com/components/code) | A code component |
| [Copy Button](https://shadcn-svelte-extras.com/components/copy-button) | A button used to copy text to the clipboard |
| [Field Set](https://shadcn-svelte-extras.com/components/field-set) | A field set component |
| [File Drop Zone](https://shadcn-svelte-extras.com/components/file-drop-zone) | A file drop zone component |
| [Image Cropper](https://shadcn-svelte-extras.com/components/image-cropper) | A component for uploading and resizing images |
| [IPv4Address Input](https://shadcn-svelte-extras.com/components/ipv4address-input) | An IPv4 address input input with all the behavior you'd expect |
| [Kbd](https://shadcn-svelte-extras.com/components/kbd) | Denotes user input from a keyboard |
| [Light Switch](https://shadcn-svelte-extras.com/components/light-switch) | A component to click and change the theme |
| [Link](https://shadcn-svelte-extras.com/components/link) | A simple link component |
| [Modal](https://shadcn-svelte-extras.com/components/modal) | A responsive dialog component |
| [NLP Date Input](https://shadcn-svelte-extras.com/components/nlp-date-input) | A natural language date input with suggestions |
| [Phone Input](https://shadcn-svelte-extras.com/components/phone-input) | A phone number input component |
| [PM Command](https://shadcn-svelte-extras.com/components/pm-command) | A package manager command component |
| [Snippet](https://shadcn-svelte-extras.com/components/snippet) | A snippet component |
| [Tags Input](https://shadcn-svelte-extras.com/components/tags-input) | A tags input component |
| [Terminal](https://shadcn-svelte-extras.com/components/terminal) | An implementation of the MacOS terminal. Useful for showcasing a command line interface |
| [Theme Selector](https://shadcn-svelte-extras.com/components/theme-selector) | Click to select the theme |
| [Tree View](https://shadcn-svelte-extras.com/components/tree-view) | A file tree component |
| [Window](https://shadcn-svelte-extras.com/components/window) | A window component |

and more...

## Install with jsrepo

**Initialize jsrepo**:
Expand Down
2 changes: 1 addition & 1 deletion src/lib/components/docs/examples/editor-file-tree.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import * as TreeView from '$lib/components/ui/tree-view';
</script>

<div class="w-full border border-border">
<div class="h-[825px] w-full border border-border">
<TreeView.Root class="p-4">
<TreeView.Folder name=".github" open={false}>
<TreeView.Folder name="workflows">
Expand Down
123 changes: 123 additions & 0 deletions src/lib/components/docs/examples/file-drop-zone.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
<script lang="ts">
import { Button } from '$lib/components/ui/button';
import {
displaySize,
FileDropZone,
MEGABYTE,
type FileDropZoneProps
} from '$lib/components/ui/file-drop-zone';
import { Progress } from '$lib/components/ui/progress';
import { sleep } from '$lib/utils/sleep';
import { X } from 'lucide-svelte';
import { onDestroy } from 'svelte';
import { toast } from 'svelte-sonner';
import { SvelteDate } from 'svelte/reactivity';
const onUpload: FileDropZoneProps['onUpload'] = async (files) => {
await Promise.allSettled(files.map((file) => uploadFile(file)));
};
const onFileRejected: FileDropZoneProps['onFileRejected'] = async ({ reason, file }) => {
toast.error(`${file.name} failed to upload!`, { description: reason });
};
const uploadFile = async (file: File) => {
// don't upload duplicate files
if (files.find((f) => f.name === file.name)) return;
const urlPromise = new Promise<string>((resolve) => {
// add some fake loading time
sleep(1000).then(() => resolve(URL.createObjectURL(file)));
});
files.push({
name: file.name,
type: file.type,
size: file.size,
uploadedAt: Date.now(),
url: urlPromise
});
// we await since we don't want the onUpload to be complete until the files are actually uploaded
await urlPromise;
};
type UploadedFile = {
name: string;
type: string;
size: number;
uploadedAt: number;
url: Promise<string>;
};
let files = $state<UploadedFile[]>([]);
let date = new SvelteDate();
onDestroy(async () => {
for (const file of files) {
URL.revokeObjectURL(await file.url);
}
});
$effect(() => {
const interval = setInterval(() => {
date.setTime(Date.now());
}, 10);
return () => {
clearInterval(interval);
};
});
</script>

<div class="flex w-full flex-col gap-2">
<FileDropZone
{onUpload}
{onFileRejected}
maxFileSize={2 * MEGABYTE}
accept="image/*"
maxFiles={4}
fileCount={files.length}
/>
<div class="flex flex-col gap-2">
{#each files as file, i}
<div
class="flex place-items-center justify-between gap-2 rounded-md border border-border p-2"
>
<div class="flex place-items-center gap-2">
{#await file.url then src}
<div class="relative size-9 overflow-clip">
<img
{src}
alt={file.name}
class="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 overflow-clip"
/>
</div>
{/await}
<div class="flex flex-col">
<span class="text-nowrap">{file.name}</span>
<span class="text-xs text-muted-foreground">{displaySize(file.size)}</span>
</div>
</div>
{#await file.url}
<Progress
class="h-2 w-full flex-grow"
value={((date.getTime() - file.uploadedAt) / 1000) * 100}
max={100}
/>
{:then url}
<Button
variant="outline"
size="icon"
onclick={() => {
URL.revokeObjectURL(url);
files = [...files.slice(0, i), ...files.slice(i + 1)];
}}
>
<X />
</Button>
{/await}
</div>
{/each}
</div>
</div>
14 changes: 13 additions & 1 deletion src/lib/components/docs/examples/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,17 @@ import ConfigureDevice from './configure-device.svelte';
import PhoneNumberSetting from './phone-number-setting.svelte';
import CodeBlock from './code-block.svelte';
import EditorFileTree from './editor-file-tree.svelte';
import Terminal from './terminal.svelte';
import FileDropZone from './file-drop-zone.svelte';
import PmCommand from './pm-command.svelte';

export { LoginForm, ConfigureDevice, PhoneNumberSetting, CodeBlock, EditorFileTree };
export {
LoginForm,
PmCommand,
ConfigureDevice,
PhoneNumberSetting,
CodeBlock,
EditorFileTree,
Terminal,
FileDropZone
};
5 changes: 5 additions & 0 deletions src/lib/components/docs/examples/pm-command.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<script lang="ts">
import { PMCommand } from '$lib/components/ui/pm-command';
</script>

<PMCommand command="execute" args={['jsrepo', 'add', 'ui/pm-command']} />
44 changes: 44 additions & 0 deletions src/lib/components/docs/examples/terminal.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<script lang="ts">
import * as Terminal from '$lib/components/ui/terminal';
</script>

<Terminal.Loop delay={5000}>
<Terminal.Root class="h-[275px] leading-5">
<Terminal.TypingAnimation>jsrepo add ui/terminal</Terminal.TypingAnimation>
<br />
<Terminal.AnimatedSpan delay={1400}>
<span class="text-muted-foreground">┌</span>
<span class="bg-yellow-400 px-2 text-black">jsrepo</span>
<span class="text-muted-foreground">v1.0.0</span>
</Terminal.AnimatedSpan>

<Terminal.AnimatedSpan delay={1450} class="text-muted-foreground">│</Terminal.AnimatedSpan>

<Terminal.Loading delay={1500}>
{#snippet loadingMessage()}
Fetching manifest from <span class="text-cyan-500">shadcn-svelte-extras</span>
{/snippet}
{#snippet completeMessage()}
<span class="text-green-500">◇</span> Fetched manifest from
<span class="text-cyan-500">shadcn-svelte-extras</span>
{/snippet}
</Terminal.Loading>

<Terminal.AnimatedSpan delay={2650} class="text-muted-foreground">│</Terminal.AnimatedSpan>

<Terminal.Loading delay={2750}>
{#snippet loadingMessage()}
Adding <span class="text-cyan-500">ui/terminal</span>
{/snippet}
{#snippet completeMessage()}
<span class="text-green-500">◇</span> Added
<span class="text-cyan-500">ui/terminal</span>
{/snippet}
</Terminal.Loading>
<Terminal.AnimatedSpan delay={3850} class="text-muted-foreground">│</Terminal.AnimatedSpan>
<Terminal.AnimatedSpan delay={3900} class="text-green-500">
<span class="text-muted-foreground">└</span>
✓ All done!
</Terminal.AnimatedSpan>
</Terminal.Root>
</Terminal.Loop>
2 changes: 1 addition & 1 deletion src/lib/components/ui/terminal/terminal.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,6 @@
});
</script>

<Window class={cn('', className)}>
<Window class={cn('font-mono', className)}>
{@render children?.()}
</Window>
8 changes: 6 additions & 2 deletions src/routes/+layout.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -88,8 +88,12 @@
<LightSwitch variant="ghost" />
</div>
</header>
<PageWrapper doc={currentDoc}>
{#if page.url.pathname !== '/'}
<PageWrapper doc={currentDoc}>
{@render children()}
</PageWrapper>
{:else}
{@render children()}
</PageWrapper>
{/if}
</Sidebar.Inset>
</Sidebar.Provider>
75 changes: 58 additions & 17 deletions src/routes/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -4,32 +4,73 @@
ConfigureDevice,
PhoneNumberSetting,
CodeBlock,
EditorFileTree
EditorFileTree,
FileDropZone,
Terminal,
PmCommand
} from '$lib/components/docs/examples';
import ShadcnSvelteExtras from '$lib/components/shadcn-svelte-extras.svelte';
import { Snippet } from '$lib/components/ui/snippet';
import SearchButton from '$lib/components/search-button.svelte';
import ChatExample from './components/chat/basic.svelte';
import { TagsInput } from '$lib/components/ui/tags-input';
import { LucideArrowRight, TerminalIcon } from 'lucide-svelte';
import { map } from '$lib/map';
let tags = $state(['shadcn-svelte', 'extras']);
const components = $derived(
Array.from(Object.entries(map))
.filter(([cat]) => cat === 'Components')
.flatMap(([_, components]) =>
components.map((comp, i) => `${i === components.length - 1 ? 'and ' : ''}${comp.name}`)
)
.join(', ')
);
</script>

<p>
<ShadcnSvelteExtras /> provides you with the rest of the components you need to finish your application.
</p>
<div class="grid grid-cols-1 gap-4 rounded-lg border p-6 lg:grid-cols-2">
<div class="flex flex-col gap-4 lg:col-start-1">
<LoginForm />
<CodeBlock />
<TagsInput bind:value={tags} placeholder="Add a tag" />
<ChatExample />
<SearchButton />
<svelte:head>
<title>shadcn-svelte-extras</title>
<meta
name="description"
content="Finish your app with awesome svelte components like {components}"
/>
</svelte:head>

<div class="flex flex-col gap-8 p-8">
<div class="flex flex-col gap-2">
<a href="/components/terminal" class="flex place-items-center gap-1 text-sm font-medium">
<TerminalIcon class="size-4" />
<span class="hover:underline">{'jsrepo add ui/terminal'}</span>
<LucideArrowRight class="size-4" />
</a>
<h1 class="text-5xl font-bold">shadcn-svelte-extras</h1>
<p class="text-lg text-muted-foreground">Finish your app.</p>
</div>
<div class="flex flex-col gap-4 lg:col-start-2">
<PhoneNumberSetting />
<Snippet text="npx shadcn-svelte@next init" />
<ConfigureDevice />
<EditorFileTree />
<div class="grid grid-cols-1 gap-4 lg:grid-cols-2 2xl:grid-cols-3">
<div class="flex flex-col gap-4 lg:col-start-1">
<ChatExample />
<TagsInput bind:value={tags} placeholder="Add a tag" />
<LoginForm />
<SearchButton />
<div class="flex flex-col gap-4 2xl:hidden">
<EditorFileTree />
</div>
</div>
<div class="flex flex-col gap-4 lg:col-start-2">
<PhoneNumberSetting />
<CodeBlock />
<ConfigureDevice />
<Snippet text="npx shadcn-svelte@next init" />
<FileDropZone />
<div class="flex flex-col gap-4 2xl:hidden">
<Terminal />
<PmCommand />
</div>
</div>
<div class="hidden flex-col gap-4 2xl:col-start-3 2xl:flex">
<Terminal />
<PmCommand />
<EditorFileTree />
</div>
</div>
</div>
Binary file added static/og.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit c72f103

Please sign in to comment.