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>
|
||||
@@ -10,12 +10,12 @@ hero:
|
||||
html: |
|
||||
<video class="rounded-xl hidden md:block" poster="/favicon.svg" width="100%" src="/trailer.mp4" controls autoplay />
|
||||
actions:
|
||||
- text: Try the demo
|
||||
link: https://demo.wanderer.to
|
||||
icon: rocket
|
||||
- text: Join a server
|
||||
link: /servers
|
||||
icon: right-arrow
|
||||
- text: Read the docs
|
||||
link: /welcome
|
||||
icon: right-arrow
|
||||
icon: open-book
|
||||
variant: secondary
|
||||
|
||||
---
|
||||
@@ -23,7 +23,7 @@ hero:
|
||||
import { Card, CardGrid } from '@astrojs/starlight/components';
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-16">
|
||||
<div>
|
||||
<div class="order-">
|
||||

|
||||
</div>
|
||||
<div>
|
||||
@@ -39,10 +39,10 @@ import { Card, CardGrid } from '@astrojs/starlight/components';
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||

|
||||

|
||||
</div>
|
||||
<div>
|
||||

|
||||

|
||||
</div>
|
||||
<div>
|
||||
<h1>Explore together, even apart</h1>
|
||||
|
||||
4
docs/src/models/select_item.ts
Normal file
4
docs/src/models/select_item.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export type SelectItem = {
|
||||
text: string;
|
||||
value: any;
|
||||
};
|
||||
9
docs/src/models/server.ts
Normal file
9
docs/src/models/server.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
export interface Server {
|
||||
name: string;
|
||||
url: string;
|
||||
description: string;
|
||||
image: string;
|
||||
region: string;
|
||||
language: string;
|
||||
category: string;
|
||||
}
|
||||
16
docs/src/pages/servers.astro
Normal file
16
docs/src/pages/servers.astro
Normal file
@@ -0,0 +1,16 @@
|
||||
---
|
||||
import StarlightPage from "@astrojs/starlight/components/StarlightPage.astro";
|
||||
|
||||
import ServerList from "../components/server_list.svelte";
|
||||
import servers from "../../public/server/servers.json";
|
||||
---
|
||||
|
||||
<StarlightPage
|
||||
frontmatter={{
|
||||
title: "Discover wanderer Servers",
|
||||
description: "A list of wanderer servers",
|
||||
template: "splash",
|
||||
}}
|
||||
>
|
||||
<ServerList {servers} client:load />
|
||||
</StarlightPage>
|
||||
@@ -5,8 +5,83 @@
|
||||
@import "tailwindcss/preflight.css" layer(base);
|
||||
@import "tailwindcss/utilities.css";
|
||||
|
||||
/* Dark mode colors. */
|
||||
:root {
|
||||
--primary: 36, 39, 52;
|
||||
--primary-hover: 55, 60, 80;
|
||||
--secondary-hover: 55, 65, 81;
|
||||
--secondary-hover-inverse: 243, 244, 246;
|
||||
|
||||
--background: 18, 20, 28;
|
||||
--background-inverse: 255, 255, 255;
|
||||
|
||||
--content: 255, 255, 255;
|
||||
--content-inverse: 0, 0, 0;
|
||||
|
||||
--input-background: 38, 40, 49;
|
||||
--input-background-error: 76, 17, 17;
|
||||
--input-border: 50, 54, 64;
|
||||
--input-border-focus: 148, 163, 184;
|
||||
--input-ring: 148, 163, 184;
|
||||
|
||||
--menu-background: 30, 32, 40;
|
||||
--menu-item-background-hover: 55, 65, 81;
|
||||
--menu-item-background-focus: 75, 85, 99;
|
||||
|
||||
--footer-background: 24, 26, 36;
|
||||
|
||||
--separator: 55, 65, 81;
|
||||
}
|
||||
|
||||
/* Light mode colors. */
|
||||
:root[data-theme='light'] {
|
||||
--primary: 36, 39, 52;
|
||||
--primary-hover: 17, 24, 39;
|
||||
--secondary-hover: 243, 244, 246;
|
||||
--secondary-hover-inverse: 36, 39, 52;
|
||||
|
||||
--background: 255, 255, 255;
|
||||
--background-inverse: 7, 10, 36;
|
||||
|
||||
--content: 0, 0, 0;
|
||||
--content-inverse: 255, 255, 255;
|
||||
|
||||
--input-background: 249, 250, 251;
|
||||
--input-background-error: 254, 242, 242;
|
||||
--input-border: 209, 213, 219;
|
||||
--input-border-focus: 36, 39, 52;
|
||||
--input-ring: 148, 163, 184;
|
||||
|
||||
--menu-background: 255, 255, 255;
|
||||
--menu-item-background-hover: 243, 244, 246;
|
||||
--menu-item-background-focus: 229, 231, 235;
|
||||
|
||||
--footer-background: 36, 39, 52;
|
||||
|
||||
--separator: 209, 213, 219;
|
||||
}
|
||||
|
||||
@theme {
|
||||
--color-primary: #242734;
|
||||
/* wanderer theme */
|
||||
--color-primary: rgba(var(--primary));
|
||||
--color-background: rgba(var(--background));
|
||||
--color-background-inverse: rgba(var(--background-inverse));
|
||||
--color-content: rgba(var(--content));
|
||||
--color-content-inverse: rgba(var(--content-inverse));
|
||||
|
||||
--color-primary: rgba(var(--primary));
|
||||
--color-primary-hover: rgba(var(--primary-hover));
|
||||
--color-secondary-hover: rgba(var(--secondary-hover));
|
||||
--color-input-background: rgba(var(--input-background));
|
||||
--color-input-background-error: rgba(var(--input-background-error));
|
||||
--color-input-border: rgba(var(--input-border));
|
||||
--color-input-border-focus: rgba(var(--input-border-focus));
|
||||
--color-input-ring: rgba(var(--input-ring));
|
||||
--color-menu-background: rgba(var(--menu-background));
|
||||
--color-menu-item-background-hover: rgba(var(--menu-item-background-hover));
|
||||
--color-menu-item-background-focus: rgba(var(--menu-item-background-focus));
|
||||
--color-footer-background: rgba(var(--footer-background));
|
||||
--color-separator: rgba(var(--separator));
|
||||
|
||||
/* Generated accent color palettes. */
|
||||
--color-accent-200: #b3c7ff;
|
||||
|
||||
Reference in New Issue
Block a user