Revert "update components"

This reverts commit 4ab7bb2f8e.
This commit is contained in:
Jesse Winton
2024-08-23 12:20:13 -04:00
parent d41196dca0
commit 428cbb0c14
27 changed files with 1613 additions and 1573 deletions

View File

@@ -1,35 +1,35 @@
<script lang="ts"> <script lang="ts">
export let open: boolean = false; export let open: boolean = false;
export let title: string; export let title: string;
</script> </script>
<li class="collapsible-item"> <li class="collapsible-item">
<details class="collapsible-wrapper" {open}> <details class="collapsible-wrapper" {open}>
<summary class="collapsible-button"> <summary class="collapsible-button">
<span class="text">{title}</span> <span class="text">{title}</span>
<div class="icon web-u-color-text-primary"> <div class="icon web-u-color-text-primary">
<span class="icon-cheveron-down" aria-hidden="true" /> <span class="icon-cheveron-down" aria-hidden="true" />
</div> </div>
</summary> </summary>
<div class="collapsible-content u-flex-vertical"> <div class="collapsible-content flex flex-col">
<slot /> <slot />
</div> </div>
</details> </details>
</li> </li>
<style> <style>
.collapsible-item { .collapsible-item {
border-block-end: 0.0625rem solid hsl(var(--web-color-offset)); border-block-end: 0.0625rem solid hsl(var(--web-color-offset));
} }
.collapsible-button { .collapsible-button {
padding-block-start: 1rem; padding-block-start: 1rem;
padding-block-end: 1rem; padding-block-end: 1rem;
} }
.collapsible-content { .collapsible-content {
margin-block-start: 0; margin-block-start: 0;
padding-block-end: 0; padding-block-end: 0;
gap: 4px; gap: 4px;
} }
</style> </style>

View File

@@ -1,10 +1,13 @@
<ul class="collapsible u-width-full-line" style="--p-toggle-border-color: var(--web-color-border);"> <ul
<slot /> class="collapsible w-full"
style="--p-toggle-border-color: var(--web-color-border);"
>
<slot />
</ul> </ul>
<style> <style>
.collapsible { .collapsible {
padding-block-start: 0; padding-block-start: 0;
padding-block-end: 2rem; padding-block-end: 2rem;
} }
</style> </style>

View File

@@ -1,7 +1,4 @@
import Root from "./Root.svelte"; import Root from './Root.svelte';
import Item from "./Item.svelte"; import Item from './Item.svelte';
export { export { Root as Accordion, Item as AccordionItem };
Root as Accordion,
Item as AccordionItem
}

View File

@@ -1,45 +1,52 @@
<script lang="ts"> <script lang="ts">
import Media from '$lib/UI/Media.svelte'; import Media from "$lib/UI/Media.svelte";
import { formatDate } from '$lib/utils/date'; import { formatDate } from "$lib/utils/date";
export let title: string; export let title: string;
export let cover: string; export let cover: string;
export let href: string; export let href: string;
export let date: Date; export let date: Date;
export let timeToRead: number; export let timeToRead: number;
export let author: string; export let author: string;
export let avatar: string; export let avatar: string;
</script> </script>
<li> <li>
<a class="web-grid-articles-item is-transparent" {href}> <a class="web-grid-articles-item is-transparent" {href}>
<div class="web-grid-articles-item-image"> <div class="web-grid-articles-item-image">
<Media <Media
src={cover} src={cover}
class="web-u-media-ratio-16-9" class="web-u-media-ratio-16-9"
alt={title} alt={title}
autoplay autoplay
controls={false} controls={false}
/> />
</div>
<div class="web-grid-articles-item-content">
<h4 class="web-label web-u-color-text-primary">
{title}
</h4>
<div class="web-author">
<div class="flex items-center gap-2">
<img
class="web-author-image"
loading="lazy"
src={avatar}
width="24"
height="24"
alt={author}
/>
<div class="web-author-info">
<h4 class="web-sub-body-400 web-u-color-text-primary">{author}</h4>
<ul class="web-metadata web-caption-400 web-is-not-mobile">
<li>
{formatDate(date)}
</li>
<li>{timeToRead} min</li>
</ul>
</div>
</div> </div>
<div class="web-grid-articles-item-content"> </div>
<h4 class="web-label web-u-color-text-primary"> </div>
{title} </a>
</h4>
<div class="web-author">
<div class="u-flex u-cross-center u-gap-8">
<img class="web-author-image" loading="lazy" src={avatar} width="24" height="24" alt={author} />
<div class="web-author-info">
<h4 class="web-sub-body-400 web-u-color-text-primary">{author}</h4>
<ul class="web-metadata web-caption-400 web-is-not-mobile">
<li>
{formatDate(date)}
</li>
<li>{timeToRead} min</li>
</ul>
</div>
</div>
</div>
</div>
</a>
</li> </li>

View File

@@ -1,146 +1,152 @@
<script lang="ts"> <script lang="ts">
let carousel: HTMLElement; let carousel: HTMLElement;
export let size: 'default' | 'medium' | 'big' = 'default'; export let size: "default" | "medium" | "big" = "default";
export let gap = 32; export let gap = 32;
let scroll = 0; let scroll = 0;
function calculateScrollAmount(prev = false) { function calculateScrollAmount(prev = false) {
const direction = prev ? -1 : 1; const direction = prev ? -1 : 1;
const carouselSize = carousel?.clientWidth; const carouselSize = carousel?.clientWidth;
const childSize = (carousel.childNodes[0] as HTMLUListElement)?.clientWidth + gap; const childSize =
(carousel.childNodes[0] as HTMLUListElement)?.clientWidth + gap;
scroll = scroll || carouselSize; scroll = scroll || carouselSize;
const numberOfItems = Math.floor(carouselSize / childSize); const numberOfItems = Math.floor(carouselSize / childSize);
const overflow = scroll % childSize; const overflow = scroll % childSize;
const amount = numberOfItems * childSize - overflow * direction; const amount = numberOfItems * childSize - overflow * direction;
scroll += amount * direction; scroll += amount * direction;
return amount * direction; return amount * direction;
} }
function next() { function next() {
carousel.scrollBy({ carousel.scrollBy({
left: calculateScrollAmount(), left: calculateScrollAmount(),
behavior: 'smooth' behavior: "smooth",
}); });
} }
function prev() { function prev() {
carousel.scrollBy({ carousel.scrollBy({
left: calculateScrollAmount(true), left: calculateScrollAmount(true),
behavior: 'smooth' behavior: "smooth",
}); });
} }
let isEnd = false; let isEnd = false;
let isStart = true; let isStart = true;
function handleScroll() { function handleScroll() {
isStart = carousel.scrollLeft <= 0; isStart = carousel.scrollLeft <= 0;
isEnd = Math.ceil(carousel.scrollLeft + carousel.offsetWidth) >= carousel.scrollWidth; isEnd =
} Math.ceil(carousel.scrollLeft + carousel.offsetWidth) >=
carousel.scrollWidth;
}
</script> </script>
<div> <div>
<div class="u-flex u-flex-wrap u-cross-center u-margin-block-start-8"> <div class="mt-2 flex flex-wrap items-center">
<slot name="header" /> <slot name="header" />
<div class="nav u-flex u-gap-12 u-cross-end u-margin-inline-start-auto"> <div class="nav ml-auto flex items-end gap-3">
<button <button
class="web-icon-button" class="web-icon-button"
aria-label="Move carousel backward" aria-label="Move carousel backward"
disabled={isStart} disabled={isStart}
on:click={prev} on:click={prev}
> >
<span class="web-icon-arrow-left" aria-hidden="true" /> <span class="web-icon-arrow-left" aria-hidden="true" />
</button> </button>
<button <button
class="web-icon-button" class="web-icon-button"
aria-label="Move carousel forward" aria-label="Move carousel forward"
disabled={isEnd} disabled={isEnd}
on:click={next} on:click={next}
> >
<span class="web-icon-arrow-right" aria-hidden="true" /> <span class="web-icon-arrow-right" aria-hidden="true" />
</button> </button>
</div>
</div> </div>
</div>
<div class="carousel-wrapper" data-state={isStart ? 'start' : isEnd ? 'end' : 'middle'}> <div
<ul class="carousel-wrapper"
class="web-grid-articles u-margin-block-start-32 carousel" data-state={isStart ? "start" : isEnd ? "end" : "middle"}
class:is-medium={size === 'medium'} >
class:is-big={size === 'big'} <ul
style:gap="{gap}px" class="web-grid-articles carousel mt-8"
bind:this={carousel} class:is-medium={size === "medium"}
on:scroll={handleScroll} class:is-big={size === "big"}
> style:gap="{gap}px"
<slot /> bind:this={carousel}
</ul> on:scroll={handleScroll}
</div> >
<slot />
</ul>
</div>
</div> </div>
<style lang="scss"> <style lang="scss">
.nav { .nav {
button { button {
@media screen and (max-width: 1023.9px) { @media screen and (max-width: 1023.9px) {
display: none !important; display: none !important;
} }
}
} }
.carousel-wrapper { }
position: relative; .carousel-wrapper {
position: relative;
&::before, &::before,
&::after { &::after {
content: ''; content: "";
position: absolute; position: absolute;
top: 0; top: 0;
width: 60px; width: 60px;
height: 100%; height: 100%;
transition: ease 250ms; transition: ease 250ms;
z-index: 100; z-index: 100;
}
&::before {
left: 0;
background: linear-gradient(
to right,
hsl(var(--web-color-background-docs)),
transparent
);
}
&[data-state='start']::before {
opacity: 0;
}
&::after {
right: 0;
background: linear-gradient(
to left,
hsl(var(--web-color-background-docs)),
transparent
);
}
&[data-state='end']::after {
opacity: 0;
}
} }
.carousel { &::before {
grid-auto-flow: column; left: 0;
overflow-x: scroll; background: linear-gradient(
scroll-snap-type: x proximity; to right,
hsl(var(--web-color-background-docs)),
scrollbar-width: none; transparent
-ms-overflow-style: none; );
&::-webkit-scrollbar {
display: none;
}
} }
.carousel :global(li) { &[data-state="start"]::before {
scroll-margin: 48px; opacity: 0;
} }
&::after {
right: 0;
background: linear-gradient(
to left,
hsl(var(--web-color-background-docs)),
transparent
);
}
&[data-state="end"]::after {
opacity: 0;
}
}
.carousel {
grid-auto-flow: column;
overflow-x: scroll;
scroll-snap-type: x proximity;
scrollbar-width: none;
-ms-overflow-style: none;
&::-webkit-scrollbar {
display: none;
}
}
.carousel :global(li) {
scroll-margin: 48px;
}
</style> </style>

View File

