mirror of
https://github.com/LukeHagar/website.git
synced 2025-12-06 04:22:07 +00:00
pretty selects everywhere
This commit is contained in:
@@ -28,7 +28,8 @@
|
||||
},
|
||||
title: 'Auth',
|
||||
subtitle: 'Secure login for all users',
|
||||
description: 'Authenticate users securely with multiple login methods like Email/Password, SMS, OAuth, Annoymous, Magic URLs and more.',
|
||||
description:
|
||||
'Authenticate users securely with multiple login methods like Email/Password, SMS, OAuth, Annoymous, Magic URLs and more.',
|
||||
features: [
|
||||
'30+ login methods',
|
||||
'Support for teams, roles and user labels',
|
||||
@@ -44,14 +45,13 @@
|
||||
},
|
||||
title: 'Databases',
|
||||
subtitle: 'Store, query and manage data',
|
||||
description:
|
||||
'Scalable and robust database backed by your favorite technologies.',
|
||||
description: 'Scalable and robust database backed by your favorite technologies.',
|
||||
features: [
|
||||
'Never paused',
|
||||
'Fast in-memory caching',
|
||||
'Advanced permission models',
|
||||
'Custom data validation',
|
||||
'Relationships support',
|
||||
'Relationships support'
|
||||
],
|
||||
shot: './images/products/databases.png'
|
||||
},
|
||||
@@ -78,7 +78,8 @@
|
||||
},
|
||||
title: 'Storage',
|
||||
subtitle: 'Upload and manage files',
|
||||
description: 'Securely store files with advanced compression, encryption and image transformations.',
|
||||
description:
|
||||
'Securely store files with advanced compression, encryption and image transformations.',
|
||||
features: [
|
||||
'File encryption at rest and transit',
|
||||
'Built-in image transformation capabilities',
|
||||
@@ -97,7 +98,7 @@
|
||||
features: [
|
||||
'Unlimited subscriptions',
|
||||
'Built-in permission management',
|
||||
'Support for DBs, Auth, Storage & Functions',
|
||||
'Support for DBs, Auth, Storage & Functions'
|
||||
],
|
||||
shot: './images/products/realtime.png'
|
||||
}
|
||||
@@ -123,6 +124,7 @@
|
||||
import { Realtime, realtimeController } from './realtime';
|
||||
import { postController } from './post';
|
||||
import Post from './post/post.svelte';
|
||||
import { anyify } from '$lib/utils/anyify';
|
||||
|
||||
/* Basic Animation setup */
|
||||
let scrollInfo = {
|
||||
@@ -180,8 +182,6 @@
|
||||
}
|
||||
});
|
||||
})();
|
||||
|
||||
const anyify = (x: unknown) => x as any;
|
||||
</script>
|
||||
|
||||
<div
|
||||
@@ -202,9 +202,14 @@
|
||||
in:fly={{ duration: 250, delay: 250, y: -300 }}
|
||||
>
|
||||
{#if scrollInfo.percentage > -0.1}
|
||||
<span class="aw-badges aw-eyebrow" transition:slide={{ axis: 'x' }}>Products_</span>
|
||||
<span class="aw-badges aw-eyebrow" transition:slide={{ axis: 'x' }}
|
||||
>Products_</span
|
||||
>
|
||||
|
||||
<h2 class="aw-display aw-u-color-text-primary " transition:fly={{ y: 16, delay: 250 }}>
|
||||
<h2
|
||||
class="aw-display aw-u-color-text-primary"
|
||||
transition:fly={{ y: 16, delay: 250 }}
|
||||
>
|
||||
Your backend, minus the hassle
|
||||
</h2>
|
||||
<p
|
||||
@@ -214,7 +219,8 @@
|
||||
delay: 400
|
||||
}}
|
||||
>
|
||||
Build secure and scalable applications with less code. Add authentication, databases, storage, and more using Appwrite's development platform.
|
||||
Build secure and scalable applications with less code. Add authentication,
|
||||
databases, storage, and more using Appwrite's development platform.
|
||||
</p>
|
||||
{/if}
|
||||
</div>
|
||||
@@ -226,7 +232,9 @@
|
||||
data-active={scrollInfo.percentage > 0.075 ? '' : undefined}
|
||||
>
|
||||
<div class="text" id="pd-{$elId}">
|
||||
<ScrollIndicator percentage={toScale(scrollInfo.percentage, animScale, [0, 1])} />
|
||||
<ScrollIndicator
|
||||
percentage={toScale(scrollInfo.percentage, animScale, [0, 1])}
|
||||
/>
|
||||
<ul class="descriptions">
|
||||
{#each products as product}
|
||||
{@const copy = infos[product]}
|
||||
@@ -235,7 +243,12 @@
|
||||
{#if copy}
|
||||
<li data-active={isActive ? '' : undefined}>
|
||||
<h3>
|
||||
<img src={isActive ? copy.icon.active : copy.icon.inactive} alt="" width="32" height="32" />
|
||||
<img
|
||||
src={isActive ? copy.icon.active : copy.icon.inactive}
|
||||
alt=""
|
||||
width="32"
|
||||
height="32"
|
||||
/>
|
||||
<span class="aw-label">{copy.title}</span>
|
||||
</h3>
|
||||
{#if isActive}
|
||||
|
||||
@@ -9,37 +9,49 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { createSelect, melt, type CreateSelectProps } from '@melt-ui/svelte';
|
||||
import { fly } from 'svelte/transition';
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import { fly, type FlyParams } from 'svelte/transition';
|
||||
|
||||
export let options: Array<SelectOption>;
|
||||
export let nativeMobile = false;
|
||||
export let selected: unknown | undefined = undefined;
|
||||
export let value: unknown | undefined = undefined;
|
||||
export let onSelectedChange: CreateSelectProps['onSelectedChange'] = undefined;
|
||||
// TODO: This id currently gets overriden by Melt. We should either use the label el, or
|
||||
// allow passing down ids in Melt.
|
||||
export let id: string | undefined = undefined;
|
||||
export let preventScroll = false;
|
||||
export let placement: NonNullable<CreateSelectProps['positioning']>['placement'] = 'bottom';
|
||||
|
||||
const dispatch = createEventDispatcher<{
|
||||
change: unknown;
|
||||
}>();
|
||||
|
||||
const {
|
||||
elements: { trigger, menu, option: optionEl, group: groupEl, groupLabel },
|
||||
states: { open, selected: localSelected, selectedLabel }
|
||||
states: { open, selected, selectedLabel }
|
||||
} = createSelect<unknown>({
|
||||
preventScroll: false,
|
||||
preventScroll,
|
||||
positioning: {
|
||||
sameWidth: true,
|
||||
fitViewport: true
|
||||
fitViewport: true,
|
||||
placement
|
||||
},
|
||||
forceVisible: true,
|
||||
onSelectedChange({ curr, next }) {
|
||||
if (onSelectedChange) {
|
||||
onSelectedChange({ curr, next });
|
||||
}
|
||||
selected = next?.value;
|
||||
value = next?.value;
|
||||
dispatch('change', next?.value);
|
||||
|
||||
return next;
|
||||
}
|
||||
});
|
||||
|
||||
$: selectedOption = options.find((o) => o.value === selected);
|
||||
$: selectedOption = options.find((o) => o.value === value);
|
||||
|
||||
$: if (selectedOption) {
|
||||
localSelected.set(selectedOption);
|
||||
selected.set(selectedOption);
|
||||
}
|
||||
|
||||
const DEFAULT_GROUP = 'default';
|
||||
@@ -59,10 +71,16 @@
|
||||
|
||||
return Object.entries(groups).map(([label, options]) => ({ label, options }));
|
||||
})();
|
||||
|
||||
$: flyParams = {
|
||||
duration: 150,
|
||||
y: placement === 'top' ? 4 : -4
|
||||
} as FlyParams;
|
||||
</script>
|
||||
|
||||
<button
|
||||
class="aw-select is-colored"
|
||||
{id}
|
||||
class:aw-is-not-mobile={nativeMobile}
|
||||
use:melt={$trigger}
|
||||
aria-label="Select theme"
|
||||
@@ -82,14 +100,25 @@
|
||||
class:aw-is-not-mobile={nativeMobile}
|
||||
style:z-index={10000}
|
||||
use:melt={$menu}
|
||||
transition:fly={{ y: 4, duration: 150 }}
|
||||
transition:fly={flyParams}
|
||||
>
|
||||
{#each groups as group}
|
||||
{@const isDefault = group.label === DEFAULT_GROUP}
|
||||
<div class="aw-select-group" use:melt={$groupEl(group.label)}>
|
||||
{#if !isDefault}
|
||||
<span class="aw-select-group-label" use:melt={$groupLabel}>{group.label}</span>
|
||||
{#if isDefault}
|
||||
{#each options as option}
|
||||
<button class="aw-select-option" use:melt={$optionEl(option)}>
|
||||
{#if option.icon}
|
||||
<span class={option.icon} aria-hidden="true" />
|
||||
{/if}
|
||||
<span style:text-transform="capitalize">{option.label}</span>
|
||||
</button>
|
||||
{/each}
|
||||
{:else}
|
||||
<div class="aw-select-group" use:melt={$groupEl(group.label)}>
|
||||
<span class="aw-select-group-label" use:melt={$groupLabel(group.label)}>
|
||||
{group.label}
|
||||
</span>
|
||||
|
||||
{#each options as option}
|
||||
<button class="aw-select-option" use:melt={$optionEl(option)}>
|
||||
{#if option.icon}
|
||||
@@ -99,6 +128,7 @@
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
@@ -110,19 +140,19 @@
|
||||
{#if selectedOption?.icon}
|
||||
<span class={selectedOption.icon} aria-hidden="true" />
|
||||
{/if}
|
||||
<select bind:value={selected}>
|
||||
<select {id} bind:value>
|
||||
{#each groups as group}
|
||||
{@const isDefault = group.label === DEFAULT_GROUP}
|
||||
{#if isDefault}
|
||||
{#each options as option}
|
||||
<option value={option.value} selected={option.value === selected}>
|
||||
<option value={option.value} selected={option.value === value}>
|
||||
{option.label}
|
||||
</option>
|
||||
{/each}
|
||||
{:else}
|
||||
<optgroup label={isDefault ? undefined : group.label}>
|
||||
{#each options as option}
|
||||
<option value={option.value} selected={option.value === selected}>
|
||||
<option value={option.value} selected={option.value === value}>
|
||||
{option.label}
|
||||
</option>
|
||||
{/each}
|
||||
@@ -132,3 +162,9 @@
|
||||
</select>
|
||||
<span class="icon-cheveron-down" aria-hidden="true" />
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.aw-select {
|
||||
min-width: var(--min-width, var(--p-select-min-width));
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -22,4 +22,4 @@
|
||||
];
|
||||
</script>
|
||||
|
||||
<Select {options} bind:selected={$currentTheme} nativeMobile />
|
||||
<Select {options} bind:value={$currentTheme} placement="top" />
|
||||
|
||||
1
src/lib/utils/anyify.ts
Normal file
1
src/lib/utils/anyify.ts
Normal file
@@ -0,0 +1 @@
|
||||
export const anyify = (x: unknown) => x as any;
|
||||
@@ -2,15 +2,13 @@ import { writable } from 'svelte/store';
|
||||
import type { Language } from './code';
|
||||
import { browser } from '$app/environment';
|
||||
|
||||
export type Version = '1.4.x' | '1.3.x' | '1.2.x' | '1.1.x' | '1.0.x' | '0.15.x' | 'cloud';
|
||||
export const versions: Readonly<Array<Omit<Version, 'cloud'>>> = [
|
||||
'1.4.x',
|
||||
'1.3.x',
|
||||
'1.2.x',
|
||||
'1.1.x',
|
||||
'1.0.x',
|
||||
'0.15.x'
|
||||
] as const;
|
||||
const allVersions = ['1.4.x', '1.3.x', '1.2.x', '1.1.x', '1.0.x', '0.15.x', 'cloud'] as const;
|
||||
|
||||
export type Version = (typeof allVersions)[number];
|
||||
|
||||
export const versions: Readonly<Array<Omit<Version, 'cloud'>>> = allVersions.filter(
|
||||
(v) => v !== 'cloud'
|
||||
);
|
||||
|
||||
export enum Service {
|
||||
Account = 'account',
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { goto } from '$app/navigation';
|
||||
import { page } from '$app/stores';
|
||||
import { MainFooter } from '$lib/components';
|
||||
import { MainFooter, Select } from '$lib/components';
|
||||
import { DEFAULT_HOST } from '$lib/utils/metadata';
|
||||
import { layoutState, toggleReferences } from '$lib/layouts/Docs.svelte';
|
||||
import { parse } from '$lib/utils/markdown';
|
||||
@@ -19,6 +19,7 @@
|
||||
import { API_REFERENCE_TITLE_SUFFIX } from '$routes/titles.js';
|
||||
import { getContext, onMount, setContext } from 'svelte';
|
||||
import { writable } from 'svelte/store';
|
||||
import { anyify } from '$lib/utils/anyify.js';
|
||||
|
||||
export let data;
|
||||
|
||||
@@ -40,18 +41,18 @@
|
||||
}
|
||||
});
|
||||
|
||||
function selectPlatform(event: Event & { currentTarget: EventTarget & HTMLSelectElement }) {
|
||||
function selectPlatform(event: CustomEvent<unknown>) {
|
||||
const { version, service } = $page.params;
|
||||
const platform = event.currentTarget.value as Platform;
|
||||
const platform = event.detail as Platform;
|
||||
preferredPlatform.set(platform);
|
||||
goto(`/docs/references/${version}/${event.currentTarget.value}/${service}`, {
|
||||
goto(`/docs/references/${version}/${platform}/${service}`, {
|
||||
noScroll: true
|
||||
});
|
||||
}
|
||||
|
||||
function selectVersion(event: Event & { currentTarget: EventTarget & HTMLSelectElement }) {
|
||||
function selectVersion(event: CustomEvent<unknown>) {
|
||||
const { platform, service } = $page.params;
|
||||
const version = event.currentTarget.value as Version;
|
||||
const version = event.detail as Version;
|
||||
preferredVersion.set(version);
|
||||
goto(`/docs/references/${version}/${platform}/${service}`, {
|
||||
noScroll: true
|
||||
@@ -99,37 +100,44 @@
|
||||
<div class="u-flex u-gap-24 aw-u-color-text-primary">
|
||||
<div class="u-flex u-cross-center u-gap-8">
|
||||
<label class="u-small is-not-mobile" for="platform">Platform</label>
|
||||
<div class="aw-select is-colored">
|
||||
<select id="platform" on:change={selectPlatform} value={platform}>
|
||||
<optgroup label="Client">
|
||||
{#each Object.values(Platform).filter( (p) => p.startsWith('client-') ) as platform}
|
||||
<option value={platform}>{platformMap[platform]}</option>
|
||||
{/each}
|
||||
</optgroup>
|
||||
<optgroup label="Server">
|
||||
{#each Object.values(Platform).filter( (p) => p.startsWith('server-') ) as platform}
|
||||
<option value={platform}>{platformMap[platform]}</option>
|
||||
{/each}
|
||||
</optgroup>
|
||||
</select>
|
||||
<span class="icon-cheveron-down" aria-hidden="true" />
|
||||
</div>
|
||||
<Select
|
||||
--min-width="10rem"
|
||||
id="platform"
|
||||
value={platform}
|
||||
on:change={selectPlatform}
|
||||
options={[
|
||||
...Object.values(Platform)
|
||||
.filter((p) => p.startsWith('client-'))
|
||||
.map((p) => ({
|
||||
value: p,
|
||||
label: platformMap[p],
|
||||
group: 'Client'
|
||||
})),
|
||||
...Object.values(Platform)
|
||||
.filter((p) => p.startsWith('server-'))
|
||||
.map((p) => ({
|
||||
value: p,
|
||||
label: platformMap[p],
|
||||
group: 'Server'
|
||||
}))
|
||||
]}
|
||||
nativeMobile
|
||||
/>
|
||||
</div>
|
||||
<div class="u-flex u-cross-center u-gap-8">
|
||||
<label class="u-small is-not-mobile" for="version">Version</label>
|
||||
<div class="aw-select is-colored">
|
||||
<select
|
||||
id="version"
|
||||
|
||||
<Select
|
||||
on:change={selectVersion}
|
||||
value={$page.params.version}
|
||||
>
|
||||
<option value="cloud">Cloud</option>
|
||||
{#each versions as version}
|
||||
<option value={version}>{version}</option>
|
||||
{/each}
|
||||
</select>
|
||||
<span class="icon-cheveron-down" aria-hidden="true" />
|
||||
</div>
|
||||
options={[
|
||||
{ value: 'cloud', label: 'Cloud' },
|
||||
...versions.map((version) => ({
|
||||
value: version,
|
||||
label: anyify(version)
|
||||
}))
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
@use '../abstract' as *;
|
||||
|
||||
.#{$p}-select {
|
||||
--p-select-min-width: #{pxToRem(130)};
|
||||
|
||||
all:unset; display:flex; align-items:center; position:relative; line-height:1; user-select:none; cursor:pointer;
|
||||
color:hsl(var(--aw-color-primary)); font-size:pxToRem(14); min-width: pxToRem(130); height: pxToRem(30);
|
||||
color:hsl(var(--aw-color-primary)); font-size:pxToRem(14); min-width: var(--p-select-min-width); height: pxToRem(30);
|
||||
|
||||
select, .physical-select {
|
||||
all:unset; display:flex; align-items:center;
|
||||
@@ -129,6 +131,9 @@
|
||||
padding-block: pxToRem(4);
|
||||
box-sizing: border-box;
|
||||
|
||||
max-height: pxToRem(300) !important;
|
||||
overflow-y: auto;
|
||||
|
||||
@mixin light-mode() {
|
||||
--p-select-menu-bg-color: var(--aw-color-white);
|
||||
--p-select-menu-border-color: var(--aw-color-black) / 0.06;
|
||||
@@ -155,6 +160,12 @@
|
||||
|
||||
&-label {
|
||||
margin-block-end: pxToRem(4);
|
||||
padding-inline-start: pxToRem(4);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.#{$p}-select-option {
|
||||
padding-inline-start: pxToRem(16);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -166,6 +177,7 @@
|
||||
padding-block: pxToRem(4);
|
||||
padding-inline: pxToRem(4);
|
||||
border-radius: pxToRem(6);
|
||||
scroll-margin-block: pxToRem(8);
|
||||
|
||||
cursor: pointer;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user