pretty selects everywhere

This commit is contained in:
tglide
2023-10-12 12:44:56 +01:00
parent 776bcc9bee
commit f2073ee532
7 changed files with 670 additions and 602 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -9,37 +9,49 @@
<script lang="ts"> <script lang="ts">
import { createSelect, melt, type CreateSelectProps } from '@melt-ui/svelte'; import { createSelect, melt, type CreateSelectProps } from '@melt-ui/svelte';
import { fly } from 'svelte/transition'; import { createEventDispatcher } from 'svelte';
import { fly, type FlyParams } from 'svelte/transition';
export let options: Array<SelectOption>; export let options: Array<SelectOption>;
export let nativeMobile = false; export let nativeMobile = false;
export let selected: unknown | undefined = undefined; 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';
const dispatch = createEventDispatcher<{
change: unknown;
}>();
const { const {
elements: { trigger, menu, option: optionEl, group: groupEl, groupLabel }, elements: { trigger, menu, option: optionEl, group: groupEl, groupLabel },
states: { open, selected: localSelected, selectedLabel } states: { open, selected, selectedLabel }
} = createSelect<unknown>({ } = createSelect<unknown>({
preventScroll: false, preventScroll,
positioning: { positioning: {
sameWidth: true, sameWidth: true,
fitViewport: true fitViewport: true,
placement
}, },
forceVisible: true, forceVisible: true,
onSelectedChange({ curr, next }) { onSelectedChange({ curr, next }) {
if (onSelectedChange) { if (onSelectedChange) {
onSelectedChange({ curr, next }); onSelectedChange({ curr, next });
} }
selected = next?.value; value = next?.value;
dispatch('change', next?.value);
return next; return next;
} }
}); });
$: selectedOption = options.find((o) => o.value === selected); $: selectedOption = options.find((o) => o.value === value);
$: if (selectedOption) { $: if (selectedOption) {
localSelected.set(selectedOption); selected.set(selectedOption);
} }
const DEFAULT_GROUP = 'default'; const DEFAULT_GROUP = 'default';
@@ -59,10 +71,16 @@
return Object.entries(groups).map(([label, options]) => ({ label, options })); return Object.entries(groups).map(([label, options]) => ({ label, options }));
})(); })();
$: flyParams = {
duration: 150,
y: placement === 'top' ? 4 : -4
} as FlyParams;
</script> </script>
<button <button
class="aw-select is-colored" class="aw-select is-colored"
{id}
class:aw-is-not-mobile={nativeMobile} class:aw-is-not-mobile={nativeMobile}
use:melt={$trigger} use:melt={$trigger}
aria-label="Select theme" aria-label="Select theme"
@@ -82,14 +100,11 @@
class:aw-is-not-mobile={nativeMobile} class:aw-is-not-mobile={nativeMobile}
style:z-index={10000} style:z-index={10000}
use:melt={$menu} use:melt={$menu}
transition:fly={{ y: 4, duration: 150 }} transition:fly={flyParams}
> >
{#each groups as group} {#each groups as group}
{@const isDefault = group.label === DEFAULT_GROUP} {@const isDefault = group.label === DEFAULT_GROUP}
<div class="aw-select-group" use:melt={$groupEl(group.label)}> {#if isDefault}
{#if !isDefault}
<span class="aw-select-group-label" use:melt={$groupLabel}>{group.label}</span>
{/if}
{#each options as option} {#each options as option}
<button class="aw-select-option" use:melt={$optionEl(option)}> <button class="aw-select-option" use:melt={$optionEl(option)}>
{#if option.icon} {#if option.icon}
@@ -98,7 +113,22 @@
<span style:text-transform="capitalize">{option.label}</span> <span style:text-transform="capitalize">{option.label}</span>
</button> </button>
{/each} {/each}
</div> {: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}
<span class={option.icon} aria-hidden="true" />
{/if}
<span style:text-transform="capitalize">{option.label}</span>
</button>
{/each}
</div>
{/if}
{/each} {/each}
</div> </div>
{/if} {/if}
@@ -110,19 +140,19 @@
{#if selectedOption?.icon} {#if selectedOption?.icon}
<span class={selectedOption.icon} aria-hidden="true" /> <span class={selectedOption.icon} aria-hidden="true" />
{/if} {/if}
<select bind:value={selected}> <select {id} bind:value>
{#each groups as group} {#each groups as group}
{@const isDefault = group.label === DEFAULT_GROUP} {@const isDefault = group.label === DEFAULT_GROUP}
{#if isDefault} {#if isDefault}
{#each options as option} {#each options as option}
<option value={option.value} selected={option.value === selected}> <option value={option.value} selected={option.value === value}>
{option.label} {option.label}
</option> </option>
{/each} {/each}
{:else} {:else}
<optgroup label={isDefault ? undefined : group.label}> <optgroup label={isDefault ? undefined : group.label}>
{#each options as option} {#each options as option}
<option value={option.value} selected={option.value === selected}> <option value={option.value} selected={option.value === value}>
{option.label} {option.label}
</option> </option>
{/each} {/each}
@@ -132,3 +162,9 @@
</select> </select>
<span class="icon-cheveron-down" aria-hidden="true" /> <span class="icon-cheveron-down" aria-hidden="true" />
</div> </div>
<style lang="scss">
.aw-select {
min-width: var(--min-width, var(--p-select-min-width));
}
</style>

View File

@@ -22,4 +22,4 @@
]; ];
</script> </script>
<Select {options} bind:selected={$currentTheme} nativeMobile /> <Select {options} bind:value={$currentTheme} placement="top" />

1
src/lib/utils/anyify.ts Normal file
View File

@@ -0,0 +1 @@
export const anyify = (x: unknown) => x as any;

View File

@@ -2,15 +2,13 @@ import { writable } from 'svelte/store';
import type { Language } from './code'; import type { Language } from './code';
import { browser } from '$app/environment'; 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'; const allVersions = ['1.4.x', '1.3.x', '1.2.x', '1.1.x', '1.0.x', '0.15.x', 'cloud'] as const;
export const versions: Readonly<Array<Omit<Version, 'cloud'>>> = [
'1.4.x', export type Version = (typeof allVersions)[number];
'1.3.x',
'1.2.x', export const versions: Readonly<Array<Omit<Version, 'cloud'>>> = allVersions.filter(
'1.1.x', (v) => v !== 'cloud'
'1.0.x', );
'0.15.x'
] as const;
export enum Service { export enum Service {
Account = 'account', Account = 'account',

View File

@@ -1,7 +1,7 @@
<script lang="ts"> <script lang="ts">
import { goto } from '$app/navigation'; import { goto } from '$app/navigation';
import { page } from '$app/stores'; import { page } from '$app/stores';
import { MainFooter } from '$lib/components'; import { MainFooter, Select } from '$lib/components';
import { DEFAULT_HOST } from '$lib/utils/metadata'; import { DEFAULT_HOST } from '$lib/utils/metadata';
import { layoutState, toggleReferences } from '$lib/layouts/Docs.svelte'; import { layoutState, toggleReferences } from '$lib/layouts/Docs.svelte';
import { parse } from '$lib/utils/markdown'; import { parse } from '$lib/utils/markdown';
@@ -19,6 +19,7 @@
import { API_REFERENCE_TITLE_SUFFIX } from '$routes/titles.js'; import { API_REFERENCE_TITLE_SUFFIX } from '$routes/titles.js';
import { getContext, onMount, setContext } from 'svelte'; import { getContext, onMount, setContext } from 'svelte';
import { writable } from 'svelte/store'; import { writable } from 'svelte/store';
import { anyify } from '$lib/utils/anyify.js';
export let data; 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 { version, service } = $page.params;
const platform = event.currentTarget.value as Platform; const platform = event.detail as Platform;
preferredPlatform.set(platform); preferredPlatform.set(platform);
goto(`/docs/references/${version}/${event.currentTarget.value}/${service}`, { goto(`/docs/references/${version}/${platform}/${service}`, {
noScroll: true noScroll: true
}); });
} }
function selectVersion(event: Event & { currentTarget: EventTarget & HTMLSelectElement }) { function selectVersion(event: CustomEvent<unknown>) {
const { platform, service } = $page.params; const { platform, service } = $page.params;
const version = event.currentTarget.value as Version; const version = event.detail as Version;
preferredVersion.set(version); preferredVersion.set(version);
goto(`/docs/references/${version}/${platform}/${service}`, { goto(`/docs/references/${version}/${platform}/${service}`, {
noScroll: true noScroll: true
@@ -99,37 +100,44 @@
<div class="u-flex u-gap-24 aw-u-color-text-primary"> <div class="u-flex u-gap-24 aw-u-color-text-primary">
<div class="u-flex u-cross-center u-gap-8"> <div class="u-flex u-cross-center u-gap-8">
<label class="u-small is-not-mobile" for="platform">Platform</label> <label class="u-small is-not-mobile" for="platform">Platform</label>
<div class="aw-select is-colored"> <Select
<select id="platform" on:change={selectPlatform} value={platform}> --min-width="10rem"
<optgroup label="Client"> id="platform"
{#each Object.values(Platform).filter( (p) => p.startsWith('client-') ) as platform} value={platform}
<option value={platform}>{platformMap[platform]}</option> on:change={selectPlatform}
{/each} options={[
</optgroup> ...Object.values(Platform)
<optgroup label="Server"> .filter((p) => p.startsWith('client-'))
{#each Object.values(Platform).filter( (p) => p.startsWith('server-') ) as platform} .map((p) => ({
<option value={platform}>{platformMap[platform]}</option> value: p,
{/each} label: platformMap[p],
</optgroup> group: 'Client'
</select> })),
<span class="icon-cheveron-down" aria-hidden="true" /> ...Object.values(Platform)
</div> .filter((p) => p.startsWith('server-'))
.map((p) => ({
value: p,
label: platformMap[p],
group: 'Server'
}))
]}
nativeMobile
/>
</div> </div>
<div class="u-flex u-cross-center u-gap-8"> <div class="u-flex u-cross-center u-gap-8">
<label class="u-small is-not-mobile" for="version">Version</label> <label class="u-small is-not-mobile" for="version">Version</label>
<div class="aw-select is-colored">
<select <Select
id="version" on:change={selectVersion}
on:change={selectVersion} value={$page.params.version}
value={$page.params.version} options={[
> { value: 'cloud', label: 'Cloud' },
<option value="cloud">Cloud</option> ...versions.map((version) => ({
{#each versions as version} value: version,
<option value={version}>{version}</option> label: anyify(version)
{/each} }))
</select> ]}
<span class="icon-cheveron-down" aria-hidden="true" /> />
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -1,8 +1,10 @@
@use '../abstract' as *; @use '../abstract' as *;
.#{$p}-select { .#{$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; 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 { select, .physical-select {
all:unset; display:flex; align-items:center; all:unset; display:flex; align-items:center;
@@ -129,6 +131,9 @@
padding-block: pxToRem(4); padding-block: pxToRem(4);
box-sizing: border-box; box-sizing: border-box;
max-height: pxToRem(300) !important;
overflow-y: auto;
@mixin light-mode() { @mixin light-mode() {
--p-select-menu-bg-color: var(--aw-color-white); --p-select-menu-bg-color: var(--aw-color-white);
--p-select-menu-border-color: var(--aw-color-black) / 0.06; --p-select-menu-border-color: var(--aw-color-black) / 0.06;
@@ -155,6 +160,12 @@
&-label { &-label {
margin-block-end: pxToRem(4); 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-block: pxToRem(4);
padding-inline: pxToRem(4); padding-inline: pxToRem(4);
border-radius: pxToRem(6); border-radius: pxToRem(6);
scroll-margin-block: pxToRem(8);
cursor: pointer; cursor: pointer;