Revert "update components"

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

View File

@@ -11,7 +11,7 @@
<span class="icon-cheveron-down" aria-hidden="true" />
</div>
</summary>
<div class="collapsible-content u-flex-vertical">
<div class="collapsible-content flex flex-col">
<slot />
</div>
</details>

View File

@@ -1,4 +1,7 @@
<ul class="collapsible u-width-full-line" style="--p-toggle-border-color: var(--web-color-border);">
<ul
class="collapsible w-full"
style="--p-toggle-border-color: var(--web-color-border);"
>
<slot />
</ul>

View File

@@ -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 };

View File

@@ -1,6 +1,6 @@
<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;
@@ -27,8 +27,15 @@
{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="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">

View File

@@ -1,14 +1,15 @@
<script lang="ts">
let carousel: HTMLElement;
export let size: 'default' | 'medium' | 'big' = 'default';
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;
const childSize =
(carousel.childNodes[0] as HTMLUListElement)?.clientWidth + gap;
scroll = scroll || carouselSize;
@@ -22,13 +23,13 @@
function next() {
carousel.scrollBy({
left: calculateScrollAmount(),
behavior: 'smooth'
behavior: "smooth",
});
}
function prev() {
carousel.scrollBy({
left: calculateScrollAmount(true),
behavior: 'smooth'
behavior: "smooth",
});
}
@@ -37,14 +38,16 @@
function handleScroll() {
isStart = carousel.scrollLeft <= 0;
isEnd = Math.ceil(carousel.scrollLeft + carousel.offsetWidth) >= carousel.scrollWidth;
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">
<div class="mt-2 flex flex-wrap items-center">
<slot name="header" />
<div class="nav u-flex u-gap-12 u-cross-end u-margin-inline-start-auto">
<div class="nav ml-auto flex items-end gap-3">
<button
class="web-icon-button"
aria-label="Move carousel backward"
@@ -64,11 +67,14 @@
</div>
</div>
<div class="carousel-wrapper" data-state={isStart ? 'start' : isEnd ? 'end' : 'middle'}>
<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'}
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}
@@ -91,7 +97,7 @@
&::before,
&::after {
content: '';
content: "";
position: absolute;
top: 0;
width: 60px;
@@ -109,7 +115,7 @@
);
}
&[data-state='start']::before {
&[data-state="start"]::before {
opacity: 0;
}
@@ -122,7 +128,7 @@
);
}
&[data-state='end']::after {
&[data-state="end"]::after {
opacity: 0;
}
}

View File

@@ -1,11 +1,11 @@
<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 feedbackType = "";
let email = "";
let comment = "";
let error: string | undefined;
let submitted = false;
let submitting = false;
@@ -13,30 +13,34 @@
async function handleSubmit() {
submitting = true;
error = undefined;
const response = await fetch('https://growth.appwrite.io/v1/feedback/docs', {
method: 'POST',
const response = await fetch(
"https://growth.appwrite.io/v1/feedback/docs",
{
method: "POST",
headers: {
'Content-Type': 'application/json'
"Content-Type": "application/json",
},
body: JSON.stringify({
email,
type: feedbackType,
route: $page.route.id,
comment
})
});
comment,
}),
},
);
submitting = false;
if (response.status >= 400) {
error = response.status >= 500 ? 'Server Error.' : 'Error submitting form.';
error =
response.status >= 500 ? "Server Error." : "Error submitting form.";
return;
}
comment = email = '';
comment = email = "";
submitted = true;
}
function reset() {
comment = email = '';
feedbackType = '';
comment = email = "";
feedbackType = "";
submitted = false;
error = undefined;
}
@@ -47,20 +51,22 @@
</script>
<section class="web-content-footer">
<header class="web-content-footer-header u-width-full-line">
<header class="web-content-footer-header w-full">
<div
class="u-flex u-gap-32 u-main-space-between u-cross-center u-width-full-line"
class="flex w-full items-center justify-between gap-8"
style="flex-wrap: wrap-reverse;"
>
<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">
<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';
showFeedback = feedbackType === "positive" ? false : true;
feedbackType = "positive";
}}
>
<span class="icon-thumb-up" />
@@ -69,8 +75,8 @@
class="web-radio-button"
aria-label="unhelpful"
on:click={() => {
showFeedback = feedbackType === 'negative' ? false : true;
feedbackType = 'negative';
showFeedback = feedbackType === "negative" ? false : true;
feedbackType = "negative";
}}
>
<!-- TODO: fix the icon name on pink -->
@@ -88,9 +94,9 @@
href="https://github.com/appwrite/website"
target="_blank"
rel="noopener noreferrer"
class="web-link u-flex u-gap-4 u-cross-baseline"
class="web-link flex items-baseline gap-1"
>
<span class="icon-pencil-alt u-contents" aria-hidden="true" />
<span class="icon-pencil-alt contents" aria-hidden="true" />
<span>Update on GitHub</span>
</a>
</li>
@@ -104,10 +110,11 @@
class="web-card is-normal"
style="--card-padding:1rem"
>
<div class="u-flex-vertical u-gap-8">
<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)
What did you {feedbackType === "negative" ? "dislike" : "like"}?
(optional)
</span>
</label>
<textarea
@@ -116,7 +123,7 @@
placeholder="Write your message"
bind:value={comment}
/>
<label for="message" class="u-margin-block-start-8">
<label for="message" class="mt-2">
<span class="web-u-color-text-primary">Email</span>
</label>
<input
@@ -129,21 +136,28 @@
/>
</div>
{#if submitted}
<p class="web-u-color-text-primary u-margin-block-start-16">
<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 u-margin-block-start-16">
<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)}>
<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}>
<button
type="submit"
class="web-button"
disabled={submitting || !email}
>
<span>Submit</span>
</button>
</div>