@@ -1,152 +1,166 @@
<script lang="ts"> <script lang="ts">
import { page } from '$app/stores'; import { page } from "$app/stores";
export let date: string | undefined = undefined; export let date: string | undefined = undefined;
let showFeedback = false; let showFeedback = false;
let feedbackType = ''; let feedbackType = "";
let email = ''; let email = "";
let comment = ''; let comment = "";
let error: string | undefined; let error: string | undefined;
let submitted = false; let submitted = false;
let submitting = false; let submitting = false;
async function handleSubmit() { async function handleSubmit() {
submitting = true; submitting = true;
error = undefined; error = undefined;
const response = await fetch('https://growth.appwrite.io/v1/feedback/docs', { const response = await fetch(
method: 'POST', "https://growth.appwrite.io/v1/feedback/docs",
headers: { {
'Content-Type': 'application/json' method: "POST",
}, headers: {
body: JSON.stringify({ "Content-Type": "application/json",
email, },
type: feedbackType, body: JSON.stringify({
route: $page.route.id, email,
comment type: feedbackType,
}) route: $page.route.id,
}); comment,
submitting = false; }),
if (response.status >= 400) { },
error = response.status >= 500 ? 'Server Error.' : 'Error submitting form.'; );
return; submitting = false;
} if (response.status >= 400) {
comment = email = ''; error =
submitted = true; response.status >= 500 ? "Server Error." : "Error submitting form.";
return;
} }
comment = email = "";
submitted = true;
}
function reset() { function reset() {
comment = email = ''; comment = email = "";
feedbackType = ''; feedbackType = "";
submitted = false; submitted = false;
error = undefined; error = undefined;
} }
$: if (!showFeedback) { $: if (!showFeedback) {
reset(); reset();
} }
</script> </script>
<section class="web-content-footer"> <section class="web-content-footer">
<header class="web-content-footer-header u-width-full-line"> <header class="web-content-footer-header w-full">
<div <div
class="u-flex u-gap-32 u-main-space-between u-cross-center u-width-full-line" class="flex w-full items-center justify-between gap-8"
style="flex-wrap: wrap-reverse;" style="flex-wrap: wrap-reverse;"
> >
<div class="u-flex u-gap-16 u-cross-center"> <div class="flex items-center gap-4">
<h5 class="web-main-body-600 web-u-color-text-primary">Was this page helpful?</h5> <h5 class="web-main-body-600 web-u-color-text-primary">
<div class="u-flex u-gap-8"> Was this page helpful?
<button </h5>
class="web-radio-button" <div class="flex gap-2">
aria-label="helpful" <button
on:click={() => { class="web-radio-button"
showFeedback = feedbackType === 'positive' ? false : true; aria-label="helpful"
feedbackType = 'positive'; on:click={() => {
}} showFeedback = feedbackType === "positive" ? false : true;
> feedbackType = "positive";
<span class="icon-thumb-up" /> }}
</button> >
<button <span class="icon-thumb-up" />
class="web-radio-button" </button>
aria-label="unhelpful" <button
on:click={() => { class="web-radio-button"
showFeedback = feedbackType === 'negative' ? false : true; aria-label="unhelpful"
feedbackType = 'negative'; on:click={() => {
}} showFeedback = feedbackType === "negative" ? false : true;
> feedbackType = "negative";
<!-- TODO: fix the icon name on pink --> }}
<span class="icon-thumb-dowm" /> >
</button> <!-- TODO: fix the icon name on pink -->
</div> <span class="icon-thumb-dowm" />
</div> </button>
<div class="web-content-footer-header-end">
<ul class="web-metadata web-caption-400">
{#if date}
<li>Last updated on {new Date(date)?.toLocaleDateString()}</li>
{/if}
<li>
<a
href="https://github.com/appwrite/website"
target="_blank"
rel="noopener noreferrer"
class="web-link u-flex u-gap-4 u-cross-baseline"
>
<span class="icon-pencil-alt u-contents" aria-hidden="true" />
<span>Update on GitHub</span>
</a>
</li>
</ul>
</div>
</div> </div>
</header> </div>
{#if showFeedback} <div class="web-content-footer-header-end">
<form <ul class="web-metadata web-caption-400">
on:submit|preventDefault={handleSubmit} {#if date}
class="web-card is-normal" <li>Last updated on {new Date(date)?.toLocaleDateString()}</li>
style="--card-padding:1rem" {/if}
> <li>
<div class="u-flex-vertical u-gap-8"> <a
<label for="message"> href="https://github.com/appwrite/website"
<span class="web-u-color-text-primary"> target="_blank"
What did you {feedbackType === 'negative' ? 'dislike' : 'like'}? (optional) rel="noopener noreferrer"
</span> class="web-link flex items-baseline gap-1"
</label> >
<textarea <span class="icon-pencil-alt contents" aria-hidden="true" />
class="web-input-text" <span>Update on GitHub</span>
id="message" </a>
placeholder="Write your message" </li>
bind:value={comment} </ul>
/> </div>
<label for="message" class="u-margin-block-start-8"> </div>
<span class="web-u-color-text-primary">Email</span> </header>
</label> {#if showFeedback}
<input <form
class="web-input-text" on:submit|preventDefault={handleSubmit}
placeholder="Enter your email" class="web-card is-normal"
type="email" style="--card-padding:1rem"
name="email" >
required <div class="flex flex-col gap-2">
bind:value={email} <label for="message">
/> <span class="web-u-color-text-primary">
</div> What did you {feedbackType === "negative" ? "dislike" : "like"}?
{#if submitted} (optional)
<p class="web-u-color-text-primary u-margin-block-start-16"> </span>
Your message has been sent successfully. We appreciate your feedback. </label>
</p> <textarea
{/if} class="web-input-text"
{#if error} id="message"
<p class="web-u-color-text-primary u-margin-block-start-16"> placeholder="Write your message"
There was an error submitting your feedback. Please try again later. bind:value={comment}
</p> />
{/if} <label for="message" class="mt-2">
<span class="web-u-color-text-primary">Email</span>
</label>
<input
class="web-input-text"
placeholder="Enter your email"
type="email"
name="email"
required
bind:value={email}
/>
</div>
{#if submitted}
<p class="web-u-color-text-primary mt-4">
Your message has been sent successfully. We appreciate your feedback.
</p>
{/if}
{#if error}
<p class="web-u-color-text-primary mt-4">
There was an error submitting your feedback. Please try again later.
</p>
{/if}
<div class="u-flex u-main-end u-margin-block-start-16 u-gap-8"> <div class="mt-4 flex justify-end gap-2">
<button class="web-button is-text" on:click={() => (showFeedback = false)}> <button
<span>Cancel</span> class="web-button is-text"
</button> on:click={() => (showFeedback = false)}
<button type="submit" class="web-button" disabled={submitting || !email}> >
<span>Submit</span> <span>Cancel</span>
</button> </button>
</div> <button
</form> type="submit"
{/if} class="web-button"
disabled={submitting || !email}
>
<span>Submit</span>
</button>
</div>
</form>
{/if}
</section> </section>

View File

@@ -23,7 +23,7 @@
export let images: Array<string>; export let images: Array<string>;
</script> </script>
<div class="u-position-absolute web-u-hide-mobile root"> <div class="absolute web-u-hide-mobile root">
{#each headPositions as [size, top, left], i} {#each headPositions as [size, top, left], i}
{@const image = clamp(0, images.length - 1, i % images.length)} {@const image = clamp(0, images.length - 1, i % images.length)}
<FloatingHead <FloatingHead
@@ -33,7 +33,7 @@
{size} {size}
/> />
<div style:margin-block-end="0" style:padding="10%"> <div style:margin-block-end="0" style:padding="10%">
<img style:border-radius="50%" class="u-block" alt="" /> <img style:border-radius="50%" class="block" alt="" />
</div> </div>
{/each} {/each}
</div> </div>

View File

@@ -37,7 +37,7 @@
{ label: 'Functions', href: '/docs/products/functions' }, { label: 'Functions', href: '/docs/products/functions' },
{ label: 'Messaging', href: '/products/messaging' }, { label: 'Messaging', href: '/products/messaging' },
{ label: 'Storage', href: '/docs/products/storage' }, { label: 'Storage', href: '/docs/products/storage' },
{ label: 'Realtime', href: '/docs/apis/realtime' }, { label: 'Realtime', href: '/docs/apis/realtime' }
], ],
Learn: [ Learn: [
{ label: 'Docs', href: '/docs' }, { label: 'Docs', href: '/docs' },
@@ -93,7 +93,7 @@
<nav <nav
aria-label="Footer" aria-label="Footer"
class="web-footer-nav u-margin-block-start-100 u-position-relative" class="web-footer-nav relative mt-24"
class:web-u-sep-block-start={!noBorder} class:web-u-sep-block-start={!noBorder}
> >
<img class="web-logo" src="/images/logos/appwrite.svg" alt="appwrite" height="24" width="130" /> <img class="web-logo" src="/images/logos/appwrite.svg" alt="appwrite" height="24" width="130" />

View File

@@ -1,27 +1,32 @@
<script lang="ts"> <script lang="ts">
import { PUBLIC_APPWRITE_DASHBOARD } from '$env/static/public'; import Button from "./ui/Button.svelte";
import { PUBLIC_APPWRITE_DASHBOARD } from "$env/static/public";
export let classes = ''; export let classes = "";
</script> </script>
<a href={PUBLIC_APPWRITE_DASHBOARD} class={`web-button ${classes}`}> <Button class={classes} href={PUBLIC_APPWRITE_DASHBOARD}>
<span class="logged-in"><slot name="isLoggedIn">Go to Console</slot></span> <span class="hidden [data-logged-in]:block"
<span class="not-logged-in"><slot name="isNotLoggedIn">Get started</slot></span> ><slot name="isLoggedIn">Go to Console</slot></span
</a> >
<span class="btton [data-logged-in]:hidden"
><slot name="isNotLoggedIn">Get started</slot></span
>
</Button>
<style lang="scss"> <style lang="scss">
:global(body[data-logged-in]) { :global(body[data-logged-in]) {
.logged-in { .logged-in {
display: block; display: block;
}
.not-logged-in {
display: none;
}
} }
.not-logged-in { .not-logged-in {
display: block; display: none;
}
.logged-in {
display: none;
} }
}
.not-logged-in {
display: block;
}
.logged-in {
display: none;
}
</style> </style>

View File

@@ -0,0 +1,98 @@
<script lang="ts">
export let title =
"Trusted by developers from the world's leading organizations";
const logos = [
{
src: "/images/logos/trusted-by/apple.svg",
alt: "Apple",
width: 42,
height: 48,
},
{
src: "/images/logos/trusted-by/oracle.svg",
alt: "ORACLE",
width: 136,
height: 17,
},
{
src: "/images/logos/trusted-by/tiktok.svg",
alt: "TikTok",
width: 133,
height: 32,
},
{
src: "/images/logos/trusted-by/intel.svg",
alt: "intel",
width: 76,
height: 30,
},
{
src: "/images/logos/trusted-by/ibm.svg",
alt: "IBM",
width: 74,
height: 30,
},
{
src: "/images/logos/trusted-by/american-airlines.svg",
alt: "American Airlines",
width: 147,
height: 24,
},
{
src: "/images/logos/trusted-by/deloitte.svg",
alt: "Deloitte.",
width: 103,
height: 20,
},
{
src: "/images/logos/trusted-by/gm.svg",
alt: "GM",
width: 48,
height: 48,
},
{
src: "/images/logos/trusted-by/ey.svg",
alt: "EY",
width: 46,
height: 48,
},
{
src: "/images/logos/trusted-by/nestle.svg",
alt: "Nestle",
width: 119,
height: 34,
},
{
src: "/images/logos/trusted-by/bosch.svg",
alt: "BOSCH",
width: 110,
height: 37,
},
{
src: "/images/logos/trusted-by/decathlon.svg",
alt: "DECATHLON",
width: 127,
height: 32,
},
];
</script>
<div class="my-32">
<div class="container">
<h2
class="font-aeonik-pro text-greyscale-100 mx-auto max-w-xl text-center text-4xl leading-10"
>
{title}
</h2>
<ul
class="web-u-padding-block-start-80 grid grid-cols-3 text-center md:grid-cols-6 md:gap-10"
>
{#each logos as { src, alt, width, height }, i}
<li class="grid place-content-center">
<img {src} {alt} {width} {height} />
</li>
{/each}
</ul>
</div>
</div>

View File

@@ -1,114 +1,118 @@
<script lang="ts"> <script lang="ts">
import { socials } from '$lib/constants'; import { socials } from "$lib/constants";
import ThemeSelect from './ThemeSelect.svelte'; import ThemeSelect from "./ThemeSelect.svelte";
export let variant: 'homepage' | 'docs' = 'homepage'; export let variant: "homepage" | "docs" = "homepage";
const year = new Date().getFullYear(); const year = new Date().getFullYear();
</script> </script>
{#if variant === 'homepage'} {#if variant === "homepage"}
<footer class="web-main-footer u-position-relative u-margin-block-start-48"> <footer class="web-main-footer relative mt-12">
<ul class="u-flex u-gap-8"> <ul class="flex gap-2">
{#each socials as social} {#each socials as social}
<li> <li>
<a <a
href={social.link} href={social.link}
class="web-icon-button" class="web-icon-button"
aria-label={social.label} aria-label={social.label}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
> >
<span class={social.icon} aria-hidden="true" /> <span class={social.icon} aria-hidden="true" />
</a> </a>
</li> </li>
{/each} {/each}
</ul> </ul>
<div class="e-main-footer"> <div class="e-main-footer">
<div>Copyright © {year} Appwrite</div> <div>Copyright © {year} Appwrite</div>
<iframe <iframe
class="status" class="status"
title="Appwrite Status" title="Appwrite Status"
src="https://status.appwrite.online/badge?theme=dark" src="https://status.appwrite.online/badge?theme=dark"
width="190" width="190"
height="30" height="30"
frameborder="0" frameborder="0"
scrolling="no" scrolling="no"
style:color-scheme="none" style:color-scheme="none"
style:margin-top="-4px" style:margin-top="-4px"
/> />
<ul class="u-flex u-gap-16"> <ul class="flex gap-4">
<li><a class="web-link" href="/terms">Terms</a></li> <li><a class="web-link" href="/terms">Terms</a></li>
<li><a class="web-link" href="/privacy">Privacy</a></li> <li><a class="web-link" href="/privacy">Privacy</a></li>
<li><a class="web-link" href="/cookies">Cookies</a></li> <li><a class="web-link" href="/cookies">Cookies</a></li>
</ul> </ul>
</div> </div>
</footer> </footer>
{:else if variant === 'docs'} {:else if variant === "docs"}
<footer <footer class="web-main-footer is-with-bg-color relative mt-8 text-sm">
class="web-main-footer is-with-bg-color u-margin-block-start-32 u-small u-position-relative" <div class="web-main-footer-grid-1">
> <ul class="web-main-footer-grid-1-column-1 flex gap-2">
<div class="web-main-footer-grid-1"> {#each socials as social}
<ul class="web-main-footer-grid-1-column-1 u-flex u-gap-8"> <li>
{#each socials as social} <a
<li> href={social.link}
<a class="web-icon-button"
href={social.link} aria-label={social.label}
class="web-icon-button" target="_blank"
aria-label={social.label} rel="noopener noreferrer"
target="_blank" >
rel="noopener noreferrer" <span class={social.icon} aria-hidden="true" />
> </a>
<span class={social.icon} aria-hidden="true" /> </li>
</a> {/each}
</li> </ul>
{/each} <div class="web-main-footer-grid-1-column-2">
</ul> <ThemeSelect />
<div class="web-main-footer-grid-1-column-2"> </div>
<ThemeSelect /> <ul
</div> class="web-main-footer-grid-1-column-3 web-main-footer-links items-center"
<ul class="web-main-footer-grid-1-column-3 u-cross-center web-main-footer-links"> >
<li> <li>
<a href="/discord" target="_blank" rel="noopener noreferrer">Support</a> <a href="/discord" target="_blank" rel="noopener noreferrer"
</li> >Support</a
<li> >
<a href="https://appwrite.online" target="_blank" rel="noopener noreferrer" </li>
>Status</a <li>
> <a
</li> href="https://appwrite.online"
<!-- <li> target="_blank"
rel="noopener noreferrer">Status</a
>
</li>
<!-- <li>
<a href="https://github.com/appwrite/appwrite/releases" target="_blank" rel="noopener noreferrer">Changelog</a> <a href="https://github.com/appwrite/appwrite/releases" target="_blank" rel="noopener noreferrer">Changelog</a>
</li> --> </li> -->
</ul> </ul>
<div class="web-main-footer-grid-1-column-4 web-main-footer-copyright"> <div class="web-main-footer-grid-1-column-4 web-main-footer-copyright">
Copyright © {year} Appwrite Copyright © {year} Appwrite
</div> </div>
</div> </div>
</footer> </footer>
{/if} {/if}
<style lang="scss"> <style lang="scss">
@use '$scss/abstract/variables/devices'; @use "$scss/abstract/variables/devices";
.web-icon-button { .web-icon-button {
display: grid; display: grid;
} }
.e-main-footer { .e-main-footer {
display: flex; display: flex;
@media #{devices.$break1} { @media #{devices.$break1} {
flex-direction: column; flex-direction: column;
> * { > * {
padding-block: 1rem; padding-block: 1rem;
&:not(:first-child) { &:not(:first-child) {
border-block-start: solid 0.0625rem hsl(var(--web-color-border)); border-block-start: solid 0.0625rem hsl(var(--web-color-border));
}
}
}
@media #{devices.$break2open} {
display: flex;
gap: 2rem;
} }
}
} }
@media #{devices.$break2open} {
display: flex;
gap: 2rem;
}
}
</style> </style>

View File

@@ -1,55 +1,54 @@
<script lang="ts"> <script lang="ts">
import { afterNavigate } from '$app/navigation'; import { afterNavigate } from "$app/navigation";
import { PUBLIC_APPWRITE_DASHBOARD } from '$env/static/public'; import { IsLoggedIn } from "$lib/components";
import { IsLoggedIn } from '$lib/components'; import { GITHUB_STARS } from "$lib/constants";
import { GITHUB_STARS } from '$lib/constants'; import type { NavLink } from "$lib/layouts/Main.svelte";
import type { NavLink } from '$lib/layouts/Main.svelte';
export let open = false; export let open = false;
export let links: NavLink[]; export let links: NavLink[];
afterNavigate(() => { afterNavigate(() => {
open = false; open = false;
}); });
</script> </script>
<svelte:window on:resize={() => open && (open = false)} /> <svelte:window on:resize={() => open && (open = false)} />
<nav class="web-side-nav web-is-not-desktop" class:u-hide={!open}> <nav class="web-side-nav web-is-not-desktop" class:hidden={!open}>
<div class="web-side-nav-wrapper web-u-padding-inline-16"> <div class="web-side-nav-wrapper ps-4 pe-4">
<div class="u-flex items-center u-gap-8"> <div class="flex items-center gap-2">
<a <a
href={`${PUBLIC_APPWRITE_DASHBOARD}/register`} href="https://cloud.appwrite.io/register"
class="web-button is-secondary web-u-flex-1" class="web-button is-secondary flex-1"
> >
Sign up Sign up
</a> </a>
<IsLoggedIn classes="web-u-flex-1" /> <IsLoggedIn classes="flex-1" />
</div>
<div class="web-side-nav-scroll">
<section>
<ul>
{#each links as { href, label }}
<li>
<a class="web-side-nav-button" {href}>
<span class="web-caption-400">{label}</span>
</a>
</li>
{/each}
</ul>
</section>
</div>
<div class="web-side-nav-mobile-footer-buttons">
<a
href="https://github.com/appwrite/appwrite/stargazers"
target="_blank"
rel="noopener noreferrer"
class="web-button is-text web-u-inline-width-100-percent-mobile"
>
<span class="web-icon-star" aria-hidden="true" />
<span class="text">Star on GitHub</span>
<span class="web-inline-tag web-sub-body-400">{GITHUB_STARS}</span>
</a>
</div>
</div> </div>
<div class="web-side-nav-scroll">
<section>
<ul>
{#each links as { href, label }}
<li>
<a class="web-side-nav-button" {href}>
<span class="web-caption-400">{label}</span>
</a>
</li>
{/each}
</ul>
</section>
</div>
<div class="web-side-nav-mobile-footer-buttons">
<a
href="https://github.com/appwrite/appwrite/stargazers"
target="_blank"
rel="noopener noreferrer"
class="web-button is-text web-u-inline-width-100-percent-mobile"
>
<span class="web-icon-star" aria-hidden="true" />
<span class="text">Star on GitHub</span>
<span class="web-inline-tag web-sub-body-400">{GITHUB_STARS}</span>
</a>
</div>
</div>
</nav> </nav>

View File

@@ -1,181 +1,206 @@
<script context="module" lang="ts"> <script context="module" lang="ts">
export async function newsletter(name: string, email: string) { export async function newsletter(name: string, email: string) {
const response = await fetch('https://growth.appwrite.io/v1/newsletter/subscribe', { const response = await fetch(
method: 'POST', "https://growth.appwrite.io/v1/newsletter/subscribe",
headers: { {
'Content-Type': 'application/json' method: "POST",
}, headers: {
body: JSON.stringify({ "Content-Type": "application/json",
name, },
email body: JSON.stringify({
}) name,
}); email,
return response; }),
} },
);
return response;
}
</script> </script>
<script lang="ts"> <script lang="ts">
let email = ''; let email = "";
let name = ''; let name = "";
let submitted = false; let submitted = false;
let error: string | undefined; let error: string | undefined;
let submitting = false; let submitting = false;
async function submit() { async function submit() {
submitting = true; submitting = true;
error = undefined; error = undefined;
const response = await newsletter(name, email); const response = await newsletter(name, email);
submitting = false; submitting = false;
if (response.status >= 400) { if (response.status >= 400) {
error = response.status >= 500 ? 'Server Error.' : 'Error submitting form.'; error =
return; response.status >= 500 ? "Server Error." : "Error submitting form.";
} return;
submitted = true; }
} submitted = true;
}
</script> </script>
<div class="pre-footer-bg" style="pointer-events:none;"> <div class="pre-footer-bg" style="pointer-events:none;">
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
width="692" width="692"
height="1171" height="1171"
viewBox="0 0 692 1171" viewBox="0 0 692 1171"
fill="none" fill="none"
style="max-inline-size:100%;" style="max-inline-size:100%;"
> >
<g opacity="0.4" filter="url(#filter0_f_1577_37321)"> <g opacity="0.4" filter="url(#filter0_f_1577_37321)">
<path <path
d="M-96.9811 29.2126C-329.155 33.7322 -513.706 225.611 -509.186 457.785C-504.667 689.959 -312.788 874.51 -80.6141 869.99C33.1857 867.775 -132.237 523.592 -36.8339 437.579C62.4044 348.109 394.063 627.529 391.759 509.155C387.239 276.98 135.193 24.693 -96.9811 29.2126Z" d="M-96.9811 29.2126C-329.155 33.7322 -513.706 225.611 -509.186 457.785C-504.667 689.959 -312.788 874.51 -80.6141 869.99C33.1857 867.775 -132.237 523.592 -36.8339 437.579C62.4044 348.109 394.063 627.529 391.759 509.155C387.239 276.98 135.193 24.693 -96.9811 29.2126Z"
fill="url(#paint0_radial_1577_37321)" fill="url(#paint0_radial_1577_37321)"
/> />
</g> </g>
<defs> <defs>
<filter <filter
id="filter0_f_1577_37321" id="filter0_f_1577_37321"
x="-809.268" x="-809.268"
y="-270.847" y="-270.847"
width="1501.04" width="1501.04"
height="1440.92" height="1440.92"
filterUnits="userSpaceOnUse" filterUnits="userSpaceOnUse"
color-interpolation-filters="sRGB" color-interpolation-filters="sRGB"
> >
<feFlood flood-opacity="0" result="BackgroundImageFix" /> <feFlood flood-opacity="0" result="BackgroundImageFix" />
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape" /> <feBlend
<feGaussianBlur stdDeviation="150" result="effect1_foregroundBlur_1577_37321" /> mode="normal"
</filter> in="SourceGraphic"
<radialGradient in2="BackgroundImageFix"
id="paint0_radial_1577_37321" result="shape"
cx="0" />
cy="0" <feGaussianBlur
r="1" stdDeviation="150"
gradientUnits="userSpaceOnUse" result="effect1_foregroundBlur_1577_37321"
gradientTransform="translate(-88.7975 449.601) rotate(178.885) scale(420.468 420.468)" />
> </filter>
<stop offset="0.281696" stop-color="#FE9567" /> <radialGradient
<stop offset="0.59375" stop-color="#FD366E" /> id="paint0_radial_1577_37321"
</radialGradient> cx="0"
</defs> cy="0"
</svg> r="1"
gradientUnits="userSpaceOnUse"
gradientTransform="translate(-88.7975 449.601) rotate(178.885) scale(420.468 420.468)"
>
<stop offset="0.281696" stop-color="#FE9567" />
<stop offset="0.59375" stop-color="#FD366E" />
</radialGradient>
</defs>
</svg>
</div> </div>
<div class="web-big-padding-section"> <div class="web-big-padding-section">
<div class="web-big-padding-section-level-1"> <div class="web-big-padding-section-level-1">
<div class="web-big-padding-section-level-2"> <div class="web-big-padding-section-level-2">
<div class="web-container"> <div class="container">
<div class="web-grid-1-1-opt-2 u-gap-32"> <div class="grid-cols-2-opt-2 grid gap-8">
<div class=""> <div class="">
<div class="web-u-max-inline-size-none-mobile" class:web-u-max-width-380={!submitted}> <div
<section class="u-flex-vertical web-u-gap-20"> class="web-u-max-inline-size-none-mobile"
<h1 class="web-title web-u-color-text-primary">Subscribe to our newsletter</h1> class:web-max-w-[380px]={!submitted}
<p class="web-description web-u-padding-block-end-40"> >
Sign up to our company blog and get the latest insights from Appwrite. Learn more <section class="web-gap-5 flex flex-col">
about engineering, product design, building community, and tips & tricks for using <h1 class="web-title web-u-color-text-primary">
Appwrite. Subscribe to our newsletter
</p> </h1>
</section> <p class="web-description web-u-padding-block-end-40">
</div> Sign up to our company blog and get the latest insights from
</div> Appwrite. Learn more about engineering, product design,
{#if submitted} building community, and tips & tricks for using Appwrite.
<div class="u-flex u-gap-8 u-cross-center"> </p>
<svg </section>
width="18" </div>
height="18" </div>
viewBox="0 0 18 18" {#if submitted}
fill="none" <div class="flex items-center gap-2">
xmlns="http://www.w3.org/2000/svg" <svg
> width="18"
<circle height="18"
cx="9" viewBox="0 0 18 18"
cy="9" fill="none"
r="8" xmlns="http://www.w3.org/2000/svg"
fill="#FD366E" >
fill-opacity="0.08" <circle
stroke="#FD366E" cx="9"
stroke-opacity="0.32" cy="9"
stroke-width="1.2" r="8"
stroke-linecap="round" fill="#FD366E"
stroke-linejoin="round" fill-opacity="0.08"
/> stroke="#FD366E"
<path stroke-opacity="0.32"
d="M5.25 10.5L7.75 12.5L12.75 6" stroke-width="1.2"
stroke="#E4E4E7" stroke-linecap="round"
stroke-width="1.2" stroke-linejoin="round"
stroke-linecap="round" />
stroke-linejoin="round" <path
/> d="M5.25 10.5L7.75 12.5L12.75 6"
</svg> stroke="#E4E4E7"
stroke-width="1.2"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
<span class="text"> <span class="text">
Thank you for subscribing! An email has been sent to your inbox. Thank you for subscribing! An email has been sent to your inbox.
</span> </span>
</div> </div>
{:else} {:else}
<form method="post" on:submit|preventDefault={submit} class="u-flex-vertical u-gap-16"> <form
<div class="u-flex u-flex-vertical u-gap-4"> method="post"
<label for="name">Your name</label> on:submit|preventDefault={submit}
<input class="flex flex-col gap-4"
class="web-input-text" >
type="text" <div class="flex flex-col gap-1">
placeholder="Enter your name" <label for="name">Your name</label>
id="name" <input
name="name" class="web-input-text"
required type="text"
bind:value={name} placeholder="Enter your name"
/> id="name"
</div> name="name"
<div class="u-flex u-flex-vertical u-gap-4"> required
<label for="email">Your email</label> bind:value={name}
<input />
class="web-input-text" </div>
type="email" <div class="flex flex-col gap-1">
placeholder="Enter your email" <label for="email">Your email</label>
required <input
id="email" class="web-input-text"
name="email" type="email"
bind:value={email} placeholder="Enter your email"
/> required
</div> id="email"
<button type="submit" class="web-button" disabled={submitting}>Sign up</button> name="email"
{#if error} bind:value={email}
<span class="text"> Something went wrong. Please try again later. </span> />
{/if} </div>
</form> <button type="submit" class="web-button" disabled={submitting}
{/if} >Sign up</button
</div> >
</div> {#if error}
</div> <span class="text">
</div> Something went wrong. Please try again later.
</span>
{/if}
</form>
{/if}
</div>
</div>
</div>
</div>
</div> </div>
<style lang="scss"> <style lang="scss">
.pre-footer-bg { .pre-footer-bg {
position: absolute; position: absolute;
top: clamp(300px, 50vw, 50%); top: clamp(300px, 50vw, 50%);
left: clamp(300px, 50vw, 50%); left: clamp(300px, 50vw, 50%);
transform: translate(-50%, -70%); transform: translate(-50%, -70%);
width: clamp(1200px, 100vw, 3000px); width: clamp(1200px, 100vw, 3000px);
height: auto; height: auto;
max-inline-size: unset; max-inline-size: unset;
max-block-size: unset; max-block-size: unset;
} }
</style> </style>

View File

@@ -1,101 +1,107 @@
<script lang="ts"> <script lang="ts">
import { PUBLIC_APPWRITE_DASHBOARD } from '$env/static/public'; import { PUBLIC_APPWRITE_DASHBOARD } from "$env/static/public";
</script> </script>
<img src="/images/bgs/pre-footer.png" alt="" class="web-pre-footer-bg" style="z-index:-1" /> <img
src="/images/bgs/pre-footer.png"
alt=""
class="web-pre-footer-bg"
style="z-index:-1"
/>
<div class="web-grid-1-1 u-gap-32 web-u-row-gap-80 u-position-relative"> <div class="web-u-row-gap-80 relative grid grid-cols-2 gap-8">
<section class="web-hero u-flex web-u-row-gap-32 u-main-center u-cross-center"> <section class="web-hero web-u-row-gap-32 flex items-center justify-center">
<h2 class="web-display u-max-width-500 web-u-text-align-center web-u-color-text-primary"> <h2 class="web-display web-u-color-text-primary max-w-[500px] text-center">
Start building today Start building today
</h2> </h2>
<a <a
href={PUBLIC_APPWRITE_DASHBOARD} href={PUBLIC_APPWRITE_DASHBOARD}
class="web-button is-transparent web-u-cross-child-center" class="web-button is-transparent web-self-center"
>
<span class="text">Get started</span>
</a>
</section>
<section
class="web-card is-transparent has-border-gradient web-u-max-inline-width-584-mobile web-u-margin-inline-auto-mobile web-u-inline-width-100-percent-mobile"
> >
<header class="web-strip-plans-header"> <span class="text">Get started</span>
<div class="web-strip-plans-header-wrapper web-u-row-gap-24"> </a>
<h3 class="web-title web-u-color-text-primary">Our plans</h3> </section>
</div> <section
</header> class="web-card is-transparent has-border-gradient web-u-max-inline-width-584-mobile web-mx-auto-mobile web-u-inline-width-100-percent-mobile"
>
<header class="web-strip-plans-header">
<div class="web-strip-plans-header-wrapper web-u-row-gap-24">
<h3 class="web-title web-u-color-text-primary">Our plans</h3>
</div>
</header>
<ul class="web-strip-plans"> <ul class="web-strip-plans">
<li class="web-strip-plans-item web-strip-plans-container-query"> <li class="web-strip-plans-item web-strip-plans-container-query">
<div class="web-strip-plans-item-wrapper"> <div class="web-strip-plans-item-wrapper">
<div class="web-strip-plans-plan"> <div class="web-strip-plans-plan">
<h4 class="title web-description">Free</h4> <h4 class="title web-description">Free</h4>
<div class="web-title web-u-color-text-primary">$0</div> <div class="web-title web-u-color-text-primary">$0</div>
<div class="info web-caption-500" /> <div class="info web-caption-500" />
</div> </div>
<p class="web-strip-plans-info web-caption-500"> <p class="web-strip-plans-info web-caption-500">
For personal hobby projects and students. For personal hobby projects and students.
</p> </p>
<a <a
href={`${PUBLIC_APPWRITE_DASHBOARD}/register`} href={`${PUBLIC_APPWRITE_DASHBOARD}/register`}
class="web-button is-secondary is-full-width-mobile web-u-cross-child-end" class="web-button is-secondary is-full-width-mobile web-u-cross-child-end"
> >
<span class="text">Get started</span> <span class="text">Get started</span>
</a> </a>
</div> </div>
</li> </li>
<li class="web-strip-plans-item web-strip-plans-container-query"> <li class="web-strip-plans-item web-strip-plans-container-query">
<div class="web-strip-plans-item-wrapper"> <div class="web-strip-plans-item-wrapper">
<div class="web-strip-plans-plan"> <div class="web-strip-plans-plan">
<h4 class="title web-description">Pro</h4> <h4 class="title web-description">Pro</h4>
<div class="web-title web-u-color-text-primary">$15</div> <div class="web-title web-u-color-text-primary">$15</div>
<div class="info web-caption-500">per member/month</div> <div class="info web-caption-500">per member/month</div>
</div> </div>
<p class="web-strip-plans-info web-caption-500"> <p class="web-strip-plans-info web-caption-500">
For pro developers and teams that need to scale their products. For pro developers and teams that need to scale their products.
</p> </p>
<a <a
href={`${PUBLIC_APPWRITE_DASHBOARD}/console?type=createPro`} href={`${PUBLIC_APPWRITE_DASHBOARD}/console?type=createPro`}
class="web-button is-full-width-mobile web-u-cross-child-end" class="web-button is-full-width-mobile web-u-cross-child-end"
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
> >
<!-- <span class="text">Start trial</span> --> <!-- <span class="text">Start trial</span> -->
<span class="text">Start building</span> <span class="text">Start building</span>
</a> </a>
</div> </div>
</li> </li>
<li class="web-strip-plans-item web-strip-plans-container-query"> <li class="web-strip-plans-item web-strip-plans-container-query">
<div class="web-strip-plans-item-wrapper"> <div class="web-strip-plans-item-wrapper">
<div class="web-strip-plans-plan"> <div class="web-strip-plans-plan">
<h4 class="title web-description">Scale</h4> <h4 class="title web-description">Scale</h4>
<div class="web-title web-u-color-text-primary">$599</div> <div class="web-title web-u-color-text-primary">$599</div>
<div class="info web-caption-500">per org/month</div> <div class="info web-caption-500">per org/month</div>
</div> </div>
<p class="web-strip-plans-info web-caption-500"> <p class="web-strip-plans-info web-caption-500">
For pro developers and production projects that need the ability to scale. For pro developers and production projects that need the ability to
</p> scale.
<button </p>
class="web-button is-full-width-mobile is-secondary web-u-cross-child-end" <button
disabled class="web-button is-full-width-mobile is-secondary web-u-cross-child-end"
> disabled
<span class="text">Coming soon</span> >
</button> <span class="text">Coming soon</span>
</div> </button>
</li> </div>
</ul> </li>
</section> </ul>
</section>
</div> </div>
<style lang="scss"> <style lang="scss">
.web-pre-footer-bg { .web-pre-footer-bg {
position: absolute; position: absolute;
top: clamp(300px, 50vw, 50%); top: clamp(300px, 50vw, 50%);
left: clamp(300px, 50vw, 50%); left: clamp(300px, 50vw, 50%);
transform: translate(-58%, -72%); transform: translate(-58%, -72%);
width: clamp(1200px, 200vw, 3000px); width: clamp(1200px, 200vw, 3000px);
height: auto; height: auto;
max-inline-size: unset; max-inline-size: unset;
max-block-size: unset; max-block-size: unset;
} }
</style> </style>

View File

@@ -1,145 +0,0 @@
<div class="web-big-padding-section-level-2">
<div class="web-container">
<h3 class="web-label web-u-color-text-primary u-text-center">
Keep exploring similar integrations
</h3>
<ul class="u-margin-block-start-32 l-grid-1">
<li>
<a
href="/docs/products/auth"
class="web-card product-card is-transparent u-block u-height-100-percent"
style="--card-padding:1.5rem; --card-padding-mobile:1.5rem;"
>
<div class="u-flex u-cross-center u-gap-8">
<img
src="/images/icons/illustrated/dark/auth.png"
alt=""
class=""
width="32"
height="32"
/>
<h4 class="web-main-body-400 web-u-color-text-primary">Auth</h4>
<span class="icon-arrow-right u-margin-inline-start-auto" aria-hidden="true"
></span>
</div>
<p class="web-sub-body-400 u-margin-block-start-4">
Build secure authentication and manage your users.
</p>
</a>
</li>
<li>
<a
href="/docs/products/functions"
class="web-card product-card is-transparent u-block u-height-100-percent"
style="--card-padding:1.5rem; --card-padding-mobile:1.5rem;"
>
<div class="u-flex u-cross-center u-gap-8">
<img
src="/images/icons/illustrated/dark/functions.png"
alt=""
class=""
width="32"
height="32"
/>
<h4 class="web-main-body-400 web-u-color-text-primary">Functions</h4>
<span class="icon-arrow-right u-margin-inline-start-auto" aria-hidden="true"
></span>
</div>
<p class="web-sub-body-400 u-margin-block-start-4">
Scale big and unlock limitless potential with Appwrite functions.
</p>
</a>
</li>
<li>
<a
href="/docs/products/databases"
class="web-card product-card is-transparent u-block u-height-100-percent"
style="--card-padding:1.5rem; --card-padding-mobile:1.5rem;"
>
<div class="u-flex u-cross-center u-gap-8">
<img
src="/images/icons/illustrated/dark/databases.png"
alt=""
class=""
width="32"
height="32"
/>
<h4 class="web-main-body-400 web-u-color-text-primary">Databases</h4>
<span class="icon-arrow-right u-margin-inline-start-auto" aria-hidden="true"
></span>
</div>
<p class="web-sub-body-400 u-margin-block-start-4">
Store and query structured data, ensuring scalable storage.
</p>
</a>
</li>
<li>
<a
href="/docs/products/messaging"
class="web-card product-card is-transparent u-block u-height-100-percent"
style="--card-padding:1.5rem;--card-padding-mobile:1.5rem;"
>
<div class="u-flex u-cross-center u-gap-8">
<img
src="/images/icons/illustrated/dark/messaging.png"
alt=""
class=""
width="32"
height="32"
/>
<h4 class="web-main-body-400 web-u-color-text-primary">Messaging</h4>
<span class="icon-arrow-right u-margin-inline-start-auto" aria-hidden="true"
></span>
</div>
<p class="web-sub-body-400 u-margin-block-start-4">
Manage your files project, using convenient APIs and utilities.
</p>
</a>
</li>
<li>
<a
href="/docs/apis/realtime"
class="web-card product-card is-transparent u-block u-height-100-percent"
style="--card-padding:1.5rem; --card-padding-mobile:1.5rem;"
>
<div class="u-flex u-cross-center u-gap-8">
<img
src="/images/icons/illustrated/dark/realtime.png"
alt=""
class=""
width="32"
height="32"
/>
<h4 class="web-main-body-400 web-u-color-text-primary">Realtime</h4>
<span class="icon-arrow-right u-margin-inline-start-auto" aria-hidden="true"
></span>
</div>
<p class="web-sub-body-400 u-margin-block-start-4">
Utilize realtime information from all Appwrite services.
</p>
</a>
</li>
</ul>
</div>
</div>
<style lang="scss">
.l-grid-1 {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 2rem;
@media (max-width: 600px) {
gap: 1.25rem;
}
.product-card {
transition: all 100ms ease-in-out;
box-shadow: inset 0px 0px 0px 1px rgba(255, 255, 255, 0);
&:hover {
box-shadow: inset 0px 0px 0px 1px rgba(255, 255, 255, 0.12);
}
}
}
</style>

View File

@@ -1,145 +1,150 @@
<script lang="ts"> <script lang="ts">
import { afterNavigate, goto } from '$app/navigation'; import { afterNavigate, goto } from "$app/navigation";
import { layoutState } from '$lib/layouts/Docs.svelte'; import { layoutState } from "$lib/layouts/Docs.svelte";
import { isMac } from '$lib/utils/platform'; import { isMac } from "$lib/utils/platform";
import { createCombobox, melt } from '@melt-ui/svelte'; import { createCombobox, melt } from "@melt-ui/svelte";
import { MeiliSearch, type Hit, type Hits } from 'meilisearch'; import { MeiliSearch, type Hit, type Hits } from "meilisearch";
import { tick } from 'svelte'; import { tick } from "svelte";
export let open = true; export let open = true;
let value: string; let value: string;
let container: HTMLDivElement; let container: HTMLDivElement;
const client = new MeiliSearch({ const client = new MeiliSearch({
host: 'https://search.appwrite.org', host: "https://search.appwrite.org",
apiKey: 'd7e83e21c0daf2a471ef4c463c7872e55b91b0cd02e2d20e9c6f6f1c4cd09ed3' apiKey: "d7e83e21c0daf2a471ef4c463c7872e55b91b0cd02e2d20e9c6f6f1c4cd09ed3",
});
const index = client.index<Props>("website");
type Props = {
url: string;
title?: string;
uid?: string;
meta?: Record<string, string>;
page_block?: number;
urls_tags?: Array<string>;
h1?: string;
h2?: string;
h3?: string;
h4?: string;
h5?: string;
h6?: string;
p?: string;
anchor?: string;
};
let results: Hits<Props> = [];
async function search(value: string) {
return index.search(value, {
limit: 20,
}); });
const index = client.index<Props>('website'); }
type Props = { async function handleInput(value: string) {
url: string; if (!value) {
title?: string; results = [];
uid?: string; } else {
meta?: Record<string, string>; const response = await search(value);
page_block?: number; results = response.hits;
urls_tags?: Array<string>; }
h1?: string; }
h2?: string;
h3?: string;
h4?: string;
h5?: string;
h6?: string;
p?: string;
anchor?: string;
};
let results: Hits<Props> = []; function handleExit(
event: MouseEvent & { currentTarget: EventTarget & HTMLDivElement },
) {
if (event.target === container) {
open = false;
value = "";
}
}
async function search(value: string) { function createHref(hit: Hit<Props>): string {
return index.search(value, { const anchor = hit.anchor === "#" ? "" : (hit.anchor ?? "");
limit: 20 const target = hit.url + anchor;
return target.toString();
}
const recommended: Hits<Props> = [
{
uid: "recommended-references-account",
url: "/docs/references/cloud/client-web/databases",
h1: "API reference",
h2: "Databases",
},
{
uid: "recommended-references-teans",
url: "/docs/references/cloud/client-web/teams",
h1: "API reference",
h2: "Teams",
},
{
uid: "recommended-references-databases",
url: "/docs/references/cloud/client-web/databases",
h1: "API reference",
h2: "Databases",
},
{
uid: "recommended-references-storage",
url: "/docs/references/cloud/client-web/storage",
h1: "API reference",
h2: "Storage",
},
];
afterNavigate(() => {
open = false;
});
$: handleInput(value);
let inputEl: HTMLInputElement;
$: if (open && inputEl) {
inputEl.value = "";
inputEl?.focus();
}
const {
elements: { input, menu, option },
states: { inputValue },
} = createCombobox<Props>({
forceVisible: true,
preventScroll: false,
portal: null,
positioning: null,
onSelectedChange({ next }) {
if (next) {
goto(next.value.url);
tick().then(() => {
inputValue.set("");
}); });
inputValue.set("");
}
return undefined;
},
});
function handleKeypress(event: KeyboardEvent) {
const cmdPressed = isMac() ? event.metaKey : event.ctrlKey;
if (cmdPressed && event.key.toLowerCase() === "k") {
event.preventDefault();
$layoutState.showSearch = true;
} else if (
event.key.toLowerCase() === "escape" ||
event.key.toLowerCase() === "esc"
) {
event.preventDefault();
$layoutState.showSearch = false;
} }
}
async function handleInput(value: string) { function getRelevantSubtitle(hit: Hit): string {
if (!value) { return hit.h2 ?? hit.h3 ?? hit.h4 ?? hit.h5 ?? hit.h6 ?? null;
results = []; }
} else {
const response = await search(value);
results = response.hits;
}
}
function handleExit(event: MouseEvent & { currentTarget: EventTarget & HTMLDivElement }) {
if (event.target === container) {
open = false;
value = '';
}
}
function createHref(hit: Hit<Props>): string {
const anchor = hit.anchor === '#' ? '' : hit.anchor ?? '';
const target = hit.url + anchor;
return target.toString();
}
const recommended: Hits<Props> = [
{
uid: 'recommended-references-account',
url: '/docs/references/cloud/client-web/databases',
h1: 'API reference',
h2: 'Databases'
},
{
uid: 'recommended-references-teans',
url: '/docs/references/cloud/client-web/teams',
h1: 'API reference',
h2: 'Teams'
},
{
uid: 'recommended-references-databases',
url: '/docs/references/cloud/client-web/databases',
h1: 'API reference',
h2: 'Databases'
},
{
uid: 'recommended-references-storage',
url: '/docs/references/cloud/client-web/storage',
h1: 'API reference',
h2: 'Storage'
}
];
afterNavigate(() => {
open = false;
});
$: handleInput(value);
let inputEl: HTMLInputElement;
$: if (open && inputEl) {
inputEl.value = '';
inputEl?.focus();
}
const {
elements: { input, menu, option },
states: { inputValue }
} = createCombobox<Props>({
forceVisible: true,
preventScroll: false,
portal: null,
positioning: null,
onSelectedChange({ next }) {
if (next) {
goto(next.value.url);
tick().then(() => {
inputValue.set('');
});
inputValue.set('');
}
return undefined;
}
});
function handleKeypress(event: KeyboardEvent) {
const cmdPressed = isMac() ? event.metaKey : event.ctrlKey;
if (cmdPressed && event.key.toLowerCase() === 'k') {
event.preventDefault();
$layoutState.showSearch = true;
} else if (event.key.toLowerCase() === 'escape' || event.key.toLowerCase() === 'esc') {
event.preventDefault();
$layoutState.showSearch = false;
}
}
function getRelevantSubtitle(hit: Hit): string {
return hit.h2 ?? hit.h3 ?? hit.h4 ?? hit.h5 ?? hit.h6 ?? null;
}
</script> </script>
<svelte:window on:keydown={handleKeypress} /> <svelte:window on:keydown={handleKeypress} />
@@ -147,146 +152,144 @@
<!-- svelte-ignore a11y-no-static-element-interactions --> <!-- svelte-ignore a11y-no-static-element-interactions -->
<!-- svelte-ignore a11y-click-events-have-key-events --> <!-- svelte-ignore a11y-click-events-have-key-events -->
<div <div
class="wrapper u-position-fixed u-padding-0 u-inset-0 u-flex u-main-center u-cross-center" class="wrapper fixed inset-0 flex items-center justify-center p-0"
data-visible={open ? true : undefined} data-visible={open ? true : undefined}
style:z-index="100" style:z-index="100"
style:background="hsl(var(--web-color-black) / 0.3)" style:background="hsl(var(--web-color-black) / 0.3)"
style:backdrop-filter="blur(15px)" style:backdrop-filter="blur(15px)"
style:-webkit-backdrop-filter="blur(15px)" style:-webkit-backdrop-filter="blur(15px)"
bind:this={container} bind:this={container}
on:click={handleExit} on:click={handleExit}
> >
<div <div
class="web-input-text-search-wrapper web-u-max-width-680 web-u-margin-inline-20 u-width-full-line" class="web-input-text-search-wrapper web-max-w-[680px] web-u-margin-inline-20 w-full"
> >
<span <span
class="web-icon-search u-z-index-5" class="web-icon-search z-[5]"
aria-hidden="true" aria-hidden="true"
style="inset-block-start:0.9rem" style="inset-block-start:0.9rem"
/> />
<div id="searchbox" /> <div id="searchbox" />
<input <input
class="web-input-button -u-padding-block-0 u-position-relative u-z-index-1" class="web-input-button relative z-1"
type="text" type="text"
id="search" id="search"
bind:value bind:value
placeholder="Search in docs" placeholder="Search in docs"
style="border-end-start-radius:0; border-end-end-radius:0;" style="border-end-start-radius:0; border-end-end-radius:0;"
style:inline-size="100%" style:inline-size="100%"
use:melt={$input} use:melt={$input}
bind:this={inputEl} bind:this={inputEl}
data-hit="-1" data-hit="-1"
on:keydown={(e) => { on:keydown={(e) => {
if (e.key === 'Tab') { if (e.key === "Tab") {
e.preventDefault(); e.preventDefault();
} }
}} }}
/> />
<div <div
class="web-card is-normal u-flex-vertical u-gap-24" class="web-card is-normal flex flex-col gap-6"
use:melt={$menu} use:melt={$menu}
style="--card-padding-mobile:1rem; border-radius:0 0 0.5rem 0.5rem;" style="--card-padding-mobile:1rem; border-radius:0 0 0.5rem 0.5rem;"
> >
{#if value} {#if value}
<section> <section>
{#if results.length > 0} {#if results.length > 0}
<h6 class="web-eyebrow">{results.length} results found</h6> <h6 class="web-eyebrow">{results.length} results found</h6>
<ul class="u-flex-vertical u-gap-4 u-margin-block-start-8"> <ul class="mt-2 flex flex-col gap-1">
{#each results as hit, i (hit.uid)} {#each results as hit, i (hit.uid)}
{@const relevantSubtitle = getRelevantSubtitle(hit)} {@const relevantSubtitle = getRelevantSubtitle(hit)}
<li> <li>
<a <a
data-hit={i} data-hit={i}
href={createHref(hit)} href={createHref(hit)}
class="web-button web-caption-400 is-text u-flex-vertical u-gap-8 u-min-width-100-percent class="web-button web-caption-400 is-text web-u-padding-block-8 web-padding-inline-12 web-u-cross-start flex
web-u-padding-block-8 web-padding-inline-12 web-u-cross-start u-max-width-100-percent" max-w-full min-w-full flex-col gap-2"
use:melt={$option({ use:melt={$option({
value: hit, value: hit,
label: hit.title ?? i.toString() label: hit.title ?? i.toString(),
})} })}
> >
<div class="web-u-trim-1"> <div class="web-u-trim-1">
<span class="web-u-color-text-secondary">{hit.h1}</span> <span class="web-u-color-text-secondary">{hit.h1}</span>
{#if relevantSubtitle} {#if relevantSubtitle}
<span class="web-u-color-text-secondary"> / </span> <span class="web-u-color-text-secondary"> / </span>
<span class="web-u-color-text-primary"> <span class="web-u-color-text-primary">
{relevantSubtitle} {relevantSubtitle}
</span> </span>
{/if} {/if}
</div> </div>
{#if hit.p} {#if hit.p}
<div <div class="web-u-color-text-secondary web-u-trim-1">
class="web-u-color-text-secondary web-u-trim-1" {hit.p}
> </div>
{hit.p}
</div>
{/if}
</a>
</li>
{/each}
</ul>
{:else}
<p class="web-caption-400">
No results found for <span class="u-bold">{value}</span>
</p>
{/if} {/if}
</section> </a>
{/if} </li>
<section> {/each}
<h6 class="web-eyebrow">Recommended</h6> </ul>
<ul class="u-flex-vertical u-gap-4 u-margin-block-start-8"> {:else}
{#each recommended as hit, i (hit.uid)} <p class="web-caption-400">
{@const index = i + (results.length ? results.length : 0)} No results found for <span class="font-bold">{value}</span>
<li> </p>
<a {/if}
data-hit={index} </section>
href={createHref(hit)} {/if}
use:melt={$option({ <section>
value: hit, <h6 class="web-eyebrow">Recommended</h6>
label: hit.title ?? i.toString() <ul class="mt-2 flex flex-col gap-1">
})} {#each recommended as hit, i (hit.uid)}
class="web-button web-caption-400 is-text u-flex-vertical u-gap-8 u-min-width-100-percent web-u-padding-block-4 web-u-cross-start" {@const index = i + (results.length ? results.length : 0)}
> <li>
<div class="web-u-trim-1"> <a
<span class="web-u-color-text-secondary">{hit.h1}</span> data-hit={index}
{#if hit.h2} href={createHref(hit)}
<span class="web-u-color-text-secondary"> / </span> use:melt={$option({
<span class="web-u-color-text-primary">{hit.h2}</span> value: hit,
{/if} label: hit.title ?? i.toString(),
</div> })}
</a> class="web-button web-caption-400 is-text web-u-padding-block-4 web-u-cross-start flex min-w-full flex-col gap-2"
</li> >
{/each} <div class="web-u-trim-1">
</ul> <span class="web-u-color-text-secondary">{hit.h1}</span>
</section> {#if hit.h2}
</div> <span class="web-u-color-text-secondary"> / </span>
<span class="web-u-color-text-primary">{hit.h2}</span>
{/if}
</div>
</a>
</li>
{/each}
</ul>
</section>
</div> </div>
</div>
</div> </div>
<style lang="scss"> <style lang="scss">
.wrapper { .wrapper {
opacity: 0; opacity: 0;
pointer-events: none; pointer-events: none;
transition: opacity 250ms ease; transition: opacity 250ms ease;
} }
.wrapper[data-visible] { .wrapper[data-visible] {
opacity: 1; opacity: 1;
pointer-events: auto; pointer-events: auto;
} }
a.web-button { a.web-button {
scroll-margin-block: 1rem; scroll-margin-block: 1rem;
} }
.web-card { .web-card {
margin-block-start: -0.0625rem; margin-block-start: -0.0625rem;
max-block-size: min(18.75rem, calc(100vh - 5.5rem)); max-block-size: min(18.75rem, calc(100vh - 5.5rem));
border-block-start-width: 0; border-block-start-width: 0;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
overflow-y: auto; overflow-y: auto;
} }
</style> </style>

View File

@@ -1,173 +1,185 @@
<script lang="ts" context="module"> <script lang="ts" context="module">
export type SelectOption<T = unknown> = { export type SelectOption<T = unknown> = {
value: T; value: T;
label: string; label: string;
icon?: string; icon?: string;
group?: string; group?: string;
}; };
</script> </script>
<script lang="ts"> <script lang="ts">
import { createSelect, melt, type CreateSelectProps } from '@melt-ui/svelte'; import { createSelect, melt, type CreateSelectProps } from "@melt-ui/svelte";
import { createEventDispatcher } from 'svelte'; import { createEventDispatcher } from "svelte";
import { fly, type FlyParams } from 'svelte/transition'; import { fly, type FlyParams } from "svelte/transition";
export let options: Array<SelectOption>; export let options: Array<SelectOption>;
export let nativeMobile = false; export let nativeMobile = false;
export let value: unknown | undefined = undefined; export let value: unknown | undefined = undefined;
export let onSelectedChange: CreateSelectProps['onSelectedChange'] = undefined; export let onSelectedChange: CreateSelectProps["onSelectedChange"] =
// TODO: This id currently gets overriden by Melt. We should either use the label el, or undefined;
// allow passing down ids in Melt. // TODO: This id currently gets overriden by Melt. We should either use the label el, or
export let id: string | undefined = undefined; // allow passing down ids in Melt.
export let preventScroll = false; export let id: string | undefined = undefined;
export let placement: NonNullable<CreateSelectProps['positioning']>['placement'] = 'bottom'; export let preventScroll = false;
export let placement: NonNullable<
CreateSelectProps["positioning"]
>["placement"] = "bottom";
const dispatch = createEventDispatcher<{ const dispatch = createEventDispatcher<{
change: unknown; change: unknown;
}>(); }>();
const { const {
elements: { trigger, menu, option: optionEl, group: groupEl, groupLabel }, elements: { trigger, menu, option: optionEl, group: groupEl, groupLabel },
states: { open, selected, selectedLabel } states: { open, selected, selectedLabel },
} = createSelect<unknown>({ } = createSelect<unknown>({
preventScroll, preventScroll,
positioning: { positioning: {
sameWidth: true, sameWidth: true,
placement placement,
}, },
forceVisible: true, forceVisible: true,
onSelectedChange({ curr, next }) { onSelectedChange({ curr, next }) {
if (onSelectedChange) { if (onSelectedChange) {
onSelectedChange({ curr, next }); onSelectedChange({ curr, next });
} }
value = next?.value; value = next?.value;
dispatch('change', next?.value); dispatch("change", next?.value);
return next; return next;
}, },
portal: null, portal: null,
scrollAlignment: 'center' scrollAlignment: "center",
}); });
$: selectedOption = options.find((o) => o.value === value); $: selectedOption = options.find((o) => o.value === value);
$: if (selectedOption) { $: if (selectedOption) {
selected.set(selectedOption); selected.set(selectedOption);
} }
const DEFAULT_GROUP = 'default'; const DEFAULT_GROUP = "default";
type Group = { type Group = {
label: string; label: string;
options: SelectOption<unknown>[]; options: SelectOption<unknown>[];
}; };
$: groups = (function getGroups(): Group[] { $: groups = (function getGroups(): Group[] {
const groups = options.reduce<Record<string, SelectOption[]>>((carry, option) => { const groups = options.reduce<Record<string, SelectOption[]>>(
const group = option.group ?? DEFAULT_GROUP; (carry, option) => {
if (!carry[group]) { const group = option.group ?? DEFAULT_GROUP;
carry[group] = []; if (!carry[group]) {
} carry[group] = [];
carry[group].push(option); }
return carry; carry[group].push(option);
}, {}); return carry;
},
{},
);
return Object.entries(groups).map(([label, options]) => ({ label, options })); return Object.entries(groups).map(([label, options]) => ({
})(); label,
options,
}));
})();
$: flyParams = { $: flyParams = {
duration: 150, duration: 150,
y: placement === 'top' ? 4 : -4 y: placement === "top" ? 4 : -4,
} as FlyParams; } as FlyParams;
</script> </script>
<button <button
class="web-select is-colored" class="web-select is-colored"
{id} {id}
class:web-is-not-mobile={nativeMobile} class:web-is-not-mobile={nativeMobile}
use:melt={$trigger} use:melt={$trigger}
aria-label="Select theme" aria-label="Select theme"
> >
<div class="physical-select"> <div class="physical-select">
{#if selectedOption?.icon} {#if selectedOption?.icon}
<span class={selectedOption.icon} aria-hidden="true" /> <span class={selectedOption.icon} aria-hidden="true" />
{/if} {/if}
<span>{$selectedLabel}</span> <span>{$selectedLabel}</span>
</div> </div>
<span class="icon-cheveron-{$open ? 'up' : 'down'}" aria-hidden="true" /> <span class="icon-cheveron-{$open ? 'up' : 'down'}" aria-hidden="true" />
</button> </button>
{#if $open} {#if $open}
<div <div
class="web-select-menu" class="web-select-menu"
class:web-is-not-mobile={nativeMobile} class:web-is-not-mobile={nativeMobile}
style:z-index={10000} style:z-index={10000}
use:melt={$menu} use:melt={$menu}
transition:fly={flyParams} transition:fly={flyParams}
> >
{#each groups as group} {#each groups as group}
{@const isDefault = group.label === DEFAULT_GROUP} {@const isDefault = group.label === DEFAULT_GROUP}
{#if isDefault} {#if isDefault}
<div class="u-flex u-flex-vertical u-gap-2"> <div class="flex flex-col gap-0.5">
{#each group.options as option} {#each group.options as option}
<button class="web-select-option" use:melt={$optionEl(option)}> <button class="web-select-option" use:melt={$optionEl(option)}>
{#if option.icon} {#if option.icon}
<span class={option.icon} aria-hidden="true" /> <span class={option.icon} aria-hidden="true" />
{/if} {/if}
<span style:text-transform="capitalize">{option.label}</span> <span style:text-transform="capitalize">{option.label}</span>
</button> </button>
{/each} {/each}
</div> </div>
{:else} {:else}
<div class="web-select-group" use:melt={$groupEl(group.label)}> <div class="web-select-group" use:melt={$groupEl(group.label)}>
<span class="web-select-group-label" use:melt={$groupLabel(group.label)}> <span
{group.label} class="web-select-group-label"
</span> use:melt={$groupLabel(group.label)}
>
{group.label}
</span>
{#each group.options as option} {#each group.options as option}
<button class="web-select-option" use:melt={$optionEl(option)}> <button class="web-select-option" use:melt={$optionEl(option)}>
{#if option.icon} {#if option.icon}
<span class={option.icon} aria-hidden="true" /> <span class={option.icon} aria-hidden="true" />
{/if} {/if}
<span style:text-transform="capitalize">{option.label}</span> <span style:text-transform="capitalize">{option.label}</span>
</button> </button>
{/each} {/each}
</div> </div>
{/if} {/if}
{/each} {/each}
</div> </div>
{/if} {/if}
<div <div
class="web-select is-colored web-is-only-mobile web-u-inline-width-100-percent-mobile-break1" class="web-select is-colored web-is-only-mobile web-u-inline-width-100-percent-mobile-break1"
style:display={nativeMobile ? undefined : 'none'} style:display={nativeMobile ? undefined : "none"}
> >
{#if selectedOption?.icon} {#if selectedOption?.icon}
<span class={selectedOption.icon} aria-hidden="true" /> <span class={selectedOption.icon} aria-hidden="true" />
{/if} {/if}
<select {id} bind:value> <select {id} bind:value>
{#each groups as group} {#each groups as group}
{@const isDefault = group.label === DEFAULT_GROUP} {@const isDefault = group.label === DEFAULT_GROUP}
{#if isDefault} {#if isDefault}
{#each group.options as option} {#each group.options as option}
<option value={option.value} selected={option.value === value}> <option value={option.value} selected={option.value === value}>
{option.label} {option.label}
</option> </option>
{/each}
{:else}
<optgroup label={isDefault ? undefined : group.label}>
{#each group.options as option}
<option value={option.value} selected={option.value === value}>
{option.label}
</option>
{/each}
</optgroup>
{/if}
{/each} {/each}
</select> {:else}
<span class="icon-cheveron-{$open ? 'up' : 'down'}" aria-hidden="true" /> <optgroup label={isDefault ? undefined : group.label}>
{#each group.options as option}
<option value={option.value} selected={option.value === value}>
{option.label}
</option>
{/each}
</optgroup>
{/if}
{/each}
</select>
<span class="icon-cheveron-{$open ? 'up' : 'down'}" aria-hidden="true" />
</div> </div>
<style lang="scss"> <style lang="scss">
.web-select { .web-select {
min-width: var(--min-width, var(--p-select-min-width)); min-width: var(--min-width, var(--p-select-min-width));
} }
</style> </style>

View File

@@ -1,68 +1,68 @@
<script lang="ts"> <script lang="ts">
import { createSwitch, melt } from '@melt-ui/svelte'; import { createSwitch, melt } from "@melt-ui/svelte";
export let checked = false; export let checked = false;
const { const {
elements: { root }, elements: { root },
states: { checked: meltChecked } states: { checked: meltChecked },
} = createSwitch({ } = createSwitch({
onCheckedChange({ next }) { onCheckedChange({ next }) {
checked = next; checked = next;
return next; return next;
} },
}); });
$: meltChecked.set(checked); $: meltChecked.set(checked);
</script> </script>
<div class="melt-switch"> <div class="melt-switch">
<button use:melt={$root}> <button use:melt={$root}>
<span class="thumb" /> <span class="thumb" />
</button> </button>
</div> </div>
<style lang="scss"> <style lang="scss">
.melt-switch { .melt-switch {
display: flex; display: flex;
align-items: center; align-items: center;
} }
button { button {
--padding: 0.125rem; --padding: 0.125rem;
--width: 2.25rem; --width: 2.25rem;
position: relative; position: relative;
height: 1.5rem; height: 1.5rem;
width: var(--width); width: var(--width);
cursor: default; cursor: default;
border-radius: 9999px; border-radius: 9999px;
background-color: hsl(var(--web-color-smooth)); background-color: hsl(var(--web-color-smooth));
transition: ease 150ms; transition: ease 150ms;
} }
.melt-switch :global([data-state='checked']) { .melt-switch :global([data-state="checked"]) {
background-color: #fd366e; background-color: #fd366e;
} }
.thumb { .thumb {
--thumb-size: 1.25rem; --thumb-size: 1.25rem;
position: absolute; position: absolute;
top: 50%; top: 50%;
display: block; display: block;
height: var(--thumb-size); height: var(--thumb-size);
width: var(--thumb-size); width: var(--thumb-size);
border-radius: 9999px; border-radius: 9999px;
background-color: #ffffff; background-color: #ffffff;
transform: translateX(var(--padding)) translateY(-50%); transform: translateX(var(--padding)) translateY(-50%);
transition: ease 150ms; transition: ease 150ms;
} }
:global(button[data-state='checked']) .thumb { :global(button[data-state="checked"]) .thumb {
--x: calc(var(--width) - var(--thumb-size) - var(--padding)); --x: calc(var(--width) - var(--thumb-size) - var(--padding));
transform: translateX(var(--x)) translateY(-50%); transform: translateX(var(--x)) translateY(-50%);
} }
</style> </style>

View File

@@ -38,7 +38,7 @@
href: '/docs/quick-starts/angular', href: '/docs/quick-starts/angular',
image: `/images/platforms/${$themeInUse}/angular.svg` image: `/images/platforms/${$themeInUse}/angular.svg`
}, },
{ {
name: 'Refine', name: 'Refine',
href: '/docs/quick-starts/refine', href: '/docs/quick-starts/refine',
image: `/images/platforms/${$themeInUse}/refine.svg` image: `/images/platforms/${$themeInUse}/refine.svg`
@@ -57,8 +57,7 @@
name: 'React Native', name: 'React Native',
href: '/docs/quick-starts/react-native', href: '/docs/quick-starts/react-native',
image: `/images/platforms/${$themeInUse}/react-native.svg` image: `/images/platforms/${$themeInUse}/react-native.svg`
}, }
] as Array<{ ] as Array<{
name: string; name: string;
href: string; href: string;
@@ -66,12 +65,17 @@
}>; }>;
</script> </script>
<ul class="u-flex u-flex-wrap u-gap-16 web-u-margin-block-32-mobile web-u-margin-block-40-not-mobile"> <ul class="flex flex-wrap gap-4 web-u-margin-block-32-mobile web-u-margin-block-40-not-mobile">
{#each platforms as platform} {#each platforms as platform}
<Tooltip> <Tooltip>
<li> <li>
<a href={platform.href} class="web-icon-button web-box-icon has-border-gradient"> <a href={platform.href} class="web-icon-button web-box-icon has-border-gradient">
<img src={platform.image} alt="{platform.name} quick start" width="32" height="32" /> <img
src={platform.image}
alt="{platform.name} quick start"
width="32"
height="32"
/>
</a> </a>
</li> </li>
<svelte:fragment slot="tooltip">{platform.name}</svelte:fragment> <svelte:fragment slot="tooltip">{platform.name}</svelte:fragment>

View File

@@ -16,7 +16,10 @@
<aside class="web-grid-120-1fr-auto-side" class:web-is-mobile-closed={!showToc}> <aside class="web-grid-120-1fr-auto-side" class:web-is-mobile-closed={!showToc}>
<div class="web-page-steps"> <div class="web-page-steps">
<div class="web-page-steps-location web-is-not-mobile" style="--location:{progress * 100}%;"> <div
class="web-page-steps-location web-is-not-mobile"
style="--location:{progress * 100}%;"
>
<span class="web-page-steps-location-button"> <span class="web-page-steps-location-button">
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"

View File

@@ -1,168 +0,0 @@
<script lang="ts">
import embla from 'embla-carousel-svelte';
import {
type EmblaCarouselType,
type EmblaEventType,
type EmblaOptionsType,
type EmblaPluginType
} from 'embla-carousel';
import { WheelGesturesPlugin } from 'embla-carousel-wheel-gestures';
let emblaApi: EmblaCarouselType;
let options: EmblaOptionsType = {
align: 'center',
skipSnaps: true,
loop: true
};
let hasPrev: boolean = false;
let hasNext: boolean = true;
const togglePrevNextBtnsState = () => {
if (emblaApi.canScrollPrev()) hasPrev = true;
else hasPrev = false;
if (emblaApi.canScrollNext()) hasNext = true;
else hasNext = false;
};
let plugins: EmblaPluginType[] = [WheelGesturesPlugin()];
let selectedScrollIndex = 0;
const onSelect = (index: number) => {
emblaApi.scrollTo(index);
selectedScrollIndex = emblaApi.selectedScrollSnap();
};
const onPrev = () => {
emblaApi.scrollPrev();
selectedScrollIndex = emblaApi.selectedScrollSnap();
};
const onNext = () => {
emblaApi.scrollNext();
selectedScrollIndex = emblaApi.selectedScrollSnap();
};
const TWEEN_FACTOR_BASE = 0.52;
let tweenFactor = 0;
let tweenNodes: HTMLElement[] = [];
const numberWithinRange = (number: number, min: number, max: number) =>
Math.min(Math.max(number, min), max);
const setTweenNodes = (emblaApi: EmblaCarouselType): void => {
tweenNodes = emblaApi.slideNodes().map((slideNode) => {
return slideNode.querySelector('.embla__slide__number') as HTMLElement;
});
};
const setTweenFactor = (emblaApi: EmblaCarouselType): void => {
tweenFactor = TWEEN_FACTOR_BASE * emblaApi.scrollSnapList().length;
};
const tweenScale = (emblaApi: EmblaCarouselType, eventName?: EmblaEventType): void => {
const engine = emblaApi.internalEngine();
const scrollProgress = emblaApi.scrollProgress();
const slidesInView = emblaApi.slidesInView();
const isScrollEvent = eventName === 'scroll';
emblaApi.scrollSnapList().forEach((scrollSnap, snapIndex) => {
let diffToTarget = scrollSnap - scrollProgress;
const slidesInSnap = engine.slideRegistry[snapIndex];
slidesInSnap.forEach((slideIndex) => {
if (isScrollEvent && !slidesInView.includes(slideIndex)) return;
if (engine.options.loop) {
engine.slideLooper.loopPoints.forEach((loopItem) => {
const target = loopItem.target();
if (slideIndex === loopItem.index && target !== 0) {
const sign = Math.sign(target);
if (sign === -1) {
diffToTarget = scrollSnap - (1 + scrollProgress);
}
if (sign === 1) {
diffToTarget = scrollSnap + (1 - scrollProgress);
}
}
});
}
const tweenValue = 1 - Math.abs(diffToTarget * tweenFactor);
const scale = numberWithinRange(tweenValue, 0.8, 1).toString();
const tweenNode = tweenNodes[slideIndex];
tweenNode.style.transform = `scale(${scale})`;
});
});
};
const onEmblaInit = (event: CustomEvent<EmblaCarouselType>) => {
emblaApi = event.detail;
setTweenNodes(emblaApi);
setTweenFactor(emblaApi);
tweenScale(emblaApi);
emblaApi
.on('scroll', () => {
selectedScrollIndex = emblaApi.selectedScrollSnap();
})
.on('init', togglePrevNextBtnsState)
.on('select', togglePrevNextBtnsState)
.on('reInit', togglePrevNextBtnsState)
.on('reInit', setTweenNodes)
.on('reInit', setTweenFactor)
.on('reInit', tweenScale)
.on('scroll', tweenScale)
.on('slideFocus', tweenScale);
};
</script>
<div class="embla web-carousel">
{#if hasPrev}
<button class="web-carousel-button web-carousel-button-start" on:click={onPrev}>
<span class="web-icon-arrow-left" aria-hidden="true"></span>
</button>
{/if}
{#if hasNext}
<button class="web-carousel-button web-carousel-button-end" on:click={onNext}>
<span class="web-icon-arrow-right" aria-hidden="true"></span>
</button>
{/if}
<div class="embla__viewport" use:embla={{ options, plugins }} on:emblaInit={onEmblaInit}>
<ul class="embla__container">
<slot />
</ul>
</div>
</div>
<div class="web-carousel-bullets">
<ul class="web-carousel-bullets-list">
{#each Array.from({ length: emblaApi?.scrollSnapList().length }) as _, i}
<li class="web-carousel-bullets-item">
<button
class="web-carousel-bullets-button"
class:is-selected={selectedScrollIndex === i}
aria-label={`gallery item ${i + 1}`}
on:click={() => onSelect(i)}
></button>
</li>
{/each}
</ul>
</div>
<style>
.embla {
overflow: hidden;
position: relative;
}
.embla__container {
display: flex;
}
</style>

View File

@@ -1,25 +0,0 @@
<li class="slide web-carousel-item">
<div class="embla__slide__number">
<slot />
</div>
</li>
<style lang="scss">
@use '$scss/abstract' as *;
.slide {
cursor: grab;
&:active {
cursor: grabbing;
}
@media (max-width: 768px) {
flex: 0 0 100%;
}
flex: 0 0 50%;
min-width: 0;
margin-right: pxToRem(16);
}
</style>

View File

@@ -1,2 +0,0 @@
export { default as Root } from './Carousel.svelte';
export { default as Slide } from './CarouselSlide.svelte';

View File

@@ -1,16 +1,16 @@
export { default as FooterNav } from './FooterNav.svelte'; export { default as FooterNav } from "./FooterNav.svelte";
export { default as MainFooter } from './MainFooter.svelte'; export { default as MainFooter } from "./MainFooter.svelte";
export { default as PreFooter } from './PreFooter.svelte'; export { default as PreFooter } from "./PreFooter.svelte";
export { default as MobileNav } from './MobileNav.svelte'; export { default as MobileNav } from "./MobileNav.svelte";
export { default as Switch } from './Switch.svelte'; export { default as Switch } from "./Switch.svelte";
export { default as Newsletter } from './Newsletter.svelte'; export { default as Newsletter } from "./Newsletter.svelte";
export { default as Tooltip } from './Tooltip.svelte'; export { default as Tooltip } from "./Tooltip.svelte";
export { default as Article } from './Article.svelte'; export { default as Article } from "./Article.svelte";
export { default as Carousel } from './Carousel.svelte'; export { default as Carousel } from "./Carousel.svelte";
export { default as FloatingHeads } from './FloatingHeads.svelte'; export { default as FloatingHeads } from "./FloatingHeads.svelte";
export { default as FloatingHead } from './FloatingHead.svelte'; export { default as FloatingHead } from "./FloatingHead.svelte";
export { default as Feedback } from './Feedback.svelte'; export { default as Feedback } from "./Feedback.svelte";
export { default as Select } from './Select.svelte'; export { default as Select } from "./Select.svelte";
export { default as MetricCard } from './MetricCard.svelte'; export { default as MetricCard } from "./MetricCard.svelte";
export { default as IsLoggedIn } from './IsLoggedIn.svelte'; export { default as IsLoggedIn } from "./IsLoggedIn.svelte";
export { default as Search } from './Search.svelte'; export { default as Search } from "./Search.svelte";

View File

@@ -0,0 +1,97 @@
<script lang="ts">
import { classNames } from "$lib/utils/classnames";
import type {
HTMLButtonAttributes,
HTMLAnchorAttributes,
} from "svelte/elements";
import { cva, type VariantProps } from "cva";
const button = cva(
[
"flex w-fit min-h-10 items-center justify-center gap-2 rounded-lg px-4 text-white transition-all select-none",
],
{
variants: {
variant: {
primary: [
"to-secondary-100 bg-gradient-to-br from-pink-500 via-pink-500",
"hover:shadow-[0_0_2rem_#fd366e52]",
],
secondary: ["bg-[#fd366e0a] relative"],
text: [
"bg-transparent border-transparent",
"hover:bg-gradient-to-b from-[#ffffff0f] via-[#ffffff1a] to-[#ffffff0f];",
],
},
},
},
);
type ButtonProps =
| (HTMLButtonAttributes & { href?: undefined })
| (HTMLAnchorAttributes & { href: string });
type $$Props = ButtonProps & VariantProps<typeof button>;
export let href: $$Props["href"] = undefined;
export let variant: $$Props["variant"] = "primary";
const { class: classes, ...props } = $$restProps;
const buttonClasses = classNames(button({ variant }), classes);
</script>
{#if href}
<a
{...props}
{href}
class={buttonClasses}
class:has-border-gradient={variant === "secondary"}
>
<slot />
</a>
{:else}
<button
{...props}
class={buttonClasses}
class:has-border-gradient={variant === "secondary"}
>
<slot />
</button>
{/if}
<style>
.has-border-gradient {
--border-gradient-before: linear-gradient(
to bottom,
rgba(253, 54, 110, 0.48) 0%,
rgba(253, 54, 110, 0) 180%
);
--border-gradient-after: radial-gradient(
42.86% 42.86% at 50.55% -0%,
rgba(255, 255, 255, 0.2) 0%,
rgba(255, 255, 255, 0) 100%
);
&::before {
background: var(--border-gradient-before) border-box;
}
&::after {
background: var(--border-gradient-after);
}
&::before,
&::after {
content: "";
position: absolute;
inset: 0;
border-radius: var(--radius-lg);
border: 1px solid transparent;
mask:
linear-gradient(#fff 0 0) padding-box,
linear-gradient(#fff 0 0);
mask-composite: exclude;
pointer-events: none;
}
}
</style>

View File

@@ -0,0 +1,97 @@
<script lang="ts">
import { classNames } from "$lib/utils/classnames";
import type {
HTMLButtonAttributes,
HTMLAnchorAttributes,
} from "svelte/elements";
import { cva, type VariantProps } from "cva";
const card = cva(
[
"flex w-fit min-h-10 items-center justify-center gap-2 rounded-2xl px-6 text-white transition-all select-none",
],
{
variants: {
variant: {
primary: [
"to-secondary-100 bg-gradient-to-br from-pink-500 via-pink-500",
"hover:shadow-[0_0_2rem_#fd366e52]",
],
secondary: ["bg-[#fd366e0a] relative"],
text: [
"bg-transparent border-transparent",
"hover:bg-gradient-to-b from-[#ffffff0f] via-[#ffffff1a] to-[#ffffff0f];",
],
},
},
},
);
type ButtonProps =
| (HTMLButtonAttributes & { href?: undefined })
| (HTMLAnchorAttributes & { href: string });
type $$Props = ButtonProps & VariantProps<typeof card>;
export let href: $$Props["href"] = undefined;
export let variant: $$Props["variant"] = "primary";
const { class: classes, ...props } = $$restProps;
const buttonClasses = classNames(card({ variant }), classes);
</script>
{#if href}
<a
{...props}
{href}
class={buttonClasses}
class:has-border-gradient={variant === "secondary"}
>
<slot />
</a>
{:else}
<button
{...props}
class={buttonClasses}
class:has-border-gradient={variant === "secondary"}
>
<slot />
</button>
{/if}
<style>
.has-border-gradient {
--border-gradient-before: linear-gradient(
to bottom,
rgba(253, 54, 110, 0.48) 0%,
rgba(253, 54, 110, 0) 180%
);
--border-gradient-after: radial-gradient(
42.86% 42.86% at 50.55% -0%,
rgba(255, 255, 255, 0.2) 0%,
rgba(255, 255, 255, 0) 100%
);
&::before {
background: var(--border-gradient-before) border-box;
}
&::after {
background: var(--border-gradient-after);
}
&::before,
&::after {
content: "";
position: absolute;
inset: 0;
border-radius: var(--radius-lg);
border: 1px solid transparent;
mask:
linear-gradient(#fff 0 0) padding-box,
linear-gradient(#fff 0 0);
mask-composite: exclude;
pointer-events: none;
}
}
</style>

View File