From 9864b86ce56df1ba4b225ce5714fd55cb73e8543 Mon Sep 17 00:00:00 2001 From: Dinh-Van Colomban Date: Sat, 9 Nov 2024 00:48:04 +0100 Subject: [PATCH] feat(tabs): adds base tab bar component --- demo/App.svelte | 33 ++++-- demo/components/DemoButtonGroups.svelte | 133 +++++++-------------- demo/components/DemoButtons.svelte | 107 +++++------------ demo/components/DemoTabs.svelte | 136 ++++++++++++++++++++++ demo/router/routes.ts | 8 ++ demo/utils/use-button-state.svelte.ts | 31 +++++ package.json | 4 +- pnpm-lock.yaml | 22 ++-- src/lib/buttons/NeoButton.svelte | 55 ++++++--- src/lib/buttons/NeoButtonGroup.svelte | 55 +++++++-- src/lib/buttons/neo-button-group.model.ts | 19 +-- src/lib/buttons/neo-button.model.ts | 12 +- src/lib/icons/IconAdd.svelte | 10 ++ src/lib/icons/IconClose.svelte | 14 +++ src/lib/nav/NeoTab.svelte | 128 ++++++++++++++++++++ src/lib/nav/NeoTabs.svelte | 104 +++++++++++++++++ src/lib/nav/neo-tab.model.ts | 15 +++ src/lib/nav/neo-tabs-context.svelte.ts | 99 ++++++++++++++++ src/lib/nav/neo-tabs.model.ts | 27 +++++ src/lib/styles/common/spacing.scss | 3 + src/lib/styles/mixin.scss | 2 +- src/lib/styles/theme.scss | 1 + src/lib/utils/transition.utils.ts | 31 +++++ 23 files changed, 827 insertions(+), 222 deletions(-) create mode 100644 demo/components/DemoTabs.svelte create mode 100644 demo/utils/use-button-state.svelte.ts create mode 100644 src/lib/icons/IconAdd.svelte create mode 100644 src/lib/icons/IconClose.svelte create mode 100644 src/lib/nav/NeoTab.svelte create mode 100644 src/lib/nav/NeoTabs.svelte create mode 100644 src/lib/nav/neo-tab.model.ts create mode 100644 src/lib/nav/neo-tabs-context.svelte.ts create mode 100644 src/lib/nav/neo-tabs.model.ts create mode 100644 src/lib/utils/transition.utils.ts diff --git a/demo/App.svelte b/demo/App.svelte index 0b22516..edc99cc 100644 --- a/demo/App.svelte +++ b/demo/App.svelte @@ -9,35 +9,50 @@ import { router } from './router/router.js'; - import { Route } from './router/routes'; + import { routes } from './router/routes.js'; + import type { Routes } from './router/routes.js'; import type { TransitionProps } from '@dvcol/svelte-simple-router/models'; import NeoButton from '~/buttons/NeoButton.svelte'; import NeoButtonGroup from '~/buttons/NeoButtonGroup.svelte'; import IconMoon from '~/icons/IconMoon.svelte'; import IconSun from '~/icons/IconSun.svelte'; + import NeoTab from '~/nav/NeoTab.svelte'; + import NeoTabs from '~/nav/NeoTabs.svelte'; const transition: TransitionProps = { in: fade, out: fade, - params: { in: { delay: 200, duration: 200 }, out: { duration: 200 } }, + params: { in: { delay: 100, duration: 100 }, out: { duration: 100 } }, + props: { container: { style: 'display: flex; justify-content: center; align-items: center; overflow:hidden;' } }, skipFirst: true, }; const active = $derived(router.route?.name); let transitioning = $state(false); + let first = true; const onChange = async () => { + if (first) return; transitioning = true; - await wait(150); + await wait(200); }; const onLoaded = async () => { - await wait(350); + if (active && first) { + first = false; + return; + } + await wait(200); transitioning = false; }; + const onClick = (id?: Routes) => { + if (id === undefined || id === active) return; + router.push({ name: id }); + }; + const initial = localStorage.getItem('theme'); let dark = $state(initial ? initial === 'dark' : window.matchMedia('(prefers-color-scheme: dark)').matches); let remember = $state(!!localStorage.getItem('theme')); @@ -48,19 +63,15 @@ if (remember) localStorage.setItem('theme', dark ? 'dark' : 'light'); else localStorage.removeItem('theme'); }); - - const routes = [Route.Buttons, Route.ButtonGroups];
- + {#each routes as route} - router.push({ name: route })}> - {route} - + {route} {/each} - + diff --git a/demo/components/DemoButtonGroups.svelte b/demo/components/DemoButtonGroups.svelte index a3e95d0..a28843f 100644 --- a/demo/components/DemoButtonGroups.svelte +++ b/demo/components/DemoButtonGroups.svelte @@ -3,112 +3,63 @@ import SphereBackdrop from '../utils/SphereBackdrop.svelte'; + import { useButtonState } from '../utils/use-button-state.svelte'; + + import type { NeoButtonGroupContext } from '~/buttons/neo-button-group.model'; + import NeoButton from '~/buttons/NeoButton.svelte'; import NeoButtonGroup from '~/buttons/NeoButtonGroup.svelte'; import IconAccount from '~/icons/IconAccount.svelte'; - const onClick = (...args: any) => { - console.info(...args); - }; - - let loading = $state(false); - const onLoading = (e: MouseEvent, checked?: boolean, duration = 5000) => { - loading = !loading; - setTimeout(() => { - loading = !loading; - }, duration); - onClick(e); - }; - - let skeleton = $state(false); - const onSkeleton = (e: MouseEvent, checked?: boolean, duration = 5000) => { - skeleton = !skeleton; - setTimeout(() => { - skeleton = !skeleton; - }, duration); - onClick(e); - }; + const { onClick, loading: loading$, onLoading, skeleton: skeleton$, onSkeleton } = useButtonState('DemoGroupClicked'); + const loading = $derived.by(loading$); + const skeleton = $derived.by(skeleton$); const { matches } = useWatchMedia('(max-width: 1550px)'); const vertical = $derived.by(matches); + + const columns = [ + { label: 'Default' }, + { label: 'Rounded', props: { rounded: true } }, + { label: 'Flat', props: { flat: true } }, + { label: 'Text', props: { text: true } }, + { label: 'Glass', props: { glass: true } }, + ]; {#snippet icon()} {/snippet} -
-
- Default - - Button - Toggle - Disabled - Loading - - Icon - Reversed - Skeleton - -
- -
- Rounded - - Button - Toggle - Disabled - Loading - - Icon - Reversed - Skeleton - -
- -
- Flat - - Button - Toggle - Disabled - Loading - - Icon - Reversed - Skeleton - -
+{#snippet buttons()} + Button + Toggle + Disabled + Loading + + Icon + Reversed + Skeleton +{/snippet} -
- Text - - Button - Toggle - Disabled - Loading - - Icon - Reversed - Skeleton - -
+{#snippet group(props: NeoButtonGroupContext = {})} + + {@render buttons()} + +{/snippet} -
- Glass - - - Button - Toggle - Disabled - Loading - - Icon - Reversed - Skeleton - - -
+
+ {#each columns as { label, props }} +
+ {label} + + {#if props?.glass} + {@render group(props)} + {:else} + {@render group(props)} + {/if} +
+ {/each}
Pulse diff --git a/demo/components/DemoButtons.svelte b/demo/components/DemoButtons.svelte index 9e46ed6..ef3e112 100644 --- a/demo/components/DemoButtons.svelte +++ b/demo/components/DemoButtons.svelte @@ -1,92 +1,49 @@ {#snippet icon()} {/snippet} -
-
- Default - Button - Toggle - Disabled - Loading - - Icon - Reversed - Pulse - Coalesce - Skeleton -
- -
- Rounded - Button - Toggle - Disabled - Loading - - Icon - Reversed - Pulse - Coalesce - Skeleton -
- -
- Flat - Button - Toggle - Disabled - Loading - - Icon - Reversed - Pulse - Coalesce - Skeleton -
+{#snippet buttons(opts: NeoButtonProps = {})} + Button + Toggle + Disabled + Loading + + Icon + Reversed + Pulse + Coalesce + Skeleton +{/snippet} -
- Text - Button - Toggle - Disabled - Loading - - Icon - Reversed - Pulse - Coalesce - Skeleton -
+
+ {#each columns as { label, props }} +
+ {label} + {@render buttons(props)} +
+ {/each}
Glass diff --git a/demo/components/DemoTabs.svelte b/demo/components/DemoTabs.svelte new file mode 100644 index 0000000..714afae --- /dev/null +++ b/demo/components/DemoTabs.svelte @@ -0,0 +1,136 @@ + + +{#snippet icon()} + +{/snippet} + +{#snippet buttons()} + Button + Disabled + Loading + + Icon + Reversed + Skeleton +{/snippet} + +{#snippet group(props: NeoButtonGroupContext = {})} + + {@render buttons()} + {#each added as { text, ...tab }} + {text} + {/each} + +{/snippet} + +
+ Active: {active} + Value: {typeof value === 'object' ? JSON.stringify(value, undefined, 2) : value} +
+ +
+
+ + Disabled + Add + Close + Slide + +
+
+ +
+ {#each columns as { label, props }} +
+ {label} + + {#if props?.glass} + {@render group(props)} + {:else} + {@render group(props)} + {/if} +
+ {/each} +
+ + diff --git a/demo/router/routes.ts b/demo/router/routes.ts index 6923121..50fae44 100644 --- a/demo/router/routes.ts +++ b/demo/router/routes.ts @@ -4,10 +4,13 @@ export const Route = { Any: 'any' as const, Buttons: 'buttons' as const, ButtonGroups: 'button-groups' as const, + Tabs: 'tabs' as const, } as const; export type Routes = (typeof Route)[keyof typeof Route]; +export const routes = Object.values(Route).filter(key => key !== Route.Any); + export const options: RouterOptions = { hash: true, routes: [ @@ -21,6 +24,11 @@ export const options: RouterOptions = { path: '/buttons/groups', component: () => import('../components/DemoButtonGroups.svelte'), }, + { + name: Route.Tabs, + path: '/tabs', + component: () => import('../components/DemoTabs.svelte'), + }, { name: Route.Any, path: '*', diff --git a/demo/utils/use-button-state.svelte.ts b/demo/utils/use-button-state.svelte.ts new file mode 100644 index 0000000..a5646f6 --- /dev/null +++ b/demo/utils/use-button-state.svelte.ts @@ -0,0 +1,31 @@ +export const useButtonState = (prefix = '') => { + const onClick = (...args: any) => { + console.info(prefix, ...args); + }; + + let loading = $state(false); + const onLoading = (e: MouseEvent, checked?: boolean, duration = 5000) => { + loading = !loading; + setTimeout(() => { + loading = !loading; + }, duration); + onClick(e); + }; + + let skeleton = $state(false); + const onSkeleton = (e: MouseEvent, checked?: boolean, duration = 5000) => { + skeleton = !skeleton; + setTimeout(() => { + skeleton = !skeleton; + }, duration); + onClick(e); + }; + + return { + onClick, + loading: () => loading, + skeleton: () => skeleton, + onLoading, + onSkeleton, + }; +}; diff --git a/package.json b/package.json index 98155f9..1bd5a3e 100644 --- a/package.json +++ b/package.json @@ -79,7 +79,7 @@ }, "dependencies": { "@dvcol/common-utils": "^1.18.1", - "@dvcol/svelte-utils": "^1.2.1", + "@dvcol/svelte-utils": "^1.3.0", "svelte": "^5.1.9", "vite": "^5.4.10" }, @@ -88,7 +88,7 @@ "@commitlint/config-conventional": "^19.4.1", "@dvcol/eslint-plugin-presets": "^1.3.11", "@dvcol/stylelint-plugin-presets": "^2.1.2", - "@dvcol/svelte-simple-router": "^1.7.0", + "@dvcol/svelte-simple-router": "^1.7.1", "@sveltejs/adapter-auto": "^3.2.5", "@sveltejs/kit": "^2.7.4", "@sveltejs/package": "^2.3.7", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 967c248..2263476 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -12,8 +12,8 @@ importers: specifier: ^1.18.1 version: 1.18.1 '@dvcol/svelte-utils': - specifier: ^1.2.1 - version: 1.2.1(@types/node@22.5.4)(sass@1.78.0)(sugarss@2.0.0)(svelte@5.1.9) + specifier: ^1.3.0 + version: 1.3.0(@types/node@22.5.4)(sass@1.78.0)(sugarss@2.0.0)(svelte@5.1.9) svelte: specifier: ^5.1.9 version: 5.1.9 @@ -34,8 +34,8 @@ importers: specifier: ^2.1.2 version: 2.1.2(postcss-html@0.36.0)(postcss-scss@4.0.9(postcss@8.4.45))(postcss@8.4.45)(prettier@3.3.3)(stylelint@16.9.0(typescript@5.5.4)) '@dvcol/svelte-simple-router': - specifier: ^1.7.0 - version: 1.7.0(@types/node@22.5.4)(sass@1.78.0)(sugarss@2.0.0)(svelte@5.1.9) + specifier: ^1.7.1 + version: 1.7.1(@types/node@22.5.4)(sass@1.78.0)(sugarss@2.0.0)(svelte@5.1.9) '@sveltejs/adapter-auto': specifier: ^3.2.5 version: 3.2.5(@sveltejs/kit@2.7.4(@sveltejs/vite-plugin-svelte@4.0.0(svelte@5.1.9)(vite@5.4.10(@types/node@22.5.4)(sass@1.78.0)(sugarss@2.0.0)))(svelte@5.1.9)(vite@5.4.10(@types/node@22.5.4)(sass@1.78.0)(sugarss@2.0.0))) @@ -457,14 +457,14 @@ packages: postcss-scss: optional: true - '@dvcol/svelte-simple-router@1.7.0': - resolution: {integrity: sha512-ZLSG5hYeiKO4ckJmCTwqc95pEy+iTxWGxyzOS0f8pDbqnLwbk1eRHg7GB6mZEqF+jmBi3t3qpM/aI6AQ6ndnPQ==} + '@dvcol/svelte-simple-router@1.7.1': + resolution: {integrity: sha512-8WNuPypsQftcFLrA6xJ8225ijlgyshAYAf2ID8uwfVUHophxkY48BFGcsOXJUR/7BevWABFWwFTTl1+eFE2mbQ==} engines: {node: '>=20', pnpm: '>= 8'} peerDependencies: svelte: '>=5' - '@dvcol/svelte-utils@1.2.1': - resolution: {integrity: sha512-YbiwoBobA9iV11dUixmeuED0bJkT5Wn003/2wnyINj9RmCxr7NlIhFRIV21+/RgauDxM+vb9SYhXmCtUY4oVtA==} + '@dvcol/svelte-utils@1.3.0': + resolution: {integrity: sha512-sEyKQFRil+IH3r4Ejm/rCVj/H8ktgS+WNv5nsLHiZe87OQrMFdhqf1pbQovG2GZnuuRAJaAXI3aFOLy+Jh0ujQ==} engines: {node: '>=20', pnpm: '>= 8'} peerDependencies: svelte: '>=5' @@ -5163,10 +5163,10 @@ snapshots: transitivePeerDependencies: - supports-color - '@dvcol/svelte-simple-router@1.7.0(@types/node@22.5.4)(sass@1.78.0)(sugarss@2.0.0)(svelte@5.1.9)': + '@dvcol/svelte-simple-router@1.7.1(@types/node@22.5.4)(sass@1.78.0)(sugarss@2.0.0)(svelte@5.1.9)': dependencies: '@dvcol/common-utils': 1.18.1 - '@dvcol/svelte-utils': 1.2.1(@types/node@22.5.4)(sass@1.78.0)(sugarss@2.0.0)(svelte@5.1.9) + '@dvcol/svelte-utils': 1.3.0(@types/node@22.5.4)(sass@1.78.0)(sugarss@2.0.0)(svelte@5.1.9) svelte: 5.1.9 vite: 5.4.10(@types/node@22.5.4)(sass@1.78.0)(sugarss@2.0.0) transitivePeerDependencies: @@ -5179,7 +5179,7 @@ snapshots: - sugarss - terser - '@dvcol/svelte-utils@1.2.1(@types/node@22.5.4)(sass@1.78.0)(sugarss@2.0.0)(svelte@5.1.9)': + '@dvcol/svelte-utils@1.3.0(@types/node@22.5.4)(sass@1.78.0)(sugarss@2.0.0)(svelte@5.1.9)': dependencies: '@dvcol/common-utils': 1.18.1 svelte: 5.1.9 diff --git a/src/lib/buttons/NeoButton.svelte b/src/lib/buttons/NeoButton.svelte index acc89bc..f41c39a 100644 --- a/src/lib/buttons/NeoButton.svelte +++ b/src/lib/buttons/NeoButton.svelte @@ -4,6 +4,7 @@ import type { NeoButtonProps } from '~/buttons/neo-button.model.js'; import IconCircleLoading from '~/icons/IconCircleLoading.svelte'; + import { emptyFn } from '~/utils/transition.utils.js'; /* eslint-disable prefer-const -- necessary for binding checked */ let { @@ -15,12 +16,14 @@ loading, skeleton, disabled, + empty: only, toggle, + readonly, checked = $bindable(false), // Styles class: classNames, - start = true, + start, text, flat, glass, @@ -34,7 +37,16 @@ onclick, onkeydown, onkeyup, - // other button props + + // Transition + in: inFn, + inProps, + out: outFn, + outProps, + transition: transitionFn, + transitionProps, + + // Other props ...rest }: NeoButtonProps = $props(); /* eslint-enable prefer-const */ @@ -42,6 +54,7 @@ let enter = $state(false); let active = $state(false); const pressed = $derived(enter || active || checked); + const empty = $derived(only || !children); const onKeydownEnter = (e: KeyboardEvent) => { if (loading) return; @@ -67,7 +80,7 @@ const onClick = (e: MouseEvent) => { if (loading) return; if (toggle) { - checked = !checked; + if (!readonly) checked = !checked; onchecked?.(checked); onclick?.(e, checked); return; @@ -76,6 +89,9 @@ onclick?.(e); onActive(); }; + + const _inFn = $derived(inFn ?? transitionFn ?? emptyFn); + const _outFn = $derived(outFn ?? transitionFn ?? emptyFn); diff --git a/src/lib/nav/NeoTabs.svelte b/src/lib/nav/NeoTabs.svelte new file mode 100644 index 0000000..a4afc28 --- /dev/null +++ b/src/lib/nav/NeoTabs.svelte @@ -0,0 +1,104 @@ + + +{#snippet icon()} + +{/snippet} + +
+ + {@render children?.({ active, disabled, slide, close, add, vertical: rest.vertical })} + {#if add} +
+ +
+ {/if} +
+
+ + diff --git a/src/lib/nav/neo-tab.model.ts b/src/lib/nav/neo-tab.model.ts new file mode 100644 index 0000000..aca7919 --- /dev/null +++ b/src/lib/nav/neo-tab.model.ts @@ -0,0 +1,15 @@ +import type { Snippet } from 'svelte'; +import type { NeoButtonProps } from '~/buttons/neo-button.model.js'; + +export type TabId = string | number | symbol; +export type NeoTabProps = { + // Snippets + children?: Snippet<[{ active: boolean; tabId: TabId; value?: unknown }]>; + + // States + tabId?: TabId; + value?: unknown; + + // Styles + close?: boolean; +} & Omit; diff --git a/src/lib/nav/neo-tabs-context.svelte.ts b/src/lib/nav/neo-tabs-context.svelte.ts new file mode 100644 index 0000000..0973d05 --- /dev/null +++ b/src/lib/nav/neo-tabs-context.svelte.ts @@ -0,0 +1,99 @@ +import { getContext, setContext } from 'svelte'; +import { SvelteMap } from 'svelte/reactivity'; + +import type { TabId } from '~/nav/neo-tab.model.js'; +import type { OnChange } from '~/nav/neo-tabs.model.js'; + +type NeoTabContextOptions = { + slide?: boolean; + closeable?: boolean; + disabled?: boolean; + vertical?: boolean; +}; + +type NeoTabContextCallbacks = { onChange?: OnChange; onClose?: OnChange }; + +export class NeoTabContext { + readonly #tabs: Map = new SvelteMap(); + readonly #onChange?: OnChange; + readonly #onClose?: OnChange; + #active?: TabId = $state(); + #options: NeoTabContextOptions = $state({}); + + get active() { + return this.#active; + } + + get value() { + return this.#getValue(this.active); + } + + get disabled() { + return this.#options?.disabled; + } + + get slide() { + return this.#options?.slide; + } + + get closeable() { + return this.#options?.closeable; + } + + get vertical() { + return this.#options?.vertical; + } + + get state() { + return { + active: this.active, + disabled: this.disabled, + slide: this.slide, + closeable: this.closeable, + vertical: this.vertical, + }; + } + + constructor(active?: TabId, { onChange, onClose }: NeoTabContextCallbacks = {}) { + this.#active = active; + this.#onChange = onChange; + this.#onClose = onClose; + } + + #getValue(tabId?: TabId) { + if (!tabId) return; + return this.#tabs.get(tabId); + } + + onOption(options: NeoTabContextOptions) { + Object.assign(this.#options, options); + } + + onChange(tabId?: TabId, emit = true) { + this.#active = tabId; + if (emit) this.#onChange?.(this.active, this.value); + } + + onClose(tabId?: TabId, value?: T) { + this.#onClose?.(tabId, value ?? this.#getValue(tabId)); + } + + register(tabId: TabId, value: T) { + this.#tabs.set(tabId, value); + } + + remove(tabId: TabId) { + this.#tabs.delete(tabId); + if (this.#active === tabId) this.onChange(); + } +} + +const TabContextSymbol = Symbol('NeoTabContext'); + +export const getTabContext = (): NeoTabContext => { + return getContext>(TabContextSymbol); +}; + +export const setTabContext = (active?: TabId, callback?: NeoTabContextCallbacks) => { + return setContext(TabContextSymbol, new NeoTabContext(active, callback)); +}; diff --git a/src/lib/nav/neo-tabs.model.ts b/src/lib/nav/neo-tabs.model.ts new file mode 100644 index 0000000..dd9239a --- /dev/null +++ b/src/lib/nav/neo-tabs.model.ts @@ -0,0 +1,27 @@ +import type { Snippet } from 'svelte'; +import type { NeoButtonGroup } from '~/buttons/neo-button-group.model.js'; +import type { NeoTabProps, TabId } from '~/nav/neo-tab.model.js'; + +export type OnChange = (tabId?: TabId, value?: T) => unknown; +export type NeoTabsContext = { + // States + active?: TabId; + disabled?: boolean; + + // Styles + slide?: boolean; + add?: boolean; + close?: boolean; + vertical?: boolean; +}; + +export type TabsProps = { + // Snippets + children?: Snippet<[NeoTabsContext]>; + + // Events + onchange?: OnChange; + onclose?: OnChange; + onadd?: NeoTabProps['onclick']; +} & NeoTabsContext & + Omit; diff --git a/src/lib/styles/common/spacing.scss b/src/lib/styles/common/spacing.scss index f6b5da3..0127923 100644 --- a/src/lib/styles/common/spacing.scss +++ b/src/lib/styles/common/spacing.scss @@ -12,4 +12,7 @@ --gap-lg: 2rem; --gap-xl: 3rem; --gap-xxl: 5rem; + + /* Line height */ + --line-height: 1.5rem; } diff --git a/src/lib/styles/mixin.scss b/src/lib/styles/mixin.scss index 4142570..0e08338 100644 --- a/src/lib/styles/mixin.scss +++ b/src/lib/styles/mixin.scss @@ -61,7 +61,7 @@ @mixin coalesce( $box-shadow: var(--coalesce-box-shadow, var(--box-shadow-raised-2)), - $box-shadow-reverse: var(--pulse-box-shadow-reverse, var(--box-shadow-inset-1)), + $box-shadow-reverse: var(--coalesce-box-shadow-reverse, var(--box-shadow-inset-1)), $duration: var(--coalesce-duration, 6s), $delay: var(--coalesce-delay, 1s), $interval: var(--coalesce-interval, 3s), diff --git a/src/lib/styles/theme.scss b/src/lib/styles/theme.scss index 2012b6c..0715ef9 100644 --- a/src/lib/styles/theme.scss +++ b/src/lib/styles/theme.scss @@ -9,6 +9,7 @@ :root, :host { color: var(--text-color); + line-height: var(--line-height); background-color: var(--background-color); transition: color 0.2s var(--transition-bezier), diff --git a/src/lib/utils/transition.utils.ts b/src/lib/utils/transition.utils.ts new file mode 100644 index 0000000..2d55177 --- /dev/null +++ b/src/lib/utils/transition.utils.ts @@ -0,0 +1,31 @@ +import type { TransitionFunction } from '@dvcol/svelte-utils/transition'; + +export type TransitionProps = Record; +export const emptyFn: TransitionFunction = () => () => ({}); + +export type HTMLTransitionProps = { + /** + * Optional enter transition function. + */ + in?: TransitionFunction; + /** + * Optional enter transition props. + */ + inProps?: T; + /** + * Optional exit transition function. + */ + out?: TransitionFunction; + /** + * Optional exit transition props. + */ + outProps?: T; + /** + * Optional transition function. + */ + transition?: TransitionFunction; + /** + * Optional transition props. + */ + transitionProps?: T; +};