This commit is contained in:
Face 2024-07-07 11:09:13 +03:00
commit e550073cb7
75 changed files with 5429 additions and 0 deletions

21
.gitignore vendored Normal file
View File

@ -0,0 +1,21 @@
node_modules
# Output
.output
.vercel
/.svelte-kit
/build
# OS
.DS_Store
Thumbs.db
# Env
.env
.env.*
!.env.example
!.env.test
# Vite
vite.config.js.timestamp-*
vite.config.ts.timestamp-*

1
.npmrc Normal file
View File

@ -0,0 +1 @@
engine-strict=true

4
.prettierignore Normal file
View File

@ -0,0 +1,4 @@
# Package Managers
package-lock.json
pnpm-lock.yaml
yarn.lock

8
.prettierrc Normal file
View File

@ -0,0 +1,8 @@
{
"useTabs": true,
"singleQuote": true,
"trailingComma": "none",
"printWidth": 100,
"plugins": ["prettier-plugin-svelte", "prettier-plugin-tailwindcss"],
"overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }]
}

38
README.md Normal file
View File

@ -0,0 +1,38 @@
# create-svelte
Everything you need to build a Svelte project, powered by [`create-svelte`](https://github.com/sveltejs/kit/tree/main/packages/create-svelte).
## Creating a project
If you're seeing this, you've probably already done this step. Congrats!
```bash
# create a new project in the current directory
npm create svelte@latest
# create a new project in my-app
npm create svelte@latest my-app
```
## Developing
Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server:
```bash
npm run dev
# or start the server and open the app in a new browser tab
npm run dev -- --open
```
## Building
To create a production version of your app:
```bash
npm run build
```
You can preview the production build with `npm run preview`.
> To deploy your app, you may need to install an [adapter](https://kit.svelte.dev/docs/adapters) for your target environment.

14
components.json Normal file
View File

@ -0,0 +1,14 @@
{
"$schema": "https://shadcn-svelte.com/schema.json",
"style": "default",
"tailwind": {
"config": "tailwind.config.ts",
"css": "src\\app.css",
"baseColor": "stone"
},
"aliases": {
"components": "$lib/components",
"utils": "$lib/utils"
},
"typescript": true
}

3602
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

45
package.json Normal file
View File

@ -0,0 +1,45 @@
{
"name": "lyntr",
"version": "0.0.1",
"private": true,
"scripts": {
"dev": "vite dev",
"build": "vite build",
"preview": "vite preview",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
"lint": "prettier --check .",
"format": "prettier --write ."
},
"devDependencies": {
"@sveltejs/adapter-auto": "^3.0.0",
"@sveltejs/kit": "^2.0.0",
"@sveltejs/vite-plugin-svelte": "^3.0.0",
"autoprefixer": "^10.4.19",
"postcss": "^8.4.38",
"prettier": "^3.1.1",
"prettier-plugin-svelte": "^3.1.2",
"prettier-plugin-tailwindcss": "^0.6.4",
"svelte": "^4.2.7",
"svelte-check": "^3.6.0",
"tailwindcss": "^3.4.4",
"tslib": "^2.4.1",
"typescript": "^5.0.0",
"vite": "^5.0.3"
},
"type": "module",
"dependencies": {
"@fontsource/roboto": "^5.0.13",
"@tailwindcss/line-clamp": "^0.4.4",
"bits-ui": "^0.21.11",
"clsx": "^2.1.1",
"formsnap": "^1.0.1",
"lucide-svelte": "^0.399.0",
"mode-watcher": "^0.3.1",
"svelte-sonner": "^0.3.24",
"sveltekit-superforms": "^2.15.2",
"tailwind-merge": "^2.3.0",
"tailwind-variants": "^0.2.1",
"zod": "^3.23.8"
}
}

6
postcss.config.js Normal file
View File

@ -0,0 +1,6 @@
export default {
plugins: {
tailwindcss: {},
autoprefixer: {}
}
};

94
src/app.css Normal file
View File

@ -0,0 +1,94 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
:root {
--background: 53 24% 93%;
--foreground: 20 14.3% 4.1%;
--muted: 60 4.8% 95.9%;
--muted-foreground: 25 5.3% 44.7%;
--popover: 0 0% 100%;
--popover-foreground: 20 14.3% 4.1%;
--card: 0 0% 100%;
--card-foreground: 20 14.3% 4.1%;
--border: 48 26% 89%;
--input: 20 5.9% 90%;
--primary: 22 100% 15%;
--primary-foreground: 60 9.1% 97.8%;
--secondary: 60 4.8% 95.9%;
--secondary-foreground: 24 9.8% 10%;
--accent: 60 4.8% 95.9%;
--accent-foreground: 24 9.8% 10%;
--destructive: 0 72.2% 50.6%;
--destructive-foreground: 60 9.1% 97.8%;
--ring: 20 14.3% 4.1%;
--radius: 0.5rem;
}
.dark {
--background: 20 14.3% 4.1%;
--foreground: 60 9.1% 97.8%;
--muted: 12 6.5% 15.1%;
--muted-foreground: 24 5.4% 63.9%;
--popover: 20 14.3% 4.1%;
--popover-foreground: 60 9.1% 97.8%;
--card: 20 14.3% 4.1%;
--card-foreground: 60 9.1% 97.8%;
--border: 12 6.5% 15.1%;
--input: 12 6.5% 15.1%;
--primary: 60 9.1% 97.8%;
--primary-foreground: 24 9.8% 10%;
--secondary: 12 6.5% 15.1%;
--secondary-foreground: 60 9.1% 97.8%;
--accent: 12 6.5% 15.1%;
--accent-foreground: 60 9.1% 97.8%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 60 9.1% 97.8%;
--ring: 24 5.7% 82.9%;
}
}
@layer base {
* {
@apply border-border;
}
body {
@apply bg-background text-foreground;
}
}
.no-scrollbar::-webkit-scrollbar {
display: none;
}
[contenteditable=true]:empty:before{
content:attr(placeholder);
color:grey;
pointer-events: none;
font-style:italic;
}
.overflow-wrap-anywhere {
overflow-wrap: anywhere;
word-break: break-word;
}

13
src/app.d.ts vendored Normal file
View File

@ -0,0 +1,13 @@
// See https://kit.svelte.dev/docs/types#app
// for information about these interfaces
declare global {
namespace App {
// interface Error {}
// interface Locals {}
// interface PageData {}
// interface PageState {}
// interface Platform {}
}
}
export {};

17
src/app.html Normal file
View File

@ -0,0 +1,17 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Lexend:wght@100..900&display=swap" rel="stylesheet">
%sveltekit.head%
</head>
<body data-sveltekit-preload-data="hover">
<div style="display: contents">%sveltekit.body%</div>
</body>
</html>

View File

@ -0,0 +1,16 @@
<script lang="ts">
import { Avatar as AvatarPrimitive } from "bits-ui";
import { cn } from "$lib/utils.js";
type $$Props = AvatarPrimitive.FallbackProps;
let className: $$Props["class"] = undefined;
export { className as class };
</script>
<AvatarPrimitive.Fallback
class={cn("flex h-full w-full items-center justify-center rounded-full bg-muted", className)}
{...$$restProps}
>
<slot />
</AvatarPrimitive.Fallback>

View File

@ -0,0 +1,18 @@
<script lang="ts">
import { Avatar as AvatarPrimitive } from "bits-ui";
import { cn } from "$lib/utils.js";
type $$Props = AvatarPrimitive.ImageProps;
let className: $$Props["class"] = undefined;
export let src: $$Props["src"] = undefined;
export let alt: $$Props["alt"] = undefined;
export { className as class };
</script>
<AvatarPrimitive.Image
{src}
{alt}
class={cn("aspect-square h-full w-full", className)}
{...$$restProps}
/>

View File

@ -0,0 +1,18 @@
<script lang="ts">
import { Avatar as AvatarPrimitive } from "bits-ui";
import { cn } from "$lib/utils.js";
type $$Props = AvatarPrimitive.Props;
let className: $$Props["class"] = undefined;
export let delayMs: $$Props["delayMs"] = undefined;
export { className as class };
</script>
<AvatarPrimitive.Root
{delayMs}
class={cn("relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full", className)}
{...$$restProps}
>
<slot />
</AvatarPrimitive.Root>

View File

@ -0,0 +1,13 @@
import Root from "./avatar.svelte";
import Image from "./avatar-image.svelte";
import Fallback from "./avatar-fallback.svelte";
export {
Root,
Image,
Fallback,
//
Root as Avatar,
Image as AvatarImage,
Fallback as AvatarFallback,
};

View File

@ -0,0 +1,18 @@
<script lang="ts">
import { type Variant, badgeVariants } from "./index.js";
import { cn } from "$lib/utils.js";
let className: string | undefined | null = undefined;
export let href: string | undefined = undefined;
export let variant: Variant = "default";
export { className as class };
</script>
<svelte:element
this={href ? "a" : "span"}
{href}
class={cn(badgeVariants({ variant, className }))}
{...$$restProps}
>
<slot />
</svelte:element>

View File

@ -0,0 +1,21 @@
import { type VariantProps, tv } from "tailwind-variants";
export { default as Badge } from "./badge.svelte";
export const badgeVariants = tv({
base: "inline-flex select-none items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
variants: {
variant: {
default: "border-transparent bg-primary text-primary-foreground hover:bg-primary/80",
secondary:
"border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
destructive:
"border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
outline: "text-foreground",
},
},
defaultVariants: {
variant: "default",
},
});
export type Variant = VariantProps<typeof badgeVariants>["variant"];

View File

@ -0,0 +1,25 @@
<script lang="ts">
import { Button as ButtonPrimitive } from "bits-ui";
import { type Events, type Props, buttonVariants } from "./index.js";
import { cn } from "$lib/utils.js";
type $$Props = Props;
type $$Events = Events;
let className: $$Props["class"] = undefined;
export let variant: $$Props["variant"] = "default";
export let size: $$Props["size"] = "default";
export let builders: $$Props["builders"] = [];
export { className as class };
</script>
<ButtonPrimitive.Root
{builders}
class={cn(buttonVariants({ variant, size, className }))}
type="button"
{...$$restProps}
on:click
on:keydown
>
<slot />
</ButtonPrimitive.Root>

View File

@ -0,0 +1,49 @@
import { type VariantProps, tv } from "tailwind-variants";
import type { Button as ButtonPrimitive } from "bits-ui";
import Root from "./button.svelte";
const buttonVariants = tv({
base: "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
variants: {
variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/90",
destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
outline:
"border border-input bg-background hover:bg-accent hover:text-accent-foreground",
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground",
link: "text-primary underline-offset-4 hover:underline",
},
size: {
default: "h-10 px-4 py-2",
sm: "h-9 rounded-md px-3",
lg: "h-11 rounded-md px-8",
icon: "h-10 w-10",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
});
type Variant = VariantProps<typeof buttonVariants>["variant"];
type Size = VariantProps<typeof buttonVariants>["size"];
type Props = ButtonPrimitive.Props & {
variant?: Variant;
size?: Size;
};
type Events = ButtonPrimitive.Events;
export {
Root,
type Props,
type Events,
//
Root as Button,
type Props as ButtonProps,
type Events as ButtonEvents,
buttonVariants,
};

View File

@ -0,0 +1,36 @@
<script lang="ts">
import { Dialog as DialogPrimitive } from "bits-ui";
import X from "lucide-svelte/icons/x";
import * as Dialog from "./index.js";
import { cn, flyAndScale } from "$lib/utils.js";
type $$Props = DialogPrimitive.ContentProps;
let className: $$Props["class"] = undefined;
export let transition: $$Props["transition"] = flyAndScale;
export let transitionConfig: $$Props["transitionConfig"] = {
duration: 200,
};
export { className as class };
</script>
<Dialog.Portal>
<Dialog.Overlay />
<DialogPrimitive.Content
{transition}
{transitionConfig}
class={cn(
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg sm:rounded-lg md:w-full",
className
)}
{...$$restProps}
>
<slot />
<DialogPrimitive.Close
class="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground"
>
<X class="h-4 w-4" />
<span class="sr-only">Close</span>
</DialogPrimitive.Close>
</DialogPrimitive.Content>
</Dialog.Portal>

View File

@ -0,0 +1,16 @@
<script lang="ts">
import { Dialog as DialogPrimitive } from "bits-ui";
import { cn } from "$lib/utils.js";
type $$Props = DialogPrimitive.DescriptionProps;
let className: $$Props["class"] = undefined;
export { className as class };
</script>
<DialogPrimitive.Description
class={cn("text-sm text-muted-foreground", className)}
{...$$restProps}
>
<slot />
</DialogPrimitive.Description>

View File

@ -0,0 +1,16 @@
<script lang="ts">
import type { HTMLAttributes } from "svelte/elements";
import { cn } from "$lib/utils.js";
type $$Props = HTMLAttributes<HTMLDivElement>;
let className: $$Props["class"] = undefined;
export { className as class };
</script>
<div
class={cn("flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2", className)}
{...$$restProps}
>
<slot />
</div>

View File

@ -0,0 +1,13 @@
<script lang="ts">
import type { HTMLAttributes } from "svelte/elements";
import { cn } from "$lib/utils.js";
type $$Props = HTMLAttributes<HTMLDivElement>;
let className: $$Props["class"] = undefined;
export { className as class };
</script>
<div class={cn("flex flex-col space-y-1.5 text-center sm:text-left", className)} {...$$restProps}>
<slot />
</div>

View File

@ -0,0 +1,21 @@
<script lang="ts">
import { Dialog as DialogPrimitive } from "bits-ui";
import { fade } from "svelte/transition";
import { cn } from "$lib/utils.js";
type $$Props = DialogPrimitive.OverlayProps;
let className: $$Props["class"] = undefined;
export let transition: $$Props["transition"] = fade;
export let transitionConfig: $$Props["transitionConfig"] = {
duration: 150,
};
export { className as class };
</script>
<DialogPrimitive.Overlay
{transition}
{transitionConfig}
class={cn("fixed inset-0 z-50 bg-background/80 backdrop-blur-sm", className)}
{...$$restProps}
/>

View File

@ -0,0 +1,8 @@
<script lang="ts">
import { Dialog as DialogPrimitive } from "bits-ui";
type $$Props = DialogPrimitive.PortalProps;
</script>
<DialogPrimitive.Portal {...$$restProps}>
<slot />
</DialogPrimitive.Portal>

View File

@ -0,0 +1,16 @@
<script lang="ts">
import { Dialog as DialogPrimitive } from "bits-ui";
import { cn } from "$lib/utils.js";
type $$Props = DialogPrimitive.TitleProps;
let className: $$Props["class"] = undefined;
export { className as class };
</script>
<DialogPrimitive.Title
class={cn("text-lg font-semibold leading-none tracking-tight", className)}
{...$$restProps}
>
<slot />
</DialogPrimitive.Title>

View File

@ -0,0 +1,37 @@
import { Dialog as DialogPrimitive } from "bits-ui";
import Title from "./dialog-title.svelte";
import Portal from "./dialog-portal.svelte";
import Footer from "./dialog-footer.svelte";
import Header from "./dialog-header.svelte";
import Overlay from "./dialog-overlay.svelte";
import Content from "./dialog-content.svelte";
import Description from "./dialog-description.svelte";
const Root = DialogPrimitive.Root;
const Trigger = DialogPrimitive.Trigger;
const Close = DialogPrimitive.Close;
export {
Root,
Title,
Portal,
Footer,
Header,
Trigger,
Overlay,
Content,
Description,
Close,
//
Root as Dialog,
Title as DialogTitle,
Portal as DialogPortal,
Footer as DialogFooter,
Header as DialogHeader,
Trigger as DialogTrigger,
Overlay as DialogOverlay,
Content as DialogContent,
Description as DialogDescription,
Close as DialogClose,
};

View File

@ -0,0 +1,35 @@
<script lang="ts">
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
import Check from "lucide-svelte/icons/check";
import { cn } from "$lib/utils.js";
type $$Props = DropdownMenuPrimitive.CheckboxItemProps;
type $$Events = DropdownMenuPrimitive.CheckboxItemEvents;
let className: $$Props["class"] = undefined;
export let checked: $$Props["checked"] = undefined;
export { className as class };
</script>
<DropdownMenuPrimitive.CheckboxItem
bind:checked
class={cn(
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none data-[disabled]:pointer-events-none data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground data-[disabled]:opacity-50",
className
)}
{...$$restProps}
on:click
on:keydown
on:focusin
on:focusout
on:pointerdown
on:pointerleave
on:pointermove
>
<span class="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<DropdownMenuPrimitive.CheckboxIndicator>
<Check class="h-4 w-4" />
</DropdownMenuPrimitive.CheckboxIndicator>
</span>
<slot />
</DropdownMenuPrimitive.CheckboxItem>

View File

@ -0,0 +1,27 @@
<script lang="ts">
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
import { cn, flyAndScale } from "$lib/utils.js";
type $$Props = DropdownMenuPrimitive.ContentProps;
type $$Events = DropdownMenuPrimitive.ContentEvents;
let className: $$Props["class"] = undefined;
export let sideOffset: $$Props["sideOffset"] = 4;
export let transition: $$Props["transition"] = flyAndScale;
export let transitionConfig: $$Props["transitionConfig"] = undefined;
export { className as class };
</script>
<DropdownMenuPrimitive.Content
{transition}
{transitionConfig}
{sideOffset}
class={cn(
"z-50 min-w-[8rem] rounded-md border bg-popover p-1 text-popover-foreground shadow-md focus:outline-none",
className
)}
{...$$restProps}
on:keydown
>
<slot />
</DropdownMenuPrimitive.Content>

View File

@ -0,0 +1,31 @@
<script lang="ts">
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
import { cn } from "$lib/utils.js";
type $$Props = DropdownMenuPrimitive.ItemProps & {
inset?: boolean;
};
type $$Events = DropdownMenuPrimitive.ItemEvents;
let className: $$Props["class"] = undefined;
export let inset: $$Props["inset"] = undefined;
export { className as class };
</script>
<DropdownMenuPrimitive.Item
class={cn(
"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[disabled]:pointer-events-none data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground data-[disabled]:opacity-50",
inset && "pl-8",
className
)}
{...$$restProps}
on:click
on:keydown
on:focusin
on:focusout
on:pointerdown
on:pointerleave
on:pointermove
>
<slot />
</DropdownMenuPrimitive.Item>

View File

@ -0,0 +1,19 @@
<script lang="ts">
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
import { cn } from "$lib/utils.js";
type $$Props = DropdownMenuPrimitive.LabelProps & {
inset?: boolean;
};
let className: $$Props["class"] = undefined;
export let inset: $$Props["inset"] = undefined;
export { className as class };
</script>
<DropdownMenuPrimitive.Label
class={cn("px-2 py-1.5 text-sm font-semibold", inset && "pl-8", className)}
{...$$restProps}
>
<slot />
</DropdownMenuPrimitive.Label>

View File

@ -0,0 +1,11 @@
<script lang="ts">
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
type $$Props = DropdownMenuPrimitive.RadioGroupProps;
export let value: $$Props["value"] = undefined;
</script>
<DropdownMenuPrimitive.RadioGroup {...$$restProps} bind:value>
<slot />
</DropdownMenuPrimitive.RadioGroup>

View File

@ -0,0 +1,35 @@
<script lang="ts">
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
import Circle from "lucide-svelte/icons/circle";
import { cn } from "$lib/utils.js";
type $$Props = DropdownMenuPrimitive.RadioItemProps;
type $$Events = DropdownMenuPrimitive.RadioItemEvents;
let className: $$Props["class"] = undefined;
export let value: $$Props["value"];
export { className as class };
</script>
<DropdownMenuPrimitive.RadioItem
class={cn(
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none data-[disabled]:pointer-events-none data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground data-[disabled]:opacity-50",
className
)}
{value}
{...$$restProps}
on:click
on:keydown
on:focusin
on:focusout
on:pointerdown
on:pointerleave
on:pointermove
>
<span class="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<DropdownMenuPrimitive.RadioIndicator>
<Circle class="h-2 w-2 fill-current" />
</DropdownMenuPrimitive.RadioIndicator>
</span>
<slot />
</DropdownMenuPrimitive.RadioItem>

View File

@ -0,0 +1,14 @@
<script lang="ts">
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
import { cn } from "$lib/utils.js";
type $$Props = DropdownMenuPrimitive.SeparatorProps;
let className: $$Props["class"] = undefined;
export { className as class };
</script>
<DropdownMenuPrimitive.Separator
class={cn("-mx-1 my-1 h-px bg-muted", className)}
{...$$restProps}
/>

View File

@ -0,0 +1,13 @@
<script lang="ts">
import type { HTMLAttributes } from "svelte/elements";
import { cn } from "$lib/utils.js";
type $$Props = HTMLAttributes<HTMLSpanElement>;
let className: $$Props["class"] = undefined;
export { className as class };
</script>
<span class={cn("ml-auto text-xs tracking-widest opacity-60", className)} {...$$restProps}>
<slot />
</span>

View File

@ -0,0 +1,30 @@
<script lang="ts">
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
import { cn, flyAndScale } from "$lib/utils.js";
type $$Props = DropdownMenuPrimitive.SubContentProps;
type $$Events = DropdownMenuPrimitive.SubContentEvents;
let className: $$Props["class"] = undefined;
export let transition: $$Props["transition"] = flyAndScale;
export let transitionConfig: $$Props["transitionConfig"] = {
x: -10,
y: 0,
};
export { className as class };
</script>
<DropdownMenuPrimitive.SubContent
{transition}
{transitionConfig}
class={cn(
"z-50 min-w-[8rem] rounded-md border bg-popover p-1 text-popover-foreground shadow-lg focus:outline-none",
className
)}
{...$$restProps}
on:keydown
on:focusout
on:pointermove
>
<slot />
</DropdownMenuPrimitive.SubContent>

View File

@ -0,0 +1,32 @@
<script lang="ts">
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
import ChevronRight from "lucide-svelte/icons/chevron-right";
import { cn } from "$lib/utils.js";
type $$Props = DropdownMenuPrimitive.SubTriggerProps & {
inset?: boolean;
};
type $$Events = DropdownMenuPrimitive.SubTriggerEvents;
let className: $$Props["class"] = undefined;
export let inset: $$Props["inset"] = undefined;
export { className as class };
</script>
<DropdownMenuPrimitive.SubTrigger
class={cn(
"flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[highlighted]:bg-accent data-[state=open]:bg-accent data-[highlighted]:text-accent-foreground data-[state=open]:text-accent-foreground",
inset && "pl-8",
className
)}
{...$$restProps}
on:click
on:keydown
on:focusin
on:focusout
on:pointerleave
on:pointermove
>
<slot />
<ChevronRight class="ml-auto h-4 w-4" />
</DropdownMenuPrimitive.SubTrigger>

View File

@ -0,0 +1,48 @@
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
import Item from "./dropdown-menu-item.svelte";
import Label from "./dropdown-menu-label.svelte";
import Content from "./dropdown-menu-content.svelte";
import Shortcut from "./dropdown-menu-shortcut.svelte";
import RadioItem from "./dropdown-menu-radio-item.svelte";
import Separator from "./dropdown-menu-separator.svelte";
import RadioGroup from "./dropdown-menu-radio-group.svelte";
import SubContent from "./dropdown-menu-sub-content.svelte";
import SubTrigger from "./dropdown-menu-sub-trigger.svelte";
import CheckboxItem from "./dropdown-menu-checkbox-item.svelte";
const Sub = DropdownMenuPrimitive.Sub;
const Root = DropdownMenuPrimitive.Root;
const Trigger = DropdownMenuPrimitive.Trigger;
const Group = DropdownMenuPrimitive.Group;
export {
Sub,
Root,
Item,
Label,
Group,
Trigger,
Content,
Shortcut,
Separator,
RadioItem,
SubContent,
SubTrigger,
RadioGroup,
CheckboxItem,
//
Root as DropdownMenu,
Sub as DropdownMenuSub,
Item as DropdownMenuItem,
Label as DropdownMenuLabel,
Group as DropdownMenuGroup,
Content as DropdownMenuContent,
Trigger as DropdownMenuTrigger,
Shortcut as DropdownMenuShortcut,
RadioItem as DropdownMenuRadioItem,
Separator as DropdownMenuSeparator,
RadioGroup as DropdownMenuRadioGroup,
SubContent as DropdownMenuSubContent,
SubTrigger as DropdownMenuSubTrigger,
CheckboxItem as DropdownMenuCheckboxItem,
};

View File

@ -0,0 +1,10 @@
<script lang="ts">
import * as Button from "$lib/components/ui/button/index.js";
type $$Props = Button.Props;
type $$Events = Button.Events;
</script>
<Button.Root type="submit" on:click on:keydown {...$$restProps}>
<slot />
</Button.Root>

View File

@ -0,0 +1,17 @@
<script lang="ts">
import * as FormPrimitive from "formsnap";
import type { HTMLAttributes } from "svelte/elements";
import { cn } from "$lib/utils.js";
type $$Props = HTMLAttributes<HTMLSpanElement>;
let className: string | undefined | null = undefined;
export { className as class };
</script>
<FormPrimitive.Description
class={cn("text-sm text-muted-foreground", className)}
{...$$restProps}
let:descriptionAttrs
>
<slot {descriptionAttrs} />
</FormPrimitive.Description>

View File

@ -0,0 +1,25 @@
<script lang="ts" context="module">
import type { FormPathLeaves, SuperForm } from "sveltekit-superforms";
type T = Record<string, unknown>;
type U = FormPathLeaves<T>;
</script>
<script lang="ts" generics="T extends Record<string, unknown>, U extends FormPathLeaves<T>">
import type { HTMLAttributes } from "svelte/elements";
import * as FormPrimitive from "formsnap";
import { cn } from "$lib/utils.js";
type $$Props = FormPrimitive.ElementFieldProps<T, U> & HTMLAttributes<HTMLElement>;
export let form: SuperForm<T>;
export let name: U;
let className: $$Props["class"] = undefined;
export { className as class };
</script>
<FormPrimitive.ElementField {form} {name} let:constraints let:errors let:tainted let:value>
<div class={cn("space-y-2", className)}>
<slot {constraints} {errors} {tainted} {value} />
</div>
</FormPrimitive.ElementField>

View File

@ -0,0 +1,26 @@
<script lang="ts">
import * as FormPrimitive from "formsnap";
import { cn } from "$lib/utils.js";
type $$Props = FormPrimitive.FieldErrorsProps & {
errorClasses?: string | undefined | null;
};
let className: $$Props["class"] = undefined;
export { className as class };
export let errorClasses: $$Props["class"] = undefined;
</script>
<FormPrimitive.FieldErrors
class={cn("text-sm font-medium text-destructive", className)}
{...$$restProps}
let:errors
let:fieldErrorsAttrs
let:errorAttrs
>
<slot {errors} {fieldErrorsAttrs} {errorAttrs}>
{#each errors as error}
<div {...errorAttrs} class={cn(errorClasses)}>{error}</div>
{/each}
</slot>
</FormPrimitive.FieldErrors>

View File

@ -0,0 +1,25 @@
<script lang="ts" context="module">
import type { FormPath, SuperForm } from "sveltekit-superforms";
type T = Record<string, unknown>;
type U = FormPath<T>;
</script>
<script lang="ts" generics="T extends Record<string, unknown>, U extends FormPath<T>">
import type { HTMLAttributes } from "svelte/elements";
import * as FormPrimitive from "formsnap";
import { cn } from "$lib/utils.js";
type $$Props = FormPrimitive.FieldProps<T, U> & HTMLAttributes<HTMLElement>;
export let form: SuperForm<T>;
export let name: U;
let className: $$Props["class"] = undefined;
export { className as class };
</script>
<FormPrimitive.Field {form} {name} let:constraints let:errors let:tainted let:value>
<div class={cn("space-y-2", className)}>
<slot {constraints} {errors} {tainted} {value} />
</div>
</FormPrimitive.Field>

View File

@ -0,0 +1,30 @@
<script lang="ts" context="module">
import type { FormPath, SuperForm } from "sveltekit-superforms";
type T = Record<string, unknown>;
type U = FormPath<T>;
</script>
<script lang="ts" generics="T extends Record<string, unknown>, U extends FormPath<T>">
import * as FormPrimitive from "formsnap";
import { cn } from "$lib/utils.js";
type $$Props = FormPrimitive.FieldsetProps<T, U>;
export let form: SuperForm<T>;
export let name: U;
let className: $$Props["class"] = undefined;
export { className as class };
</script>
<FormPrimitive.Fieldset
{form}
{name}
let:constraints
let:errors
let:tainted
let:value
class={cn("space-y-2", className)}
>
<slot {constraints} {errors} {tainted} {value} />
</FormPrimitive.Fieldset>

View File

@ -0,0 +1,17 @@
<script lang="ts">
import type { Label as LabelPrimitive } from "bits-ui";
import { getFormControl } from "formsnap";
import { cn } from "$lib/utils.js";
import { Label } from "$lib/components/ui/label/index.js";
type $$Props = LabelPrimitive.Props;
let className: $$Props["class"] = undefined;
export { className as class };
const { labelAttrs } = getFormControl();
</script>
<Label {...$labelAttrs} class={cn("data-[fs-error]:text-destructive", className)} {...$$restProps}>
<slot {labelAttrs} />
</Label>

View File

@ -0,0 +1,17 @@
<script lang="ts">
import * as FormPrimitive from "formsnap";
import { cn } from "$lib/utils.js";
type $$Props = FormPrimitive.LegendProps;
let className: $$Props["class"] = undefined;
export { className as class };
</script>
<FormPrimitive.Legend
{...$$restProps}
class={cn("text-sm font-medium leading-none data-[fs-error]:text-destructive", className)}
let:legendAttrs
>
<slot {legendAttrs} />
</FormPrimitive.Legend>

View File

@ -0,0 +1,33 @@
import * as FormPrimitive from "formsnap";
import Description from "./form-description.svelte";
import Label from "./form-label.svelte";
import FieldErrors from "./form-field-errors.svelte";
import Field from "./form-field.svelte";
import Fieldset from "./form-fieldset.svelte";
import Legend from "./form-legend.svelte";
import ElementField from "./form-element-field.svelte";
import Button from "./form-button.svelte";
const Control = FormPrimitive.Control;
export {
Field,
Control,
Label,
Button,
FieldErrors,
Description,
Fieldset,
Legend,
ElementField,
//
Field as FormField,
Control as FormControl,
Description as FormDescription,
Label as FormLabel,
FieldErrors as FormFieldErrors,
Fieldset as FormFieldset,
Legend as FormLegend,
ElementField as FormElementField,
Button as FormButton,
};

View File

@ -0,0 +1,29 @@
import Root from "./input.svelte";
export type FormInputEvent<T extends Event = Event> = T & {
currentTarget: EventTarget & HTMLInputElement;
};
export type InputEvents = {
blur: FormInputEvent<FocusEvent>;
change: FormInputEvent<Event>;
click: FormInputEvent<MouseEvent>;
focus: FormInputEvent<FocusEvent>;
focusin: FormInputEvent<FocusEvent>;
focusout: FormInputEvent<FocusEvent>;
keydown: FormInputEvent<KeyboardEvent>;
keypress: FormInputEvent<KeyboardEvent>;
keyup: FormInputEvent<KeyboardEvent>;
mouseover: FormInputEvent<MouseEvent>;
mouseenter: FormInputEvent<MouseEvent>;
mouseleave: FormInputEvent<MouseEvent>;
mousemove: FormInputEvent<MouseEvent>;
paste: FormInputEvent<ClipboardEvent>;
input: FormInputEvent<InputEvent>;
wheel: FormInputEvent<WheelEvent>;
};
export {
Root,
//
Root as Input,
};

View File

@ -0,0 +1,42 @@
<script lang="ts">
import type { HTMLInputAttributes } from "svelte/elements";
import type { InputEvents } from "./index.js";
import { cn } from "$lib/utils.js";
type $$Props = HTMLInputAttributes;
type $$Events = InputEvents;
let className: $$Props["class"] = undefined;
export let value: $$Props["value"] = undefined;
export { className as class };
// Workaround for https://github.com/sveltejs/svelte/issues/9305
// Fixed in Svelte 5, but not backported to 4.x.
export let readonly: $$Props["readonly"] = undefined;
</script>
<input
class={cn(
"flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
className
)}
bind:value
{readonly}
on:blur
on:change
on:click
on:focus
on:focusin
on:focusout
on:keydown
on:keypress
on:keyup
on:mouseover
on:mouseenter
on:mouseleave
on:mousemove
on:paste
on:input
on:wheel|passive
{...$$restProps}
/>

View File

@ -0,0 +1,7 @@
import Root from "./label.svelte";
export {
Root,
//
Root as Label,
};

View File

@ -0,0 +1,21 @@
<script lang="ts">
import { Label as LabelPrimitive } from "bits-ui";
import { cn } from "$lib/utils.js";
type $$Props = LabelPrimitive.Props;
type $$Events = LabelPrimitive.Events;
let className: $$Props["class"] = undefined;
export { className as class };
</script>
<LabelPrimitive.Root
class={cn(
"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70",
className
)}
{...$$restProps}
on:mousedown
>
<slot />
</LabelPrimitive.Root>

View File

@ -0,0 +1,7 @@
import Root from "./separator.svelte";
export {
Root,
//
Root as Separator,
};

View File

@ -0,0 +1,22 @@
<script lang="ts">
import { Separator as SeparatorPrimitive } from "bits-ui";
import { cn } from "$lib/utils.js";
type $$Props = SeparatorPrimitive.Props;
let className: $$Props["class"] = undefined;
export let orientation: $$Props["orientation"] = "horizontal";
export let decorative: $$Props["decorative"] = undefined;
export { className as class };
</script>
<SeparatorPrimitive.Root
class={cn(
"shrink-0 bg-border",
orientation === "horizontal" ? "h-[1px] w-full" : "h-full w-[1px]",
className
)}
{orientation}
{decorative}
{...$$restProps}
/>

View File

@ -0,0 +1,28 @@
import Root from "./textarea.svelte";
type FormTextareaEvent<T extends Event = Event> = T & {
currentTarget: EventTarget & HTMLTextAreaElement;
};
type TextareaEvents = {
blur: FormTextareaEvent<FocusEvent>;
change: FormTextareaEvent<Event>;
click: FormTextareaEvent<MouseEvent>;
focus: FormTextareaEvent<FocusEvent>;
keydown: FormTextareaEvent<KeyboardEvent>;
keypress: FormTextareaEvent<KeyboardEvent>;
keyup: FormTextareaEvent<KeyboardEvent>;
mouseover: FormTextareaEvent<MouseEvent>;
mouseenter: FormTextareaEvent<MouseEvent>;
mouseleave: FormTextareaEvent<MouseEvent>;
paste: FormTextareaEvent<ClipboardEvent>;
input: FormTextareaEvent<InputEvent>;
};
export {
Root,
//
Root as Textarea,
type TextareaEvents,
type FormTextareaEvent,
};

View File

@ -0,0 +1,38 @@
<script lang="ts">
import type { HTMLTextareaAttributes } from "svelte/elements";
import type { TextareaEvents } from "./index.js";
import { cn } from "$lib/utils.js";
type $$Props = HTMLTextareaAttributes;
type $$Events = TextareaEvents;
let className: $$Props["class"] = undefined;
export let value: $$Props["value"] = undefined;
export { className as class };
// Workaround for https://github.com/sveltejs/svelte/issues/9305
// Fixed in Svelte 5, but not backported to 4.x.
export let readonly: $$Props["readonly"] = undefined;
</script>
<textarea
class={cn(
"flex min-h-[80px] w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
className
)}
bind:value
{readonly}
on:blur
on:change
on:click
on:focus
on:keydown
on:keypress
on:keyup
on:mouseover
on:mouseenter
on:mouseleave
on:paste
on:input
{...$$restProps}
></textarea>

View File

@ -0,0 +1,15 @@
import { Tooltip as TooltipPrimitive } from "bits-ui";
import Content from "./tooltip-content.svelte";
const Root = TooltipPrimitive.Root;
const Trigger = TooltipPrimitive.Trigger;
export {
Root,
Trigger,
Content,
//
Root as Tooltip,
Content as TooltipContent,
Trigger as TooltipTrigger,
};

View File

@ -0,0 +1,28 @@
<script lang="ts">
import { Tooltip as TooltipPrimitive } from "bits-ui";
import { cn, flyAndScale } from "$lib/utils.js";
type $$Props = TooltipPrimitive.ContentProps;
let className: $$Props["class"] = undefined;
export let sideOffset: $$Props["sideOffset"] = 4;
export let transition: $$Props["transition"] = flyAndScale;
export let transitionConfig: $$Props["transitionConfig"] = {
y: 8,
duration: 150,
};
export { className as class };
</script>
<TooltipPrimitive.Content
{transition}
{transitionConfig}
{sideOffset}
class={cn(
"z-50 overflow-hidden rounded-md border bg-popover px-3 py-1.5 text-sm text-popover-foreground shadow-md",
className
)}
{...$$restProps}
>
<slot />
</TooltipPrimitive.Content>

1
src/lib/index.ts Normal file
View File

@ -0,0 +1 @@
// place files you want to import through the `$lib` alias in this folder.

62
src/lib/utils.ts Normal file
View File

@ -0,0 +1,62 @@
import { type ClassValue, clsx } from "clsx";
import { twMerge } from "tailwind-merge";
import { cubicOut } from "svelte/easing";
import type { TransitionConfig } from "svelte/transition";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
type FlyAndScaleParams = {
y?: number;
x?: number;
start?: number;
duration?: number;
};
export const flyAndScale = (
node: Element,
params: FlyAndScaleParams = { y: -8, x: 0, start: 0.95, duration: 150 }
): TransitionConfig => {
const style = getComputedStyle(node);
const transform = style.transform === "none" ? "" : style.transform;
const scaleConversion = (
valueA: number,
scaleA: [number, number],
scaleB: [number, number]
) => {
const [minA, maxA] = scaleA;
const [minB, maxB] = scaleB;
const percentage = (valueA - minA) / (maxA - minA);
const valueB = percentage * (maxB - minB) + minB;
return valueB;
};
const styleToString = (
style: Record<string, number | string | undefined>
): string => {
return Object.keys(style).reduce((str, key) => {
if (style[key] === undefined) return str;
return str + `${key}:${style[key]};`;
}, "");
};
return {
duration: params.duration ?? 200,
delay: 0,
css: (t) => {
const y = scaleConversion(t, [0, 1], [params.y ?? 5, 0]);
const x = scaleConversion(t, [0, 1], [params.x ?? 0, 0]);
const scale = scaleConversion(t, [0, 1], [params.start ?? 0.95, 1]);
return styleToString({
transform: `${transform} translate3d(${x}px, ${y}px, 0) scale(${scale})`,
opacity: t
});
},
easing: cubicOut
};
};

View File

@ -0,0 +1,7 @@
<script lang="ts">
import '../app.css';
import { ModeWatcher } from "mode-watcher";
</script>
<ModeWatcher />
<slot />

91
src/routes/+page.svelte Normal file
View File

@ -0,0 +1,91 @@
<script lang="ts">
import { Label } from '$lib/components/ui/label';
import { Button } from '$lib/components/ui/button';
import { Separator } from '$lib/components/ui/separator';
import { Reply } from 'lucide-svelte';
import Lynt from './Lynt.svelte';
import Navigation from './Navigation.svelte';
import PostButton from './PostButton.svelte';
import ProfileButton from './ProfileButton.svelte';
let currentTab = 'foryou';
let comment: string;
</script>
<div class="flex h-screen overflow-hidden gap-4 justify-center">
<div class="static ml-5 mt-5 inline-flex w-auto flex-col items-start gap-2">
<img class="mb-5 size-20 cursor-pointer" src="logo.svg" alt="Logo" />
<Navigation />
<PostButton />
<ProfileButton src="https://github.com/face-hh.png" name="Face" handle="@facedevstuff" />
</div>
<Separator orientation="vertical" />
<div class="w-[600px] flex flex-col overflow-hidden">
<div class="flex justify-center gap-20">
<div>
<Label class="cursor-pointer select-none text-xl">For you</Label>
{#if currentTab === 'foryou'}
<div class="mt-1 h-2 w-auto rounded-b-lg bg-primary"></div>
{/if}
</div>
<div>
<Label class="cursor-pointer select-none text-xl">Following</Label>
{#if currentTab === 'following'}
<div class="mt-1 h-2 w-auto rounded-b-lg bg-primary"></div>
{/if}
</div>
</div>
<Separator class="mt-4" />
<!-- Feed -->
<div class="no-scrollbar flex flex-col gap-4 overflow-y-auto flex-grow">
<Lynt username="Face" handle="facedevstuff" postedAt={new Date(1468959781804)} verified={true} />
<Lynt username="That one LONG username. haha" handle="thatonelongusernamereal" postedAt={new Date(1468959781804)} verified={false} />
<Lynt username="." handle="a" postedAt={new Date(1468959781804)} verified={false} />
<Lynt username="ABC news" handle="abc" postedAt={new Date(1468959781804)} verified={true} />
<Lynt username="Face" handle="facedevstuff" postedAt={new Date(1468959781804)} verified={true} />
<Lynt username="That one LONG username." handle="thatonelongusername" postedAt={new Date(1468959781804)} verified={false} />
<Lynt username="." handle="a" postedAt={new Date(1468959781804)} verified={false} />
<Lynt username="ABC news" handle="abc" postedAt={new Date(1468959781804)} verified={true} />
<Lynt username="Face" handle="facedevstuff" postedAt={new Date(1468959781804)} verified={true} />
<Lynt username="That one LONG username." handle="thatonelongusername" postedAt={new Date(1468959781804)} verified={false} />
<Lynt username="." handle="a" postedAt={new Date(1468959781804)} verified={false} />
<Lynt username="ABC news" handle="abc" postedAt={new Date(1468959781804)} verified={true} />
<div class="mt-24"></div>
</div>
</div>
<Separator orientation="vertical" />
<div class="w-[500px] space-y-2 mt-1 no-scrollbar overflow-y-auto" >
<Lynt username="Face" handle="facedevstuff" postedAt={new Date(1468959781804)} verified={true} />
<div class="w-full rounded-xl bg-border p-3 inline-flex gap-2 items-center">
<Reply size={32} />
<div
contenteditable="true"
role="textbox"
spellcheck="true"
tabindex="0"
bind:textContent={comment}
class="overflow-wrap-anywhere w-full outline-none text-lg"
placeholder="Reply..."
/>
<Button>Post</Button>
</div>
<Separator />
<div class="space-y-2">
<Lynt username="Face" handle="facedevstuff" postedAt={new Date(1468959781804)} verified={true} />
<Lynt username="Face" handle="facedevstuff" postedAt={new Date(1468959781804)} verified={true} />
<Lynt username="Face" handle="facedevstuff" postedAt={new Date(1468959781804)} verified={true} />
<Lynt username="Face" handle="facedevstuff" postedAt={new Date(1468959781804)} verified={true} />
<Lynt username="Face" handle="facedevstuff" postedAt={new Date(1468959781804)} verified={true} />
<Lynt username="Face" handle="facedevstuff" postedAt={new Date(1468959781804)} verified={true} />
</div>
</div>
</div>

7
src/routes/Avatar.svelte Normal file
View File

@ -0,0 +1,7 @@
<script>
export let size = 12;
export let src = 'https://github.com/face-hh.png';
export let alt = 'Avatar';
</script>
<img {src} {alt} class="h-{size} w-{size} rounded-full" />

81
src/routes/Lynt.svelte Normal file
View File

@ -0,0 +1,81 @@
<script lang="ts">
import { Separator } from '@/components/ui/separator';
import * as Tooltip from '@/components/ui/tooltip';
import { BarChart2, Heart, MessageCircle, Repeat2, Share2 } from 'lucide-svelte';
import { Label } from '@/components/ui/label';
import Avatar from './Avatar.svelte';
import OutlineButton from './OutlineButton.svelte';
function getTimeElapsed(date: Date) {
const now = new Date();
const elapsed = now.getTime() - date.getTime();
const seconds = Math.floor(elapsed / 1000);
const minutes = Math.floor(seconds / 60);
const hours = Math.floor(minutes / 60);
const days = Math.floor(hours / 24);
const weeks = Math.floor(days / 7);
const years = Math.floor(days / 365);
if (years > 0) return `${years}y`;
if (weeks > 0) return `${weeks}w`;
if (days > 0) return `${days}d`;
if (hours > 0) return `${hours}h`;
if (minutes > 0) return `${minutes}m`;
return `${seconds}s`;
}
export let postedAt: Date;
export let verified: boolean;
export let username: String;
export let handle: String;
</script>
<div class="flex gap-3 rounded-xl bg-border p-3">
<Avatar size={10} src="https://github.com/face-hh.png" alt="A profile picture." />
<div class="flex flex-col max-w-[530px]">
<div class="flex items-center gap-1">
<span class="truncate max-w-[50%] text-xl font-bold">{username}</span>
{#if verified}
<Tooltip.Root>
<Tooltip.Trigger
><img
class="h-7 w-7"
src="verified.png"
alt="This user is verified."
/></Tooltip.Trigger
>
<Tooltip.Content>
<p>This user is <span class="rounded-xl bg-border p-1">verified</span>.</p>
</Tooltip.Content>
</Tooltip.Root>
{/if}
<span
class="py-0.25 flex select-none items-center rounded-xl border border-transparent bg-primary px-1.5 text-base font-semibold text-primary-foreground transition-colors hover:bg-primary/90 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2"
>104</span
>
<Label class="truncate max-w-[30%] text-lg text-gray-600">@{handle}</Label>
<Label class="text-lg text-gray-600">{getTimeElapsed(postedAt)}</Label>
</div>
<Label class="text-lg"
>Moments like this are why I enjoy programming and game development so much.</Label
>
<br />
<br />
<Label class="text-lg"
>You put in hours of focused work and these little wins where stuff works make it all worth
it!</Label
>
<div class="mt-2 flex gap-2">
<OutlineButton icon={MessageCircle} text="1.3K" colorOnClick={true} />
<OutlineButton icon={Repeat2} text="334" colorOnClick={true} />
<OutlineButton icon={Heart} text="41.5K" className="active" colorOnClick={true} />
<OutlineButton icon={BarChart2} text="1.43M" className="ml-auto" />
<OutlineButton icon={Share2} />
</div>
</div>
</div>
<Separator />

View File

@ -0,0 +1,19 @@
<script>
import { Button } from '@/components/ui/button/index';
import { House, Search, Bell } from 'lucide-svelte';
import OutlineButton from './OutlineButton.svelte';
export let navItems = [
{ icon: House, label: 'Home' },
{ icon: Search, label: 'Search' },
{ icon: Bell, label: 'Notifications' }
];
</script>
<div
class="inline-flex w-full min-w-[250px] flex-col items-start gap-2 rounded-[12px] bg-border p-[12px]"
>
{#each navItems as item}
<OutlineButton icon={item.icon} text={item.label} className="border-none" />
{/each}
</div>

View File

@ -0,0 +1,48 @@
<script lang="ts">
import type { ComponentType, SvelteComponent } from 'svelte';
export let icon: ComponentType<SvelteComponent>;
export let text: string | undefined = undefined;
export let strokeWidth: number = 2.5;
export let className: string = '';
export let colorOnClick = false;
let isActive = false;
</script>
<button
class:active={isActive}
on:click={() => {
if (colorOnClick) {
isActive = !isActive;
}
}}
class="shit inline-flex items-center justify-center rounded-xl border-4 border-solid border-primary p-1 font-bold text-primary {className}"
>
<svelte:component this={icon} {strokeWidth} class="h-6 w-6 {text ? 'mr-1' : ''}" />
{#if text}
<span>{text}</span>
{/if}
</button>
<style>
.shit {
transition:
filter 0.2s ease-in-out,
border 0.2s ease-in-out,
transform 0.2s ease-in-out,
background-color 0.1s ease-in-out,
color 0.1s ease-in-out;
}
.shit:hover {
filter: drop-shadow(0 0px 20px rgba(38, 14, 0, 0.5));
transform: rotate(3deg);
}
.shit.active {
background-color: hsl(22 100% 15%);
color: hsl(var(--background) / var(--tw-text-opacity));
transform: rotate(0deg);
}
</style>

View File

@ -0,0 +1,33 @@
<script>
import { buttonVariants } from '@/components/ui/button/index';
import * as Dialog from '@/components/ui/dialog/index';
import * as Form from '@/components/ui/form/index';
import Avatar from './Avatar.svelte';
let lynt = '';
</script>
<Dialog.Root>
<Dialog.Trigger class={`${buttonVariants({ variant: 'default' })} w-full`}>Post</Dialog.Trigger>
<Dialog.Content class="sm:max-w-[425px]">
<div class="flex items-start space-x-3">
<Avatar size={10} src="https://github.com/face-hh.png" alt="Your profile picture." />
<div class="flex-grow">
<div
contenteditable="true"
role="textbox"
spellcheck="true"
tabindex="0"
bind:textContent={lynt}
class="overflow-wrap-anywhere min-h-[80px] w-full outline-none"
placeholder="What's happening?"
/>
</div>
</div>
<div class="flex justify-end">
<Form.Button>Post</Form.Button>
</div>
</Dialog.Content>
</Dialog.Root>

View File

@ -0,0 +1,21 @@
<script>
import Avatar from './Avatar.svelte';
export let src = 'https://github.com/face-hh.png';
export let name = 'Face oifneoangoaen kfpeakfpae';
export let handle = '@facedevstuff';
</script>
<div
class="absolute bottom-2 flex w-[250px] max-w-md cursor-pointer items-center gap-4 rounded-full bg-border p-4"
>
<Avatar size={10} {src} alt="Your profile picture." />
<div class="flex flex-col gap-1 overflow-hidden">
<span class="truncate text-lg font-medium leading-none peer-enabled:cursor-pointer">
{name}
</span>
<span class="truncate text-sm font-medium leading-none text-gray-600">
{handle}
</span>
</div>
</div>

BIN
static/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

14
static/logo.svg Normal file
View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.5;">
<path d="M80.857,136.863L41.954,75.33C126.082,55.762 280.939,54.792 369.283,102.268C534.05,198.145 604.001,338.802 652.136,514.184C665.095,561.4 667.22,635.318 661.014,680.084C645.661,790.822 612.616,814.587 564.677,891.713C426.058,913.22 181.792,964.296 70.533,942.024C211.993,816.183 322.457,632.469 283.845,530.648C283.845,530.648 249.749,429.98 80.857,136.863Z" style="fill:rgb(234,231,219);"/>
<g transform="matrix(0.981386,0,0,0.990855,-42.7194,39.6133)">
<path d="M125.182,97.462C286.537,393.294 328.171,491.248 328.171,491.248C368.153,574.745 256.318,776.782 114.859,902.624C226.117,924.896 485,881.367 623.62,859.86C671.558,782.734 689.986,751.421 705.339,640.683C711.546,595.918 709.42,521.999 696.461,474.784C648.326,299.402 578.375,158.744 413.609,62.867C325.265,15.391 170.408,16.361 86.279,35.93L125.182,97.462Z" style="fill:none;stroke:rgb(78,28,0);stroke-width:40.56px;"/>
</g>
<g transform="matrix(0.981386,0,0,0.990855,0.780922,0.573235)">
<path d="M663.521,654.694C755.381,642.719 745.788,706.365 954.889,673.188C1023.14,662.359 1027.19,843.941 983.723,910.402C983.723,910.402 912.586,1029.63 589.863,898.748C616.967,852.068 618.582,842.887 636.486,785.856C637.297,783.272 641.361,774.03 657.961,706.207C661.399,692.157 662.008,674.647 663.521,654.694Z" style="fill:rgb(241,240,232);"/>
</g>
<g transform="matrix(0.981386,0,0,0.990855,0.780922,0.573235)">
<path d="M663.521,654.694C662.008,674.647 661.399,692.157 657.961,706.207C641.361,774.03 637.297,783.272 636.486,785.856C618.582,842.887 605.79,857.666 578.686,904.347C924.962,1034.84 983.723,938.048 983.723,910.402C987.057,836.367 1023.14,662.359 954.889,673.188C745.788,706.365 755.381,642.719 663.521,654.694" style="fill:none;stroke:rgb(78,28,0);stroke-width:40.56px;"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

BIN
static/verified.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

21
svelte.config.js Normal file
View File

@ -0,0 +1,21 @@
import adapter from '@sveltejs/adapter-auto';
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
/** @type {import('@sveltejs/kit').Config} */
const config = {
// Consult https://kit.svelte.dev/docs/integrations#preprocessors
// for more information about preprocessors
preprocess: vitePreprocess(),
kit: {
alias: {
"@/*": "./src/lib/*"
},
// adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list.
// If your environment is not supported, or you settled on a specific environment, switch out the adapter.
// See https://kit.svelte.dev/docs/adapters for more information about adapters.
adapter: adapter()
}
};
export default config;

63
tailwind.config.ts Normal file
View File

@ -0,0 +1,63 @@
import type { Config } from "tailwindcss";
const config: Config = {
darkMode: ["class"],
content: ["./src/**/*.{html,js,svelte,ts}"],
safelist: ["dark"],
theme: {
container: {
center: true,
padding: "2rem",
screens: {
"2xl": "1400px"
}
},
extend: {
colors: {
border: "hsl(var(--border) / <alpha-value>)",
input: "hsl(var(--input) / <alpha-value>)",
ring: "hsl(var(--ring) / <alpha-value>)",
background: "hsl(var(--background) / <alpha-value>)",
foreground: "hsl(var(--foreground) / <alpha-value>)",
primary: {
DEFAULT: "hsl(var(--primary) / <alpha-value>)",
foreground: "hsl(var(--primary-foreground) / <alpha-value>)"
},
secondary: {
DEFAULT: "hsl(var(--secondary) / <alpha-value>)",
foreground: "hsl(var(--secondary-foreground) / <alpha-value>)"
},
destructive: {
DEFAULT: "hsl(var(--destructive) / <alpha-value>)",
foreground: "hsl(var(--destructive-foreground) / <alpha-value>)"
},
muted: {
DEFAULT: "hsl(var(--muted) / <alpha-value>)",
foreground: "hsl(var(--muted-foreground) / <alpha-value>)"
},
accent: {
DEFAULT: "hsl(var(--accent) / <alpha-value>)",
foreground: "hsl(var(--accent-foreground) / <alpha-value>)"
},
popover: {
DEFAULT: "hsl(var(--popover) / <alpha-value>)",
foreground: "hsl(var(--popover-foreground) / <alpha-value>)"
},
card: {
DEFAULT: "hsl(var(--card) / <alpha-value>)",
foreground: "hsl(var(--card-foreground) / <alpha-value>)"
}
},
borderRadius: {
lg: "var(--radius)",
md: "calc(var(--radius) - 2px)",
sm: "calc(var(--radius) - 4px)"
},
fontFamily: {
sans: ['Lexend', 'sans-serif'],
},
}
},
};
export default config;

19
tsconfig.json Normal file
View File

@ -0,0 +1,19 @@
{
"extends": "./.svelte-kit/tsconfig.json",
"compilerOptions": {
"allowJs": true,
"checkJs": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"sourceMap": true,
"strict": true,
"moduleResolution": "bundler"
}
// Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias
// except $lib which is handled by https://kit.svelte.dev/docs/configuration#files
//
// If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
// from the referenced tsconfig.json - TypeScript does not merge them in
}

6
vite.config.ts Normal file
View File

@ -0,0 +1,6 @@
import { sveltekit } from '@sveltejs/kit/vite';
import { defineConfig } from 'vite';
export default defineConfig({
plugins: [sveltekit()]
});