@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,186 @@
|
|
|
1
|
+
<script lang="ts" module>
|
|
2
|
+
import type { IconNode } from '@sketchyicons/svelte';
|
|
3
|
+
import type { ButtonProps } from './Button.svelte';
|
|
4
|
+
export type IconRailItem = Pick<ButtonProps, 'href' | 'onclick' | 'disabled'> & {
|
|
5
|
+
label: string;
|
|
6
|
+
icon: IconNode;
|
|
7
|
+
active?: boolean;
|
|
8
|
+
primary?: boolean;
|
|
9
|
+
badge?: boolean | number;
|
|
10
|
+
};
|
|
11
|
+
</script>
|
|
12
|
+
|
|
13
|
+
<script lang="ts">
|
|
14
|
+
import { tick, type Snippet } from 'svelte';
|
|
15
|
+
import { MediaQuery } from 'svelte/reactivity';
|
|
16
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
17
|
+
import { Tooltip } from 'bits-ui';
|
|
18
|
+
import Button from './Button.svelte';
|
|
19
|
+
import Icon from './Icon.svelte';
|
|
20
|
+
import Modal from './Modal.svelte';
|
|
21
|
+
import { cn } from '../utils.js';
|
|
22
|
+
let {
|
|
23
|
+
label,
|
|
24
|
+
items,
|
|
25
|
+
backed = false,
|
|
26
|
+
mobile = 'bar',
|
|
27
|
+
open = $bindable(false),
|
|
28
|
+
footer,
|
|
29
|
+
class: className,
|
|
30
|
+
ref = $bindable(null),
|
|
31
|
+
...rest
|
|
32
|
+
}: Omit<HTMLAttributes<HTMLElement>, 'children'> & {
|
|
33
|
+
label: string;
|
|
34
|
+
items: readonly IconRailItem[];
|
|
35
|
+
backed?: boolean;
|
|
36
|
+
mobile?: 'bar' | 'drawer';
|
|
37
|
+
open?: boolean;
|
|
38
|
+
footer?: Snippet<[{ expanded: boolean; close: () => void }]>;
|
|
39
|
+
ref?: HTMLElement | null;
|
|
40
|
+
} = $props();
|
|
41
|
+
const small = new MediaQuery('(max-width: 600px)', false);
|
|
42
|
+
const close = () => (open = false);
|
|
43
|
+
let drawerWasOpen = false;
|
|
44
|
+
$effect(() => {
|
|
45
|
+
if (mobile !== 'drawer') return;
|
|
46
|
+
const leavingDrawer = !small.current && drawerWasOpen;
|
|
47
|
+
drawerWasOpen = small.current && open;
|
|
48
|
+
if (!small.current) open = false;
|
|
49
|
+
if (leavingDrawer) {
|
|
50
|
+
void tick().then(() => {
|
|
51
|
+
if (!small.current)
|
|
52
|
+
ref?.querySelector<HTMLElement>('a[href], button:not(:disabled)')?.focus();
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
</script>
|
|
57
|
+
|
|
58
|
+
{#if mobile === 'drawer' && small.current}
|
|
59
|
+
<Modal
|
|
60
|
+
bind:open
|
|
61
|
+
title={label}
|
|
62
|
+
onCloseAutoFocus={(event) => {
|
|
63
|
+
if (!small.current) event.preventDefault();
|
|
64
|
+
}}
|
|
65
|
+
data-slot="icon-rail-drawer"
|
|
66
|
+
class="top-0 bottom-0 left-0 flex h-dvh max-h-none w-[min(19rem,calc(100%-2rem))] max-w-none translate-x-0 translate-y-0 flex-col rounded-none border-y-0 border-l-0 px-3 pt-[max(1rem,env(safe-area-inset-top))] pb-[max(1rem,env(safe-area-inset-bottom))] [&>header]:mb-3 [&>header]:px-2 [&>header_h2]:sr-only"
|
|
67
|
+
>
|
|
68
|
+
<nav
|
|
69
|
+
{...rest}
|
|
70
|
+
bind:this={ref}
|
|
71
|
+
aria-label={label}
|
|
72
|
+
data-slot="icon-rail"
|
|
73
|
+
class={cn('flex min-h-0 flex-1 flex-col overflow-y-auto overscroll-contain', className)}
|
|
74
|
+
>
|
|
75
|
+
<div class="grid gap-1">
|
|
76
|
+
{#each items as item (item.label)}
|
|
77
|
+
<Button
|
|
78
|
+
href={item.href}
|
|
79
|
+
disabled={item.disabled}
|
|
80
|
+
variant="ghost"
|
|
81
|
+
aria-current={item.active ? 'page' : undefined}
|
|
82
|
+
data-part="item"
|
|
83
|
+
onclick={(event) => {
|
|
84
|
+
close();
|
|
85
|
+
item.onclick?.(event);
|
|
86
|
+
}}
|
|
87
|
+
class={cn(
|
|
88
|
+
'min-h-11 w-full justify-start gap-3 px-3 py-3 text-sm aria-[current=page]:bg-recess aria-[current=page]:text-action',
|
|
89
|
+
item.primary && 'mt-3 text-action'
|
|
90
|
+
)}
|
|
91
|
+
>
|
|
92
|
+
<Icon img={item.icon} size={22} />
|
|
93
|
+
<span class="min-w-0 flex-1 text-left">{item.label}</span>
|
|
94
|
+
{#if item.badge}
|
|
95
|
+
<span class="text-xs text-action">
|
|
96
|
+
{#if typeof item.badge === 'number'}{item.badge}<span class="sr-only"> unread</span>
|
|
97
|
+
{:else}<span class="sr-only">Unread</span><span aria-hidden="true">•</span>{/if}
|
|
98
|
+
</span>
|
|
99
|
+
{/if}
|
|
100
|
+
</Button>
|
|
101
|
+
{/each}
|
|
102
|
+
</div>
|
|
103
|
+
{#if footer}<div class="mt-auto pt-6">{@render footer({ expanded: true, close })}</div>{/if}
|
|
104
|
+
</nav>
|
|
105
|
+
</Modal>
|
|
106
|
+
{:else}
|
|
107
|
+
<Tooltip.Provider delayDuration={350} ignoreNonKeyboardFocus>
|
|
108
|
+
<nav
|
|
109
|
+
{...rest}
|
|
110
|
+
bind:this={ref}
|
|
111
|
+
aria-label={label}
|
|
112
|
+
data-slot="icon-rail"
|
|
113
|
+
data-backed={backed}
|
|
114
|
+
class={cn(
|
|
115
|
+
'fixed top-[var(--atmo-nav-top,0px)] bottom-0 left-0 z-40 flex w-18 flex-col items-center bg-transparent pt-6 pb-5 [@media(max-width:600px)]:top-auto [@media(max-width:600px)]:right-0 [@media(max-width:600px)]:h-[calc(62px+env(safe-area-inset-bottom))] [@media(max-width:600px)]:w-full [@media(max-width:600px)]:flex-row [@media(max-width:600px)]:justify-around [@media(max-width:600px)]:bg-page [@media(max-width:600px)]:px-2 [@media(max-width:600px)]:pt-1.5 [@media(max-width:600px)]:pb-[calc(6px+env(safe-area-inset-bottom))]',
|
|
116
|
+
mobile === 'drawer' && '[@media(max-width:600px)]:hidden',
|
|
117
|
+
className
|
|
118
|
+
)}
|
|
119
|
+
>
|
|
120
|
+
<div
|
|
121
|
+
class="flex flex-col items-center gap-3 [@media(max-width:600px)]:min-w-0 [@media(max-width:600px)]:flex-1 [@media(max-width:600px)]:flex-row [@media(max-width:600px)]:justify-around [@media(max-width:600px)]:gap-0"
|
|
122
|
+
>
|
|
123
|
+
{#each items as item (item.label)}
|
|
124
|
+
<Tooltip.Root disabled={item.disabled ?? false}
|
|
125
|
+
><Tooltip.Trigger onclick={item.onclick}
|
|
126
|
+
>{#snippet child({ props })}
|
|
127
|
+
<Button
|
|
128
|
+
{...props}
|
|
129
|
+
href={item.href}
|
|
130
|
+
disabled={item.disabled}
|
|
131
|
+
variant="ghost"
|
|
132
|
+
size="icon"
|
|
133
|
+
aria-label={`${item.label}${item.badge ? (typeof item.badge === 'number' ? `, ${item.badge} unread` : ', unread') : ''}`}
|
|
134
|
+
aria-current={item.active ? 'page' : undefined}
|
|
135
|
+
data-part="item"
|
|
136
|
+
class={cn(
|
|
137
|
+
'relative size-11 rounded-(--atmo-radius-button) text-muted hover:bg-recess/55 aria-[current=page]:text-action [@media(max-width:600px)]:size-10',
|
|
138
|
+
item.primary &&
|
|
139
|
+
'mt-2 text-action hover:text-action [@media(max-width:600px)]:mt-0',
|
|
140
|
+
backed && 'bg-recess/45 hover:bg-recess/75'
|
|
141
|
+
)}
|
|
142
|
+
>
|
|
143
|
+
<Icon img={item.icon} size={22} />{#if item.badge}<span
|
|
144
|
+
class="absolute top-2 right-1.5 size-1.5 rounded-full bg-action"
|
|
145
|
+
aria-hidden="true"
|
|
146
|
+
></span>{/if}
|
|
147
|
+
</Button>
|
|
148
|
+
{/snippet}</Tooltip.Trigger
|
|
149
|
+
><Tooltip.Content
|
|
150
|
+
role="tooltip"
|
|
151
|
+
side="right"
|
|
152
|
+
sideOffset={10}
|
|
153
|
+
collisionPadding={12}
|
|
154
|
+
class="z-60 max-w-[calc(100vw-2rem)] rounded-lg border border-line bg-field px-2.5 py-1.5 font-sans text-xs text-ink"
|
|
155
|
+
>{item.label}</Tooltip.Content
|
|
156
|
+
></Tooltip.Root
|
|
157
|
+
>
|
|
158
|
+
{/each}
|
|
159
|
+
</div>
|
|
160
|
+
{#if footer}<div
|
|
161
|
+
class="mt-auto [@media(max-width:600px)]:mt-0 [@media(max-width:600px)]:ml-1"
|
|
162
|
+
>
|
|
163
|
+
{@render footer({ expanded: false, close })}
|
|
164
|
+
</div>{/if}
|
|
165
|
+
</nav>
|
|
166
|
+
</Tooltip.Provider>
|
|
167
|
+
{/if}
|
|
168
|
+
|
|
169
|
+
<style>
|
|
170
|
+
@keyframes drawer-enter {
|
|
171
|
+
from {
|
|
172
|
+
transform: translateX(-100%);
|
|
173
|
+
}
|
|
174
|
+
to {
|
|
175
|
+
transform: translateX(0);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
:global([data-slot='icon-rail-drawer'][data-state='open']) {
|
|
179
|
+
animation: drawer-enter 130ms cubic-bezier(0.2, 0.8, 0.2, 1);
|
|
180
|
+
}
|
|
181
|
+
@media (prefers-reduced-motion: reduce) {
|
|
182
|
+
:global([data-slot='icon-rail-drawer']) {
|
|
183
|
+
animation: none;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
</style>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { IconNode } from '@sketchyicons/svelte';
|
|
2
|
+
import type { ButtonProps } from './Button.svelte';
|
|
3
|
+
export type IconRailItem = Pick<ButtonProps, 'href' | 'onclick' | 'disabled'> & {
|
|
4
|
+
label: string;
|
|
5
|
+
icon: IconNode;
|
|
6
|
+
active?: boolean;
|
|
7
|
+
primary?: boolean;
|
|
8
|
+
badge?: boolean | number;
|
|
9
|
+
};
|
|
10
|
+
import { type Snippet } from 'svelte';
|
|
11
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
12
|
+
type $$ComponentProps = Omit<HTMLAttributes<HTMLElement>, 'children'> & {
|
|
13
|
+
label: string;
|
|
14
|
+
items: readonly IconRailItem[];
|
|
15
|
+
backed?: boolean;
|
|
16
|
+
mobile?: 'bar' | 'drawer';
|
|
17
|
+
open?: boolean;
|
|
18
|
+
footer?: Snippet<[{
|
|
19
|
+
expanded: boolean;
|
|
20
|
+
close: () => void;
|
|
21
|
+
}]>;
|
|
22
|
+
ref?: HTMLElement | null;
|
|
23
|
+
};
|
|
24
|
+
declare const IconRail: import("svelte").Component<$$ComponentProps, {}, "ref" | "open">;
|
|
25
|
+
type IconRail = ReturnType<typeof IconRail>;
|
|
26
|
+
export default IconRail;
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { onMount, untrack } from 'svelte';
|
|
3
|
+
import { Editor, type JSONContent } from '@tiptap/core';
|
|
4
|
+
import Document from '@tiptap/extension-document';
|
|
5
|
+
import Paragraph from '@tiptap/extension-paragraph';
|
|
6
|
+
import Text from '@tiptap/extension-text';
|
|
7
|
+
import HardBreak from '@tiptap/extension-hard-break';
|
|
8
|
+
import History from '@tiptap/extension-history';
|
|
9
|
+
import Placeholder from '@tiptap/extension-placeholder';
|
|
10
|
+
import Mention from '@tiptap/extension-mention';
|
|
11
|
+
import type { ActorSearch } from './login/types.js';
|
|
12
|
+
import { mentionSuggestions } from '../social/mention-suggestions.js';
|
|
13
|
+
import { editorJsonToBlueskyPost, textToDoc } from '../social/richtext.js';
|
|
14
|
+
import { cn } from '../utils.js';
|
|
15
|
+
let {
|
|
16
|
+
value = $bindable(''),
|
|
17
|
+
editor = $bindable<Editor | null>(null),
|
|
18
|
+
label,
|
|
19
|
+
placeholder = '',
|
|
20
|
+
singleLine = false,
|
|
21
|
+
disabled = false,
|
|
22
|
+
searchActors,
|
|
23
|
+
onupdate,
|
|
24
|
+
class: className
|
|
25
|
+
}: {
|
|
26
|
+
value?: string;
|
|
27
|
+
editor?: Editor | null;
|
|
28
|
+
label: string;
|
|
29
|
+
placeholder?: string;
|
|
30
|
+
singleLine?: boolean;
|
|
31
|
+
disabled?: boolean;
|
|
32
|
+
searchActors?: ActorSearch;
|
|
33
|
+
onupdate?: (content: { text: string; json: JSONContent }) => void;
|
|
34
|
+
class?: string;
|
|
35
|
+
} = $props();
|
|
36
|
+
const id = $props.id();
|
|
37
|
+
let host: HTMLDivElement;
|
|
38
|
+
let element: HTMLDivElement;
|
|
39
|
+
onMount(() => {
|
|
40
|
+
const instance = new Editor({
|
|
41
|
+
element,
|
|
42
|
+
extensions: [
|
|
43
|
+
Document,
|
|
44
|
+
Paragraph,
|
|
45
|
+
Text,
|
|
46
|
+
HardBreak,
|
|
47
|
+
History,
|
|
48
|
+
Placeholder.configure({ placeholder }),
|
|
49
|
+
...(searchActors
|
|
50
|
+
? [
|
|
51
|
+
Mention.configure({
|
|
52
|
+
HTMLAttributes: { class: 'text-action' },
|
|
53
|
+
suggestion: mentionSuggestions(searchActors, () => host)
|
|
54
|
+
})
|
|
55
|
+
]
|
|
56
|
+
: [])
|
|
57
|
+
],
|
|
58
|
+
content: textToDoc(untrack(() => value)),
|
|
59
|
+
editable: !disabled,
|
|
60
|
+
editorProps: {
|
|
61
|
+
attributes: {
|
|
62
|
+
id,
|
|
63
|
+
role: searchActors ? 'combobox' : 'textbox',
|
|
64
|
+
'aria-label': label,
|
|
65
|
+
...(searchActors
|
|
66
|
+
? { 'aria-autocomplete': 'list', 'aria-expanded': 'false', 'aria-haspopup': 'listbox' }
|
|
67
|
+
: { 'aria-multiline': String(!singleLine) }),
|
|
68
|
+
class:
|
|
69
|
+
'min-h-[1.5em] outline-none whitespace-pre-wrap break-words [&_p.is-editor-empty:first-child]:before:content-[attr(data-placeholder)] [&_p.is-editor-empty:first-child]:before:text-muted [&_p.is-editor-empty:first-child]:before:float-left [&_p.is-editor-empty:first-child]:before:pointer-events-none [&_p.is-editor-empty:first-child]:before:h-0'
|
|
70
|
+
},
|
|
71
|
+
handleKeyDown: (view, event) =>
|
|
72
|
+
singleLine &&
|
|
73
|
+
event.key === 'Enter' &&
|
|
74
|
+
!event.isComposing &&
|
|
75
|
+
!view.dom.getAttribute('aria-activedescendant'),
|
|
76
|
+
transformPastedText: (text) => (singleLine ? text.replace(/[\r\n]+/g, ' ') : text),
|
|
77
|
+
// Accept plain text only, preserving the editor's deliberately small schema.
|
|
78
|
+
handlePaste: (_view, event) => {
|
|
79
|
+
const text = event.clipboardData?.getData('text/plain');
|
|
80
|
+
if (text === undefined) return false;
|
|
81
|
+
event.preventDefault();
|
|
82
|
+
if (singleLine) {
|
|
83
|
+
const cleaned = text.replace(/[\r\n]+/g, ' ');
|
|
84
|
+
if (cleaned) instance.commands.insertContent({ type: 'text', text: cleaned });
|
|
85
|
+
} else instance.commands.insertContent(textToDoc(text).content ?? []);
|
|
86
|
+
return true;
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
onUpdate: ({ editor: current }) => {
|
|
90
|
+
const json = current.getJSON();
|
|
91
|
+
value = editorJsonToBlueskyPost(json).text;
|
|
92
|
+
onupdate?.({ text: value, json });
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
editor = instance;
|
|
96
|
+
return () => {
|
|
97
|
+
instance.destroy();
|
|
98
|
+
editor = null;
|
|
99
|
+
};
|
|
100
|
+
});
|
|
101
|
+
$effect(() => {
|
|
102
|
+
if (editor && editorJsonToBlueskyPost(editor.getJSON()).text !== value)
|
|
103
|
+
editor.commands.setContent(textToDoc(value), { emitUpdate: false });
|
|
104
|
+
});
|
|
105
|
+
$effect(() => {
|
|
106
|
+
editor?.setEditable(!disabled, false);
|
|
107
|
+
editor?.view.dom.setAttribute('aria-disabled', String(disabled));
|
|
108
|
+
});
|
|
109
|
+
</script>
|
|
110
|
+
|
|
111
|
+
<div
|
|
112
|
+
bind:this={host}
|
|
113
|
+
class={cn(
|
|
114
|
+
'relative min-w-0 rounded-sm focus-within:outline-2 focus-within:outline-offset-4 focus-within:outline-focus',
|
|
115
|
+
className
|
|
116
|
+
)}
|
|
117
|
+
data-slot="inline-editor"
|
|
118
|
+
>
|
|
119
|
+
<div bind:this={element}></div>
|
|
120
|
+
</div>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Editor, type JSONContent } from '@tiptap/core';
|
|
2
|
+
import type { ActorSearch } from './login/types.js';
|
|
3
|
+
type $$ComponentProps = {
|
|
4
|
+
value?: string;
|
|
5
|
+
editor?: Editor | null;
|
|
6
|
+
label: string;
|
|
7
|
+
placeholder?: string;
|
|
8
|
+
singleLine?: boolean;
|
|
9
|
+
disabled?: boolean;
|
|
10
|
+
searchActors?: ActorSearch;
|
|
11
|
+
onupdate?: (content: {
|
|
12
|
+
text: string;
|
|
13
|
+
json: JSONContent;
|
|
14
|
+
}) => void;
|
|
15
|
+
class?: string;
|
|
16
|
+
};
|
|
17
|
+
declare const InlineEditor: import("svelte").Component<$$ComponentProps, {}, "value" | "editor">;
|
|
18
|
+
type InlineEditor = ReturnType<typeof InlineEditor>;
|
|
19
|
+
export default InlineEditor;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
<script lang="ts" module>
|
|
2
|
+
import type { HTMLInputAttributes, HTMLInputTypeAttribute } from 'svelte/elements';
|
|
3
|
+
export type InputProps = Omit<HTMLInputAttributes, 'type' | 'value'> & {
|
|
4
|
+
type?: Exclude<
|
|
5
|
+
HTMLInputTypeAttribute,
|
|
6
|
+
'checkbox' | 'radio' | 'file' | 'button' | 'submit' | 'reset' | 'image' | 'range'
|
|
7
|
+
>;
|
|
8
|
+
value?: string | number;
|
|
9
|
+
ref?: HTMLInputElement | null;
|
|
10
|
+
};
|
|
11
|
+
</script>
|
|
12
|
+
|
|
13
|
+
<script lang="ts">
|
|
14
|
+
import { cn } from '../utils.js';
|
|
15
|
+
import { control } from '../styles.js';
|
|
16
|
+
let {
|
|
17
|
+
class: className,
|
|
18
|
+
value = $bindable<string | number | undefined>(''),
|
|
19
|
+
type = 'text',
|
|
20
|
+
ref = $bindable(null),
|
|
21
|
+
...rest
|
|
22
|
+
}: InputProps = $props();
|
|
23
|
+
</script>
|
|
24
|
+
|
|
25
|
+
<input
|
|
26
|
+
{...rest}
|
|
27
|
+
{type}
|
|
28
|
+
bind:value
|
|
29
|
+
bind:this={ref}
|
|
30
|
+
data-slot="input"
|
|
31
|
+
class={cn(control, className)}
|
|
32
|
+
/>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { HTMLInputAttributes, HTMLInputTypeAttribute } from 'svelte/elements';
|
|
2
|
+
export type InputProps = Omit<HTMLInputAttributes, 'type' | 'value'> & {
|
|
3
|
+
type?: Exclude<HTMLInputTypeAttribute, 'checkbox' | 'radio' | 'file' | 'button' | 'submit' | 'reset' | 'image' | 'range'>;
|
|
4
|
+
value?: string | number;
|
|
5
|
+
ref?: HTMLInputElement | null;
|
|
6
|
+
};
|
|
7
|
+
declare const Input: import("svelte").Component<InputProps, {}, "value" | "ref">;
|
|
8
|
+
type Input = ReturnType<typeof Input>;
|
|
9
|
+
export default Input;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { HTMLImgAttributes } from 'svelte/elements';
|
|
3
|
+
import { cn } from '../utils.js';
|
|
4
|
+
let {
|
|
5
|
+
src,
|
|
6
|
+
alt,
|
|
7
|
+
ratio = 1,
|
|
8
|
+
class: className,
|
|
9
|
+
style,
|
|
10
|
+
loading = 'lazy',
|
|
11
|
+
...rest
|
|
12
|
+
}: Omit<HTMLImgAttributes, 'src' | 'alt'> & {
|
|
13
|
+
src: string;
|
|
14
|
+
alt: string;
|
|
15
|
+
ratio?: number | string;
|
|
16
|
+
} = $props();
|
|
17
|
+
</script>
|
|
18
|
+
|
|
19
|
+
<figure
|
|
20
|
+
class={cn(
|
|
21
|
+
'relative m-0 aspect-(--ak-image-ratio) overflow-hidden rounded-ui border border-line after:pointer-events-none after:absolute after:inset-0 after:rounded-[inherit] after:shadow-[inset_0_2px_7px_#0005]',
|
|
22
|
+
className
|
|
23
|
+
)}
|
|
24
|
+
{style}
|
|
25
|
+
style:--ak-image-ratio={ratio}
|
|
26
|
+
data-slot="inset-image"
|
|
27
|
+
>
|
|
28
|
+
<img {...rest} {src} {alt} {loading} class="block size-full object-cover" />
|
|
29
|
+
</figure>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { HTMLImgAttributes } from 'svelte/elements';
|
|
2
|
+
type $$ComponentProps = Omit<HTMLImgAttributes, 'src' | 'alt'> & {
|
|
3
|
+
src: string;
|
|
4
|
+
alt: string;
|
|
5
|
+
ratio?: number | string;
|
|
6
|
+
};
|
|
7
|
+
declare const InsetImage: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
8
|
+
type InsetImage = ReturnType<typeof InsetImage>;
|
|
9
|
+
export default InsetImage;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { HTMLLabelAttributes } from 'svelte/elements';
|
|
3
|
+
import { cn } from '../utils.js';
|
|
4
|
+
let {
|
|
5
|
+
class: className,
|
|
6
|
+
ref = $bindable(null),
|
|
7
|
+
children,
|
|
8
|
+
...rest
|
|
9
|
+
}: HTMLLabelAttributes & { ref?: HTMLLabelElement | null } = $props();
|
|
10
|
+
</script>
|
|
11
|
+
|
|
12
|
+
<label
|
|
13
|
+
{...rest}
|
|
14
|
+
bind:this={ref}
|
|
15
|
+
class={cn(
|
|
16
|
+
'inline-flex items-center gap-2.5 text-[.8125rem] leading-normal font-medium text-ink',
|
|
17
|
+
className
|
|
18
|
+
)}>{@render children?.()}</label
|
|
19
|
+
>
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { HTMLLabelAttributes } from 'svelte/elements';
|
|
2
|
+
type $$ComponentProps = HTMLLabelAttributes & {
|
|
3
|
+
ref?: HTMLLabelElement | null;
|
|
4
|
+
};
|
|
5
|
+
declare const Label: import("svelte").Component<$$ComponentProps, {}, "ref">;
|
|
6
|
+
type Label = ReturnType<typeof Label>;
|
|
7
|
+
export default Label;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { HTMLAnchorAttributes } from 'svelte/elements';
|
|
3
|
+
import { cn } from '../utils.js';
|
|
4
|
+
import { focus } from '../styles.js';
|
|
5
|
+
import { safeWebUrl } from '../social/richtext.js';
|
|
6
|
+
let {
|
|
7
|
+
href,
|
|
8
|
+
meta,
|
|
9
|
+
class: className,
|
|
10
|
+
...rest
|
|
11
|
+
}: Omit<HTMLAnchorAttributes, 'href' | 'children'> & {
|
|
12
|
+
href: string;
|
|
13
|
+
meta: { title?: string; description?: string; image?: string };
|
|
14
|
+
} = $props();
|
|
15
|
+
</script>
|
|
16
|
+
|
|
17
|
+
<a
|
|
18
|
+
{...rest}
|
|
19
|
+
href={safeWebUrl(href)}
|
|
20
|
+
target="_blank"
|
|
21
|
+
rel="noopener noreferrer"
|
|
22
|
+
class={cn(
|
|
23
|
+
'block overflow-hidden rounded-ui border border-line bg-surface text-ink no-underline hover:border-control-line',
|
|
24
|
+
focus,
|
|
25
|
+
className
|
|
26
|
+
)}
|
|
27
|
+
>
|
|
28
|
+
{#if meta.image && safeWebUrl(meta.image)}<div class="relative">
|
|
29
|
+
<img
|
|
30
|
+
src={meta.image}
|
|
31
|
+
alt=""
|
|
32
|
+
class="aspect-[1.91/1] w-full object-cover"
|
|
33
|
+
loading="lazy"
|
|
34
|
+
/><span
|
|
35
|
+
class="pointer-events-none absolute inset-0 [box-shadow:inset_0_0_0_1px_var(--ui-shadow),inset_0_2px_5px_var(--ui-shadow)]"
|
|
36
|
+
></span>
|
|
37
|
+
</div>{/if}
|
|
38
|
+
<div class="p-3">
|
|
39
|
+
<strong class="block text-sm font-medium">{meta.title || href}</strong>
|
|
40
|
+
{#if meta.description}<p class="mt-1 line-clamp-2 text-xs leading-relaxed text-muted">
|
|
41
|
+
{meta.description}
|
|
42
|
+
</p>{/if}
|
|
43
|
+
<span class="mt-1 block truncate text-[.6875rem] text-muted"
|
|
44
|
+
>{href.replace(/^https?:\/\//, '')}</span
|
|
45
|
+
>
|
|
46
|
+
</div>
|
|
47
|
+
</a>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { HTMLAnchorAttributes } from 'svelte/elements';
|
|
2
|
+
type $$ComponentProps = Omit<HTMLAnchorAttributes, 'href' | 'children'> & {
|
|
3
|
+
href: string;
|
|
4
|
+
meta: {
|
|
5
|
+
title?: string;
|
|
6
|
+
description?: string;
|
|
7
|
+
image?: string;
|
|
8
|
+
};
|
|
9
|
+
};
|
|
10
|
+
declare const LinkCard: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
11
|
+
type LinkCard = ReturnType<typeof LinkCard>;
|
|
12
|
+
export default LinkCard;
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
import { Dialog as Primitive } from 'bits-ui';
|
|
4
|
+
import Icon from './Icon.svelte';
|
|
5
|
+
import { X } from '@sketchyicons/svelte';
|
|
6
|
+
import { cn } from '../utils.js';
|
|
7
|
+
import { focus } from '../styles.js';
|
|
8
|
+
import { readTheme, observeTheme } from '../theme.js';
|
|
9
|
+
let {
|
|
10
|
+
open = $bindable(false),
|
|
11
|
+
title,
|
|
12
|
+
description,
|
|
13
|
+
dismissible = true,
|
|
14
|
+
class: className,
|
|
15
|
+
style,
|
|
16
|
+
children,
|
|
17
|
+
onOpenAutoFocus,
|
|
18
|
+
...rest
|
|
19
|
+
}: Omit<Primitive.ContentProps, 'children' | 'child' | 'title'> & {
|
|
20
|
+
open?: boolean;
|
|
21
|
+
title: string;
|
|
22
|
+
description?: string;
|
|
23
|
+
dismissible?: boolean;
|
|
24
|
+
children?: Snippet;
|
|
25
|
+
} = $props();
|
|
26
|
+
let origin = $state<HTMLSpanElement>();
|
|
27
|
+
let content = $state<HTMLElement | null>(null);
|
|
28
|
+
let inherited = $state('');
|
|
29
|
+
$effect(() => {
|
|
30
|
+
if (!open || !origin) {
|
|
31
|
+
inherited = '';
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
const element = origin;
|
|
35
|
+
const sync = () => (inherited = readTheme(element));
|
|
36
|
+
sync();
|
|
37
|
+
return observeTheme(element, sync);
|
|
38
|
+
});
|
|
39
|
+
</script>
|
|
40
|
+
|
|
41
|
+
<span hidden bind:this={origin} class={cn('atmo-theme', className)} {style}></span>
|
|
42
|
+
<Primitive.Root bind:open>
|
|
43
|
+
{#if inherited}<Primitive.Portal>
|
|
44
|
+
<Primitive.Overlay class="fixed inset-0 z-50 bg-black/55" />
|
|
45
|
+
<Primitive.Content
|
|
46
|
+
{...rest}
|
|
47
|
+
bind:ref={content}
|
|
48
|
+
interactOutsideBehavior={dismissible ? 'close' : 'ignore'}
|
|
49
|
+
escapeKeydownBehavior={dismissible ? 'close' : 'ignore'}
|
|
50
|
+
style={[inherited, style].filter(Boolean).join(';')}
|
|
51
|
+
class={cn(
|
|
52
|
+
'fixed top-1/2 left-1/2 z-50 max-h-[calc(100dvh-3rem)] w-[calc(100%-2rem)] max-w-lg -translate-x-1/2 -translate-y-1/2 overflow-y-auto overscroll-contain rounded-panel border border-line bg-page p-6 font-sans text-ink outline-none sm:p-7',
|
|
53
|
+
className
|
|
54
|
+
)}
|
|
55
|
+
onOpenAutoFocus={(event) => {
|
|
56
|
+
onOpenAutoFocus?.(event);
|
|
57
|
+
if (!event.defaultPrevented) {
|
|
58
|
+
const target = content?.querySelector<HTMLElement>('[data-autofocus]');
|
|
59
|
+
if (target) {
|
|
60
|
+
event.preventDefault();
|
|
61
|
+
target.focus();
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}}
|
|
65
|
+
>
|
|
66
|
+
<header class="mb-6 flex items-start justify-between gap-4">
|
|
67
|
+
<div class="min-w-0 flex-1">
|
|
68
|
+
<Primitive.Title>
|
|
69
|
+
{#snippet child({ props })}<h2
|
|
70
|
+
{...props}
|
|
71
|
+
tabindex="-1"
|
|
72
|
+
class="text-[1.375rem] leading-snug font-medium tracking-tight break-words outline-none"
|
|
73
|
+
>
|
|
74
|
+
{title}
|
|
75
|
+
</h2>{/snippet}
|
|
76
|
+
</Primitive.Title>{#if description}<Primitive.Description
|
|
77
|
+
class="mt-2.5 text-[.8125rem] leading-relaxed text-muted"
|
|
78
|
+
>{description}</Primitive.Description
|
|
79
|
+
>{/if}
|
|
80
|
+
</div>
|
|
81
|
+
<Primitive.Close
|
|
82
|
+
disabled={!dismissible}
|
|
83
|
+
aria-label="Close dialog"
|
|
84
|
+
class={cn(
|
|
85
|
+
'-mt-1 -mr-2 inline-grid size-9 shrink-0 cursor-pointer place-items-center rounded-ui text-muted hover:bg-recess hover:text-ink disabled:pointer-events-none disabled:opacity-40',
|
|
86
|
+
focus
|
|
87
|
+
)}><Icon img={X} size={19} /></Primitive.Close
|
|
88
|
+
>
|
|
89
|
+
</header>
|
|
90
|
+
{@render children?.()}
|
|
91
|
+
</Primitive.Content>
|
|
92
|
+
</Primitive.Portal>{/if}
|
|
93
|
+
</Primitive.Root>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import { Dialog as Primitive } from 'bits-ui';
|
|
3
|
+
type $$ComponentProps = Omit<Primitive.ContentProps, 'children' | 'child' | 'title'> & {
|
|
4
|
+
open?: boolean;
|
|
5
|
+
title: string;
|
|
6
|
+
description?: string;
|
|
7
|
+
dismissible?: boolean;
|
|
8
|
+
children?: Snippet;
|
|
9
|
+
};
|
|
10
|
+
declare const Modal: import("svelte").Component<$$ComponentProps, {}, "open">;
|
|
11
|
+
type Modal = ReturnType<typeof Modal>;
|
|
12
|
+
export default Modal;
|