Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Replace SelectList with SelectField. #1

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/quick-avocados-hope.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'svelte-ux': minor
---

Removes SelectList. Updates SelectField features to support SelectList's use case via property/attribute overrides. Updates QuickSearch to use SelectField.
5 changes: 5 additions & 0 deletions .changeset/sixty-mangos-melt.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'svelte-ux': patch
---

[ScrollingValue] Add `single` prop to enable individual number display (proper handling of 9)
6 changes: 6 additions & 0 deletions .changeset/slimy-starfishes-report.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'svelte-ux': patch
---

Added new `IconInput` and `IconData` types to enable inclusive & seamless passing of icon arguments between components. Also provides a `asIconData` utility function for type-safe conversion.
Fixed type errors for Button & TextField's use of Icon data.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"build": "rimraf packages/*/dist && pnpm -r build",
"lint": "pnpm -r lint",
"format": "pnpm -r format",
"changeset": "changeset",
"changeset:version": "changeset version",
"changeset:release": "changeset publish",
"up-dep": "pnpm -r up --latest"
Expand Down
5 changes: 3 additions & 2 deletions packages/svelte-ux/src/lib/components/Button.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,13 @@
import type { TailwindColors } from '$lib/types';
import { getComponentTheme } from './theme';
import { getButtonGroup } from './ButtonGroup.svelte';
import { asIconData, type IconInput } from '$lib/utils/icons';

export let type: 'button' | 'submit' | 'reset' = 'button';
export let href: string | undefined = undefined;
export let target: string | undefined = undefined;
export let fullWidth: boolean = false;
export let icon: ComponentProps<Icon>['data'] | ComponentProps<Icon> | undefined = undefined;
export let icon: IconInput = undefined;
export let iconOnly = icon !== undefined && $$slots.default !== true;
export let actions: Actions<HTMLAnchorElement | HTMLButtonElement> | undefined = undefined;

Expand Down Expand Up @@ -261,7 +262,7 @@
<span in:slide={{ axis: 'x', duration: 200 }}>
{#if typeof icon === 'string' || 'icon' in icon}
<!-- font path/url/etc or font-awesome IconDefinition -->
<Icon data={icon} class={cls('pointer-events-none', theme.icon, classes.icon)} />
<Icon data={asIconData(icon)} class={cls('pointer-events-none', theme.icon, classes.icon)} />
{:else}
<Icon class={cls('pointer-events-none', theme.icon, classes.icon)} {...icon} />
{/if}
Expand Down
13 changes: 8 additions & 5 deletions packages/svelte-ux/src/lib/components/Icon.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
export let height = size;
export let viewBox = '0 0 24 24';
export let path: string | string[] = '';
export let data: IconDefinition | string | undefined = undefined;
export let data: IconDefinition | string | null | undefined = undefined;
export let svg: string | undefined = undefined;
export let svgUrl: string | undefined = undefined;

Expand All @@ -31,7 +31,7 @@
} = {};
const theme = getComponentTheme('Icon');

