@atmo-dev/kit 0.0.0-stage → 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +214 -2
- package/dist/components/Avatar.svelte +35 -0
- package/dist/components/Avatar.svelte.d.ts +9 -0
- package/dist/components/Badge.svelte +21 -0
- package/dist/components/Badge.svelte.d.ts +7 -0
- package/dist/components/Button.svelte +94 -0
- package/dist/components/Button.svelte.d.ts +13 -0
- package/dist/components/Checkbox.svelte +29 -0
- package/dist/components/Checkbox.svelte.d.ts +8 -0
- package/dist/components/Heading.svelte +24 -0
- package/dist/components/Heading.svelte.d.ts +8 -0
- package/dist/components/Icon.svelte +24 -0
- package/dist/components/Icon.svelte.d.ts +8 -0
- package/dist/components/IconButton.svelte +24 -0
- package/dist/components/IconButton.svelte.d.ts +8 -0
- package/dist/components/IconRail.svelte +186 -0
- package/dist/components/IconRail.svelte.d.ts +26 -0
- package/dist/components/InlineEditor.svelte +120 -0
- package/dist/components/InlineEditor.svelte.d.ts +19 -0
- package/dist/components/Input.svelte +32 -0
- package/dist/components/Input.svelte.d.ts +9 -0
- package/dist/components/InsetImage.svelte +29 -0
- package/dist/components/InsetImage.svelte.d.ts +9 -0
- package/dist/components/Label.svelte +19 -0
- package/dist/components/Label.svelte.d.ts +7 -0
- package/dist/components/LinkCard.svelte +47 -0
- package/dist/components/LinkCard.svelte.d.ts +12 -0
- package/dist/components/Modal.svelte +93 -0
- package/dist/components/Modal.svelte.d.ts +12 -0
- package/dist/components/NestedComments.svelte +61 -0
- package/dist/components/NestedComments.svelte.d.ts +8 -0
- package/dist/components/Popover.svelte +49 -0
- package/dist/components/Popover.svelte.d.ts +12 -0
- package/dist/components/PostComposer.svelte +41 -0
- package/dist/components/PostComposer.svelte.d.ts +16 -0
- package/dist/components/PostContent.svelte +62 -0
- package/dist/components/PostContent.svelte.d.ts +8 -0
- package/dist/components/PostVideo.svelte +74 -0
- package/dist/components/PostVideo.svelte.d.ts +9 -0
- package/dist/components/Select.svelte +34 -0
- package/dist/components/Select.svelte.d.ts +9 -0
- package/dist/components/Subheading.svelte +22 -0
- package/dist/components/Subheading.svelte.d.ts +8 -0
- package/dist/components/Surface.svelte +40 -0
- package/dist/components/Surface.svelte.d.ts +11 -0
- package/dist/components/Tabs.svelte +47 -0
- package/dist/components/Tabs.svelte.d.ts +16 -0
- package/dist/components/Textarea.svelte +20 -0
- package/dist/components/Textarea.svelte.d.ts +7 -0
- package/dist/components/ToggleGroup.svelte +32 -0
- package/dist/components/ToggleGroup.svelte.d.ts +12 -0
- package/dist/components/ToggleGroupItem.svelte +26 -0
- package/dist/components/ToggleGroupItem.svelte.d.ts +8 -0
- package/dist/components/WorkspaceSidebar.svelte +82 -0
- package/dist/components/WorkspaceSidebar.svelte.d.ts +10 -0
- package/dist/components/login/ActorSearchField.svelte +238 -0
- package/dist/components/login/ActorSearchField.svelte.d.ts +19 -0
- package/dist/components/login/AtmosphereApps.svelte +19 -0
- package/dist/components/login/AtmosphereApps.svelte.d.ts +18 -0
- package/dist/components/login/LoginDialog.svelte +78 -0
- package/dist/components/login/LoginDialog.svelte.d.ts +9 -0
- package/dist/components/login/LoginForm.svelte +402 -0
- package/dist/components/login/LoginForm.svelte.d.ts +28 -0
- package/dist/components/login/RecentAccounts.svelte +57 -0
- package/dist/components/login/RecentAccounts.svelte.d.ts +10 -0
- package/dist/components/login/brands/README.md +7 -0
- package/dist/components/login/brands/blacksky.svg +3 -0
- package/dist/components/login/brands/bluesky.svg +3 -0
- package/dist/components/login/brands/bookhive.svg +3 -0
- package/dist/components/login/brands/graze.svg +3 -0
- package/dist/components/login/brands/leaflet.svg +3 -0
- package/dist/components/login/brands/pckt.svg +3 -0
- package/dist/components/login/brands/surf.svg +3 -0
- package/dist/components/login/brands/tangled.svg +3 -0
- package/dist/components/login/handle.d.ts +3 -0
- package/dist/components/login/handle.js +11 -0
- package/dist/components/login/permissions.d.ts +3 -0
- package/dist/components/login/permissions.js +13 -0
- package/dist/components/login/recent-accounts.d.ts +16 -0
- package/dist/components/login/recent-accounts.js +87 -0
- package/dist/components/login/search.d.ts +9 -0
- package/dist/components/login/search.js +47 -0
- package/dist/components/login/types.d.ts +27 -0
- package/dist/components/login/types.js +1 -0
- package/dist/confetti.d.ts +2 -0
- package/dist/confetti.js +13 -0
- package/dist/index.d.ts +51 -0
- package/dist/index.js +39 -0
- package/dist/palettes.d.ts +2 -0
- package/dist/palettes.js +30 -0
- package/dist/social/mention-suggestions.d.ts +35 -0
- package/dist/social/mention-suggestions.js +122 -0
- package/dist/social/posts.d.ts +95 -0
- package/dist/social/posts.js +59 -0
- package/dist/social/richtext.d.ts +25 -0
- package/dist/social/richtext.js +100 -0
- package/dist/styles.d.ts +2 -0
- package/dist/styles.js +3 -0
- package/dist/theme.css +503 -0
- package/dist/theme.d.ts +2 -0
- package/dist/theme.js +23 -0
- package/dist/utils.d.ts +2 -0
- package/dist/utils.js +6 -0
- package/package.json +99 -3
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { PostData } from '../social/posts.js';
|
|
3
|
+
import { safeWebUrl } from '../social/richtext.js';
|
|
4
|
+
import Avatar from './Avatar.svelte';
|
|
5
|
+
import PostContent from './PostContent.svelte';
|
|
6
|
+
import NestedComments from './NestedComments.svelte';
|
|
7
|
+
let { comments }: { comments: PostData[] } = $props();
|
|
8
|
+
</script>
|
|
9
|
+
|
|
10
|
+
<div class="space-y-6" data-slot="comments">
|
|
11
|
+
{#each comments as comment (comment.uri)}
|
|
12
|
+
<article class="min-w-0">
|
|
13
|
+
<div class="flex items-start gap-3">
|
|
14
|
+
<a
|
|
15
|
+
href={`https://bsky.app/profile/${encodeURIComponent(comment.author.did)}`}
|
|
16
|
+
target="_blank"
|
|
17
|
+
rel="noopener noreferrer"
|
|
18
|
+
class="shrink-0"
|
|
19
|
+
><Avatar name={comment.author.name} src={comment.author.avatar} size={32} /></a
|
|
20
|
+
>
|
|
21
|
+
<div class="min-w-0 flex-1">
|
|
22
|
+
<div class="mb-1 flex flex-wrap items-baseline gap-x-2 text-xs">
|
|
23
|
+
<a
|
|
24
|
+
href={safeWebUrl(comment.href)}
|
|
25
|
+
target="_blank"
|
|
26
|
+
rel="noopener noreferrer"
|
|
27
|
+
class="font-medium text-ink hover:underline">{comment.author.name}</a
|
|
28
|
+
><span class="truncate text-muted">@{comment.author.handle}</span
|
|
29
|
+
>{#if comment.createdAt && !Number.isNaN(Date.parse(comment.createdAt))}<time
|
|
30
|
+
datetime={comment.createdAt}
|
|
31
|
+
class="text-[.6875rem] text-muted"
|
|
32
|
+
>{new Date(comment.createdAt).toLocaleDateString(undefined, {
|
|
33
|
+
month: 'short',
|
|
34
|
+
day: 'numeric'
|
|
35
|
+
})}</time
|
|
36
|
+
>{/if}
|
|
37
|
+
</div>
|
|
38
|
+
<PostContent post={comment} />
|
|
39
|
+
<div class="mt-2 flex gap-5 text-[.6875rem] text-muted">
|
|
40
|
+
<a
|
|
41
|
+
href={safeWebUrl(comment.href)}
|
|
42
|
+
target="_blank"
|
|
43
|
+
rel="noopener noreferrer"
|
|
44
|
+
class="hover:text-action"
|
|
45
|
+
>Reply{#if comment.replyCount}
|
|
46
|
+
· {comment.replyCount}{/if}</a
|
|
47
|
+
><a
|
|
48
|
+
href={safeWebUrl(comment.href)}
|
|
49
|
+
target="_blank"
|
|
50
|
+
rel="noopener noreferrer"
|
|
51
|
+
class="hover:text-action">{comment.likeCount ?? 0} likes</a
|
|
52
|
+
>
|
|
53
|
+
</div>
|
|
54
|
+
</div>
|
|
55
|
+
</div>
|
|
56
|
+
{#if comment.replies?.length}<div class="mt-4 border-l border-line pl-3 sm:ml-4 sm:pl-6">
|
|
57
|
+
<NestedComments comments={comment.replies} />
|
|
58
|
+
</div>{/if}
|
|
59
|
+
</article>
|
|
60
|
+
{/each}
|
|
61
|
+
</div>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { PostData } from '../social/posts.js';
|
|
2
|
+
import NestedComments from './NestedComments.svelte';
|
|
3
|
+
type $$ComponentProps = {
|
|
4
|
+
comments: PostData[];
|
|
5
|
+
};
|
|
6
|
+
declare const NestedComments: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
7
|
+
type NestedComments = ReturnType<typeof NestedComments>;
|
|
8
|
+
export default NestedComments;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { Popover as Primitive } from 'bits-ui';
|
|
3
|
+
import type { Snippet } from 'svelte';
|
|
4
|
+
import { cn } from '../utils.js';
|
|
5
|
+
import { readTheme, observeTheme } from '../theme.js';
|
|
6
|
+
let {
|
|
7
|
+
open = $bindable(false),
|
|
8
|
+
trigger,
|
|
9
|
+
children,
|
|
10
|
+
class: className,
|
|
11
|
+
style,
|
|
12
|
+
role = 'dialog',
|
|
13
|
+
sideOffset = 8,
|
|
14
|
+
...rest
|
|
15
|
+
}: Omit<Primitive.ContentProps, 'children' | 'child'> & {
|
|
16
|
+
open?: boolean;
|
|
17
|
+
trigger: Snippet<[{ props: Record<string, unknown> }]>;
|
|
18
|
+
children?: Snippet;
|
|
19
|
+
} = $props();
|
|
20
|
+
let origin = $state<HTMLSpanElement>();
|
|
21
|
+
let inherited = $state('');
|
|
22
|
+
$effect(() => {
|
|
23
|
+
if (!open || !origin) return;
|
|
24
|
+
const sync = () => (inherited = readTheme(origin!));
|
|
25
|
+
sync();
|
|
26
|
+
return observeTheme(origin, sync);
|
|
27
|
+
});
|
|
28
|
+
</script>
|
|
29
|
+
|
|
30
|
+
<span hidden bind:this={origin} class={cn('atmo-theme', className)} {style}></span>
|
|
31
|
+
<Primitive.Root bind:open>
|
|
32
|
+
<Primitive.Trigger
|
|
33
|
+
>{#snippet child({ props })}{@render trigger({ props })}{/snippet}</Primitive.Trigger
|
|
34
|
+
>
|
|
35
|
+
<Primitive.Portal>
|
|
36
|
+
<Primitive.Content
|
|
37
|
+
{...rest}
|
|
38
|
+
{role}
|
|
39
|
+
{sideOffset}
|
|
40
|
+
style={[inherited, style].filter(Boolean).join(';')}
|
|
41
|
+
class={cn(
|
|
42
|
+
'z-50 max-w-[calc(100vw-2rem)] rounded-panel border border-line bg-page p-4 font-sans text-ink outline-none',
|
|
43
|
+
className
|
|
44
|
+
)}
|
|
45
|
+
>
|
|
46
|
+
{@render children?.()}
|
|
47
|
+
</Primitive.Content>
|
|
48
|
+
</Primitive.Portal>
|
|
49
|
+
</Primitive.Root>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Popover as Primitive } from 'bits-ui';
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
type $$ComponentProps = Omit<Primitive.ContentProps, 'children' | 'child'> & {
|
|
4
|
+
open?: boolean;
|
|
5
|
+
trigger: Snippet<[{
|
|
6
|
+
props: Record<string, unknown>;
|
|
7
|
+
}]>;
|
|
8
|
+
children?: Snippet;
|
|
9
|
+
};
|
|
10
|
+
declare const Popover: import("svelte").Component<$$ComponentProps, {}, "open">;
|
|
11
|
+
type Popover = ReturnType<typeof Popover>;
|
|
12
|
+
export default Popover;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Editor, JSONContent } from '@tiptap/core';
|
|
3
|
+
import InlineEditor from './InlineEditor.svelte';
|
|
4
|
+
import type { ActorSearch } from './login/types.js';
|
|
5
|
+
import { graphemeLength } from '../social/richtext.js';
|
|
6
|
+
let {
|
|
7
|
+
value = $bindable(''),
|
|
8
|
+
editor = $bindable<Editor | null>(null),
|
|
9
|
+
searchActors,
|
|
10
|
+
maxLength = 300,
|
|
11
|
+
disabled = false,
|
|
12
|
+
onupdate
|
|
13
|
+
}: {
|
|
14
|
+
value?: string;
|
|
15
|
+
editor?: Editor | null;
|
|
16
|
+
searchActors?: ActorSearch;
|
|
17
|
+
maxLength?: number;
|
|
18
|
+
disabled?: boolean;
|
|
19
|
+
onupdate?: (content: { text: string; json: JSONContent }) => void;
|
|
20
|
+
} = $props();
|
|
21
|
+
let length = $derived(graphemeLength(value));
|
|
22
|
+
</script>
|
|
23
|
+
|
|
24
|
+
<InlineEditor
|
|
25
|
+
bind:value
|
|
26
|
+
bind:editor
|
|
27
|
+
label="Post text"
|
|
28
|
+
placeholder="What’s happening?"
|
|
29
|
+
{searchActors}
|
|
30
|
+
{disabled}
|
|
31
|
+
{onupdate}
|
|
32
|
+
class="min-h-24 text-sm leading-relaxed"
|
|
33
|
+
/>
|
|
34
|
+
<p
|
|
35
|
+
class={length > maxLength
|
|
36
|
+
? 'red mt-2 text-right text-xs text-action'
|
|
37
|
+
: 'mt-2 text-right text-xs text-muted'}
|
|
38
|
+
aria-live="polite"
|
|
39
|
+
>
|
|
40
|
+
{length} / {maxLength}
|
|
41
|
+
</p>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Editor, JSONContent } from '@tiptap/core';
|
|
2
|
+
import type { ActorSearch } from './login/types.js';
|
|
3
|
+
type $$ComponentProps = {
|
|
4
|
+
value?: string;
|
|
5
|
+
editor?: Editor | null;
|
|
6
|
+
searchActors?: ActorSearch;
|
|
7
|
+
maxLength?: number;
|
|
8
|
+
disabled?: boolean;
|
|
9
|
+
onupdate?: (content: {
|
|
10
|
+
text: string;
|
|
11
|
+
json: JSONContent;
|
|
12
|
+
}) => void;
|
|
13
|
+
};
|
|
14
|
+
declare const PostComposer: import("svelte").Component<$$ComponentProps, {}, "value" | "editor">;
|
|
15
|
+
type PostComposer = ReturnType<typeof PostComposer>;
|
|
16
|
+
export default PostComposer;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { PostData } from '../social/posts.js';
|
|
3
|
+
import { richTextSegments, safeWebUrl } from '../social/richtext.js';
|
|
4
|
+
import LinkCard from './LinkCard.svelte';
|
|
5
|
+
import PostVideo from './PostVideo.svelte';
|
|
6
|
+
import PostContent from './PostContent.svelte';
|
|
7
|
+
let { post }: { post: PostData } = $props();
|
|
8
|
+
</script>
|
|
9
|
+
|
|
10
|
+
<p class="text-sm leading-relaxed break-words whitespace-pre-wrap text-ink">
|
|
11
|
+
{#each richTextSegments(post.text, post.facets) as segment, index (index)}{#if segment.href}<a
|
|
12
|
+
href={segment.href}
|
|
13
|
+
target="_blank"
|
|
14
|
+
rel="noopener noreferrer nofollow"
|
|
15
|
+
class="text-action underline decoration-action/40 underline-offset-2 hover:decoration-action"
|
|
16
|
+
>{segment.text}</a
|
|
17
|
+
>{:else}{segment.text}{/if}{/each}
|
|
18
|
+
</p>
|
|
19
|
+
{#each post.embeds ?? [] as embed, index (index)}
|
|
20
|
+
<div class="mt-3">
|
|
21
|
+
{#if embed.type === 'external'}<LinkCard
|
|
22
|
+
href={embed.uri}
|
|
23
|
+
meta={{ title: embed.title, description: embed.description, image: embed.image }}
|
|
24
|
+
/>
|
|
25
|
+
{:else if embed.type === 'images'}<div
|
|
26
|
+
class="grid gap-1 overflow-hidden rounded-ui border border-line"
|
|
27
|
+
class:grid-cols-2={embed.images.length > 1}
|
|
28
|
+
>
|
|
29
|
+
{#each embed.images as image (image.src)}<a
|
|
30
|
+
href={safeWebUrl(image.fullsize ?? image.src)}
|
|
31
|
+
target="_blank"
|
|
32
|
+
rel="noopener noreferrer"
|
|
33
|
+
><img
|
|
34
|
+
src={safeWebUrl(image.src)}
|
|
35
|
+
alt={image.alt}
|
|
36
|
+
loading="lazy"
|
|
37
|
+
width={image.width}
|
|
38
|
+
height={image.height}
|
|
39
|
+
class="max-h-80 w-full object-cover"
|
|
40
|
+
class:aspect-square={embed.images.length > 1}
|
|
41
|
+
/></a
|
|
42
|
+
>{/each}
|
|
43
|
+
</div>
|
|
44
|
+
{:else if embed.type === 'video'}<PostVideo
|
|
45
|
+
playlist={embed.playlist}
|
|
46
|
+
poster={embed.poster}
|
|
47
|
+
label={embed.alt}
|
|
48
|
+
href={post.href}
|
|
49
|
+
/>
|
|
50
|
+
{:else if embed.type === 'quote'}<div class="rounded-ui border border-line p-3">
|
|
51
|
+
{#if embed.post}<a
|
|
52
|
+
href={safeWebUrl(embed.post.href)}
|
|
53
|
+
target="_blank"
|
|
54
|
+
rel="noopener noreferrer"
|
|
55
|
+
class="mb-2 block text-xs font-medium text-muted"
|
|
56
|
+
>{embed.post.author.name} · @{embed.post.author.handle}</a
|
|
57
|
+
><PostContent post={embed.post} />{:else}<p class="text-xs text-muted">
|
|
58
|
+
Post unavailable
|
|
59
|
+
</p>{/if}
|
|
60
|
+
</div>{/if}
|
|
61
|
+
</div>
|
|
62
|
+
{/each}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { PostData } from '../social/posts.js';
|
|
2
|
+
import PostContent from './PostContent.svelte';
|
|
3
|
+
type $$ComponentProps = {
|
|
4
|
+
post: PostData;
|
|
5
|
+
};
|
|
6
|
+
declare const PostContent: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
7
|
+
type PostContent = ReturnType<typeof PostContent>;
|
|
8
|
+
export default PostContent;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { safeWebUrl } from '../social/richtext.js';
|
|
3
|
+
import Icon from './Icon.svelte';
|
|
4
|
+
import { ArrowUpRight } from '@sketchyicons/svelte';
|
|
5
|
+
let {
|
|
6
|
+
playlist,
|
|
7
|
+
poster,
|
|
8
|
+
label,
|
|
9
|
+
href
|
|
10
|
+
}: { playlist: string; poster?: string; label?: string; href: string } = $props();
|
|
11
|
+
let video = $state<HTMLVideoElement | null>(null);
|
|
12
|
+
// Writable derived state resets a playback failure when the source changes.
|
|
13
|
+
let failed = $derived(!safeWebUrl(playlist));
|
|
14
|
+
$effect(() => {
|
|
15
|
+
const source = safeWebUrl(playlist),
|
|
16
|
+
media = video;
|
|
17
|
+
if (!source || !media || failed) return;
|
|
18
|
+
let cancelled = false;
|
|
19
|
+
let destroy: (() => void) | undefined;
|
|
20
|
+
if (media.canPlayType('application/vnd.apple.mpegurl')) media.src = source;
|
|
21
|
+
else
|
|
22
|
+
void import('hls.js')
|
|
23
|
+
.then(({ default: Hls }) => {
|
|
24
|
+
if (cancelled) return;
|
|
25
|
+
if (!Hls.isSupported()) {
|
|
26
|
+
failed = true;
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
const hls = new Hls({ autoStartLoad: false });
|
|
30
|
+
hls.loadSource(source);
|
|
31
|
+
hls.attachMedia(media);
|
|
32
|
+
const play = () => hls.startLoad();
|
|
33
|
+
media.addEventListener('play', play);
|
|
34
|
+
hls.on(Hls.Events.ERROR, (_event, data) => {
|
|
35
|
+
if (data.fatal && !cancelled) failed = true;
|
|
36
|
+
});
|
|
37
|
+
destroy = () => {
|
|
38
|
+
media.removeEventListener('play', play);
|
|
39
|
+
hls.destroy();
|
|
40
|
+
};
|
|
41
|
+
})
|
|
42
|
+
.catch(() => {
|
|
43
|
+
if (!cancelled) failed = true;
|
|
44
|
+
});
|
|
45
|
+
return () => {
|
|
46
|
+
cancelled = true;
|
|
47
|
+
destroy?.();
|
|
48
|
+
media.pause();
|
|
49
|
+
media.removeAttribute('src');
|
|
50
|
+
media.load();
|
|
51
|
+
};
|
|
52
|
+
});
|
|
53
|
+
</script>
|
|
54
|
+
|
|
55
|
+
{#if !failed}
|
|
56
|
+
<!-- Bluesky's video view supplies alt text, but no caption track. -->
|
|
57
|
+
<video
|
|
58
|
+
bind:this={video}
|
|
59
|
+
poster={safeWebUrl(poster)}
|
|
60
|
+
aria-label={label || 'Bluesky video'}
|
|
61
|
+
controls
|
|
62
|
+
playsinline
|
|
63
|
+
preload="none"
|
|
64
|
+
class="max-h-96 w-full rounded-ui bg-black"
|
|
65
|
+
onerror={() => (failed = true)}
|
|
66
|
+
></video>
|
|
67
|
+
{/if}
|
|
68
|
+
<a
|
|
69
|
+
href={safeWebUrl(href)}
|
|
70
|
+
target="_blank"
|
|
71
|
+
rel="noopener noreferrer"
|
|
72
|
+
class="mt-1 inline-flex items-center gap-1 text-xs text-action hover:underline"
|
|
73
|
+
>Watch on Bluesky <Icon img={ArrowUpRight} size={14} /></a
|
|
74
|
+
>
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { HTMLSelectAttributes } from 'svelte/elements';
|
|
3
|
+
import { cn } from '../utils.js';
|
|
4
|
+
import { control } from '../styles.js';
|
|
5
|
+
import Icon from './Icon.svelte';
|
|
6
|
+
import { ChevronDown } from '@sketchyicons/svelte';
|
|
7
|
+
let {
|
|
8
|
+
class: className,
|
|
9
|
+
value = $bindable<string | undefined>(),
|
|
10
|
+
ref = $bindable(null),
|
|
11
|
+
children,
|
|
12
|
+
...rest
|
|
13
|
+
}: Omit<HTMLSelectAttributes, 'value' | 'multiple'> & {
|
|
14
|
+
value?: string;
|
|
15
|
+
multiple?: never;
|
|
16
|
+
ref?: HTMLSelectElement | null;
|
|
17
|
+
} = $props();
|
|
18
|
+
</script>
|
|
19
|
+
|
|
20
|
+
<span class="relative block">
|
|
21
|
+
<select
|
|
22
|
+
{...rest}
|
|
23
|
+
bind:value
|
|
24
|
+
bind:this={ref}
|
|
25
|
+
data-slot="select"
|
|
26
|
+
class={cn(control, 'cursor-pointer appearance-none pr-11', className)}
|
|
27
|
+
>{@render children?.()}</select
|
|
28
|
+
>
|
|
29
|
+
<Icon
|
|
30
|
+
img={ChevronDown}
|
|
31
|
+
size={17}
|
|
32
|
+
class="pointer-events-none absolute top-1/2 right-3 -translate-y-1/2 text-muted"
|
|
33
|
+
/>
|
|
34
|
+
</span>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { HTMLSelectAttributes } from 'svelte/elements';
|
|
2
|
+
type $$ComponentProps = Omit<HTMLSelectAttributes, 'value' | 'multiple'> & {
|
|
3
|
+
value?: string;
|
|
4
|
+
multiple?: never;
|
|
5
|
+
ref?: HTMLSelectElement | null;
|
|
6
|
+
};
|
|
7
|
+
declare const Select: import("svelte").Component<$$ComponentProps, {}, "value" | "ref">;
|
|
8
|
+
type Select = ReturnType<typeof Select>;
|
|
9
|
+
export default Select;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
3
|
+
import { cn } from '../utils.js';
|
|
4
|
+
let {
|
|
5
|
+
class: className,
|
|
6
|
+
level = 2,
|
|
7
|
+
ref = $bindable(null),
|
|
8
|
+
children,
|
|
9
|
+
...rest
|
|
10
|
+
}: HTMLAttributes<HTMLHeadingElement> & {
|
|
11
|
+
level?: 1 | 2 | 3 | 4 | 5 | 6;
|
|
12
|
+
ref?: HTMLHeadingElement | null;
|
|
13
|
+
} = $props();
|
|
14
|
+
</script>
|
|
15
|
+
|
|
16
|
+
<svelte:element
|
|
17
|
+
this={`h${level}`}
|
|
18
|
+
{...rest}
|
|
19
|
+
bind:this={ref}
|
|
20
|
+
class={cn('m-0 text-lg leading-snug font-medium tracking-tight text-ink', className)}
|
|
21
|
+
>{@render children?.()}</svelte:element
|
|
22
|
+
>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
2
|
+
type $$ComponentProps = HTMLAttributes<HTMLHeadingElement> & {
|
|
3
|
+
level?: 1 | 2 | 3 | 4 | 5 | 6;
|
|
4
|
+
ref?: HTMLHeadingElement | null;
|
|
5
|
+
};
|
|
6
|
+
declare const Subheading: import("svelte").Component<$$ComponentProps, {}, "ref">;
|
|
7
|
+
type Subheading = ReturnType<typeof Subheading>;
|
|
8
|
+
export default Subheading;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
3
|
+
import { cn } from '../utils.js';
|
|
4
|
+
import { focus } from '../styles.js';
|
|
5
|
+
let {
|
|
6
|
+
class: className,
|
|
7
|
+
as = 'div',
|
|
8
|
+
variant = 'recessed',
|
|
9
|
+
ref = $bindable(null),
|
|
10
|
+
type,
|
|
11
|
+
disabled,
|
|
12
|
+
children,
|
|
13
|
+
...rest
|
|
14
|
+
}: HTMLAttributes<HTMLElement> & {
|
|
15
|
+
as?: 'div' | 'section' | 'article' | 'aside' | 'main' | 'button';
|
|
16
|
+
type?: 'button' | 'submit' | 'reset';
|
|
17
|
+
disabled?: boolean;
|
|
18
|
+
variant?: 'recessed' | 'plain';
|
|
19
|
+
ref?: HTMLElement | null;
|
|
20
|
+
} = $props();
|
|
21
|
+
</script>
|
|
22
|
+
|
|
23
|
+
<svelte:element
|
|
24
|
+
this={as}
|
|
25
|
+
{...rest}
|
|
26
|
+
type={as === 'button' ? (type ?? 'button') : undefined}
|
|
27
|
+
disabled={as === 'button' ? disabled : undefined}
|
|
28
|
+
bind:this={ref}
|
|
29
|
+
data-slot="surface"
|
|
30
|
+
class={cn(
|
|
31
|
+
'rounded-panel p-6 text-ink',
|
|
32
|
+
variant === 'recessed' && 'border border-line bg-recess shadow-(--ui-recess-shadow)',
|
|
33
|
+
as === 'button' &&
|
|
34
|
+
cn(
|
|
35
|
+
'cursor-pointer touch-manipulation disabled:pointer-events-none disabled:opacity-45',
|
|
36
|
+
focus
|
|
37
|
+
),
|
|
38
|
+
className
|
|
39
|
+
)}>{@render children?.()}</svelte:element
|
|
40
|
+
>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
2
|
+
type $$ComponentProps = HTMLAttributes<HTMLElement> & {
|
|
3
|
+
as?: 'div' | 'section' | 'article' | 'aside' | 'main' | 'button';
|
|
4
|
+
type?: 'button' | 'submit' | 'reset';
|
|
5
|
+
disabled?: boolean;
|
|
6
|
+
variant?: 'recessed' | 'plain';
|
|
7
|
+
ref?: HTMLElement | null;
|
|
8
|
+
};
|
|
9
|
+
declare const Surface: import("svelte").Component<$$ComponentProps, {}, "ref">;
|
|
10
|
+
type Surface = ReturnType<typeof Surface>;
|
|
11
|
+
export default Surface;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
<script lang="ts" module>
|
|
2
|
+
export type TabItem = { value: string; label: string; disabled?: boolean };
|
|
3
|
+
</script>
|
|
4
|
+
|
|
5
|
+
<script lang="ts">
|
|
6
|
+
import type { Snippet } from 'svelte';
|
|
7
|
+
import { Tabs as Primitive } from 'bits-ui';
|
|
8
|
+
import { cn } from '../utils.js';
|
|
9
|
+
import { focus } from '../styles.js';
|
|
10
|
+
let {
|
|
11
|
+
items,
|
|
12
|
+
value = $bindable(''),
|
|
13
|
+
label,
|
|
14
|
+
class: className,
|
|
15
|
+
children,
|
|
16
|
+
...rest
|
|
17
|
+
}: Omit<Primitive.RootProps, 'children' | 'child' | 'value'> & {
|
|
18
|
+
items: readonly TabItem[];
|
|
19
|
+
value?: string;
|
|
20
|
+
label: string;
|
|
21
|
+
children: Snippet<[string]>;
|
|
22
|
+
} = $props();
|
|
23
|
+
$effect(() => {
|
|
24
|
+
if (!items.some((item) => item.value === value && !item.disabled))
|
|
25
|
+
value = items.find((item) => !item.disabled)?.value ?? '';
|
|
26
|
+
});
|
|
27
|
+
</script>
|
|
28
|
+
|
|
29
|
+
<Primitive.Root {...rest} bind:value class={cn('min-w-0', className)}>
|
|
30
|
+
<Primitive.List
|
|
31
|
+
aria-label={label}
|
|
32
|
+
class="flex w-fit max-w-full flex-wrap items-center gap-1 rounded-[12px_14px_11px_13px] border border-line bg-recess p-1 shadow-[inset_0_2px_4px_var(--ui-shadow)]"
|
|
33
|
+
>
|
|
34
|
+
{#each items as item (item.value)}<Primitive.Trigger
|
|
35
|
+
value={item.value}
|
|
36
|
+
disabled={item.disabled}
|
|
37
|
+
class={cn(
|
|
38
|
+
'cursor-pointer rounded-[8px_10px_7px_9px] px-3.5 py-1.75 text-[.8125rem] leading-normal text-muted hover:text-ink disabled:cursor-default disabled:opacity-45 data-[state=active]:bg-surface data-[state=active]:text-ink',
|
|
39
|
+
focus
|
|
40
|
+
)}>{item.label}</Primitive.Trigger
|
|
41
|
+
>{/each}
|
|
42
|
+
</Primitive.List>
|
|
43
|
+
{#each items as item (item.value)}<Primitive.Content
|
|
44
|
+
value={item.value}
|
|
45
|
+
class={cn('rounded pt-6', focus)}>{@render children(item.value)}</Primitive.Content
|
|
46
|
+
>{/each}
|
|
47
|
+
</Primitive.Root>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export type TabItem = {
|
|
2
|
+
value: string;
|
|
3
|
+
label: string;
|
|
4
|
+
disabled?: boolean;
|
|
5
|
+
};
|
|
6
|
+
import type { Snippet } from 'svelte';
|
|
7
|
+
import { Tabs as Primitive } from 'bits-ui';
|
|
8
|
+
type $$ComponentProps = Omit<Primitive.RootProps, 'children' | 'child' | 'value'> & {
|
|
9
|
+
items: readonly TabItem[];
|
|
10
|
+
value?: string;
|
|
11
|
+
label: string;
|
|
12
|
+
children: Snippet<[string]>;
|
|
13
|
+
};
|
|
14
|
+
declare const Tabs: import("svelte").Component<$$ComponentProps, {}, "value">;
|
|
15
|
+
type Tabs = ReturnType<typeof Tabs>;
|
|
16
|
+
export default Tabs;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { HTMLTextareaAttributes } from 'svelte/elements';
|
|
3
|
+
import { cn } from '../utils.js';
|
|
4
|
+
import { control } from '../styles.js';
|
|
5
|
+
let {
|
|
6
|
+
class: className,
|
|
7
|
+
value = $bindable(''),
|
|
8
|
+
ref = $bindable(null),
|
|
9
|
+
rows = 4,
|
|
10
|
+
...rest
|
|
11
|
+
}: HTMLTextareaAttributes & { ref?: HTMLTextAreaElement | null } = $props();
|
|
12
|
+
</script>
|
|
13
|
+
|
|
14
|
+
<textarea
|
|
15
|
+
{...rest}
|
|
16
|
+
{rows}
|
|
17
|
+
bind:value
|
|
18
|
+
bind:this={ref}
|
|
19
|
+
data-slot="textarea"
|
|
20
|
+
class={cn(control, 'min-h-28 resize-y', className)}></textarea>
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { HTMLTextareaAttributes } from 'svelte/elements';
|
|
2
|
+
type $$ComponentProps = HTMLTextareaAttributes & {
|
|
3
|
+
ref?: HTMLTextAreaElement | null;
|
|
4
|
+
};
|
|
5
|
+
declare const Textarea: import("svelte").Component<$$ComponentProps, {}, "value" | "ref">;
|
|
6
|
+
type Textarea = ReturnType<typeof Textarea>;
|
|
7
|
+
export default Textarea;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { ToggleGroup } from 'bits-ui';
|
|
3
|
+
import { cn } from '../utils.js';
|
|
4
|
+
let {
|
|
5
|
+
value = $bindable(''),
|
|
6
|
+
size = 'sm',
|
|
7
|
+
class: className,
|
|
8
|
+
children,
|
|
9
|
+
...rest
|
|
10
|
+
}: Omit<
|
|
11
|
+
Extract<ToggleGroup.RootProps, { type: 'single' }>,
|
|
12
|
+
'type' | 'value' | 'children' | 'child'
|
|
13
|
+
> & {
|
|
14
|
+
type?: 'single';
|
|
15
|
+
value?: string;
|
|
16
|
+
size?: 'xs' | 'sm';
|
|
17
|
+
children?: import('svelte').Snippet;
|
|
18
|
+
} = $props();
|
|
19
|
+
</script>
|
|
20
|
+
|
|
21
|
+
<ToggleGroup.Root
|
|
22
|
+
{...rest}
|
|
23
|
+
type="single"
|
|
24
|
+
bind:value
|
|
25
|
+
data-size={size}
|
|
26
|
+
class={cn(
|
|
27
|
+
'inline-flex flex-wrap gap-1 rounded-ui bg-recess p-1 [box-shadow:var(--ui-recess-shadow)]',
|
|
28
|
+
className
|
|
29
|
+
)}
|
|
30
|
+
>
|
|
31
|
+
{@render children?.()}
|
|
32
|
+
</ToggleGroup.Root>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { ToggleGroup } from 'bits-ui';
|
|
2
|
+
type $$ComponentProps = Omit<Extract<ToggleGroup.RootProps, {
|
|
3
|
+
type: 'single';
|
|
4
|
+
}>, 'type' | 'value' | 'children' | 'child'> & {
|
|
5
|
+
type?: 'single';
|
|
6
|
+
value?: string;
|
|
7
|
+
size?: 'xs' | 'sm';
|
|
8
|
+
children?: import('svelte').Snippet;
|
|
9
|
+
};
|
|
10
|
+
declare const ToggleGroup: import("svelte").Component<$$ComponentProps, {}, "value">;
|
|
11
|
+
type ToggleGroup = ReturnType<typeof ToggleGroup>;
|
|
12
|
+
export default ToggleGroup;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { ToggleGroup } from 'bits-ui';
|
|
3
|
+
import { cn } from '../utils.js';
|
|
4
|
+
import { focus } from '../styles.js';
|
|
5
|
+
let {
|
|
6
|
+
class: className,
|
|
7
|
+
size = 'sm',
|
|
8
|
+
children,
|
|
9
|
+
...rest
|
|
10
|
+
}: Omit<ToggleGroup.ItemProps, 'children'> & {
|
|
11
|
+
size?: 'xs' | 'sm';
|
|
12
|
+
children?: import('svelte').Snippet;
|
|
13
|
+
} = $props();
|
|
14
|
+
</script>
|
|
15
|
+
|
|
16
|
+
<ToggleGroup.Item
|
|
17
|
+
{...rest}
|
|
18
|
+
class={cn(
|
|
19
|
+
'cursor-pointer rounded-ui px-3 py-1.5 text-xs font-medium text-muted transition-colors duration-(--atmo-duration) hover:text-ink disabled:pointer-events-none disabled:opacity-45 data-[state=on]:bg-surface data-[state=on]:text-action motion-reduce:transition-none',
|
|
20
|
+
focus,
|
|
21
|
+
size === 'xs' && 'px-2 py-1 text-[.6875rem]',
|
|
22
|
+
className
|
|
23
|
+
)}
|
|
24
|
+
>
|
|
25
|
+
{@render children?.()}
|
|
26
|
+
</ToggleGroup.Item>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { ToggleGroup } from 'bits-ui';
|
|
2
|
+
type $$ComponentProps = Omit<ToggleGroup.ItemProps, 'children'> & {
|
|
3
|
+
size?: 'xs' | 'sm';
|
|
4
|
+
children?: import('svelte').Snippet;
|
|
5
|
+
};
|
|
6
|
+
declare const ToggleGroupItem: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
7
|
+
type ToggleGroupItem = ReturnType<typeof ToggleGroupItem>;
|
|
8
|
+
export default ToggleGroupItem;
|