View File

@@ -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>

View File

@@ -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" />

View File

@@ -1,13 +1,18 @@
<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]) {

View File

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

View File

@@ -1,15 +1,15 @@
<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();
</script>
{#if variant === 'homepage'}
<footer class="web-main-footer u-position-relative u-margin-block-start-48">
<ul class="u-flex u-gap-8">
{#if variant === "homepage"}
<footer class="web-main-footer relative mt-12">
<ul class="flex gap-2">
{#each socials as social}
<li>
<a
@@ -39,19 +39,17 @@
style:margin-top="-4px"
/>
<ul class="u-flex u-gap-16">
<ul class="flex gap-4">
<li><a class="web-link" href="/terms">Terms</a></li>
<li><a class="web-link" href="/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"
>
{: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 u-flex u-gap-8">
<ul class="web-main-footer-grid-1-column-1 flex gap-2">
{#each socials as social}
<li>
<a
@@ -69,13 +67,19 @@
<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">
<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>
<a href="/discord" target="_blank" rel="noopener noreferrer"
>Support</a
>
</li>
<li>
<a href="https://appwrite.online" target="_blank" rel="noopener noreferrer"
>Status</a
<a
href="https://appwrite.online"
target="_blank"
rel="noopener noreferrer">Status</a
>
</li>
<!-- <li>
@@ -90,7 +94,7 @@
{/if}
<style lang="scss">
@use '$scss/abstract/variables/devices';
@use "$scss/abstract/variables/devices";
.web-icon-button {
display: grid;
}

View File

@@ -1,9 +1,8 @@
<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[];
@@ -15,16 +14,16 @@
<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">
<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={`${PUBLIC_APPWRITE_DASHBOARD}/register`}
class="web-button is-secondary web-u-flex-1"
href="https://cloud.appwrite.io/register"
class="web-button is-secondary flex-1"
>
Sign up
</a>
<IsLoggedIn classes="web-u-flex-1" />
<IsLoggedIn classes="flex-1" />
</div>
<div class="web-side-nav-scroll">
<section>

View File

@@ -1,22 +1,25 @@
<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',
const response = await fetch(
"https://growth.appwrite.io/v1/newsletter/subscribe",
{
method: "POST",
headers: {
'Content-Type': 'application/json'
"Content-Type": "application/json",
},
body: JSON.stringify({
name,
email
})
});
email,
}),
},
);
return response;
}
</script>
<script lang="ts">
let email = '';
let name = '';
let email = "";
let name = "";
let submitted = false;
let error: string | undefined;
let submitting = false;
@@ -27,7 +30,8 @@
const response = await newsletter(name, email);
submitting = false;
if (response.status >= 400) {
error = response.status >= 500 ? 'Server Error.' : 'Error submitting form.';
error =
response.status >= 500 ? "Server Error." : "Error submitting form.";
return;
}
submitted = true;
@@ -60,8 +64,16 @@
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" />
<feBlend
mode="normal"
in="SourceGraphic"
in2="BackgroundImageFix"
result="shape"
/>
<feGaussianBlur
stdDeviation="150"
result="effect1_foregroundBlur_1577_37321"
/>
</filter>
<radialGradient
id="paint0_radial_1577_37321"
@@ -81,22 +93,27 @@
<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="container">
<div class="grid-cols-2-opt-2 grid gap-8">
<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>
<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.
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">
<div class="flex items-center gap-2">
<svg
width="18"
height="18"
@@ -130,8 +147,12 @@
</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">
<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"
@@ -143,7 +164,7 @@
bind:value={name}
/>
</div>
<div class="u-flex u-flex-vertical u-gap-4">
<div class="flex flex-col gap-1">
<label for="email">Your email</label>
<input
class="web-input-text"
@@ -155,9 +176,13 @@
bind:value={email}
/>
</div>
<button type="submit" class="web-button" disabled={submitting}>Sign up</button>
<button type="submit" class="web-button" disabled={submitting}
>Sign up</button
>
{#if error}
<span class="text"> Something went wrong. Please try again later. </span>
<span class="text">
Something went wrong. Please try again later.
</span>
{/if}
</form>
{/if}

View File

@@ -1,23 +1,28 @@
<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">
<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-u-cross-child-center"
class="web-button is-transparent web-self-center"
>
<span class="text">Get started</span>
</a>
</section>
<section
class="web-card is-transparent has-border-gradient web-u-max-inline-width-584-mobile web-u-margin-inline-auto-mobile web-u-inline-width-100-percent-mobile"
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">
@@ -73,7 +78,8 @@
<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.
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"

View File

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

View File

@@ -1,12 +1,12 @@
<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;
@@ -14,10 +14,10 @@
let container: HTMLDivElement;
const client = new MeiliSearch({
host: 'https://search.appwrite.org',
apiKey: 'd7e83e21c0daf2a471ef4c463c7872e55b91b0cd02e2d20e9c6f6f1c4cd09ed3'
host: "https://search.appwrite.org",
apiKey: "d7e83e21c0daf2a471ef4c463c7872e55b91b0cd02e2d20e9c6f6f1c4cd09ed3",
});
const index = client.index<Props>('website');
const index = client.index<Props>("website");
type Props = {
url: string;
@@ -40,7 +40,7 @@
async function search(value: string) {
return index.search(value, {
limit: 20
limit: 20,
});
}
@@ -53,15 +53,17 @@
}
}
function handleExit(event: MouseEvent & { currentTarget: EventTarget & HTMLDivElement }) {
function handleExit(
event: MouseEvent & { currentTarget: EventTarget & HTMLDivElement },
) {
if (event.target === container) {
open = false;
value = '';
value = "";
}
}
function createHref(hit: Hit<Props>): string {
const anchor = hit.anchor === '#' ? '' : hit.anchor ?? '';
const anchor = hit.anchor === "#" ? "" : (hit.anchor ?? "");
const target = hit.url + anchor;
return target.toString();
@@ -69,29 +71,29 @@
const recommended: Hits<Props> = [
{
uid: 'recommended-references-account',
url: '/docs/references/cloud/client-web/databases',
h1: 'API reference',
h2: 'Databases'
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-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-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'
}
uid: "recommended-references-storage",
url: "/docs/references/cloud/client-web/storage",
h1: "API reference",
h2: "Storage",
},
];
afterNavigate(() => {
@@ -102,13 +104,13 @@
let inputEl: HTMLInputElement;
$: if (open && inputEl) {
inputEl.value = '';
inputEl.value = "";
inputEl?.focus();
}
const {
elements: { input, menu, option },
states: { inputValue }
states: { inputValue },
} = createCombobox<Props>({
forceVisible: true,
preventScroll: false,
@@ -118,20 +120,23 @@
if (next) {
goto(next.value.url);
tick().then(() => {
inputValue.set('');
inputValue.set("");
});
inputValue.set('');
inputValue.set("");
}
return undefined;
}
},
});
function handleKeypress(event: KeyboardEvent) {
const cmdPressed = isMac() ? event.metaKey : event.ctrlKey;
if (cmdPressed && event.key.toLowerCase() === 'k') {
if (cmdPressed && event.key.toLowerCase() === "k") {
event.preventDefault();
$layoutState.showSearch = true;
} else if (event.key.toLowerCase() === 'escape' || event.key.toLowerCase() === 'esc') {
} else if (
event.key.toLowerCase() === "escape" ||
event.key.toLowerCase() === "esc"
) {
event.preventDefault();
$layoutState.showSearch = false;
}
@@ -147,7 +152,7 @@
<!-- 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"
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)"
@@ -157,17 +162,17 @@
on:click={handleExit}
>
<div
class="web-input-text-search-wrapper web-u-max-width-680 web-u-margin-inline-20 u-width-full-line"
class="web-input-text-search-wrapper web-max-w-[680px] web-u-margin-inline-20 w-full"
>
<span
class="web-icon-search u-z-index-5"
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"
class="web-input-button relative z-1"
type="text"
id="search"
bind:value
@@ -178,13 +183,13 @@
bind:this={inputEl}
data-hit="-1"
on:keydown={(e) => {
if (e.key === 'Tab') {
if (e.key === "Tab") {
e.preventDefault();
}
}}
/>
<div
class="web-card is-normal u-flex-vertical u-gap-24"
class="web-card is-normal flex flex-col gap-6"
use:melt={$menu}
style="--card-padding-mobile:1rem; border-radius:0 0 0.5rem 0.5rem;"
>
@@ -192,18 +197,18 @@
<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">
<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 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"
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()
label: hit.title ?? i.toString(),
})}
>
<div class="web-u-trim-1">
@@ -216,9 +221,7 @@
{/if}
</div>
{#if hit.p}
<div
class="web-u-color-text-secondary web-u-trim-1"
>
<div class="web-u-color-text-secondary web-u-trim-1">
{hit.p}
</div>
{/if}
@@ -228,14 +231,14 @@
</ul>
{:else}
<p class="web-caption-400">
No results found for <span class="u-bold">{value}</span>
No results found for <span class="font-bold">{value}</span>
</p>
{/if}
</section>
{/if}
<section>
<h6 class="web-eyebrow">Recommended</h6>
<ul class="u-flex-vertical u-gap-4 u-margin-block-start-8">
<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>
@@ -244,9 +247,9 @@
href={createHref(hit)}
use:melt={$option({
value: hit,
label: hit.title ?? i.toString()
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"
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>

View File

@@ -8,19 +8,22 @@
</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;
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 placement: NonNullable<
CreateSelectProps["positioning"]
>["placement"] = "bottom";
const dispatch = createEventDispatcher<{
change: unknown;
@@ -28,12 +31,12 @@
const {
elements: { trigger, menu, option: optionEl, group: groupEl, groupLabel },
states: { open, selected, selectedLabel }
states: { open, selected, selectedLabel },
} = createSelect<unknown>({
preventScroll,
positioning: {
sameWidth: true,
placement
placement,
},
forceVisible: true,
onSelectedChange({ curr, next }) {
@@ -41,12 +44,12 @@
onSelectedChange({ curr, next });
}
value = next?.value;
dispatch('change', next?.value);
dispatch("change", next?.value);
return next;
},
portal: null,
scrollAlignment: 'center'
scrollAlignment: "center",
});
$: selectedOption = options.find((o) => o.value === value);
@@ -55,27 +58,33 @@
selected.set(selectedOption);
}
const DEFAULT_GROUP = 'default';
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 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
y: placement === "top" ? 4 : -4,
} as FlyParams;
</script>
@@ -106,7 +115,7 @@
{#each groups as group}
{@const isDefault = group.label === DEFAULT_GROUP}
{#if isDefault}
<div class="u-flex u-flex-vertical u-gap-2">
<div class="flex flex-col gap-0.5">
{#each group.options as option}
<button class="web-select-option" use:melt={$optionEl(option)}>
{#if option.icon}
@@ -118,7 +127,10 @@
</div>
{:else}
<div class="web-select-group" use:melt={$groupEl(group.label)}>
<span class="web-select-group-label" use:melt={$groupLabel(group.label)}>
<span
class="web-select-group-label"
use:melt={$groupLabel(group.label)}
>
{group.label}
</span>
@@ -138,7 +150,7 @@
<div
class="web-select is-colored web-is-only-mobile web-u-inline-width-100-percent-mobile-break1"
style:display={nativeMobile ? undefined : 'none'}
style:display={nativeMobile ? undefined : "none"}
>
{#if selectedOption?.icon}
<span class={selectedOption.icon} aria-hidden="true" />

View File

@@ -1,16 +1,16 @@
<script lang="ts">
import { createSwitch, melt } from '@melt-ui/svelte';
import { createSwitch, melt } from "@melt-ui/svelte";
export let checked = false;
const {
elements: { root },
states: { checked: meltChecked }
states: { checked: meltChecked },
} = createSwitch({
onCheckedChange({ next }) {
checked = next;
return next;
}
},
});
$: meltChecked.set(checked);
@@ -41,7 +41,7 @@
transition: ease 150ms;
}
.melt-switch :global([data-state='checked']) {
.melt-switch :global([data-state="checked"]) {
background-color: #fd366e;
}
@@ -61,7 +61,7 @@
transition: ease 150ms;
}
:global(button[data-state='checked']) .thumb {
:global(button[data-state="checked"]) .thumb {
--x: calc(var(--width) - var(--thumb-size) - var(--padding));
transform: translateX(var(--x)) translateY(-50%);
}

View File

@@ -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>

View File

@@ -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"

View File

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

View File

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

View File

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

View File

@@ -1,16 +1,16 @@
export { default as FooterNav } from './FooterNav.svelte';
export { default as 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";

View File

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

View File

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

View File