@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.
Files changed (105) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +214 -2
  3. package/dist/components/Avatar.svelte +35 -0
  4. package/dist/components/Avatar.svelte.d.ts +9 -0
  5. package/dist/components/Badge.svelte +21 -0
  6. package/dist/components/Badge.svelte.d.ts +7 -0
  7. package/dist/components/Button.svelte +94 -0
  8. package/dist/components/Button.svelte.d.ts +13 -0
  9. package/dist/components/Checkbox.svelte +29 -0
  10. package/dist/components/Checkbox.svelte.d.ts +8 -0
  11. package/dist/components/Heading.svelte +24 -0
  12. package/dist/components/Heading.svelte.d.ts +8 -0
  13. package/dist/components/Icon.svelte +24 -0
  14. package/dist/components/Icon.svelte.d.ts +8 -0
  15. package/dist/components/IconButton.svelte +24 -0
  16. package/dist/components/IconButton.svelte.d.ts +8 -0
  17. package/dist/components/IconRail.svelte +186 -0
  18. package/dist/components/IconRail.svelte.d.ts +26 -0
  19. package/dist/components/InlineEditor.svelte +120 -0
  20. package/dist/components/InlineEditor.svelte.d.ts +19 -0
  21. package/dist/components/Input.svelte +32 -0
  22. package/dist/components/Input.svelte.d.ts +9 -0
  23. package/dist/components/InsetImage.svelte +29 -0
  24. package/dist/components/InsetImage.svelte.d.ts +9 -0
  25. package/dist/components/Label.svelte +19 -0
  26. package/dist/components/Label.svelte.d.ts +7 -0
  27. package/dist/components/LinkCard.svelte +47 -0
  28. package/dist/components/LinkCard.svelte.d.ts +12 -0
  29. package/dist/components/Modal.svelte +93 -0
  30. package/dist/components/Modal.svelte.d.ts +12 -0
  31. package/dist/components/NestedComments.svelte +61 -0
  32. package/dist/components/NestedComments.svelte.d.ts +8 -0
  33. package/dist/components/Popover.svelte +49 -0
  34. package/dist/components/Popover.svelte.d.ts +12 -0
  35. package/dist/components/PostComposer.svelte +41 -0
  36. package/dist/components/PostComposer.svelte.d.ts +16 -0
  37. package/dist/components/PostContent.svelte +62 -0
  38. package/dist/components/PostContent.svelte.d.ts +8 -0
  39. package/dist/components/PostVideo.svelte +74 -0
  40. package/dist/components/PostVideo.svelte.d.ts +9 -0
  41. package/dist/components/Select.svelte +34 -0
  42. package/dist/components/Select.svelte.d.ts +9 -0
  43. package/dist/components/Subheading.svelte +22 -0
  44. package/dist/components/Subheading.svelte.d.ts +8 -0
  45. package/dist/components/Surface.svelte +40 -0
  46. package/dist/components/Surface.svelte.d.ts +11 -0
  47. package/dist/components/Tabs.svelte +47 -0
  48. package/dist/components/Tabs.svelte.d.ts +16 -0
  49. package/dist/components/Textarea.svelte +20 -0
  50. package/dist/components/Textarea.svelte.d.ts +7 -0
  51. package/dist/components/ToggleGroup.svelte +32 -0
  52. package/dist/components/ToggleGroup.svelte.d.ts +12 -0
  53. package/dist/components/ToggleGroupItem.svelte +26 -0
  54. package/dist/components/ToggleGroupItem.svelte.d.ts +8 -0
  55. package/dist/components/WorkspaceSidebar.svelte +82 -0
  56. package/dist/components/WorkspaceSidebar.svelte.d.ts +10 -0
  57. package/dist/components/login/ActorSearchField.svelte +238 -0
  58. package/dist/components/login/ActorSearchField.svelte.d.ts +19 -0
  59. package/dist/components/login/AtmosphereApps.svelte +19 -0
  60. package/dist/components/login/AtmosphereApps.svelte.d.ts +18 -0
  61. package/dist/components/login/LoginDialog.svelte +78 -0
  62. package/dist/components/login/LoginDialog.svelte.d.ts +9 -0
  63. package/dist/components/login/LoginForm.svelte +402 -0
  64. package/dist/components/login/LoginForm.svelte.d.ts +28 -0
  65. package/dist/components/login/RecentAccounts.svelte +57 -0
  66. package/dist/components/login/RecentAccounts.svelte.d.ts +10 -0
  67. package/dist/components/login/brands/README.md +7 -0
  68. package/dist/components/login/brands/blacksky.svg +3 -0
  69. package/dist/components/login/brands/bluesky.svg +3 -0
  70. package/dist/components/login/brands/bookhive.svg +3 -0
  71. package/dist/components/login/brands/graze.svg +3 -0
  72. package/dist/components/login/brands/leaflet.svg +3 -0
  73. package/dist/components/login/brands/pckt.svg +3 -0
  74. package/dist/components/login/brands/surf.svg +3 -0
  75. package/dist/components/login/brands/tangled.svg +3 -0
  76. package/dist/components/login/handle.d.ts +3 -0
  77. package/dist/components/login/handle.js +11 -0
  78. package/dist/components/login/permissions.d.ts +3 -0
  79. package/dist/components/login/permissions.js +13 -0
  80. package/dist/components/login/recent-accounts.d.ts +16 -0
  81. package/dist/components/login/recent-accounts.js +87 -0
  82. package/dist/components/login/search.d.ts +9 -0
  83. package/dist/components/login/search.js +47 -0
  84. package/dist/components/login/types.d.ts +27 -0
  85. package/dist/components/login/types.js +1 -0
  86. package/dist/confetti.d.ts +2 -0
  87. package/dist/confetti.js +13 -0
  88. package/dist/index.d.ts +51 -0
  89. package/dist/index.js +39 -0
  90. package/dist/palettes.d.ts +2 -0
  91. package/dist/palettes.js +30 -0
  92. package/dist/social/mention-suggestions.d.ts +35 -0
  93. package/dist/social/mention-suggestions.js +122 -0
  94. package/dist/social/posts.d.ts +95 -0
  95. package/dist/social/posts.js +59 -0
  96. package/dist/social/richtext.d.ts +25 -0
  97. package/dist/social/richtext.js +100 -0
  98. package/dist/styles.d.ts +2 -0
  99. package/dist/styles.js +3 -0
  100. package/dist/theme.css +503 -0
  101. package/dist/theme.d.ts +2 -0
  102. package/dist/theme.js +23 -0
  103. package/dist/utils.d.ts +2 -0
  104. package/dist/utils.js +6 -0
  105. 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,9 @@
1
+ type $$ComponentProps = {
2
+ playlist: string;
3
+ poster?: string;
4
+ label?: string;
5
+ href: string;
6
+ };
7
+ declare const PostVideo: import("svelte").Component<$$ComponentProps, {}, "">;
8
+ type PostVideo = ReturnType<typeof PostVideo>;
9
+ export default PostVideo;
@@ -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;