mirror of
https://github.com/LukeHagar/website.git
synced 2025-12-07 21:07:44 +00:00
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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,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">
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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,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]) {
|
||||
|
||||
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,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;
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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,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>
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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%);
|
||||
}
|
||||
|
||||
@@ -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