@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,82 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { tick, type Snippet } from 'svelte';
|
|
3
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
4
|
+
import { MediaQuery } from 'svelte/reactivity';
|
|
5
|
+
import Surface from './Surface.svelte';
|
|
6
|
+
import Modal from './Modal.svelte';
|
|
7
|
+
import { cn } from '../utils.js';
|
|
8
|
+
let {
|
|
9
|
+
label,
|
|
10
|
+
open = $bindable(false),
|
|
11
|
+
children,
|
|
12
|
+
class: className,
|
|
13
|
+
style,
|
|
14
|
+
...rest
|
|
15
|
+
}: Omit<HTMLAttributes<HTMLElement>, 'children'> & {
|
|
16
|
+
label: string;
|
|
17
|
+
open?: boolean;
|
|
18
|
+
children: Snippet;
|
|
19
|
+
} = $props();
|
|
20
|
+
// Native null means “remove the attribute”; Bits UI accepts undefined instead.
|
|
21
|
+
const modalAttributes = $derived(
|
|
22
|
+
Object.fromEntries(Object.entries(rest).filter(([, value]) => value !== null))
|
|
23
|
+
);
|
|
24
|
+
let desktop = $state<HTMLElement | null>(null);
|
|
25
|
+
const mobile = new MediaQuery('(max-width: 900px)', false);
|
|
26
|
+
let drawerWasOpen = false;
|
|
27
|
+
$effect(() => {
|
|
28
|
+
const leavingDrawer = !mobile.current && drawerWasOpen;
|
|
29
|
+
drawerWasOpen = mobile.current && open;
|
|
30
|
+
if (!mobile.current) open = false;
|
|
31
|
+
if (leavingDrawer) {
|
|
32
|
+
void tick().then(() => {
|
|
33
|
+
if (!mobile.current)
|
|
34
|
+
desktop
|
|
35
|
+
?.querySelector<HTMLElement>(
|
|
36
|
+
'a[href], button:not(:disabled), input:not(:disabled), [tabindex="0"]'
|
|
37
|
+
)
|
|
38
|
+
?.focus();
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
</script>
|
|
43
|
+
|
|
44
|
+
{#if !mobile.current}
|
|
45
|
+
<Surface
|
|
46
|
+
{...rest}
|
|
47
|
+
as="aside"
|
|
48
|
+
bind:ref={desktop}
|
|
49
|
+
{style}
|
|
50
|
+
aria-label={label}
|
|
51
|
+
data-slot="workspace-sidebar"
|
|
52
|
+
class={cn(
|
|
53
|
+
'fixed top-[calc(var(--atmo-nav-top,0px)+12px)] bottom-3 left-3 z-40 w-[var(--atmo-sidebar-width,238px)] overflow-hidden rounded-[21px_18px_23px_19px] p-0 [@media(max-width:900px)]:hidden',
|
|
54
|
+
className
|
|
55
|
+
)}
|
|
56
|
+
>
|
|
57
|
+
<nav aria-label={label} class="h-full overflow-y-auto overscroll-contain px-3 py-4">
|
|
58
|
+
{@render children()}
|
|
59
|
+
</nav>
|
|
60
|
+
</Surface>
|
|
61
|
+
{:else}
|
|
62
|
+
<Modal
|
|
63
|
+
{...modalAttributes}
|
|
64
|
+
bind:open
|
|
65
|
+
title={label}
|
|
66
|
+
onCloseAutoFocus={(event) => {
|
|
67
|
+
if (!mobile.current) {
|
|
68
|
+
event.preventDefault();
|
|
69
|
+
}
|
|
70
|
+
}}
|
|
71
|
+
style={style ?? undefined}
|
|
72
|
+
data-slot="workspace-sidebar"
|
|
73
|
+
class={cn(
|
|
74
|
+
'top-[calc(var(--atmo-nav-top,0px)+12px)] bottom-3 left-3 flex h-auto max-h-none w-[min(280px,calc(100%-24px))] max-w-none translate-x-0 translate-y-0 flex-col overflow-hidden rounded-[21px_18px_23px_19px] bg-recess p-3 shadow-(--ui-recess-shadow) sm:p-3 [&>header]:mb-3 [&>header]:px-2 [&>header]:pt-2 [&>header_h2]:text-sm',
|
|
75
|
+
className
|
|
76
|
+
)}
|
|
77
|
+
>
|
|
78
|
+
<nav aria-label={label} class="min-h-0 flex-1 overflow-y-auto overscroll-contain">
|
|
79
|
+
{@render children()}
|
|
80
|
+
</nav>
|
|
81
|
+
</Modal>
|
|
82
|
+
{/if}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type Snippet } from 'svelte';
|
|
2
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
3
|
+
type $$ComponentProps = Omit<HTMLAttributes<HTMLElement>, 'children'> & {
|
|
4
|
+
label: string;
|
|
5
|
+
open?: boolean;
|
|
6
|
+
children: Snippet;
|
|
7
|
+
};
|
|
8
|
+
declare const WorkspaceSidebar: import("svelte").Component<$$ComponentProps, {}, "open">;
|
|
9
|
+
type WorkspaceSidebar = ReturnType<typeof WorkspaceSidebar>;
|
|
10
|
+
export default WorkspaceSidebar;
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { untrack } from 'svelte';
|
|
3
|
+
import { Combobox } from 'bits-ui';
|
|
4
|
+
import { AtSign, LoaderCircle } from '@sketchyicons/svelte';
|
|
5
|
+
import type { ActorSearch, ActorSummary } from './types.js';
|
|
6
|
+
import { actorName } from './types.js';
|
|
7
|
+
import { cleanActors } from './search.js';
|
|
8
|
+
import Input from '../Input.svelte';
|
|
9
|
+
import Avatar from '../Avatar.svelte';
|
|
10
|
+
import Icon from '../Icon.svelte';
|
|
11
|
+
import { control } from '../../styles.js';
|
|
12
|
+
import { cn } from '../../utils.js';
|
|
13
|
+
import { readTheme, observeTheme } from '../../theme.js';
|
|
14
|
+
let {
|
|
15
|
+
value = $bindable(''),
|
|
16
|
+
searchActors,
|
|
17
|
+
onSelect,
|
|
18
|
+
label = 'Your handle',
|
|
19
|
+
placeholder = 'yourname.bsky.social',
|
|
20
|
+
id,
|
|
21
|
+
name = 'handle',
|
|
22
|
+
disabled = false,
|
|
23
|
+
invalid = false,
|
|
24
|
+
describedby,
|
|
25
|
+
debounce = 250,
|
|
26
|
+
class: className,
|
|
27
|
+
ref = $bindable(null)
|
|
28
|
+
}: {
|
|
29
|
+
value?: string;
|
|
30
|
+
searchActors?: ActorSearch;
|
|
31
|
+
onSelect?: (actor: ActorSummary) => void;
|
|
32
|
+
label?: string;
|
|
33
|
+
placeholder?: string;
|
|
34
|
+
id?: string;
|
|
35
|
+
name?: string;
|
|
36
|
+
disabled?: boolean;
|
|
37
|
+
invalid?: boolean;
|
|
38
|
+
describedby?: string;
|
|
39
|
+
debounce?: number;
|
|
40
|
+
class?: string;
|
|
41
|
+
ref?: HTMLInputElement | null;
|
|
42
|
+
} = $props();
|
|
43
|
+
const uid = $props.id();
|
|
44
|
+
const listId = `${uid}-suggestions`;
|
|
45
|
+
let open = $state(false),
|
|
46
|
+
focused = $state(false),
|
|
47
|
+
selection = $state('');
|
|
48
|
+
let results = $state<ActorSummary[]>([]);
|
|
49
|
+
let status = $state<'idle' | 'loading' | 'ready' | 'error'>('idle');
|
|
50
|
+
let inherited = $state('');
|
|
51
|
+
let dismissedQuery = '';
|
|
52
|
+
const message = $derived(
|
|
53
|
+
status === 'error'
|
|
54
|
+
? 'Search unavailable. Enter your full handle to continue.'
|
|
55
|
+
: status === 'ready' && !results.length
|
|
56
|
+
? 'No suggestions. You can still enter a full handle.'
|
|
57
|
+
: ''
|
|
58
|
+
);
|
|
59
|
+
const announcement = $derived(
|
|
60
|
+
status === 'loading'
|
|
61
|
+
? 'Searching accounts.'
|
|
62
|
+
: message || (status === 'ready' ? `${results.length} accounts found.` : '')
|
|
63
|
+
);
|
|
64
|
+
const descriptionIds = $derived(
|
|
65
|
+
[describedby, message ? `${uid}-status` : undefined].filter(Boolean).join(' ') || undefined
|
|
66
|
+
);
|
|
67
|
+
$effect(() => {
|
|
68
|
+
const query = value.trim().replace(/^@+/, '');
|
|
69
|
+
const provider = searchActors;
|
|
70
|
+
if (disabled || !provider || query.length < 2) {
|
|
71
|
+
results = [];
|
|
72
|
+
status = 'idle';
|
|
73
|
+
open = false;
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
if (!focused) {
|
|
77
|
+
open = false;
|
|
78
|
+
untrack(() => {
|
|
79
|
+
if (status === 'loading') status = 'idle';
|
|
80
|
+
});
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
const controller = new AbortController();
|
|
84
|
+
results = [];
|
|
85
|
+
status = 'loading';
|
|
86
|
+
open = false;
|
|
87
|
+
const timer = setTimeout(
|
|
88
|
+
async () => {
|
|
89
|
+
try {
|
|
90
|
+
const found = await provider(query, controller.signal);
|
|
91
|
+
if (controller.signal.aborted) return;
|
|
92
|
+
results = cleanActors(found);
|
|
93
|
+
status = 'ready';
|
|
94
|
+
if (dismissedQuery !== query) open = results.length > 0;
|
|
95
|
+
} catch {
|
|
96
|
+
if (controller.signal.aborted) return;
|
|
97
|
+
results = [];
|
|
98
|
+
status = 'error';
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
Math.max(0, debounce)
|
|
102
|
+
);
|
|
103
|
+
return () => {
|
|
104
|
+
clearTimeout(timer);
|
|
105
|
+
controller.abort();
|
|
106
|
+
};
|
|
107
|
+
});
|
|
108
|
+
$effect(() => {
|
|
109
|
+
if (open && !results.length) open = false;
|
|
110
|
+
});
|
|
111
|
+
$effect(() => {
|
|
112
|
+
if (!open || !ref) {
|
|
113
|
+
inherited = '';
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
const element = ref;
|
|
117
|
+
const sync = () => (inherited = readTheme(element));
|
|
118
|
+
sync();
|
|
119
|
+
return observeTheme(element, sync);
|
|
120
|
+
});
|
|
121
|
+
function choose(handle: string) {
|
|
122
|
+
const actor = results.find((actor) => actor.handle === handle);
|
|
123
|
+
if (!actor) return;
|
|
124
|
+
selection = handle;
|
|
125
|
+
value = actor.handle;
|
|
126
|
+
open = false;
|
|
127
|
+
onSelect?.(actor);
|
|
128
|
+
}
|
|
129
|
+
function keydown(event: KeyboardEvent) {
|
|
130
|
+
if (event.key === 'Escape') dismissedQuery = value.trim().replace(/^@+/, '');
|
|
131
|
+
if (
|
|
132
|
+
event.key === 'Enter' &&
|
|
133
|
+
!event.isComposing &&
|
|
134
|
+
!ref?.getAttribute('aria-activedescendant')
|
|
135
|
+
) {
|
|
136
|
+
event.preventDefault();
|
|
137
|
+
open = false;
|
|
138
|
+
ref?.form?.requestSubmit();
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
</script>
|
|
142
|
+
|
|
143
|
+
<div class={cn('relative min-w-0', className)} data-slot="actor-search">
|
|
144
|
+
<Icon
|
|
145
|
+
img={AtSign}
|
|
146
|
+
size={18}
|
|
147
|
+
class="pointer-events-none absolute top-[13px] left-3 z-1 text-muted"
|
|
148
|
+
/>
|
|
149
|
+
{#if searchActors}
|
|
150
|
+
<Combobox.Root
|
|
151
|
+
type="single"
|
|
152
|
+
{disabled}
|
|
153
|
+
bind:open
|
|
154
|
+
value={selection}
|
|
155
|
+
inputValue={value}
|
|
156
|
+
allowDeselect={false}
|
|
157
|
+
onValueChange={choose}
|
|
158
|
+
>
|
|
159
|
+
<Combobox.Input
|
|
160
|
+
{id}
|
|
161
|
+
{name}
|
|
162
|
+
aria-label={label}
|
|
163
|
+
aria-controls={open ? listId : undefined}
|
|
164
|
+
aria-describedby={descriptionIds}
|
|
165
|
+
aria-invalid={invalid || undefined}
|
|
166
|
+
{placeholder}
|
|
167
|
+
bind:ref
|
|
168
|
+
data-autofocus
|
|
169
|
+
autocomplete="off"
|
|
170
|
+
autocapitalize="none"
|
|
171
|
+
spellcheck="false"
|
|
172
|
+
class={cn(control, 'pr-10 pl-10')}
|
|
173
|
+
oninput={(event) => {
|
|
174
|
+
value = event.currentTarget.value;
|
|
175
|
+
selection = '';
|
|
176
|
+
dismissedQuery = '';
|
|
177
|
+
}}
|
|
178
|
+
onfocus={() => {
|
|
179
|
+
focused = true;
|
|
180
|
+
dismissedQuery = '';
|
|
181
|
+
}}
|
|
182
|
+
onblur={() => (focused = false)}
|
|
183
|
+
onkeydown={keydown}
|
|
184
|
+
/>
|
|
185
|
+
{#if inherited}<Combobox.Portal
|
|
186
|
+
><Combobox.Content
|
|
187
|
+
id={listId}
|
|
188
|
+
aria-label={`${label} suggestions`}
|
|
189
|
+
sideOffset={6}
|
|
190
|
+
collisionPadding={12}
|
|
191
|
+
style={inherited}
|
|
192
|
+
class="z-70 max-h-[min(20rem,var(--bits-combobox-content-available-height))] w-(--bits-combobox-anchor-width) min-w-52 overflow-y-auto overscroll-contain rounded-ui border border-line bg-field p-1.5 font-sans text-ink outline-none"
|
|
193
|
+
>
|
|
194
|
+
{#each results as actor (actor.did)}<Combobox.Item
|
|
195
|
+
value={actor.handle}
|
|
196
|
+
label={actor.handle}
|
|
197
|
+
class="flex min-h-14 cursor-pointer items-center gap-3 rounded-ui px-2.5 py-2 outline-none data-highlighted:bg-accent-soft data-highlighted:text-action"
|
|
198
|
+
><Avatar
|
|
199
|
+
name={actorName(actor)}
|
|
200
|
+
src={actor.avatar}
|
|
201
|
+
size={32}
|
|
202
|
+
aria-hidden="true"
|
|
203
|
+
/><span class="grid min-w-0 gap-0.5"
|
|
204
|
+
><strong class="truncate text-[.8125rem] font-medium">{actorName(actor)}</strong
|
|
205
|
+
>{#if actor.displayName}<span class="truncate text-xs text-muted"
|
|
206
|
+
>@{actor.handle}</span
|
|
207
|
+
>{/if}</span
|
|
208
|
+
></Combobox.Item
|
|
209
|
+
>{/each}
|
|
210
|
+
</Combobox.Content></Combobox.Portal
|
|
211
|
+
>{/if}
|
|
212
|
+
</Combobox.Root>
|
|
213
|
+
{#if status === 'loading'}<Icon
|
|
214
|
+
img={LoaderCircle}
|
|
215
|
+
size={16}
|
|
216
|
+
class="pointer-events-none absolute top-3.5 right-3 text-muted motion-safe:animate-spin"
|
|
217
|
+
/>{/if}
|
|
218
|
+
{:else}<Input
|
|
219
|
+
{id}
|
|
220
|
+
{name}
|
|
221
|
+
bind:value
|
|
222
|
+
bind:ref
|
|
223
|
+
aria-label={label}
|
|
224
|
+
aria-describedby={describedby}
|
|
225
|
+
aria-invalid={invalid || undefined}
|
|
226
|
+
{disabled}
|
|
227
|
+
{placeholder}
|
|
228
|
+
autocomplete="username"
|
|
229
|
+
autocapitalize="none"
|
|
230
|
+
spellcheck="false"
|
|
231
|
+
data-autofocus
|
|
232
|
+
class="pl-10"
|
|
233
|
+
/>{/if}
|
|
234
|
+
{#if message}<p id={`${uid}-status`} class="mt-2 text-xs leading-relaxed text-muted">
|
|
235
|
+
{message}
|
|
236
|
+
</p>{/if}
|
|
237
|
+
<span class="sr-only" role="status">{announcement}</span>
|
|
238
|
+
</div>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { ActorSearch, ActorSummary } from './types.js';
|
|
2
|
+
type $$ComponentProps = {
|
|
3
|
+
value?: string;
|
|
4
|
+
searchActors?: ActorSearch;
|
|
5
|
+
onSelect?: (actor: ActorSummary) => void;
|
|
6
|
+
label?: string;
|
|
7
|
+
placeholder?: string;
|
|
8
|
+
id?: string;
|
|
9
|
+
name?: string;
|
|
10
|
+
disabled?: boolean;
|
|
11
|
+
invalid?: boolean;
|
|
12
|
+
describedby?: string;
|
|
13
|
+
debounce?: number;
|
|
14
|
+
class?: string;
|
|
15
|
+
ref?: HTMLInputElement | null;
|
|
16
|
+
};
|
|
17
|
+
declare const ActorSearchField: import("svelte").Component<$$ComponentProps, {}, "value" | "ref">;
|
|
18
|
+
type ActorSearchField = ReturnType<typeof ActorSearchField>;
|
|
19
|
+
export default ActorSearchField;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import bluesky from './brands/bluesky.svg?url';
|
|
3
|
+
import blacksky from './brands/blacksky.svg?url';
|
|
4
|
+
import graze from './brands/graze.svg?url';
|
|
5
|
+
import leaflet from './brands/leaflet.svg?url';
|
|
6
|
+
import tangled from './brands/tangled.svg?url';
|
|
7
|
+
import bookhive from './brands/bookhive.svg?url';
|
|
8
|
+
import surf from './brands/surf.svg?url';
|
|
9
|
+
import pckt from './brands/pckt.svg?url';
|
|
10
|
+
const apps = [bluesky, blacksky, graze, leaflet, tangled, bookhive, surf, pckt];
|
|
11
|
+
</script>
|
|
12
|
+
|
|
13
|
+
<!-- Existing brand marks, not replacement UI icons. Static: no endless decorative motion. -->
|
|
14
|
+
<div class="flex items-center justify-between gap-2 py-2" aria-hidden="true">
|
|
15
|
+
{#each apps as src (src)}<span
|
|
16
|
+
class="size-5 shrink-0 bg-muted [mask-size:contain] [mask-position:center] [mask-repeat:no-repeat] opacity-70 sm:size-6"
|
|
17
|
+
style:mask-image={`url("${src}")`}
|
|
18
|
+
></span>{/each}
|
|
19
|
+
</div>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
|
|
2
|
+
new (options: import('svelte').ComponentConstructorOptions<Props>): import('svelte').SvelteComponent<Props, Events, Slots> & {
|
|
3
|
+
$$bindings?: Bindings;
|
|
4
|
+
} & Exports;
|
|
5
|
+
(internal: unknown, props: {
|
|
6
|
+
$$events?: Events;
|
|
7
|
+
$$slots?: Slots;
|
|
8
|
+
}): Exports & {
|
|
9
|
+
$set?: any;
|
|
10
|
+
$on?: any;
|
|
11
|
+
};
|
|
12
|
+
z_$$bindings?: Bindings;
|
|
13
|
+
}
|
|
14
|
+
declare const AtmosphereApps: $$__sveltets_2_IsomorphicComponent<Record<string, never>, {
|
|
15
|
+
[evt: string]: CustomEvent<any>;
|
|
16
|
+
}, {}, {}, string>;
|
|
17
|
+
type AtmosphereApps = InstanceType<typeof AtmosphereApps>;
|
|
18
|
+
export default AtmosphereApps;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
<script lang="ts" module>
|
|
2
|
+
import type { LoginFormProps } from './LoginForm.svelte';
|
|
3
|
+
export type LoginDialogProps = Omit<LoginFormProps, 'showHeader' | 'ref'> & {
|
|
4
|
+
open?: boolean;
|
|
5
|
+
closeOnSuccess?: boolean;
|
|
6
|
+
formClass?: string;
|
|
7
|
+
};
|
|
8
|
+
</script>
|
|
9
|
+
|
|
10
|
+
<script lang="ts">
|
|
11
|
+
import { tick } from 'svelte';
|
|
12
|
+
import Modal from '../Modal.svelte';
|
|
13
|
+
import LoginForm from './LoginForm.svelte';
|
|
14
|
+
import type { ActorSummary, LoginStep } from './types.js';
|
|
15
|
+
import { cn } from '../../utils.js';
|
|
16
|
+
let {
|
|
17
|
+
open = $bindable(false),
|
|
18
|
+
recentAccounts = $bindable([]),
|
|
19
|
+
closeOnSuccess = true,
|
|
20
|
+
title = 'Log in with your Atmosphere account',
|
|
21
|
+
description,
|
|
22
|
+
onSuccess,
|
|
23
|
+
onStepChange,
|
|
24
|
+
class: className,
|
|
25
|
+
formClass,
|
|
26
|
+
...formProps
|
|
27
|
+
}: LoginDialogProps = $props();
|
|
28
|
+
const uid = $props.id();
|
|
29
|
+
let step = $state<LoginStep>('login');
|
|
30
|
+
const dialogTitle = $derived(
|
|
31
|
+
step === 'permissions'
|
|
32
|
+
? 'Requested permissions'
|
|
33
|
+
: step === 'signup'
|
|
34
|
+
? 'Already have an account?'
|
|
35
|
+
: title
|
|
36
|
+
);
|
|
37
|
+
const dialogDescription = $derived(
|
|
38
|
+
step === 'permissions'
|
|
39
|
+
? 'Choose what this app can access. Required permissions stay on.'
|
|
40
|
+
: step === 'signup'
|
|
41
|
+
? 'You can use a Bluesky, Blacksky, or other Atmosphere account here. Otherwise, continue to create one.'
|
|
42
|
+
: description
|
|
43
|
+
);
|
|
44
|
+
$effect(() => {
|
|
45
|
+
if (!open) step = 'login';
|
|
46
|
+
});
|
|
47
|
+
async function changed(next: LoginStep) {
|
|
48
|
+
step = next;
|
|
49
|
+
onStepChange?.(next);
|
|
50
|
+
if (next !== 'login') {
|
|
51
|
+
await tick();
|
|
52
|
+
document.getElementById(uid)?.querySelector<HTMLElement>('h2')?.focus();
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
function completed(handle: string, actor?: ActorSummary) {
|
|
56
|
+
if (closeOnSuccess) open = false;
|
|
57
|
+
onSuccess?.(handle, actor);
|
|
58
|
+
}
|
|
59
|
+
</script>
|
|
60
|
+
|
|
61
|
+
<Modal
|
|
62
|
+
bind:open
|
|
63
|
+
id={uid}
|
|
64
|
+
title={dialogTitle}
|
|
65
|
+
description={dialogDescription}
|
|
66
|
+
class={cn('max-w-sm [&>header]:mb-4', className)}
|
|
67
|
+
>
|
|
68
|
+
<LoginForm
|
|
69
|
+
{...formProps}
|
|
70
|
+
bind:recentAccounts
|
|
71
|
+
{title}
|
|
72
|
+
{description}
|
|
73
|
+
showHeader={false}
|
|
74
|
+
class={formClass}
|
|
75
|
+
onSuccess={completed}
|
|
76
|
+
onStepChange={changed}
|
|
77
|
+
/>
|
|
78
|
+
</Modal>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { LoginFormProps } from './LoginForm.svelte';
|
|
2
|
+
export type LoginDialogProps = Omit<LoginFormProps, 'showHeader' | 'ref'> & {
|
|
3
|
+
open?: boolean;
|
|
4
|
+
closeOnSuccess?: boolean;
|
|
5
|
+
formClass?: string;
|
|
6
|
+
};
|
|
7
|
+
declare const LoginDialog: import("svelte").Component<LoginDialogProps, {}, "open" | "recentAccounts">;
|
|
8
|
+
type LoginDialog = ReturnType<typeof LoginDialog>;
|
|
9
|
+
export default LoginDialog;
|