adds server list to docs
This commit is contained in:
38
docs/src/components/select.svelte
Normal file
38
docs/src/components/select.svelte
Normal file
@@ -0,0 +1,38 @@
|
||||
<script lang="ts">
|
||||
import { type SelectItem } from "../models/select_item";
|
||||
|
||||
interface Props {
|
||||
name?: string;
|
||||
items?: SelectItem[];
|
||||
value?: any;
|
||||
label?: string;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
let {
|
||||
name = "",
|
||||
items = [],
|
||||
value = $bindable(items.at(0)?.value ?? ""),
|
||||
label = "",
|
||||
disabled = false,
|
||||
}: Props = $props();
|
||||
</script>
|
||||
|
||||
<div>
|
||||
{#if label.length}
|
||||
<label for={name} class="text-sm font-medium pb-1">
|
||||
{label}
|
||||
</label>
|
||||
{/if}
|
||||
<select
|
||||
{name}
|
||||
class="block bg-input-background p-4 border-r-8 border-transparent outline-1 outline-input-border rounded-md focus:outline-input-border-focus transition-colors"
|
||||
class:text-gray-500={disabled}
|
||||
{disabled}
|
||||
bind:value
|
||||
>
|
||||
{#each items as item}
|
||||
<option value={item.value}>{item.text}</option>
|
||||
{/each}
|
||||
</select>
|
||||
</div>
|
||||
120
docs/src/components/server_list.svelte
Normal file
120
docs/src/components/server_list.svelte
Normal file
@@ -0,0 +1,120 @@
|
||||
<script lang="ts">
|
||||
import TextField from "./text_field.svelte";
|
||||
import Select from "./select.svelte";
|
||||
|
||||
interface Props {
|
||||
servers: Server[];
|
||||
}
|
||||
let { servers }: Props = $props();
|
||||
|
||||
import type { Server } from "../models/server";
|
||||
import type { SelectItem } from "../models/select_item";
|
||||
|
||||
let search = $state("");
|
||||
let region = $state("");
|
||||
let language = $state("");
|
||||
let category = $state("");
|
||||
|
||||
const uniqueValues = (key: keyof Server) => [
|
||||
...new Set(servers.map((s) => s[key])),
|
||||
];
|
||||
|
||||
const regionItems: SelectItem[] = [
|
||||
{ text: "All regions", value: "" },
|
||||
...uniqueValues("region").map((v) => ({ text: v, value: v })),
|
||||
];
|
||||
|
||||
const languageItems: SelectItem[] = [
|
||||
{ text: "All languages", value: "" },
|
||||
...uniqueValues("language").map((v) => ({ text: v, value: v })),
|
||||
];
|
||||
|
||||
const categoryItems: SelectItem[] = [
|
||||
{ text: "All categories", value: "" },
|
||||
...uniqueValues("category").map((v) => ({ text: v, value: v })),
|
||||
];
|
||||
|
||||
const filteredServers = $derived(
|
||||
servers.filter(
|
||||
(server) =>
|
||||
(server.name.toLowerCase().includes(search.toLowerCase()) ||
|
||||
server.description.toLowerCase().includes(search.toLowerCase())) &&
|
||||
(!region || server.region === region) &&
|
||||
(!language || server.language === language) &&
|
||||
(!category || server.category === category),
|
||||
),
|
||||
);
|
||||
</script>
|
||||
|
||||
<p class="max-w-3xl">
|
||||
In wanderer, a <strong>server</strong> (also called an <em>instance</em>) is
|
||||
a self-hosted installation of the platform. Each server runs independently
|
||||
and hosts its own users, trails, and content. Thanks to federation via
|
||||
ActivityPub, servers can interact—so you can follow users, view trails, and
|
||||
share content across instances.
|
||||
</p>
|
||||
<p class="max-w-3xl mb-12">
|
||||
You can join an existing server or host your own. This page helps you
|
||||
discover publicly listed wanderer servers you might want to explore or
|
||||
become part of.
|
||||
</p>
|
||||
|
||||
<div
|
||||
class="grid grid-cols-1 md:grid-cols-[1fr_auto_auto_auto] justify-end gap-4 mb-8"
|
||||
>
|
||||
<div class="sm:max-w-sm">
|
||||
<TextField
|
||||
type="text"
|
||||
placeholder="Search servers..."
|
||||
bind:value={search}
|
||||
></TextField>
|
||||
</div>
|
||||
<div class="mt-0">
|
||||
<Select items={regionItems} bind:value={region}></Select>
|
||||
</div>
|
||||
<div class="mt-0">
|
||||
<Select items={languageItems} bind:value={language}></Select>
|
||||
</div>
|
||||
<div class="mt-0">
|
||||
<Select items={categoryItems} bind:value={category}></Select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if filteredServers.length == 0}
|
||||
<p class="text-center text-gray-500 text-xl w-full mt-16">
|
||||
There are no servers that match your query
|
||||
</p>
|
||||
{/if}
|
||||
<div class="grid sm:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{#each filteredServers as server}
|
||||
<div
|
||||
class="mt-0 rounded-lg border border-input-border transition overflow-hidden"
|
||||
>
|
||||
<img
|
||||
src={server.image}
|
||||
alt={server.name}
|
||||
class="w-full h-40 object-cover"
|
||||
/>
|
||||
<div class="flex items-center gap-x-1 px-4">
|
||||
{#each ["category", "region", "language"] as key}
|
||||
<div
|
||||
class="mt-0 text-xs border border-input-border bg-menu-item-background-hover' px-2 py-1 rounded-full flex items-center gap-1"
|
||||
>
|
||||
{(server as any)[key]}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
<div class="p-4 -mt-2">
|
||||
<h4 class="mb-1">{server.name}</h4>
|
||||
<p class="text-sm">{server.description}</p>
|
||||
</div>
|
||||
<div class="mt-0 px-4 pb-4 pt-2">
|
||||
<a
|
||||
class="block text-center min-h-10 text-white rounded-lg p-4 py-2 bg-primary font-semibold transition-all hover:bg-primary-hover focus:ring-4 ring-input-ring"
|
||||
href={server.url}
|
||||
target="_blank">Create account</a
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
64
docs/src/components/text_field.svelte
Normal file
64
docs/src/components/text_field.svelte
Normal file
@@ -0,0 +1,64 @@
|
||||
<script lang="ts">
|
||||
|
||||
interface Props {
|
||||
name?: string;
|
||||
value?: string | number;
|
||||
placeholder?: string;
|
||||
disabled?: boolean;
|
||||
label?: string;
|
||||
error?: string | string[] | null;
|
||||
icon?: string;
|
||||
extraClasses?: string;
|
||||
type?: "text" | "password" | "search";
|
||||
autocomplete?: "on" | "off";
|
||||
}
|
||||
|
||||
let {
|
||||
name = "",
|
||||
value = $bindable(""),
|
||||
placeholder = "",
|
||||
disabled = false,
|
||||
label = "",
|
||||
error = "",
|
||||
icon = "",
|
||||
extraClasses = "",
|
||||
type = "text",
|
||||
autocomplete = "on",
|
||||
}: Props = $props();
|
||||
function typeAction(node: HTMLInputElement) {
|
||||
node.type = type;
|
||||
}
|
||||
</script>
|
||||
|
||||
<div>
|
||||
{#if label.length}
|
||||
<label for={name} class="text-sm font-medium pb-1">
|
||||
{label}
|
||||
</label>
|
||||
{/if}
|
||||
<div class="flex items-center gap-2">
|
||||
{#if icon.length > 0}
|
||||
<div class="w-6">
|
||||
<i class="fa fa-{icon}"></i>
|
||||
</div>
|
||||
{/if}
|
||||
<input
|
||||
{name}
|
||||
class="bg-input-background border border-input-border rounded-md p-3 transition-colors focus:border-input-border-focus focus:outline-none focus:ring-0 w-full {extraClasses}"
|
||||
class:border-red-400={(error?.length ?? 0) > 0}
|
||||
class:bg-input-background-error={(error?.length ?? 0) > 0}
|
||||
class:text-gray-500={disabled}
|
||||
{disabled}
|
||||
{autocomplete}
|
||||
use:typeAction
|
||||
bind:value
|
||||
{placeholder}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{#if error}
|
||||
<span class="textfield-error text-xs text-red-400">
|
||||
{error}
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
Reference in New Issue
Block a user