$: if (typeof data === 'object' && 'icon' in data) {
$: if (typeof data === 'object' && data && 'icon' in data) {
// Font Awesome
const [_width, _height, _ligatures, _unicode, _path] = data.icon;
viewBox = `0 0 ${_width} ${_height}`;
Expand Down Expand Up @@ -59,15 +59,18 @@
$: if (svgUrl) {
let promise;
if (cache.has(svgUrl)) {
cache.get(svgUrl).then((text) => (svg = text));
cache.get(svgUrl)?.then((text) => (svg = text));
} else {
promise = fetch(svgUrl)
.then((resp) => resp.text())
.catch((e) => {
.catch(() => {
// Failed request, remove promise so fetched again
cache.delete(svgUrl);
if (svgUrl && typeof(svgUrl) === "string") {
cache.delete(svgUrl);
}
// TODO: Consider showing error icon
// throw e;
return "";
});
cache.set(svgUrl, promise);
promise.then((text) => {
Expand Down
13 changes: 9 additions & 4 deletions packages/svelte-ux/src/lib/components/QuickSearch.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@

import Button from '$lib/components/Button.svelte';
import Dialog from '$lib/components/Dialog.svelte';
import SelectList from '$lib/components/SelectList.svelte';
import SelectField from '$lib/components/SelectField.svelte';
import { getComponentTheme } from './theme';
import { cls } from '$lib/utils/styles';
import { smScreen } from '$lib/stores';
import { autoFocus, selectOnFocus } from '$lib/actions';

export let options: { name: string; value: string; group?: string }[] = [];

Expand All @@ -18,10 +19,11 @@

let open = false;

let fieldActions = (node: any) => [autoFocus(node), selectOnFocus(node)];

/*
TODO:
- [ ] Sticky search
- [ ] Refine SelectList / reuse with SelectField (and maybe MultiSelect)
- [ ] Improve size of Dialog (move class to Dialog without breaking overflow)
- [ ] Load descriptions/meta from +page.ts
- [ ] Improve dialog positioning on small viewports (consistent top/bottom with max height)
Expand Down Expand Up @@ -62,12 +64,15 @@
}}
>
<div class="overflow-auto max-h-[min(90dvh,600px)] min-w-[400px] py-1">
<SelectList
<SelectField
icon={mdiMagnify}
placeholder="Search..."
showToggleIcon={false}
optionsMode="list"
{options}
{fieldActions}
on:change
on:change={(e) => (open = false)}
on:change={() => (open = false)}
classes={{
field: {
container: 'border-none hover:shadow-none group-focus-within:shadow-none',
Expand Down
3 changes: 2 additions & 1 deletion packages/svelte-ux/src/lib/components/ScrollingValue.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import { getComponentTheme } from './theme';

export let value = 0;
export let single = false;
export let format: (value: number) => string | number = (value) => value;

export let classes: {
Expand Down Expand Up @@ -33,7 +34,7 @@
class={cls('col-span-full row-span-full', theme.value, classes.value)}
style:transform="translateY({-100 + 100 * offset}%)"
>
{format(Math.floor($displayValue + 1))}
{format(Math.floor(single && $displayValue >= 9 ? 0 : $displayValue + 1))}
</div>
<div
class={cls('col-span-full row-span-full', theme.value, classes.value)}
Expand Down
51 changes: 40 additions & 11 deletions packages/svelte-ux/src/lib/components/SelectField.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import { mdiChevronDown, mdiClose } from '@mdi/js';

import Logger from '../utils/logger';
import { selectOnFocus } from '../actions/input';
import { autoFocus, selectOnFocus } from '../actions/input';
import { cls } from '../utils/styles';

import Button from './Button.svelte';
Expand All @@ -14,6 +14,8 @@
import MenuItem from './MenuItem.svelte';
import TextField from './TextField.svelte';
import { getComponentTheme } from './theme';
import Maybe from './Maybe.svelte';
import type { IconInput } from '$lib/utils/icons';

const dispatch = createEventDispatcher<{
change: { value: any; option: any };
Expand All @@ -31,16 +33,26 @@
export let loading: boolean = false;
export let disabled: boolean = false;
export let readonly: boolean = false;
export let icon: string | null = null;
export let icon: IconInput = undefined;
export let toggleIcon: IconInput = mdiChevronDown;
export let closeIcon: IconInput = mdiClose;
export let clearable = true;
export let base = false;
export let rounded = false;
export let dense = false;
export let clearSearchOnOpen = true;
export let tabIterable = false;
export let tabindex = 0;
export let autofocus: ComponentProps<TextField>['autofocus'] = undefined;
export let fieldActions: ComponentProps<TextField>['actions'] = autofocus
? (node) => [autoFocus(node, typeof autofocus === 'object' ? autofocus : undefined), selectOnFocus(node)]
: undefined;

export let showToggleIcon = true;

export let classes: {
root?: string;
field?: string;
field?: string | ComponentProps<TextField>['classes'];
options?: string;
option?: string;
selected?: string;
Expand All @@ -49,13 +61,17 @@
} = {};
const theme = getComponentTheme('SelectField');

let fieldClasses: ComponentProps<TextField>['classes'];
$: fieldClasses = typeof(classes.field) === "string" ? { root: classes.field } : classes.field;

// Menu props
export let placement: Placement = 'bottom-start';
export let autoPlacement = true;
export let matchWidth = true;
export let resize = true;
export let disableTransition = false;
export let menuProps: ComponentProps<Menu> | undefined = undefined;
export let optionsMode: "list" | "menu" = "menu";

$: filteredOptions = options ?? [];
let searchText = '';
Expand Down Expand Up @@ -336,7 +352,14 @@
</script>

<!-- svelte-ignore a11y-click-events-have-key-events -->
<div role="button" tabindex="-1" aria-pressed={open ? "true" : "false"} class={cls('SelectField', theme.root, classes.root, $$props.class)} on:click={onClick}>
<div
role="button"
{tabindex}
aria-pressed={open ? "true" : "false"}
aria-haspopup={optionsMode === "menu" ? "listbox" : undefined}
class={cls('SelectField', theme.root, classes.root, $$props.class)}
on:click={onClick}>

<TextField
{label}
{placeholder}
Expand All @@ -352,10 +375,15 @@
on:blur={onBlur}
on:keydown={onKeyDown}
on:keypress={onKeyPress}
actions={(node) => [selectOnFocus(node)]}
class={cls('h-full', theme.field, classes.field)}
actions={fieldActions}
classes={{
root: cls('h-full'),
...theme.field,
...fieldClasses,
}}
role="combobox"
aria-expanded={open ? "true" : "false"}
aria-autocomplete={optionsMode === "menu" ? "list" : undefined}
{...$$restProps}
>
<slot slot="prepend" name="prepend" />
Expand All @@ -371,16 +399,16 @@
<!-- Do not show chevron or clear buttons -->
{:else if value && clearable}
<Button
icon={mdiClose}
icon={closeIcon}
class="text-black/50 p-1"
on:click={(e) => {
e.stopPropagation();
clear();
}}
/>
{:else}
{:else if showToggleIcon}
<Button
icon={mdiChevronDown}
icon={toggleIcon}
class="text-black/50 p-1 transform {open ? 'rotate-180' : ''}"
tabindex="-1"
/>
Expand All @@ -390,7 +418,7 @@

<!-- Improve initial open display, still needs work when switching from No options found (options.length === 0) -->
{#if options?.length > 0 || loading !== true}
<Menu
<Maybe this={optionsMode === "menu" ? Menu : undefined}
{placement}
{autoPlacement}
{matchWidth}
Expand Down Expand Up @@ -456,6 +484,7 @@
role="option"
aria-selected={option === selected ? "true" : "false"}
aria-disabled={option?.disabled ? "true" : "false"}
tabindex={tabIterable ? 0 : -1}
>
{optionText(option)}
</MenuItem>
Expand All @@ -469,6 +498,6 @@
{/each}
</div>
<slot name="actions" {hide} />
</Menu>
</Maybe>
{/if}
</div>
Loading