mirror of
https://github.com/LukeHagar/website.git
synced 2025-12-09 12:57:48 +00:00
@@ -1,35 +1,35 @@
|
||||
<script lang="ts">
|
||||
export let open: boolean = false;
|
||||
export let title: string;
|
||||
export let open: boolean = false;
|
||||
export let title: string;
|
||||
</script>
|
||||
|
||||
<li class="collapsible-item">
|
||||
<details class="collapsible-wrapper" {open}>
|
||||
<summary class="collapsible-button">
|
||||
<span class="text">{title}</span>
|
||||
<div class="icon web-u-color-text-primary">
|
||||
<span class="icon-cheveron-down" aria-hidden="true" />
|
||||
</div>
|
||||
</summary>
|
||||
<div class="collapsible-content u-flex-vertical">
|
||||
<slot />
|
||||
</div>
|
||||
</details>
|
||||
<details class="collapsible-wrapper" {open}>
|
||||
<summary class="collapsible-button">
|
||||
<span class="text">{title}</span>
|
||||
<div class="icon web-u-color-text-primary">
|
||||
<span class="icon-cheveron-down" aria-hidden="true" />
|
||||
</div>
|
||||
</summary>
|
||||
<div class="collapsible-content flex flex-col">
|
||||
<slot />
|
||||
</div>
|
||||
</details>
|
||||
</li>
|
||||
|
||||
<style>
|
||||
.collapsible-item {
|
||||
border-block-end: 0.0625rem solid hsl(var(--web-color-offset));
|
||||
}
|
||||
.collapsible-item {
|
||||
border-block-end: 0.0625rem solid hsl(var(--web-color-offset));
|
||||
}
|
||||
|
||||
.collapsible-button {
|
||||
padding-block-start: 1rem;
|
||||
padding-block-end: 1rem;
|
||||
}
|
||||
.collapsible-button {
|
||||
padding-block-start: 1rem;
|
||||
padding-block-end: 1rem;
|
||||
}
|
||||
|
||||
.collapsible-content {
|
||||
margin-block-start: 0;
|
||||
padding-block-end: 0;
|
||||
gap: 4px;
|
||||
}
|
||||
.collapsible-content {
|
||||
margin-block-start: 0;
|
||||
padding-block-end: 0;
|
||||
gap: 4px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
<ul class="collapsible u-width-full-line" style="--p-toggle-border-color: var(--web-color-border);">
|
||||
<slot />
|
||||
<ul
|
||||
class="collapsible w-full"
|
||||
style="--p-toggle-border-color: var(--web-color-border);"
|
||||
>
|
||||
<slot />
|
||||
</ul>
|
||||
|
||||
<style>
|
||||
.collapsible {
|
||||
padding-block-start: 0;
|
||||
padding-block-end: 2rem;
|
||||
}
|
||||
</style>
|
||||
.collapsible {
|
||||
padding-block-start: 0;
|
||||
padding-block-end: 2rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
import Root from "./Root.svelte";
|
||||
import Item from "./Item.svelte";
|
||||
import Root from './Root.svelte';
|
||||
import Item from './Item.svelte';
|
||||
|
||||
export {
|
||||
Root as Accordion,
|
||||
Item as AccordionItem
|
||||
}
|
||||
export { Root as Accordion, Item as AccordionItem };
|
||||
|
||||
@@ -1,45 +1,52 @@
|
||||
<script lang="ts">
|
||||
import Media from '$lib/UI/Media.svelte';
|
||||
import { formatDate } from '$lib/utils/date';
|
||||
import Media from "$lib/UI/Media.svelte";
|
||||
import { formatDate } from "$lib/utils/date";
|
||||
|
||||
export let title: string;
|
||||
export let cover: string;
|
||||
export let href: string;
|
||||
export let date: Date;
|
||||
export let timeToRead: number;
|
||||
export let author: string;
|
||||
export let avatar: string;
|
||||
export let title: string;
|
||||
export let cover: string;
|
||||
export let href: string;
|
||||
export let date: Date;
|
||||
export let timeToRead: number;
|
||||
export let author: string;
|
||||
export let avatar: string;
|
||||
</script>
|
||||
|
||||
<li>
|
||||
<a class="web-grid-articles-item is-transparent" {href}>
|
||||
<div class="web-grid-articles-item-image">
|
||||
<Media
|
||||
src={cover}
|
||||
class="web-u-media-ratio-16-9"
|
||||
alt={title}
|
||||
autoplay
|
||||
controls={false}
|
||||
/>
|
||||
<a class="web-grid-articles-item is-transparent" {href}>
|
||||
<div class="web-grid-articles-item-image">
|
||||
<Media
|
||||
src={cover}
|
||||
class="web-u-media-ratio-16-9"
|
||||
alt={title}
|
||||
autoplay
|
||||
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 class="web-grid-articles-item-content">
|
||||
<h4 class="web-label web-u-color-text-primary">
|
||||
{title}
|
||||
</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>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
@@ -1,146 +1,152 @@
|
||||
<script lang="ts">
|
||||
let carousel: HTMLElement;
|
||||
let carousel: HTMLElement;
|
||||
|
||||
export let size: 'default' | 'medium' | 'big' = 'default';
|
||||
export let gap = 32;
|
||||
let scroll = 0;
|
||||
export let size: "default" | "medium" | "big" = "default";
|
||||
export let gap = 32;
|
||||
let scroll = 0;
|
||||
|
||||
function calculateScrollAmount(prev = false) {
|
||||
const direction = prev ? -1 : 1;
|
||||
const carouselSize = carousel?.clientWidth;
|
||||
const childSize = (carousel.childNodes[0] as HTMLUListElement)?.clientWidth + gap;
|
||||
function calculateScrollAmount(prev = false) {
|
||||
const direction = prev ? -1 : 1;
|
||||
const carouselSize = carousel?.clientWidth;
|
||||
const childSize =
|
||||
(carousel.childNodes[0] as HTMLUListElement)?.clientWidth + gap;
|
||||
|
||||
scroll = scroll || carouselSize;
|
||||
scroll = scroll || carouselSize;
|
||||
|
||||
const numberOfItems = Math.floor(carouselSize / childSize);
|
||||
const overflow = scroll % childSize;
|
||||
const amount = numberOfItems * childSize - overflow * direction;
|
||||
scroll += amount * direction;
|
||||
return amount * direction;
|
||||
}
|
||||
const numberOfItems = Math.floor(carouselSize / childSize);
|
||||
const overflow = scroll % childSize;
|
||||
const amount = numberOfItems * childSize - overflow * direction;
|
||||
scroll += amount * direction;
|
||||
return amount * direction;
|
||||
}
|
||||
|
||||
function next() {
|
||||
carousel.scrollBy({
|
||||
left: calculateScrollAmount(),
|
||||
behavior: 'smooth'
|
||||
});
|
||||
}
|
||||
function prev() {
|
||||
carousel.scrollBy({
|
||||
left: calculateScrollAmount(true),
|
||||
behavior: 'smooth'
|
||||
});
|
||||
}
|
||||
function next() {
|
||||
carousel.scrollBy({
|
||||
left: calculateScrollAmount(),
|
||||
behavior: "smooth",
|
||||
});
|
||||
}
|
||||
function prev() {
|
||||
carousel.scrollBy({
|
||||
left: calculateScrollAmount(true),
|
||||
behavior: "smooth",
|
||||
});
|
||||
}
|
||||
|
||||
let isEnd = false;
|
||||
let isStart = true;
|
||||
let isEnd = false;
|
||||
let isStart = true;
|
||||
|
||||
function handleScroll() {
|
||||
isStart = carousel.scrollLeft <= 0;
|
||||
isEnd = Math.ceil(carousel.scrollLeft + carousel.offsetWidth) >= carousel.scrollWidth;
|
||||
}
|
||||
function handleScroll() {
|
||||
isStart = carousel.scrollLeft <= 0;
|
||||
isEnd =
|
||||
Math.ceil(carousel.scrollLeft + carousel.offsetWidth) >=
|
||||
carousel.scrollWidth;
|
||||
}
|
||||
</script>
|
||||
|
||||
<div>
|
||||
<div class="u-flex u-flex-wrap u-cross-center u-margin-block-start-8">
|
||||
<slot name="header" />
|
||||
<div class="nav u-flex u-gap-12 u-cross-end u-margin-inline-start-auto">
|
||||
<button
|
||||
class="web-icon-button"
|
||||
aria-label="Move carousel backward"
|
||||
disabled={isStart}
|
||||
on:click={prev}
|
||||
>
|
||||
<span class="web-icon-arrow-left" aria-hidden="true" />
|
||||
</button>
|
||||
<button
|
||||
class="web-icon-button"
|
||||
aria-label="Move carousel forward"
|
||||
disabled={isEnd}
|
||||
on:click={next}
|
||||
>
|
||||
<span class="web-icon-arrow-right" aria-hidden="true" />
|
||||
</button>
|
||||
</div>
|
||||
<div class="mt-2 flex flex-wrap items-center">
|
||||
<slot name="header" />
|
||||
<div class="nav ml-auto flex items-end gap-3">
|
||||
<button
|
||||
class="web-icon-button"
|
||||
aria-label="Move carousel backward"
|
||||
disabled={isStart}
|
||||
on:click={prev}
|
||||
>
|
||||
<span class="web-icon-arrow-left" aria-hidden="true" />
|
||||
</button>
|
||||
<button
|
||||
class="web-icon-button"
|
||||
aria-label="Move carousel forward"
|
||||
disabled={isEnd}
|
||||
on:click={next}
|
||||
>
|
||||
<span class="web-icon-arrow-right" aria-hidden="true" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="carousel-wrapper" data-state={isStart ? 'start' : isEnd ? 'end' : 'middle'}>
|
||||
<ul
|
||||
class="web-grid-articles u-margin-block-start-32 carousel"
|
||||
class:is-medium={size === 'medium'}
|
||||
class:is-big={size === 'big'}
|
||||
style:gap="{gap}px"
|
||||
bind:this={carousel}
|
||||
on:scroll={handleScroll}
|
||||
>
|
||||
<slot />
|
||||
</ul>
|
||||
</div>
|
||||
<div
|
||||
class="carousel-wrapper"
|
||||
data-state={isStart ? "start" : isEnd ? "end" : "middle"}
|
||||
>
|
||||
<ul
|
||||
class="web-grid-articles carousel mt-8"
|
||||
class:is-medium={size === "medium"}
|
||||
class:is-big={size === "big"}
|
||||
style:gap="{gap}px"
|
||||
bind:this={carousel}
|
||||
on:scroll={handleScroll}
|
||||
>
|
||||
<slot />
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.nav {
|
||||
button {
|
||||
@media screen and (max-width: 1023.9px) {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
.nav {
|
||||
button {
|
||||
@media screen and (max-width: 1023.9px) {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
.carousel-wrapper {
|
||||
position: relative;
|
||||
}
|
||||
.carousel-wrapper {
|
||||
position: relative;
|
||||
|
||||
&::before,
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 60px;
|
||||
height: 100%;
|
||||
transition: ease 250ms;
|
||||
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;
|
||||
}
|
||||
&::before,
|
||||
&::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 60px;
|
||||
height: 100%;
|
||||
transition: ease 250ms;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.carousel {
|
||||
grid-auto-flow: column;
|
||||
overflow-x: scroll;
|
||||
scroll-snap-type: x proximity;
|
||||
|
||||
scrollbar-width: none;
|
||||
-ms-overflow-style: none;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
&::before {
|
||||
left: 0;
|
||||
background: linear-gradient(
|
||||
to right,
|
||||
hsl(var(--web-color-background-docs)),
|
||||
transparent
|
||||
);
|
||||
}
|
||||
|
||||
.carousel :global(li) {
|
||||
scroll-margin: 48px;
|
||||
&[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 {
|
||||
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>
|
||||
|
||||
@@ -1,152 +1,166 @@
|
||||
<script lang="ts">
|
||||
import { page } from '$app/stores';
|
||||
import { page } from "$app/stores";
|
||||
|
||||
export let date: string | undefined = undefined;
|
||||
let showFeedback = false;
|
||||
let feedbackType = '';
|
||||
let email = '';
|
||||
let comment = '';
|
||||
let error: string | undefined;
|
||||
let submitted = false;
|
||||
let submitting = false;
|
||||
export let date: string | undefined = undefined;
|
||||
let showFeedback = false;
|
||||
let feedbackType = "";
|
||||
let email = "";
|
||||
let comment = "";
|
||||
let error: string | undefined;
|
||||
let submitted = false;
|
||||
let submitting = false;
|
||||
|
||||
async function handleSubmit() {
|
||||
submitting = true;
|
||||
error = undefined;
|
||||
const response = await fetch('https://growth.appwrite.io/v1/feedback/docs', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
email,
|
||||
type: feedbackType,
|
||||
route: $page.route.id,
|
||||
comment
|
||||
})
|
||||
});
|
||||
submitting = false;
|
||||
if (response.status >= 400) {
|
||||
error = response.status >= 500 ? 'Server Error.' : 'Error submitting form.';
|
||||
return;
|
||||
}
|
||||
comment = email = '';
|
||||
submitted = true;
|
||||
async function handleSubmit() {
|
||||
submitting = true;
|
||||
error = undefined;
|
||||
const response = await fetch(
|
||||
"https://growth.appwrite.io/v1/feedback/docs",
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
email,
|
||||
type: feedbackType,
|
||||
route: $page.route.id,
|
||||
comment,
|
||||
}),
|
||||
},
|
||||
);
|
||||
submitting = false;
|
||||
if (response.status >= 400) {
|
||||
error =
|
||||
response.status >= 500 ? "Server Error." : "Error submitting form.";
|
||||
return;
|
||||
}
|
||||
comment = email = "";
|
||||
submitted = true;
|
||||
}
|
||||
|
||||
function reset() {
|
||||
comment = email = '';
|
||||
feedbackType = '';
|
||||
submitted = false;
|
||||
error = undefined;
|
||||
}
|
||||
function reset() {
|
||||
comment = email = "";
|
||||
feedbackType = "";
|
||||
submitted = false;
|
||||
error = undefined;
|
||||
}
|
||||
|
||||
$: if (!showFeedback) {
|
||||
reset();
|
||||
}
|
||||
$: if (!showFeedback) {
|
||||
reset();
|
||||
}
|
||||
</script>
|
||||
|
||||
<section class="web-content-footer">
|
||||
<header class="web-content-footer-header u-width-full-line">
|
||||
<div
|
||||
class="u-flex u-gap-32 u-main-space-between u-cross-center u-width-full-line"
|
||||
style="flex-wrap: wrap-reverse;"
|
||||
>
|
||||
<div class="u-flex u-gap-16 u-cross-center">
|
||||
<h5 class="web-main-body-600 web-u-color-text-primary">Was this page helpful?</h5>
|
||||
<div class="u-flex u-gap-8">
|
||||
<button
|
||||
class="web-radio-button"
|
||||
aria-label="helpful"
|
||||
on:click={() => {
|
||||
showFeedback = feedbackType === 'positive' ? false : true;
|
||||
feedbackType = 'positive';
|
||||
}}
|
||||
>
|
||||
<span class="icon-thumb-up" />
|
||||
</button>
|
||||
<button
|
||||
class="web-radio-button"
|
||||
aria-label="unhelpful"
|
||||
on:click={() => {
|
||||
showFeedback = feedbackType === 'negative' ? false : true;
|
||||
feedbackType = 'negative';
|
||||
}}
|
||||
>
|
||||
<!-- TODO: fix the icon name on pink -->
|
||||
<span class="icon-thumb-dowm" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<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>
|
||||
<header class="web-content-footer-header w-full">
|
||||
<div
|
||||
class="flex w-full items-center justify-between gap-8"
|
||||
style="flex-wrap: wrap-reverse;"
|
||||
>
|
||||
<div class="flex items-center gap-4">
|
||||
<h5 class="web-main-body-600 web-u-color-text-primary">
|
||||
Was this page helpful?
|
||||
</h5>
|
||||
<div class="flex gap-2">
|
||||
<button
|
||||
class="web-radio-button"
|
||||
aria-label="helpful"
|
||||
on:click={() => {
|
||||
showFeedback = feedbackType === "positive" ? false : true;
|
||||
feedbackType = "positive";
|
||||
}}
|
||||
>
|
||||
<span class="icon-thumb-up" />
|
||||
</button>
|
||||
<button
|
||||
class="web-radio-button"
|
||||
aria-label="unhelpful"
|
||||
on:click={() => {
|
||||
showFeedback = feedbackType === "negative" ? false : true;
|
||||
feedbackType = "negative";
|
||||
}}
|
||||
>
|
||||
<!-- TODO: fix the icon name on pink -->
|
||||
<span class="icon-thumb-dowm" />
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
{#if showFeedback}
|
||||
<form
|
||||
on:submit|preventDefault={handleSubmit}
|
||||
class="web-card is-normal"
|
||||
style="--card-padding:1rem"
|
||||
>
|
||||
<div class="u-flex-vertical u-gap-8">
|
||||
<label for="message">
|
||||
<span class="web-u-color-text-primary">
|
||||
What did you {feedbackType === 'negative' ? 'dislike' : 'like'}? (optional)
|
||||
</span>
|
||||
</label>
|
||||
<textarea
|
||||
class="web-input-text"
|
||||
id="message"
|
||||
placeholder="Write your message"
|
||||
bind:value={comment}
|
||||
/>
|
||||
<label for="message" class="u-margin-block-start-8">
|
||||
<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 u-margin-block-start-16">
|
||||
Your message has been sent successfully. We appreciate your feedback.
|
||||
</p>
|
||||
{/if}
|
||||
{#if error}
|
||||
<p class="web-u-color-text-primary u-margin-block-start-16">
|
||||
There was an error submitting your feedback. Please try again later.
|
||||
</p>
|
||||
{/if}
|
||||
</div>
|
||||
<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 flex items-baseline gap-1"
|
||||
>
|
||||
<span class="icon-pencil-alt contents" aria-hidden="true" />
|
||||
<span>Update on GitHub</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
{#if showFeedback}
|
||||
<form
|
||||
on:submit|preventDefault={handleSubmit}
|
||||
class="web-card is-normal"
|
||||
style="--card-padding:1rem"
|
||||
>
|
||||
<div class="flex flex-col gap-2">
|
||||
<label for="message">
|
||||
<span class="web-u-color-text-primary">
|
||||
What did you {feedbackType === "negative" ? "dislike" : "like"}?
|
||||
(optional)
|
||||
</span>
|
||||
</label>
|
||||
<textarea
|
||||
class="web-input-text"
|
||||
id="message"
|
||||
placeholder="Write your message"
|
||||
bind:value={comment}
|
||||
/>
|
||||
<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">
|
||||
<button class="web-button is-text" on:click={() => (showFeedback = false)}>
|
||||
<span>Cancel</span>
|
||||
</button>
|
||||
<button type="submit" class="web-button" disabled={submitting || !email}>
|
||||
<span>Submit</span>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
{/if}
|
||||
<div class="mt-4 flex justify-end gap-2">
|
||||
<button
|
||||
class="web-button is-text"
|
||||
on:click={() => (showFeedback = false)}
|
||||
>
|
||||
<span>Cancel</span>
|
||||
</button>
|
||||
<button
|
||||
type="submit"
|
||||
class="web-button"
|
||||
disabled={submitting || !email}
|
||||
>
|
||||
<span>Submit</span>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
{/if}
|
||||
</section>
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
export let images: Array<string>;
|
||||
</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}
|
||||
{@const image = clamp(0, images.length - 1, i % images.length)}
|
||||
<FloatingHead
|
||||
@@ -33,7 +33,7 @@
|
||||
{size}
|
||||
/>
|
||||
<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>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
{ label: 'Functions', href: '/docs/products/functions' },
|
||||
{ label: 'Messaging', href: '/products/messaging' },
|
||||
{ label: 'Storage', href: '/docs/products/storage' },
|
||||
{ label: 'Realtime', href: '/docs/apis/realtime' },
|
||||
{ label: 'Realtime', href: '/docs/apis/realtime' }
|
||||
],
|
||||
Learn: [
|
||||
{ label: 'Docs', href: '/docs' },
|
||||
@@ -93,7 +93,7 @@
|
||||
|
||||
<nav
|
||||
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}
|
||||
>
|
||||
<img class="web-logo" src="/images/logos/appwrite.svg" alt="appwrite" height="24" width="130" />
|
||||
|
||||
@@ -1,27 +1,32 @@
|
||||
<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>
|
||||
|
||||
<a href={PUBLIC_APPWRITE_DASHBOARD} class={`web-button ${classes}`}>
|
||||
<span class="logged-in"><slot name="isLoggedIn">Go to Console</slot></span>
|
||||
<span class="not-logged-in"><slot name="isNotLoggedIn">Get started</slot></span>
|
||||
</a>
|
||||
<Button class={classes} href={PUBLIC_APPWRITE_DASHBOARD}>
|
||||
<span class="hidden [data-logged-in]:block"
|
||||
><slot name="isLoggedIn">Go to Console</slot></span
|
||||
>
|
||||
<span class="btton [data-logged-in]:hidden"
|
||||
><slot name="isNotLoggedIn">Get started</slot></span
|
||||
>
|
||||
</Button>
|
||||
|
||||
<style lang="scss">
|
||||
:global(body[data-logged-in]) {
|
||||
.logged-in {
|
||||
display: block;
|
||||
}
|
||||
.not-logged-in {
|
||||
display: none;
|
||||
}
|
||||
:global(body[data-logged-in]) {
|
||||
.logged-in {
|
||||
display: block;
|
||||
}
|
||||
.not-logged-in {
|
||||
display: block;
|
||||
}
|
||||
.logged-in {
|
||||
display: none;
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
.not-logged-in {
|
||||
display: block;
|
||||
}
|
||||
.logged-in {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
|
||||
98
src/lib/components/LogoList.svelte
Normal file
98
src/lib/components/LogoList.svelte
Normal 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>
|
||||
@@ -1,114 +1,118 @@
|
||||
<script lang="ts">
|
||||
import { socials } from '$lib/constants';
|
||||
import ThemeSelect from './ThemeSelect.svelte';
|
||||
import { socials } from "$lib/constants";
|
||||
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>
|
||||
|
||||
{#if variant === 'homepage'}
|
||||
<footer class="web-main-footer u-position-relative u-margin-block-start-48">
|
||||
<ul class="u-flex u-gap-8">
|
||||
{#each socials as social}
|
||||
<li>
|
||||
<a
|
||||
href={social.link}
|
||||
class="web-icon-button"
|
||||
aria-label={social.label}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<span class={social.icon} aria-hidden="true" />
|
||||
</a>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
<div class="e-main-footer">
|
||||
<div>Copyright © {year} Appwrite</div>
|
||||
{#if variant === "homepage"}
|
||||
<footer class="web-main-footer relative mt-12">
|
||||
<ul class="flex gap-2">
|
||||
{#each socials as social}
|
||||
<li>
|
||||
<a
|
||||
href={social.link}
|
||||
class="web-icon-button"
|
||||
aria-label={social.label}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<span class={social.icon} aria-hidden="true" />
|
||||
</a>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
<div class="e-main-footer">
|
||||
<div>Copyright © {year} Appwrite</div>
|
||||
|
||||
<iframe
|
||||
class="status"
|
||||
title="Appwrite Status"
|
||||
src="https://status.appwrite.online/badge?theme=dark"
|
||||
width="190"
|
||||
height="30"
|
||||
frameborder="0"
|
||||
scrolling="no"
|
||||
style:color-scheme="none"
|
||||
style:margin-top="-4px"
|
||||
/>
|
||||
<iframe
|
||||
class="status"
|
||||
title="Appwrite Status"
|
||||
src="https://status.appwrite.online/badge?theme=dark"
|
||||
width="190"
|
||||
height="30"
|
||||
frameborder="0"
|
||||
scrolling="no"
|
||||
style:color-scheme="none"
|
||||
style:margin-top="-4px"
|
||||
/>
|
||||
|
||||
<ul class="u-flex u-gap-16">
|
||||
<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="/cookies">Cookies</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</footer>
|
||||
{:else if variant === 'docs'}
|
||||
<footer
|
||||
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 u-flex u-gap-8">
|
||||
{#each socials as social}
|
||||
<li>
|
||||
<a
|
||||
href={social.link}
|
||||
class="web-icon-button"
|
||||
aria-label={social.label}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<span class={social.icon} aria-hidden="true" />
|
||||
</a>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
<div class="web-main-footer-grid-1-column-2">
|
||||
<ThemeSelect />
|
||||
</div>
|
||||
<ul class="web-main-footer-grid-1-column-3 u-cross-center web-main-footer-links">
|
||||
<li>
|
||||
<a href="/discord" target="_blank" rel="noopener noreferrer">Support</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://appwrite.online" target="_blank" rel="noopener noreferrer"
|
||||
>Status</a
|
||||
>
|
||||
</li>
|
||||
<!-- <li>
|
||||
<ul class="flex gap-4">
|
||||
<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="/cookies">Cookies</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</footer>
|
||||
{:else if variant === "docs"}
|
||||
<footer class="web-main-footer is-with-bg-color relative mt-8 text-sm">
|
||||
<div class="web-main-footer-grid-1">
|
||||
<ul class="web-main-footer-grid-1-column-1 flex gap-2">
|
||||
{#each socials as social}
|
||||
<li>
|
||||
<a
|
||||
href={social.link}
|
||||
class="web-icon-button"
|
||||
aria-label={social.label}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<span class={social.icon} aria-hidden="true" />
|
||||
</a>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
<div class="web-main-footer-grid-1-column-2">
|
||||
<ThemeSelect />
|
||||
</div>
|
||||
<ul
|
||||
class="web-main-footer-grid-1-column-3 web-main-footer-links items-center"
|
||||
>
|
||||
<li>
|
||||
<a href="/discord" target="_blank" rel="noopener noreferrer"
|
||||
>Support</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href="https://appwrite.online"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer">Status</a
|
||||
>
|
||||
</li>
|
||||
<!-- <li>
|
||||
<a href="https://github.com/appwrite/appwrite/releases" target="_blank" rel="noopener noreferrer">Changelog</a>
|
||||
</li> -->
|
||||
</ul>
|
||||
<div class="web-main-footer-grid-1-column-4 web-main-footer-copyright">
|
||||
Copyright © {year} Appwrite
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
</ul>
|
||||
<div class="web-main-footer-grid-1-column-4 web-main-footer-copyright">
|
||||
Copyright © {year} Appwrite
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
{/if}
|
||||
|
||||
<style lang="scss">
|
||||
@use '$scss/abstract/variables/devices';
|
||||
.web-icon-button {
|
||||
display: grid;
|
||||
}
|
||||
@use "$scss/abstract/variables/devices";
|
||||
.web-icon-button {
|
||||
display: grid;
|
||||
}
|
||||
|
||||
.e-main-footer {
|
||||
display: flex;
|
||||
@media #{devices.$break1} {
|
||||
flex-direction: column;
|
||||
> * {
|
||||
padding-block: 1rem;
|
||||
&:not(:first-child) {
|
||||
border-block-start: solid 0.0625rem hsl(var(--web-color-border));
|
||||
}
|
||||
}
|
||||
}
|
||||
@media #{devices.$break2open} {
|
||||
display: flex;
|
||||
gap: 2rem;
|
||||
.e-main-footer {
|
||||
display: flex;
|
||||
@media #{devices.$break1} {
|
||||
flex-direction: column;
|
||||
> * {
|
||||
padding-block: 1rem;
|
||||
&:not(:first-child) {
|
||||
border-block-start: solid 0.0625rem hsl(var(--web-color-border));
|
||||
}
|
||||
}
|
||||
}
|
||||
@media #{devices.$break2open} {
|
||||
display: flex;
|
||||
gap: 2rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,55 +1,54 @@
|
||||
<script lang="ts">
|
||||
import { afterNavigate } from '$app/navigation';
|
||||
import { PUBLIC_APPWRITE_DASHBOARD } from '$env/static/public';
|
||||
import { IsLoggedIn } from '$lib/components';
|
||||
import { GITHUB_STARS } from '$lib/constants';
|
||||
import type { NavLink } from '$lib/layouts/Main.svelte';
|
||||
import { afterNavigate } from "$app/navigation";
|
||||
import { IsLoggedIn } from "$lib/components";
|
||||
import { GITHUB_STARS } from "$lib/constants";
|
||||
import type { NavLink } from "$lib/layouts/Main.svelte";
|
||||
|
||||
export let open = false;
|
||||
export let links: NavLink[];
|
||||
export let open = false;
|
||||
export let links: NavLink[];
|
||||
|
||||
afterNavigate(() => {
|
||||
open = false;
|
||||
});
|
||||
afterNavigate(() => {
|
||||
open = false;
|
||||
});
|
||||
</script>
|
||||
|
||||
<svelte:window on:resize={() => open && (open = false)} />
|
||||
|
||||
<nav class="web-side-nav web-is-not-desktop" class:u-hide={!open}>
|
||||
<div class="web-side-nav-wrapper web-u-padding-inline-16">
|
||||
<div class="u-flex items-center u-gap-8">
|
||||
<a
|
||||
href={`${PUBLIC_APPWRITE_DASHBOARD}/register`}
|
||||
class="web-button is-secondary web-u-flex-1"
|
||||
>
|
||||
Sign up
|
||||
</a>
|
||||
<IsLoggedIn classes="web-u-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>
|
||||
<nav class="web-side-nav web-is-not-desktop" class:hidden={!open}>
|
||||
<div class="web-side-nav-wrapper ps-4 pe-4">
|
||||
<div class="flex items-center gap-2">
|
||||
<a
|
||||
href="https://cloud.appwrite.io/register"
|
||||
class="web-button is-secondary flex-1"
|
||||
>
|
||||
Sign up
|
||||
</a>
|
||||
<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>
|
||||
</nav>
|
||||
|
||||
@@ -1,181 +1,206 @@
|
||||
<script context="module" lang="ts">
|
||||
export async function newsletter(name: string, email: string) {
|
||||
const response = await fetch('https://growth.appwrite.io/v1/newsletter/subscribe', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
name,
|
||||
email
|
||||
})
|
||||
});
|
||||
return response;
|
||||
}
|
||||
export async function newsletter(name: string, email: string) {
|
||||
const response = await fetch(
|
||||
"https://growth.appwrite.io/v1/newsletter/subscribe",
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
name,
|
||||
email,
|
||||
}),
|
||||
},
|
||||
);
|
||||
return response;
|
||||
}
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
let email = '';
|
||||
let name = '';
|
||||
let submitted = false;
|
||||
let error: string | undefined;
|
||||
let submitting = false;
|
||||
let email = "";
|
||||
let name = "";
|
||||
let submitted = false;
|
||||
let error: string | undefined;
|
||||
let submitting = false;
|
||||
|
||||
async function submit() {
|
||||
submitting = true;
|
||||
error = undefined;
|
||||
const response = await newsletter(name, email);
|
||||
submitting = false;
|
||||
if (response.status >= 400) {
|
||||
error = response.status >= 500 ? 'Server Error.' : 'Error submitting form.';
|
||||
return;
|
||||
}
|
||||
submitted = true;
|
||||
}
|
||||
async function submit() {
|
||||
submitting = true;
|
||||
error = undefined;
|
||||
const response = await newsletter(name, email);
|
||||
submitting = false;
|
||||
if (response.status >= 400) {
|
||||
error =
|
||||
response.status >= 500 ? "Server Error." : "Error submitting form.";
|
||||
return;
|
||||
}
|
||||
submitted = true;
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="pre-footer-bg" style="pointer-events:none;">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="692"
|
||||
height="1171"
|
||||
viewBox="0 0 692 1171"
|
||||
fill="none"
|
||||
style="max-inline-size:100%;"
|
||||
>
|
||||
<g opacity="0.4" filter="url(#filter0_f_1577_37321)">
|
||||
<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"
|
||||
fill="url(#paint0_radial_1577_37321)"
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<filter
|
||||
id="filter0_f_1577_37321"
|
||||
x="-809.268"
|
||||
y="-270.847"
|
||||
width="1501.04"
|
||||
height="1440.92"
|
||||
filterUnits="userSpaceOnUse"
|
||||
color-interpolation-filters="sRGB"
|
||||
>
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix" />
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape" />
|
||||
<feGaussianBlur stdDeviation="150" result="effect1_foregroundBlur_1577_37321" />
|
||||
</filter>
|
||||
<radialGradient
|
||||
id="paint0_radial_1577_37321"
|
||||
cx="0"
|
||||
cy="0"
|
||||
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>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="692"
|
||||
height="1171"
|
||||
viewBox="0 0 692 1171"
|
||||
fill="none"
|
||||
style="max-inline-size:100%;"
|
||||
>
|
||||
<g opacity="0.4" filter="url(#filter0_f_1577_37321)">
|
||||
<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"
|
||||
fill="url(#paint0_radial_1577_37321)"
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<filter
|
||||
id="filter0_f_1577_37321"
|
||||
x="-809.268"
|
||||
y="-270.847"
|
||||
width="1501.04"
|
||||
height="1440.92"
|
||||
filterUnits="userSpaceOnUse"
|
||||
color-interpolation-filters="sRGB"
|
||||
>
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix" />
|
||||
<feBlend
|
||||
mode="normal"
|
||||
in="SourceGraphic"
|
||||
in2="BackgroundImageFix"
|
||||
result="shape"
|
||||
/>
|
||||
<feGaussianBlur
|
||||
stdDeviation="150"
|
||||
result="effect1_foregroundBlur_1577_37321"
|
||||
/>
|
||||
</filter>
|
||||
<radialGradient
|
||||
id="paint0_radial_1577_37321"
|
||||
cx="0"
|
||||
cy="0"
|
||||
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 class="web-big-padding-section">
|
||||
<div class="web-big-padding-section-level-1">
|
||||
<div class="web-big-padding-section-level-2">
|
||||
<div class="web-container">
|
||||
<div class="web-grid-1-1-opt-2 u-gap-32">
|
||||
<div class="">
|
||||
<div class="web-u-max-inline-size-none-mobile" class:web-u-max-width-380={!submitted}>
|
||||
<section class="u-flex-vertical web-u-gap-20">
|
||||
<h1 class="web-title web-u-color-text-primary">Subscribe to our newsletter</h1>
|
||||
<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
|
||||
about engineering, product design, building community, and tips & tricks for using
|
||||
Appwrite.
|
||||
</p>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
{#if submitted}
|
||||
<div class="u-flex u-gap-8 u-cross-center">
|
||||
<svg
|
||||
width="18"
|
||||
height="18"
|
||||
viewBox="0 0 18 18"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<circle
|
||||
cx="9"
|
||||
cy="9"
|
||||
r="8"
|
||||
fill="#FD366E"
|
||||
fill-opacity="0.08"
|
||||
stroke="#FD366E"
|
||||
stroke-opacity="0.32"
|
||||
stroke-width="1.2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M5.25 10.5L7.75 12.5L12.75 6"
|
||||
stroke="#E4E4E7"
|
||||
stroke-width="1.2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
<div class="web-big-padding-section-level-1">
|
||||
<div class="web-big-padding-section-level-2">
|
||||
<div class="container">
|
||||
<div class="grid-cols-2-opt-2 grid gap-8">
|
||||
<div class="">
|
||||
<div
|
||||
class="web-u-max-inline-size-none-mobile"
|
||||
class:web-max-w-[380px]={!submitted}
|
||||
>
|
||||
<section class="web-gap-5 flex flex-col">
|
||||
<h1 class="web-title web-u-color-text-primary">
|
||||
Subscribe to our newsletter
|
||||
</h1>
|
||||
<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 about engineering, product design,
|
||||
building community, and tips & tricks for using Appwrite.
|
||||
</p>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
{#if submitted}
|
||||
<div class="flex items-center gap-2">
|
||||
<svg
|
||||
width="18"
|
||||
height="18"
|
||||
viewBox="0 0 18 18"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<circle
|
||||
cx="9"
|
||||
cy="9"
|
||||
r="8"
|
||||
fill="#FD366E"
|
||||
fill-opacity="0.08"
|
||||
stroke="#FD366E"
|
||||
stroke-opacity="0.32"
|
||||
stroke-width="1.2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M5.25 10.5L7.75 12.5L12.75 6"
|
||||
stroke="#E4E4E7"
|
||||
stroke-width="1.2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
|
||||
<span class="text">
|
||||
Thank you for subscribing! An email has been sent to your inbox.
|
||||
</span>
|
||||
</div>
|
||||
{:else}
|
||||
<form method="post" on:submit|preventDefault={submit} class="u-flex-vertical u-gap-16">
|
||||
<div class="u-flex u-flex-vertical u-gap-4">
|
||||
<label for="name">Your name</label>
|
||||
<input
|
||||
class="web-input-text"
|
||||
type="text"
|
||||
placeholder="Enter your name"
|
||||
id="name"
|
||||
name="name"
|
||||
required
|
||||
bind:value={name}
|
||||
/>
|
||||
</div>
|
||||
<div class="u-flex u-flex-vertical u-gap-4">
|
||||
<label for="email">Your email</label>
|
||||
<input
|
||||
class="web-input-text"
|
||||
type="email"
|
||||
placeholder="Enter your email"
|
||||
required
|
||||
id="email"
|
||||
name="email"
|
||||
bind:value={email}
|
||||
/>
|
||||
</div>
|
||||
<button type="submit" class="web-button" disabled={submitting}>Sign up</button>
|
||||
{#if error}
|
||||
<span class="text"> Something went wrong. Please try again later. </span>
|
||||
{/if}
|
||||
</form>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<span class="text">
|
||||
Thank you for subscribing! An email has been sent to your inbox.
|
||||
</span>
|
||||
</div>
|
||||
{:else}
|
||||
<form
|
||||
method="post"
|
||||
on:submit|preventDefault={submit}
|
||||
class="flex flex-col gap-4"
|
||||
>
|
||||
<div class="flex flex-col gap-1">
|
||||
<label for="name">Your name</label>
|
||||
<input
|
||||
class="web-input-text"
|
||||
type="text"
|
||||
placeholder="Enter your name"
|
||||
id="name"
|
||||
name="name"
|
||||
required
|
||||
bind:value={name}
|
||||
/>
|
||||
</div>
|
||||
<div class="flex flex-col gap-1">
|
||||
<label for="email">Your email</label>
|
||||
<input
|
||||
class="web-input-text"
|
||||
type="email"
|
||||
placeholder="Enter your email"
|
||||
required
|
||||
id="email"
|
||||
name="email"
|
||||
bind:value={email}
|
||||
/>
|
||||
</div>
|
||||
<button type="submit" class="web-button" disabled={submitting}
|
||||
>Sign up</button
|
||||
>
|
||||
{#if error}
|
||||
<span class="text">
|
||||
Something went wrong. Please try again later.
|
||||
</span>
|
||||
{/if}
|
||||
</form>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.pre-footer-bg {
|
||||
position: absolute;
|
||||
top: clamp(300px, 50vw, 50%);
|
||||
left: clamp(300px, 50vw, 50%);
|
||||
transform: translate(-50%, -70%);
|
||||
width: clamp(1200px, 100vw, 3000px);
|
||||
height: auto;
|
||||
max-inline-size: unset;
|
||||
max-block-size: unset;
|
||||
}
|
||||
.pre-footer-bg {
|
||||
position: absolute;
|
||||
top: clamp(300px, 50vw, 50%);
|
||||
left: clamp(300px, 50vw, 50%);
|
||||
transform: translate(-50%, -70%);
|
||||
width: clamp(1200px, 100vw, 3000px);
|
||||
height: auto;
|
||||
max-inline-size: unset;
|
||||
max-block-size: unset;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,101 +1,107 @@
|
||||
<script lang="ts">
|
||||
import { PUBLIC_APPWRITE_DASHBOARD } from '$env/static/public';
|
||||
import { PUBLIC_APPWRITE_DASHBOARD } from "$env/static/public";
|
||||
</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">
|
||||
<section class="web-hero u-flex web-u-row-gap-32 u-main-center u-cross-center">
|
||||
<h2 class="web-display u-max-width-500 web-u-text-align-center web-u-color-text-primary">
|
||||
Start building today
|
||||
</h2>
|
||||
<a
|
||||
href={PUBLIC_APPWRITE_DASHBOARD}
|
||||
class="web-button is-transparent web-u-cross-child-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"
|
||||
<div class="web-u-row-gap-80 relative grid grid-cols-2 gap-8">
|
||||
<section class="web-hero web-u-row-gap-32 flex items-center justify-center">
|
||||
<h2 class="web-display web-u-color-text-primary max-w-[500px] text-center">
|
||||
Start building today
|
||||
</h2>
|
||||
<a
|
||||
href={PUBLIC_APPWRITE_DASHBOARD}
|
||||
class="web-button is-transparent web-self-center"
|
||||
>
|
||||
<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>
|
||||
<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-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">
|
||||
<li class="web-strip-plans-item web-strip-plans-container-query">
|
||||
<div class="web-strip-plans-item-wrapper">
|
||||
<div class="web-strip-plans-plan">
|
||||
<h4 class="title web-description">Free</h4>
|
||||
<div class="web-title web-u-color-text-primary">$0</div>
|
||||
<div class="info web-caption-500" />
|
||||
</div>
|
||||
<p class="web-strip-plans-info web-caption-500">
|
||||
For personal hobby projects and students.
|
||||
</p>
|
||||
<a
|
||||
href={`${PUBLIC_APPWRITE_DASHBOARD}/register`}
|
||||
class="web-button is-secondary is-full-width-mobile web-u-cross-child-end"
|
||||
>
|
||||
<span class="text">Get started</span>
|
||||
</a>
|
||||
</div>
|
||||
</li>
|
||||
<li class="web-strip-plans-item web-strip-plans-container-query">
|
||||
<div class="web-strip-plans-item-wrapper">
|
||||
<div class="web-strip-plans-plan">
|
||||
<h4 class="title web-description">Pro</h4>
|
||||
<div class="web-title web-u-color-text-primary">$15</div>
|
||||
<div class="info web-caption-500">per member/month</div>
|
||||
</div>
|
||||
<p class="web-strip-plans-info web-caption-500">
|
||||
For pro developers and teams that need to scale their products.
|
||||
</p>
|
||||
<a
|
||||
href={`${PUBLIC_APPWRITE_DASHBOARD}/console?type=createPro`}
|
||||
class="web-button is-full-width-mobile web-u-cross-child-end"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<!-- <span class="text">Start trial</span> -->
|
||||
<span class="text">Start building</span>
|
||||
</a>
|
||||
</div>
|
||||
</li>
|
||||
<li class="web-strip-plans-item web-strip-plans-container-query">
|
||||
<div class="web-strip-plans-item-wrapper">
|
||||
<div class="web-strip-plans-plan">
|
||||
<h4 class="title web-description">Scale</h4>
|
||||
<div class="web-title web-u-color-text-primary">$599</div>
|
||||
<div class="info web-caption-500">per org/month</div>
|
||||
</div>
|
||||
<p class="web-strip-plans-info web-caption-500">
|
||||
For pro developers and production projects that need the ability to scale.
|
||||
</p>
|
||||
<button
|
||||
class="web-button is-full-width-mobile is-secondary web-u-cross-child-end"
|
||||
disabled
|
||||
>
|
||||
<span class="text">Coming soon</span>
|
||||
</button>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
<ul class="web-strip-plans">
|
||||
<li class="web-strip-plans-item web-strip-plans-container-query">
|
||||
<div class="web-strip-plans-item-wrapper">
|
||||
<div class="web-strip-plans-plan">
|
||||
<h4 class="title web-description">Free</h4>
|
||||
<div class="web-title web-u-color-text-primary">$0</div>
|
||||
<div class="info web-caption-500" />
|
||||
</div>
|
||||
<p class="web-strip-plans-info web-caption-500">
|
||||
For personal hobby projects and students.
|
||||
</p>
|
||||
<a
|
||||
href={`${PUBLIC_APPWRITE_DASHBOARD}/register`}
|
||||
class="web-button is-secondary is-full-width-mobile web-u-cross-child-end"
|
||||
>
|
||||
<span class="text">Get started</span>
|
||||
</a>
|
||||
</div>
|
||||
</li>
|
||||
<li class="web-strip-plans-item web-strip-plans-container-query">
|
||||
<div class="web-strip-plans-item-wrapper">
|
||||
<div class="web-strip-plans-plan">
|
||||
<h4 class="title web-description">Pro</h4>
|
||||
<div class="web-title web-u-color-text-primary">$15</div>
|
||||
<div class="info web-caption-500">per member/month</div>
|
||||
</div>
|
||||
<p class="web-strip-plans-info web-caption-500">
|
||||
For pro developers and teams that need to scale their products.
|
||||
</p>
|
||||
<a
|
||||
href={`${PUBLIC_APPWRITE_DASHBOARD}/console?type=createPro`}
|
||||
class="web-button is-full-width-mobile web-u-cross-child-end"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<!-- <span class="text">Start trial</span> -->
|
||||
<span class="text">Start building</span>
|
||||
</a>
|
||||
</div>
|
||||
</li>
|
||||
<li class="web-strip-plans-item web-strip-plans-container-query">
|
||||
<div class="web-strip-plans-item-wrapper">
|
||||
<div class="web-strip-plans-plan">
|
||||
<h4 class="title web-description">Scale</h4>
|
||||
<div class="web-title web-u-color-text-primary">$599</div>
|
||||
<div class="info web-caption-500">per org/month</div>
|
||||
</div>
|
||||
<p class="web-strip-plans-info web-caption-500">
|
||||
For pro developers and production projects that need the ability to
|
||||
scale.
|
||||
</p>
|
||||
<button
|
||||
class="web-button is-full-width-mobile is-secondary web-u-cross-child-end"
|
||||
disabled
|
||||
>
|
||||
<span class="text">Coming soon</span>
|
||||
</button>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.web-pre-footer-bg {
|
||||
position: absolute;
|
||||
top: clamp(300px, 50vw, 50%);
|
||||
left: clamp(300px, 50vw, 50%);
|
||||
transform: translate(-58%, -72%);
|
||||
width: clamp(1200px, 200vw, 3000px);
|
||||
height: auto;
|
||||
max-inline-size: unset;
|
||||
max-block-size: unset;
|
||||
}
|
||||
.web-pre-footer-bg {
|
||||
position: absolute;
|
||||
top: clamp(300px, 50vw, 50%);
|
||||
left: clamp(300px, 50vw, 50%);
|
||||
transform: translate(-58%, -72%);
|
||||
width: clamp(1200px, 200vw, 3000px);
|
||||
height: auto;
|
||||
max-inline-size: unset;
|
||||
max-block-size: unset;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -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>
|
||||
@@ -1,145 +1,150 @@
|
||||
<script lang="ts">
|
||||
import { afterNavigate, goto } from '$app/navigation';
|
||||
import { layoutState } from '$lib/layouts/Docs.svelte';
|
||||
import { isMac } from '$lib/utils/platform';
|
||||
import { afterNavigate, goto } from "$app/navigation";
|
||||
import { layoutState } from "$lib/layouts/Docs.svelte";
|
||||
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 { tick } from 'svelte';
|
||||
import { MeiliSearch, type Hit, type Hits } from "meilisearch";
|
||||
import { tick } from "svelte";
|
||||
|
||||
export let open = true;
|
||||
export let open = true;
|
||||
|
||||
let value: string;
|
||||
let container: HTMLDivElement;
|
||||
let value: string;
|
||||
let container: HTMLDivElement;
|
||||
|
||||
const client = new MeiliSearch({
|
||||
host: 'https://search.appwrite.org',
|
||||
apiKey: 'd7e83e21c0daf2a471ef4c463c7872e55b91b0cd02e2d20e9c6f6f1c4cd09ed3'
|
||||
const client = new MeiliSearch({
|
||||
host: "https://search.appwrite.org",
|
||||
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 = {
|
||||
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;
|
||||
};
|
||||
async function handleInput(value: string) {
|
||||
if (!value) {
|
||||
results = [];
|
||||
} else {
|
||||
const response = await search(value);
|
||||
results = response.hits;
|
||||
}
|
||||
}
|
||||
|
||||
let results: Hits<Props> = [];
|
||||
function handleExit(
|
||||
event: MouseEvent & { currentTarget: EventTarget & HTMLDivElement },
|
||||
) {
|
||||
if (event.target === container) {
|
||||
open = false;
|
||||
value = "";
|
||||
}
|
||||
}
|
||||
|
||||
async function search(value: string) {
|
||||
return index.search(value, {
|
||||
limit: 20
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
async function handleInput(value: string) {
|
||||
if (!value) {
|
||||
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;
|
||||
}
|
||||
function getRelevantSubtitle(hit: Hit): string {
|
||||
return hit.h2 ?? hit.h3 ?? hit.h4 ?? hit.h5 ?? hit.h6 ?? null;
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:window on:keydown={handleKeypress} />
|
||||
@@ -147,146 +152,144 @@
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<div
|
||||
class="wrapper u-position-fixed u-padding-0 u-inset-0 u-flex u-main-center u-cross-center"
|
||||
data-visible={open ? true : undefined}
|
||||
style:z-index="100"
|
||||
style:background="hsl(var(--web-color-black) / 0.3)"
|
||||
style:backdrop-filter="blur(15px)"
|
||||
style:-webkit-backdrop-filter="blur(15px)"
|
||||
bind:this={container}
|
||||
on:click={handleExit}
|
||||
class="wrapper fixed inset-0 flex items-center justify-center p-0"
|
||||
data-visible={open ? true : undefined}
|
||||
style:z-index="100"
|
||||
style:background="hsl(var(--web-color-black) / 0.3)"
|
||||
style:backdrop-filter="blur(15px)"
|
||||
style:-webkit-backdrop-filter="blur(15px)"
|
||||
bind:this={container}
|
||||
on:click={handleExit}
|
||||
>
|
||||
<div
|
||||
class="web-input-text-search-wrapper web-u-max-width-680 web-u-margin-inline-20 u-width-full-line"
|
||||
>
|
||||
<span
|
||||
class="web-icon-search u-z-index-5"
|
||||
aria-hidden="true"
|
||||
style="inset-block-start:0.9rem"
|
||||
/>
|
||||
<div id="searchbox" />
|
||||
<div
|
||||
class="web-input-text-search-wrapper web-max-w-[680px] web-u-margin-inline-20 w-full"
|
||||
>
|
||||
<span
|
||||
class="web-icon-search z-[5]"
|
||||
aria-hidden="true"
|
||||
style="inset-block-start:0.9rem"
|
||||
/>
|
||||
<div id="searchbox" />
|
||||
|
||||
<input
|
||||
class="web-input-button -u-padding-block-0 u-position-relative u-z-index-1"
|
||||
type="text"
|
||||
id="search"
|
||||
bind:value
|
||||
placeholder="Search in docs"
|
||||
style="border-end-start-radius:0; border-end-end-radius:0;"
|
||||
style:inline-size="100%"
|
||||
use:melt={$input}
|
||||
bind:this={inputEl}
|
||||
data-hit="-1"
|
||||
on:keydown={(e) => {
|
||||
if (e.key === 'Tab') {
|
||||
e.preventDefault();
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<div
|
||||
class="web-card is-normal u-flex-vertical u-gap-24"
|
||||
use:melt={$menu}
|
||||
style="--card-padding-mobile:1rem; border-radius:0 0 0.5rem 0.5rem;"
|
||||
>
|
||||
{#if value}
|
||||
<section>
|
||||
{#if results.length > 0}
|
||||
<h6 class="web-eyebrow">{results.length} results found</h6>
|
||||
<ul class="u-flex-vertical u-gap-4 u-margin-block-start-8">
|
||||
{#each results as hit, i (hit.uid)}
|
||||
{@const relevantSubtitle = getRelevantSubtitle(hit)}
|
||||
<li>
|
||||
<a
|
||||
data-hit={i}
|
||||
href={createHref(hit)}
|
||||
class="web-button web-caption-400 is-text u-flex-vertical u-gap-8 u-min-width-100-percent
|
||||
web-u-padding-block-8 web-padding-inline-12 web-u-cross-start u-max-width-100-percent"
|
||||
use:melt={$option({
|
||||
value: hit,
|
||||
label: hit.title ?? i.toString()
|
||||
})}
|
||||
>
|
||||
<div class="web-u-trim-1">
|
||||
<span class="web-u-color-text-secondary">{hit.h1}</span>
|
||||
{#if relevantSubtitle}
|
||||
<span class="web-u-color-text-secondary"> / </span>
|
||||
<span class="web-u-color-text-primary">
|
||||
{relevantSubtitle}
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
{#if hit.p}
|
||||
<div
|
||||
class="web-u-color-text-secondary web-u-trim-1"
|
||||
>
|
||||
{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>
|
||||
<input
|
||||
class="web-input-button relative z-1"
|
||||
type="text"
|
||||
id="search"
|
||||
bind:value
|
||||
placeholder="Search in docs"
|
||||
style="border-end-start-radius:0; border-end-end-radius:0;"
|
||||
style:inline-size="100%"
|
||||
use:melt={$input}
|
||||
bind:this={inputEl}
|
||||
data-hit="-1"
|
||||
on:keydown={(e) => {
|
||||
if (e.key === "Tab") {
|
||||
e.preventDefault();
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<div
|
||||
class="web-card is-normal flex flex-col gap-6"
|
||||
use:melt={$menu}
|
||||
style="--card-padding-mobile:1rem; border-radius:0 0 0.5rem 0.5rem;"
|
||||
>
|
||||
{#if value}
|
||||
<section>
|
||||
{#if results.length > 0}
|
||||
<h6 class="web-eyebrow">{results.length} results found</h6>
|
||||
<ul class="mt-2 flex flex-col gap-1">
|
||||
{#each results as hit, i (hit.uid)}
|
||||
{@const relevantSubtitle = getRelevantSubtitle(hit)}
|
||||
<li>
|
||||
<a
|
||||
data-hit={i}
|
||||
href={createHref(hit)}
|
||||
class="web-button web-caption-400 is-text web-u-padding-block-8 web-padding-inline-12 web-u-cross-start flex
|
||||
max-w-full min-w-full flex-col gap-2"
|
||||
use:melt={$option({
|
||||
value: hit,
|
||||
label: hit.title ?? i.toString(),
|
||||
})}
|
||||
>
|
||||
<div class="web-u-trim-1">
|
||||
<span class="web-u-color-text-secondary">{hit.h1}</span>
|
||||
{#if relevantSubtitle}
|
||||
<span class="web-u-color-text-secondary"> / </span>
|
||||
<span class="web-u-color-text-primary">
|
||||
{relevantSubtitle}
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
{#if hit.p}
|
||||
<div class="web-u-color-text-secondary web-u-trim-1">
|
||||
{hit.p}
|
||||
</div>
|
||||
{/if}
|
||||
</section>
|
||||
{/if}
|
||||
<section>
|
||||
<h6 class="web-eyebrow">Recommended</h6>
|
||||
<ul class="u-flex-vertical u-gap-4 u-margin-block-start-8">
|
||||
{#each recommended as hit, i (hit.uid)}
|
||||
{@const index = i + (results.length ? results.length : 0)}
|
||||
<li>
|
||||
<a
|
||||
data-hit={index}
|
||||
href={createHref(hit)}
|
||||
use:melt={$option({
|
||||
value: hit,
|
||||
label: hit.title ?? i.toString()
|
||||
})}
|
||||
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"
|
||||
>
|
||||
<div class="web-u-trim-1">
|
||||
<span class="web-u-color-text-secondary">{hit.h1}</span>
|
||||
{#if hit.h2}
|
||||
<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>
|
||||
</a>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
{:else}
|
||||
<p class="web-caption-400">
|
||||
No results found for <span class="font-bold">{value}</span>
|
||||
</p>
|
||||
{/if}
|
||||
</section>
|
||||
{/if}
|
||||
<section>
|
||||
<h6 class="web-eyebrow">Recommended</h6>
|
||||
<ul class="mt-2 flex flex-col gap-1">
|
||||
{#each recommended as hit, i (hit.uid)}
|
||||
{@const index = i + (results.length ? results.length : 0)}
|
||||
<li>
|
||||
<a
|
||||
data-hit={index}
|
||||
href={createHref(hit)}
|
||||
use:melt={$option({
|
||||
value: hit,
|
||||
label: hit.title ?? i.toString(),
|
||||
})}
|
||||
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"
|
||||
>
|
||||
<div class="web-u-trim-1">
|
||||
<span class="web-u-color-text-secondary">{hit.h1}</span>
|
||||
{#if hit.h2}
|
||||
<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>
|
||||
|
||||
<style lang="scss">
|
||||
.wrapper {
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
transition: opacity 250ms ease;
|
||||
}
|
||||
.wrapper {
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
transition: opacity 250ms ease;
|
||||
}
|
||||
|
||||
.wrapper[data-visible] {
|
||||
opacity: 1;
|
||||
pointer-events: auto;
|
||||
}
|
||||
.wrapper[data-visible] {
|
||||
opacity: 1;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
a.web-button {
|
||||
scroll-margin-block: 1rem;
|
||||
}
|
||||
a.web-button {
|
||||
scroll-margin-block: 1rem;
|
||||
}
|
||||
|
||||
.web-card {
|
||||
margin-block-start: -0.0625rem;
|
||||
max-block-size: min(18.75rem, calc(100vh - 5.5rem));
|
||||
border-block-start-width: 0;
|
||||
.web-card {
|
||||
margin-block-start: -0.0625rem;
|
||||
max-block-size: min(18.75rem, calc(100vh - 5.5rem));
|
||||
border-block-start-width: 0;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow-y: auto;
|
||||
}
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow-y: auto;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,173 +1,185 @@
|
||||
<script lang="ts" context="module">
|
||||
export type SelectOption<T = unknown> = {
|
||||
value: T;
|
||||
label: string;
|
||||
icon?: string;
|
||||
group?: string;
|
||||
};
|
||||
export type SelectOption<T = unknown> = {
|
||||
value: T;
|
||||
label: string;
|
||||
icon?: string;
|
||||
group?: string;
|
||||
};
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import { createSelect, melt, type CreateSelectProps } from '@melt-ui/svelte';
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import { fly, type FlyParams } from 'svelte/transition';
|
||||
import { createSelect, melt, type CreateSelectProps } from "@melt-ui/svelte";
|
||||
import { createEventDispatcher } from "svelte";
|
||||
import { fly, type FlyParams } from "svelte/transition";
|
||||
|
||||
export let options: Array<SelectOption>;
|
||||
export let nativeMobile = false;
|
||||
export let value: unknown | undefined = undefined;
|
||||
export let onSelectedChange: CreateSelectProps['onSelectedChange'] = undefined;
|
||||
// TODO: This id currently gets overriden by Melt. We should either use the label el, or
|
||||
// allow passing down ids in Melt.
|
||||
export let id: string | undefined = undefined;
|
||||
export let preventScroll = false;
|
||||
export let placement: NonNullable<CreateSelectProps['positioning']>['placement'] = 'bottom';
|
||||
export let options: Array<SelectOption>;
|
||||
export let nativeMobile = false;
|
||||
export let value: unknown | undefined = undefined;
|
||||
export let onSelectedChange: CreateSelectProps["onSelectedChange"] =
|
||||
undefined;
|
||||
// TODO: This id currently gets overriden by Melt. We should either use the label el, or
|
||||
// allow passing down ids in Melt.
|
||||
export let id: string | undefined = undefined;
|
||||
export let preventScroll = false;
|
||||
export let placement: NonNullable<
|
||||
CreateSelectProps["positioning"]
|
||||
>["placement"] = "bottom";
|
||||
|
||||
const dispatch = createEventDispatcher<{
|
||||
change: unknown;
|
||||
}>();
|
||||
const dispatch = createEventDispatcher<{
|
||||
change: unknown;
|
||||
}>();
|
||||
|
||||
const {
|
||||
elements: { trigger, menu, option: optionEl, group: groupEl, groupLabel },
|
||||
states: { open, selected, selectedLabel }
|
||||
} = createSelect<unknown>({
|
||||
preventScroll,
|
||||
positioning: {
|
||||
sameWidth: true,
|
||||
placement
|
||||
},
|
||||
forceVisible: true,
|
||||
onSelectedChange({ curr, next }) {
|
||||
if (onSelectedChange) {
|
||||
onSelectedChange({ curr, next });
|
||||
}
|
||||
value = next?.value;
|
||||
dispatch('change', next?.value);
|
||||
const {
|
||||
elements: { trigger, menu, option: optionEl, group: groupEl, groupLabel },
|
||||
states: { open, selected, selectedLabel },
|
||||
} = createSelect<unknown>({
|
||||
preventScroll,
|
||||
positioning: {
|
||||
sameWidth: true,
|
||||
placement,
|
||||
},
|
||||
forceVisible: true,
|
||||
onSelectedChange({ curr, next }) {
|
||||
if (onSelectedChange) {
|
||||
onSelectedChange({ curr, next });
|
||||
}
|
||||
value = next?.value;
|
||||
dispatch("change", next?.value);
|
||||
|
||||
return next;
|
||||
},
|
||||
portal: null,
|
||||
scrollAlignment: 'center'
|
||||
});
|
||||
return next;
|
||||
},
|
||||
portal: null,
|
||||
scrollAlignment: "center",
|
||||
});
|
||||
|
||||
$: selectedOption = options.find((o) => o.value === value);
|
||||
$: selectedOption = options.find((o) => o.value === value);
|
||||
|
||||
$: if (selectedOption) {
|
||||
selected.set(selectedOption);
|
||||
}
|
||||
$: if (selectedOption) {
|
||||
selected.set(selectedOption);
|
||||
}
|
||||
|
||||
const DEFAULT_GROUP = 'default';
|
||||
type Group = {
|
||||
label: string;
|
||||
options: SelectOption<unknown>[];
|
||||
};
|
||||
$: groups = (function getGroups(): Group[] {
|
||||
const groups = options.reduce<Record<string, SelectOption[]>>((carry, option) => {
|
||||
const group = option.group ?? DEFAULT_GROUP;
|
||||
if (!carry[group]) {
|
||||
carry[group] = [];
|
||||
}
|
||||
carry[group].push(option);
|
||||
return carry;
|
||||
}, {});
|
||||
const DEFAULT_GROUP = "default";
|
||||
type Group = {
|
||||
label: string;
|
||||
options: SelectOption<unknown>[];
|
||||
};
|
||||
$: groups = (function getGroups(): Group[] {
|
||||
const groups = options.reduce<Record<string, SelectOption[]>>(
|
||||
(carry, option) => {
|
||||
const group = option.group ?? DEFAULT_GROUP;
|
||||
if (!carry[group]) {
|
||||
carry[group] = [];
|
||||
}
|
||||
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 = {
|
||||
duration: 150,
|
||||
y: placement === 'top' ? 4 : -4
|
||||
} as FlyParams;
|
||||
$: flyParams = {
|
||||
duration: 150,
|
||||
y: placement === "top" ? 4 : -4,
|
||||
} as FlyParams;
|
||||
</script>
|
||||
|
||||
<button
|
||||
class="web-select is-colored"
|
||||
{id}
|
||||
class:web-is-not-mobile={nativeMobile}
|
||||
use:melt={$trigger}
|
||||
aria-label="Select theme"
|
||||
class="web-select is-colored"
|
||||
{id}
|
||||
class:web-is-not-mobile={nativeMobile}
|
||||
use:melt={$trigger}
|
||||
aria-label="Select theme"
|
||||
>
|
||||
<div class="physical-select">
|
||||
{#if selectedOption?.icon}
|
||||
<span class={selectedOption.icon} aria-hidden="true" />
|
||||
{/if}
|
||||
<span>{$selectedLabel}</span>
|
||||
</div>
|
||||
<span class="icon-cheveron-{$open ? 'up' : 'down'}" aria-hidden="true" />
|
||||
<div class="physical-select">
|
||||
{#if selectedOption?.icon}
|
||||
<span class={selectedOption.icon} aria-hidden="true" />
|
||||
{/if}
|
||||
<span>{$selectedLabel}</span>
|
||||
</div>
|
||||
<span class="icon-cheveron-{$open ? 'up' : 'down'}" aria-hidden="true" />
|
||||
</button>
|
||||
|
||||
{#if $open}
|
||||
<div
|
||||
class="web-select-menu"
|
||||
class:web-is-not-mobile={nativeMobile}
|
||||
style:z-index={10000}
|
||||
use:melt={$menu}
|
||||
transition:fly={flyParams}
|
||||
>
|
||||
{#each groups as group}
|
||||
{@const isDefault = group.label === DEFAULT_GROUP}
|
||||
{#if isDefault}
|
||||
<div class="u-flex u-flex-vertical u-gap-2">
|
||||
{#each group.options as option}
|
||||
<button class="web-select-option" use:melt={$optionEl(option)}>
|
||||
{#if option.icon}
|
||||
<span class={option.icon} aria-hidden="true" />
|
||||
{/if}
|
||||
<span style:text-transform="capitalize">{option.label}</span>
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
{:else}
|
||||
<div class="web-select-group" use:melt={$groupEl(group.label)}>
|
||||
<span class="web-select-group-label" use:melt={$groupLabel(group.label)}>
|
||||
{group.label}
|
||||
</span>
|
||||
<div
|
||||
class="web-select-menu"
|
||||
class:web-is-not-mobile={nativeMobile}
|
||||
style:z-index={10000}
|
||||
use:melt={$menu}
|
||||
transition:fly={flyParams}
|
||||
>
|
||||
{#each groups as group}
|
||||
{@const isDefault = group.label === DEFAULT_GROUP}
|
||||
{#if isDefault}
|
||||
<div class="flex flex-col gap-0.5">
|
||||
{#each group.options as option}
|
||||
<button class="web-select-option" use:melt={$optionEl(option)}>
|
||||
{#if option.icon}
|
||||
<span class={option.icon} aria-hidden="true" />
|
||||
{/if}
|
||||
<span style:text-transform="capitalize">{option.label}</span>
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
{:else}
|
||||
<div class="web-select-group" use:melt={$groupEl(group.label)}>
|
||||
<span
|
||||
class="web-select-group-label"
|
||||
use:melt={$groupLabel(group.label)}
|
||||
>
|
||||
{group.label}
|
||||
</span>
|
||||
|
||||
{#each group.options as option}
|
||||
<button class="web-select-option" use:melt={$optionEl(option)}>
|
||||
{#if option.icon}
|
||||
<span class={option.icon} aria-hidden="true" />
|
||||
{/if}
|
||||
<span style:text-transform="capitalize">{option.label}</span>
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
{/each}
|
||||
</div>
|
||||
{#each group.options as option}
|
||||
<button class="web-select-option" use:melt={$optionEl(option)}>
|
||||
{#if option.icon}
|
||||
<span class={option.icon} aria-hidden="true" />
|
||||
{/if}
|
||||
<span style:text-transform="capitalize">{option.label}</span>
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div
|
||||
class="web-select is-colored web-is-only-mobile web-u-inline-width-100-percent-mobile-break1"
|
||||
style:display={nativeMobile ? undefined : 'none'}
|
||||
class="web-select is-colored web-is-only-mobile web-u-inline-width-100-percent-mobile-break1"
|
||||
style:display={nativeMobile ? undefined : "none"}
|
||||
>
|
||||
{#if selectedOption?.icon}
|
||||
<span class={selectedOption.icon} aria-hidden="true" />
|
||||
{/if}
|
||||
<select {id} bind:value>
|
||||
{#each groups as group}
|
||||
{@const isDefault = group.label === DEFAULT_GROUP}
|
||||
{#if isDefault}
|
||||
{#each group.options as option}
|
||||
<option value={option.value} selected={option.value === value}>
|
||||
{option.label}
|
||||
</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}
|
||||
{#if selectedOption?.icon}
|
||||
<span class={selectedOption.icon} aria-hidden="true" />
|
||||
{/if}
|
||||
<select {id} bind:value>
|
||||
{#each groups as group}
|
||||
{@const isDefault = group.label === DEFAULT_GROUP}
|
||||
{#if isDefault}
|
||||
{#each group.options as option}
|
||||
<option value={option.value} selected={option.value === value}>
|
||||
{option.label}
|
||||
</option>
|
||||
{/each}
|
||||
</select>
|
||||
<span class="icon-cheveron-{$open ? 'up' : 'down'}" aria-hidden="true" />
|
||||
{: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}
|
||||
</select>
|
||||
<span class="icon-cheveron-{$open ? 'up' : 'down'}" aria-hidden="true" />
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.web-select {
|
||||
min-width: var(--min-width, var(--p-select-min-width));
|
||||
}
|
||||
.web-select {
|
||||
min-width: var(--min-width, var(--p-select-min-width));
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,68 +1,68 @@
|
||||
<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 {
|
||||
elements: { root },
|
||||
states: { checked: meltChecked }
|
||||
} = createSwitch({
|
||||
onCheckedChange({ next }) {
|
||||
checked = next;
|
||||
return next;
|
||||
}
|
||||
});
|
||||
const {
|
||||
elements: { root },
|
||||
states: { checked: meltChecked },
|
||||
} = createSwitch({
|
||||
onCheckedChange({ next }) {
|
||||
checked = next;
|
||||
return next;
|
||||
},
|
||||
});
|
||||
|
||||
$: meltChecked.set(checked);
|
||||
$: meltChecked.set(checked);
|
||||
</script>
|
||||
|
||||
<div class="melt-switch">
|
||||
<button use:melt={$root}>
|
||||
<span class="thumb" />
|
||||
</button>
|
||||
<button use:melt={$root}>
|
||||
<span class="thumb" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.melt-switch {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.melt-switch {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
button {
|
||||
--padding: 0.125rem;
|
||||
--width: 2.25rem;
|
||||
button {
|
||||
--padding: 0.125rem;
|
||||
--width: 2.25rem;
|
||||
|
||||
position: relative;
|
||||
height: 1.5rem;
|
||||
width: var(--width);
|
||||
cursor: default;
|
||||
border-radius: 9999px;
|
||||
background-color: hsl(var(--web-color-smooth));
|
||||
transition: ease 150ms;
|
||||
}
|
||||
position: relative;
|
||||
height: 1.5rem;
|
||||
width: var(--width);
|
||||
cursor: default;
|
||||
border-radius: 9999px;
|
||||
background-color: hsl(var(--web-color-smooth));
|
||||
transition: ease 150ms;
|
||||
}
|
||||
|
||||
.melt-switch :global([data-state='checked']) {
|
||||
background-color: #fd366e;
|
||||
}
|
||||
.melt-switch :global([data-state="checked"]) {
|
||||
background-color: #fd366e;
|
||||
}
|
||||
|
||||
.thumb {
|
||||
--thumb-size: 1.25rem;
|
||||
.thumb {
|
||||
--thumb-size: 1.25rem;
|
||||
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
|
||||
display: block;
|
||||
height: var(--thumb-size);
|
||||
width: var(--thumb-size);
|
||||
border-radius: 9999px;
|
||||
background-color: #ffffff;
|
||||
transform: translateX(var(--padding)) translateY(-50%);
|
||||
display: block;
|
||||
height: var(--thumb-size);
|
||||
width: var(--thumb-size);
|
||||
border-radius: 9999px;
|
||||
background-color: #ffffff;
|
||||
transform: translateX(var(--padding)) translateY(-50%);
|
||||
|
||||
transition: ease 150ms;
|
||||
}
|
||||
transition: ease 150ms;
|
||||
}
|
||||
|
||||
:global(button[data-state='checked']) .thumb {
|
||||
--x: calc(var(--width) - var(--thumb-size) - var(--padding));
|
||||
transform: translateX(var(--x)) translateY(-50%);
|
||||
}
|
||||
:global(button[data-state="checked"]) .thumb {
|
||||
--x: calc(var(--width) - var(--thumb-size) - var(--padding));
|
||||
transform: translateX(var(--x)) translateY(-50%);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
href: '/docs/quick-starts/angular',
|
||||
image: `/images/platforms/${$themeInUse}/angular.svg`
|
||||
},
|
||||
{
|
||||
{
|
||||
name: 'Refine',
|
||||
href: '/docs/quick-starts/refine',
|
||||
image: `/images/platforms/${$themeInUse}/refine.svg`
|
||||
@@ -57,8 +57,7 @@
|
||||
name: 'React Native',
|
||||
href: '/docs/quick-starts/react-native',
|
||||
image: `/images/platforms/${$themeInUse}/react-native.svg`
|
||||
},
|
||||
|
||||
}
|
||||
] as Array<{
|
||||
name: string;
|
||||
href: string;
|
||||
@@ -66,12 +65,17 @@
|
||||
}>;
|
||||
</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}
|
||||
<Tooltip>
|
||||
<li>
|
||||
<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>
|
||||
</li>
|
||||
<svelte:fragment slot="tooltip">{platform.name}</svelte:fragment>
|
||||
|
||||
@@ -16,7 +16,10 @@
|
||||
|
||||
<aside class="web-grid-120-1fr-auto-side" class:web-is-mobile-closed={!showToc}>
|
||||
<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">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -1,2 +0,0 @@
|
||||
export { default as Root } from './Carousel.svelte';
|
||||
export { default as Slide } from './CarouselSlide.svelte';
|
||||
@@ -1,16 +1,16 @@
|
||||
export { default as FooterNav } from './FooterNav.svelte';
|
||||
export { default as MainFooter } from './MainFooter.svelte';
|
||||
export { default as PreFooter } from './PreFooter.svelte';
|
||||
export { default as MobileNav } from './MobileNav.svelte';
|
||||
export { default as Switch } from './Switch.svelte';
|
||||
export { default as Newsletter } from './Newsletter.svelte';
|
||||
export { default as Tooltip } from './Tooltip.svelte';
|
||||
export { default as Article } from './Article.svelte';
|
||||
export { default as Carousel } from './Carousel.svelte';
|
||||
export { default as FloatingHeads } from './FloatingHeads.svelte';
|
||||
export { default as FloatingHead } from './FloatingHead.svelte';
|
||||
export { default as Feedback } from './Feedback.svelte';
|
||||
export { default as Select } from './Select.svelte';
|
||||
export { default as MetricCard } from './MetricCard.svelte';
|
||||
export { default as IsLoggedIn } from './IsLoggedIn.svelte';
|
||||
export { default as Search } from './Search.svelte';
|
||||
export { default as FooterNav } from "./FooterNav.svelte";
|
||||
export { default as MainFooter } from "./MainFooter.svelte";
|
||||
export { default as PreFooter } from "./PreFooter.svelte";
|
||||
export { default as MobileNav } from "./MobileNav.svelte";
|
||||
export { default as Switch } from "./Switch.svelte";
|
||||
export { default as Newsletter } from "./Newsletter.svelte";
|
||||
export { default as Tooltip } from "./Tooltip.svelte";
|
||||
export { default as Article } from "./Article.svelte";
|
||||
export { default as Carousel } from "./Carousel.svelte";
|
||||
export { default as FloatingHeads } from "./FloatingHeads.svelte";
|
||||
export { default as FloatingHead } from "./FloatingHead.svelte";
|
||||
export { default as Feedback } from "./Feedback.svelte";
|
||||
export { default as Select } from "./Select.svelte";
|
||||
export { default as MetricCard } from "./MetricCard.svelte";
|
||||
export { default as IsLoggedIn } from "./IsLoggedIn.svelte";
|
||||
export { default as Search } from "./Search.svelte";
|
||||
|
||||
97
src/lib/components/ui/Button.svelte
Normal file
97
src/lib/components/ui/Button.svelte
Normal 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>
|
||||
97
src/lib/components/ui/Card.svelte
Normal file
97
src/lib/components/ui/Card.svelte
Normal 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>
|
||||
0
src/lib/components/ui/Header.svelte
Normal file
0
src/lib/components/ui/Header.svelte
Normal file
Reference in New Issue
Block a user