Skip to content

Commit 7034808

Browse files
committed
feat: add headless handle typeahead components
1 parent 3d41750 commit 7034808

12 files changed

Lines changed: 553 additions & 2 deletions

.npmignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
*
2+
!dist/
23
!dist/**
34
!package.json
45
!README.md

README.md

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,45 @@ To add sign in, create a sign in page and add a standard HTML form that submits
6969
</form>
7070
```
7171

72+
For an unstyled handle typeahead, compose the optional components around the same native form:
73+
74+
```astro
75+
---
76+
import HandleField from "at-astro/components/HandleField"
77+
import HandleInput from "at-astro/components/HandleInput"
78+
import HandleOption from "at-astro/components/HandleOption"
79+
import HandleOptions from "at-astro/components/HandleOptions"
80+
---
81+
82+
<form action="/oauth/login" method="post">
83+
<HandleField>
84+
<label>
85+
Handle
86+
<HandleInput autocomplete="off" placeholder="you.bsky.social" required />
87+
</label>
88+
89+
<HandleOptions>
90+
<HandleOption>
91+
<span data-at-field="displayName"></span>
92+
<span>@<span data-at-field="handle"></span></span>
93+
</HandleOption>
94+
</HandleOptions>
95+
</HandleField>
96+
97+
<button>Sign in</button>
98+
</form>
99+
```
100+
101+
`HandleField` uses the integration's configured `publicEndpoint` by default. Pass `endpoint` to query a different service for this field:
102+
103+
```astro
104+
<HandleField endpoint="https://another-appview.example.com">
105+
<!-- HandleInput and HandleOptions -->
106+
</HandleField>
107+
```
108+
109+
The single child of `HandleOptions` is an inert native template that is cloned for each suggestion. Bind returned actor fields with `data-at-field="did"`, `data-at-field="handle"`, `data-at-field="displayName"`, or `data-at-field="avatar"`; the avatar binding must be placed on an `<img>`. `HandleField` reports `data-state="idle"`, `loading`, `success`, `empty`, or `error` on its root element so application CSS can respond without coupling to the implementation. The components add no styles, and the submitted value remains an ordinary `input[name="handle"]`. Suggestions are optional and do not restrict which handles can be submitted.
110+
72111
Sign out is just as simple:
73112

74113
```html

package.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@
2828
"types": "./dist/middleware.d.mts",
2929
"default": "./dist/middleware.mjs"
3030
},
31+
"./components/HandleField": "./dist/components/HandleField.astro",
32+
"./components/HandleInput": "./dist/components/HandleInput.astro",
33+
"./components/HandleOptions": "./dist/components/HandleOptions.astro",
34+
"./components/HandleOption": "./dist/components/HandleOption.astro",
3135
"./pages/*": {
3236
"types": "./dist/pages/*.d.mts",
3337
"default": "./dist/pages/*.mjs"

src/components/HandleField.astro

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
---
2+
import type { HTMLAttributes } from "astro/types"
3+
import { config } from "at-astro:config"
4+
5+
type Props = HTMLAttributes<"div"> & {
6+
/** Override the configured public AT Protocol endpoint for actor typeahead queries. */
7+
endpoint?: string | URL
8+
}
9+
10+
const { endpoint = config.publicEndpoint, ...attributes } = Astro.props
11+
---
12+
13+
<at-astro-handle-field {...attributes} endpoint={endpoint.toString()}>
14+
<slot />
15+
</at-astro-handle-field>
16+
17+
<script>
18+
import "./handle-field-element.mjs"
19+
</script>

src/components/HandleInput.astro

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
import type { HTMLAttributes } from "astro/types"
3+
4+
type Props = Omit<HTMLAttributes<"input">, "name">
5+
6+
const attributes = Astro.props
7+
---
8+
9+
<input {...attributes} name="handle" data-at-handle-input />

src/components/HandleOption.astro

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
import type { HTMLAttributes } from "astro/types"
3+
4+
type Props = HTMLAttributes<"li">
5+
6+
const attributes = Astro.props
7+
---
8+
9+
<li {...attributes}>
10+
<slot />
11+
</li>

src/components/HandleOptions.astro

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
---
2+
import type { HTMLAttributes } from "astro/types"
3+
4+
type Props = HTMLAttributes<"ul">
5+
6+
if (!Astro.slots.has("default")) {
7+
throw new Error("HandleOptions requires one li template")
8+
}
9+
10+
const attributes = Astro.props
11+
---
12+
13+
<ul {...attributes} data-at-handle-options role="listbox" hidden>
14+
<template data-at-handle-template>
15+
<slot />
16+
</template>
17+
</ul>

0 commit comments

Comments
 (0)