mirror of
https://github.com/LukeHagar/skeleton.git
synced 2025-12-06 12:47:44 +00:00
feat/dynamic-transitions (#1533)
Co-authored-by: AdrianGonz97 <31664583+AdrianGonz97@users.noreply.github.com> Co-authored-by: endigo9740 <gundamx9740@gmail.com>
This commit is contained in:
5
.changeset/calm-dryers-peel.md
Normal file
5
.changeset/calm-dryers-peel.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@skeletonlabs/skeleton": minor
|
||||
---
|
||||
|
||||
breaking: Introduced dynamic transitions for various components
|
||||
@@ -37,6 +37,7 @@
|
||||
"cpath",
|
||||
"Cruisin",
|
||||
"csvg",
|
||||
"customizability",
|
||||
"datetime",
|
||||
"delisted",
|
||||
"describedby",
|
||||
|
||||
@@ -1,18 +1,23 @@
|
||||
<script lang="ts">
|
||||
<script lang="ts" context="module">
|
||||
import { slide } from 'svelte/transition';
|
||||
import { type Transition, type TransitionParams, type CssClasses, prefersReducedMotionStore } from '../../index.js';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
type SlideTransition = typeof slide;
|
||||
type TransitionIn = Transition;
|
||||
type TransitionOut = Transition;
|
||||
</script>
|
||||
|
||||
<script lang="ts" generics="TransitionIn extends Transition = SlideTransition, TransitionOut extends Transition = SlideTransition">
|
||||
// Slots:
|
||||
// NOTE: we cannot describe the default slot.
|
||||
|
||||
import { writable, type Writable } from 'svelte/store';
|
||||
import { setContext } from 'svelte';
|
||||
|
||||
// Types
|
||||
import type { CssClasses } from '../../index.js';
|
||||
|
||||
// Props
|
||||
/** Set the auto-collapse mode. */
|
||||
export let autocollapse = false;
|
||||
/** Set the drawer animation duration in milliseconds. */
|
||||
export let duration = 200; // ms
|
||||
|
||||
// Props (parent)
|
||||
/** Provide classes to set the accordion width. */
|
||||
@@ -44,13 +49,39 @@
|
||||
/** Provide arbitrary classes to the caret icon region. */
|
||||
export let regionCaret: CssClasses = '';
|
||||
|
||||
// Props (transition)
|
||||
/**
|
||||
* Enable/Disable transitions
|
||||
* @type {boolean}
|
||||
*/
|
||||
export let transitions = !$prefersReducedMotionStore;
|
||||
/**
|
||||
* Provide the transition to used on entry.
|
||||
* @type {TransitionIn}
|
||||
*/
|
||||
export let transitionIn: TransitionIn = slide as TransitionIn;
|
||||
/**
|
||||
* Transition params provided to `transitionIn`.
|
||||
* @type {TransitionParams}
|
||||
*/
|
||||
export let transitionInParams: TransitionParams<TransitionIn> = { duration: 200 };
|
||||
/**
|
||||
* Provide the transition to used on exit.
|
||||
* @type {TransitionOut}
|
||||
*/
|
||||
export let transitionOut: TransitionOut = slide as TransitionOut;
|
||||
/**
|
||||
* Transition params provided to `transitionOut`.
|
||||
* @type {TransitionParams}
|
||||
*/
|
||||
export let transitionOutParams: TransitionParams<TransitionOut> = { duration: 200 };
|
||||
|
||||
// Local
|
||||
const active: Writable<string | null> = writable(null);
|
||||
|
||||
// Context API
|
||||
setContext('active', active);
|
||||
setContext('autocollapse', autocollapse);
|
||||
setContext('duration', duration);
|
||||
setContext('disabled', disabled);
|
||||
setContext('padding', padding);
|
||||
setContext('hover', hover);
|
||||
@@ -60,6 +91,11 @@
|
||||
setContext('regionControl', regionControl);
|
||||
setContext('regionPanel', regionPanel);
|
||||
setContext('regionCaret', regionCaret);
|
||||
setContext('transitions', transitions);
|
||||
setContext('transitionIn', transitionIn);
|
||||
setContext('transitionInParams', transitionInParams);
|
||||
setContext('transitionOut', transitionOut);
|
||||
setContext('transitionOutParams', transitionOutParams);
|
||||
|
||||
// Reactive
|
||||
$: classesBase = `${width} ${spacing} ${$$props.class ?? ''}`;
|
||||
|
||||
@@ -13,7 +13,6 @@ describe('Accordion.svelte', () => {
|
||||
const { getByTestId } = render(Accordion, {
|
||||
props: {
|
||||
autocollapse: true,
|
||||
duration: 200,
|
||||
spacing: 'space-y-1',
|
||||
padding: 'py-2 px-4',
|
||||
hover: 'hover:bg-primary-hover-token',
|
||||
|
||||
@@ -11,8 +11,8 @@
|
||||
|
||||
import { getContext } from 'svelte';
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import { dynamicTransition } from '../../internal/transitions.js';
|
||||
import type { Writable } from 'svelte/store';
|
||||
import { slide } from 'svelte/transition';
|
||||
|
||||
// Event Dispatcher
|
||||
type AccordionItemEvent = {
|
||||
@@ -21,7 +21,9 @@
|
||||
const dispatch = createEventDispatcher<AccordionItemEvent>();
|
||||
|
||||
// Types
|
||||
import type { CssClasses, SvelteEvent } from '../../index.js';
|
||||
import type { CssClasses, Transition, TransitionParams, SvelteEvent } from '../../index.js';
|
||||
type TransitionIn = $$Generic<Transition>;
|
||||
type TransitionOut = $$Generic<Transition>;
|
||||
|
||||
// Props (state)
|
||||
/** Set open by default on load. */
|
||||
@@ -44,8 +46,7 @@
|
||||
export let autocollapse: boolean = getContext('autocollapse');
|
||||
/** The writable store that houses the auto-collapse active item UUID. */
|
||||
export let active: Writable<string | null> = getContext('active');
|
||||
/** Set the drawer animation duration. */
|
||||
export let duration: number = getContext('duration');
|
||||
|
||||
// ---
|
||||
/** Set the disabled state for this item. */
|
||||
export let disabled: boolean = getContext('disabled');
|
||||
@@ -68,6 +69,30 @@
|
||||
/** Provide arbitrary classes caret icon region. */
|
||||
export let regionCaret: CssClasses = getContext('regionCaret');
|
||||
|
||||
// Props (transitions)
|
||||
/** Enable/Disable transitions */
|
||||
export let transitions: boolean = getContext('transitions');
|
||||
/**
|
||||
* Provide the transition to used on entry.
|
||||
* @type {TransitionIn}
|
||||
*/
|
||||
export let transitionIn: TransitionIn = getContext('transitionIn');
|
||||
/**
|
||||
* Transition params provided to `transitionIn`.
|
||||
* @type {TransitionParams}
|
||||
*/
|
||||
export let transitionInParams: TransitionParams<TransitionIn> = getContext('transitionInParams');
|
||||
/**
|
||||
* Provide the transition to used on exit.
|
||||
* @type {TransitionOut}
|
||||
*/
|
||||
export let transitionOut: TransitionOut = getContext('transitionOut');
|
||||
/**
|
||||
* Transition params provided to `transitionOut`.
|
||||
* @type {TransitionParams}
|
||||
*/
|
||||
export let transitionOutParams: TransitionParams<TransitionOut> = getContext('transitionOutParams');
|
||||
|
||||
// Change open behavior based on auto-collapse mode
|
||||
function setActive(event?: SvelteEvent<MouseEvent, HTMLButtonElement>): void {
|
||||
if (autocollapse === true) {
|
||||
@@ -143,7 +168,8 @@
|
||||
<div
|
||||
class="accordion-panel {classesPanel}"
|
||||
id="accordion-panel-{id}"
|
||||
transition:slide|local={{ duration }}
|
||||
in:dynamicTransition|local={{ transition: transitionIn, params: transitionInParams, enabled: transitions }}
|
||||
out:dynamicTransition|local={{ transition: transitionOut, params: transitionOutParams, enabled: transitions }}
|
||||
role="region"
|
||||
aria-hidden={!openState}
|
||||
aria-labelledby="accordion-control-{id}"
|
||||
|
||||
@@ -1,4 +1,15 @@
|
||||
<script lang="ts">
|
||||
<script lang="ts" context="module">
|
||||
import { slide } from 'svelte/transition';
|
||||
import { type Transition, type TransitionParams, prefersReducedMotionStore } from '../../index.js';
|
||||
import { dynamicTransition } from '../../internal/transitions.js';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
type SlideTransition = typeof slide;
|
||||
type TransitionIn = Transition;
|
||||
type TransitionOut = Transition;
|
||||
</script>
|
||||
|
||||
<script lang="ts" generics="TransitionIn extends Transition = SlideTransition, TransitionOut extends Transition = SlideTransition">
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
// import { flip } from 'svelte/animate';
|
||||
// import {slide} from 'svelte/transition';
|
||||
@@ -53,10 +64,35 @@
|
||||
export let whitelist: unknown[] = [];
|
||||
/** DEPRECATED: replace with denylist */
|
||||
export let blacklist: unknown[] = [];
|
||||
/** DEPRECATED: Set the animation duration. Use zero to disable. */
|
||||
export let duration = 200;
|
||||
// Silence warning about unused props:
|
||||
const deprecated = [whitelist, blacklist, duration];
|
||||
const deprecated = [whitelist, blacklist];
|
||||
|
||||
// Props (transition)
|
||||
/**
|
||||
* Enable/Disable transitions
|
||||
* @type {boolean}
|
||||
*/
|
||||
export let transitions = !$prefersReducedMotionStore;
|
||||
/**
|
||||
* Provide the transition used on entry.
|
||||
* @type {TransitionIn}
|
||||
*/
|
||||
export let transitionIn: TransitionIn = slide as TransitionIn;
|
||||
/**
|
||||
* Transition params provided to `transitionIn`.
|
||||
* @type {TransitionParams}
|
||||
*/
|
||||
export let transitionInParams: TransitionParams<TransitionIn> = { duration: 200 };
|
||||
/**
|
||||
* Provide the transition used on exit.
|
||||
* @type {TransitionOut}
|
||||
*/
|
||||
export let transitionOut: TransitionOut = slide as TransitionOut;
|
||||
/**
|
||||
* Transition params provided to `transitionOut`.
|
||||
* @type {TransitionParams}
|
||||
*/
|
||||
export let transitionOutParams: TransitionParams<TransitionOut> = { duration: 200 };
|
||||
|
||||
// Local
|
||||
$: listedOptions = options;
|
||||
@@ -116,13 +152,17 @@
|
||||
$: classesEmpty = `${regionEmpty}`;
|
||||
</script>
|
||||
|
||||
<!-- animate:flip={{ duration }} transition:slide|local={{ duration }} -->
|
||||
<!-- animate:flip={{ duration }} -->
|
||||
<div class="autocomplete {classesBase}" data-testid="autocomplete">
|
||||
{#if optionsFiltered.length > 0}
|
||||
<nav class="autocomplete-nav {classesNav}">
|
||||
<ul class="autocomplete-list {classesList}">
|
||||
{#each optionsFiltered.slice(0, sliceLimit) as option (option)}
|
||||
<li class="autocomplete-item {classesItem}">
|
||||
<li
|
||||
class="autocomplete-item {classesItem}"
|
||||
in:dynamicTransition|local={{ transition: transitionIn, params: transitionInParams, enabled: transitions }}
|
||||
out:dynamicTransition|local={{ transition: transitionOut, params: transitionOutParams, enabled: transitions }}
|
||||
>
|
||||
<button class="autocomplete-button {classesButton}" type="button" on:click={() => onSelection(option)} on:click on:keypress>
|
||||
{@html option.label}
|
||||
</button>
|
||||
|
||||
@@ -1,6 +1,24 @@
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher, onMount } from 'svelte';
|
||||
<script lang="ts" context="module">
|
||||
import { fly, scale } from 'svelte/transition';
|
||||
import { type Transition, type TransitionParams, prefersReducedMotionStore } from '../../index.js';
|
||||
import { dynamicTransition } from '../../internal/transitions.js';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
type FlyTransition = typeof fly;
|
||||
type ListTransitionIn = Transition;
|
||||
type ListTransitionOut = Transition;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
type ScaleTransition = typeof scale;
|
||||
type ChipTransitionIn = Transition;
|
||||
type ChipTransitionOut = Transition;
|
||||
</script>
|
||||
|
||||
<script
|
||||
lang="ts"
|
||||
generics="ListTransitionIn extends Transition = FlyTransition, ListTransitionOut extends Transition = FlyTransition, ChipTransitionIn extends Transition = ScaleTransition, ChipTransitionOut extends Transition = ScaleTransition"
|
||||
>
|
||||
import { createEventDispatcher, onMount } from 'svelte';
|
||||
import { flip } from 'svelte/animate';
|
||||
|
||||
// Types
|
||||
@@ -45,7 +63,7 @@
|
||||
*/
|
||||
export let validation: (...args: any[]) => boolean = () => true;
|
||||
|
||||
/** The duration of the animated fly effect. */
|
||||
/** The duration of the flip (first, last, invert, play) animation. */
|
||||
export let duration = 150;
|
||||
/** Set the required state for this input field. */
|
||||
export let required = false;
|
||||
@@ -60,6 +78,53 @@
|
||||
/** Provide classes to set border radius styles. */
|
||||
export let rounded: CssClasses = 'rounded-container-token';
|
||||
|
||||
// Props (transition)
|
||||
/**
|
||||
* Enable/Disable transitions
|
||||
* @type {boolean}
|
||||
*/
|
||||
export let transitions = !$prefersReducedMotionStore;
|
||||
/**
|
||||
* Provide the transition used in list on entry.
|
||||
* @type {ListTransitionIn}
|
||||
*/
|
||||
export let listTransitionIn: ListTransitionIn = fly as ListTransitionIn;
|
||||
/**
|
||||
* Transition params provided to `ListTransitionIn`.
|
||||
* @type {TransitionParams}
|
||||
*/
|
||||
export let listTransitionInParams: TransitionParams<ListTransitionIn> = { duration: 150, opacity: 0, y: -20 };
|
||||
/**
|
||||
* Provide the transition used in list on exit.
|
||||
* @type {ListTransitionOut}
|
||||
*/
|
||||
export let listTransitionOut: ListTransitionOut = fly as ListTransitionOut;
|
||||
/**
|
||||
* Transition params provided to `ListTransitionOut`.
|
||||
* @type {TransitionParams}
|
||||
*/
|
||||
export let listTransitionOutParams: TransitionParams<ListTransitionOut> = { duration: 150, opacity: 0, y: -20 };
|
||||
/**
|
||||
* Provide the transition used in chip on entry.
|
||||
* @type {ChipTransitionIn}
|
||||
*/
|
||||
export let chipTransitionIn: ChipTransitionIn = scale as ChipTransitionIn;
|
||||
/**
|
||||
* Transition params provided to `ChipTransitionIn`.
|
||||
* @type {TransitionParams}
|
||||
*/
|
||||
export let chipTransitionInParams: TransitionParams<ChipTransitionIn> = { duration: 150, opacity: 0 };
|
||||
/**
|
||||
* Provide the transition used in chip on exit.
|
||||
* @type {ChipTransitionOut}
|
||||
*/
|
||||
export let chipTransitionOut: ChipTransitionOut = scale as ChipTransitionOut;
|
||||
/**
|
||||
* Transition params provided to `ChipTransitionOut`.
|
||||
* @type {TransitionParams}
|
||||
*/
|
||||
export let chipTransitionOutParams: TransitionParams<ChipTransitionOut> = { duration: 150, opacity: 0 };
|
||||
|
||||
// Classes
|
||||
const cBase = 'textarea cursor-pointer';
|
||||
const cInterface = 'space-y-4';
|
||||
@@ -191,7 +256,11 @@
|
||||
</form>
|
||||
<!-- Chip List -->
|
||||
{#if chipValues.length}
|
||||
<div class="input-chip-list {classesChipList}" transition:fly|local={{ duration, opacity: 0, y: -20 }}>
|
||||
<div
|
||||
class="input-chip-list {classesChipList}"
|
||||
in:dynamicTransition|local={{ transition: listTransitionIn, params: listTransitionInParams, enabled: transitions }}
|
||||
out:dynamicTransition|local={{ transition: listTransitionOut, params: listTransitionOutParams, enabled: transitions }}
|
||||
>
|
||||
{#each chipValues as { id, val }, i (id)}
|
||||
<!-- Wrapping div required for FLIP animation -->
|
||||
<div animate:flip={{ duration }}>
|
||||
@@ -205,7 +274,8 @@
|
||||
on:keypress
|
||||
on:keydown
|
||||
on:keyup
|
||||
transition:scale|local={{ duration, opacity: 0 }}
|
||||
in:dynamicTransition|local={{ transition: chipTransitionIn, params: chipTransitionInParams, enabled: transitions }}
|
||||
out:dynamicTransition|local={{ transition: chipTransitionOut, params: chipTransitionOutParams, enabled: transitions }}
|
||||
>
|
||||
<span>{val}</span>
|
||||
<span>✕</span>
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
// Types
|
||||
import type { CssClasses, PaginationSettings } from '../../index.js';
|
||||
import type { PaginationSettings } from './types.js';
|
||||
import type { CssClasses } from '../../index.js';
|
||||
import { leftAngles, leftArrow, rightAngles, rightArrow } from './icons.js';
|
||||
|
||||
// Event Dispatcher
|
||||
|
||||
@@ -59,14 +59,20 @@
|
||||
<!-- eslint-disable-next-line @typescript-eslint/no-unused-vars -->
|
||||
{#each Array(max) as _, i}
|
||||
{#if Math.floor(value) >= i + 1}
|
||||
<!-- TODO: Remove for V2 -->
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<svelte:element this={elemInteractive} {...attrInteractive} class="rating-icon {regionIcon}" on:click={() => iconClick(i)}>
|
||||
<slot name="full" />
|
||||
</svelte:element>
|
||||
{:else if value === i + 0.5}
|
||||
<!-- TODO: Remove for V2 -->
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<svelte:element this={elemInteractive} {...attrInteractive} class="rating-icon {regionIcon}" on:click={() => iconClick(i)}>
|
||||
<slot name="half" />
|
||||
</svelte:element>
|
||||
{:else}
|
||||
<!-- TODO: Remove for V2 -->
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<svelte:element this={elemInteractive} {...attrInteractive} class="rating-icon {regionIcon}" on:click={() => iconClick(i)}>
|
||||
<slot name="empty" />
|
||||
</svelte:element>
|
||||
|
||||
@@ -8,11 +8,13 @@
|
||||
|
||||
import { getContext, onDestroy } from 'svelte';
|
||||
import type { Writable } from 'svelte/store';
|
||||
import { fade } from 'svelte/transition';
|
||||
import { dynamicTransition } from '../../internal/transitions.js';
|
||||
|
||||
// Types
|
||||
import type { CssClasses } from '../../index.js';
|
||||
import type { StepperEventDispatcher, StepperState } from './types.js';
|
||||
import type { CssClasses, Transition, TransitionParams } from '../../index.js';
|
||||
type TransitionIn = $$Generic<Transition>;
|
||||
type TransitionOut = $$Generic<Transition>;
|
||||
|
||||
// Props
|
||||
export let locked = false;
|
||||
@@ -41,6 +43,30 @@
|
||||
export let buttonCompleteType: 'submit' | 'reset' | 'button' = getContext('buttonCompleteType');
|
||||
export let buttonCompleteLabel: string = getContext('buttonCompleteLabel');
|
||||
|
||||
// Props (transitions)
|
||||
/** Enable/Disable transitions */
|
||||
export let transitions: boolean = getContext('transitions');
|
||||
/**
|
||||
* Provide the transition to used on entry.
|
||||
* @type {TransitionIn}
|
||||
*/
|
||||
export let transitionIn: TransitionIn = getContext('transitionIn');
|
||||
/**
|
||||
* Transition params provided to `transitionIn`.
|
||||
* @type {TransitionParams}
|
||||
*/
|
||||
export let transitionInParams: TransitionParams<TransitionIn> = getContext('transitionInParams');
|
||||
/**
|
||||
* Provide the transition to used on exit.
|
||||
* @type {TransitionOut}
|
||||
*/
|
||||
export let transitionOut: TransitionOut = getContext('transitionOut');
|
||||
/**
|
||||
* Transition params provided to `transitionOut`.
|
||||
* @type {TransitionParams}
|
||||
*/
|
||||
export let transitionOutParams: TransitionParams<TransitionOut> = getContext('transitionOutParams');
|
||||
|
||||
// Register step on init (keep these paired)
|
||||
const stepIndex = $state.total;
|
||||
$state.total++;
|
||||
@@ -97,7 +123,11 @@
|
||||
</div>
|
||||
<!-- Navigation -->
|
||||
{#if $state.total > 1}
|
||||
<div class="step-navigation {classesNavigation}" transition:fade|local={{ duration: 100 }}>
|
||||
<div
|
||||
class="step-navigation {classesNavigation}"
|
||||
in:dynamicTransition|local={{ transition: transitionIn, params: transitionInParams, enabled: transitions }}
|
||||
out:dynamicTransition|local={{ transition: transitionOut, params: transitionOutParams, enabled: transitions }}
|
||||
>
|
||||
{#if stepIndex === 0 && $$slots.navigation}
|
||||
<!-- Slot: Navigation -->
|
||||
<div class="step-navigation-slot">
|
||||
|
||||
@@ -1,7 +1,17 @@
|
||||
<script lang="ts">
|
||||
<script lang="ts" context="module">
|
||||
import { fade } from 'svelte/transition';
|
||||
import { type Transition, type TransitionParams, prefersReducedMotionStore } from '../../index.js';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
type FadeTransition = typeof fade;
|
||||
type TransitionIn = Transition;
|
||||
type TransitionOut = Transition;
|
||||
</script>
|
||||
|
||||
<script lang="ts" generics="TransitionIn extends Transition = FadeTransition, TransitionOut extends Transition = FadeTransition">
|
||||
import { createEventDispatcher, setContext } from 'svelte';
|
||||
import { type Writable, writable } from 'svelte/store';
|
||||
import { fade } from 'svelte/transition';
|
||||
import { dynamicTransition } from '../../internal/transitions.js';
|
||||
|
||||
// Types
|
||||
import type { CssClasses } from '../../index.js';
|
||||
@@ -60,6 +70,33 @@
|
||||
/** Provide arbitrary classes to the stepper content region. */
|
||||
export let regionContent: CssClasses = '';
|
||||
|
||||
// Props (transition)
|
||||
/**
|
||||
* Enable/Disable transitions
|
||||
* @type {boolean}
|
||||
*/
|
||||
export let transitions = !$prefersReducedMotionStore;
|
||||
/**
|
||||
* Provide the transition to used on entry.
|
||||
* @type {TransitionIn}
|
||||
*/
|
||||
export let transitionIn: TransitionIn = fade as TransitionIn;
|
||||
/**
|
||||
* Transition params provided to `transitionIn`.
|
||||
* @type {TransitionParams}
|
||||
*/
|
||||
export let transitionInParams: TransitionParams<TransitionIn> = { duration: 100 };
|
||||
/**
|
||||
* Provide the transition to used on exit.
|
||||
* @type {TransitionOut}
|
||||
*/
|
||||
export let transitionOut: TransitionOut = fade as TransitionOut;
|
||||
/**
|
||||
* Transition params provided to `transitionOut`.
|
||||
* @type {TransitionParams}
|
||||
*/
|
||||
export let transitionOutParams: TransitionParams<TransitionOut> = { duration: 100 };
|
||||
|
||||
// Stores
|
||||
let state: Writable<StepperState> = writable({ current: start, total: 0 });
|
||||
|
||||
@@ -81,6 +118,12 @@
|
||||
setContext('buttonComplete', buttonComplete);
|
||||
setContext('buttonCompleteType', buttonCompleteType);
|
||||
setContext('buttonCompleteLabel', buttonCompleteLabel);
|
||||
// ---
|
||||
setContext('transitions', transitions);
|
||||
setContext('transitionIn', transitionIn);
|
||||
setContext('transitionInParams', transitionInParams);
|
||||
setContext('transitionOut', transitionOut);
|
||||
setContext('transitionOutParams', transitionOutParams);
|
||||
|
||||
// Classes
|
||||
const cBase = 'space-y-4';
|
||||
@@ -101,7 +144,11 @@
|
||||
<div class="stepper {classesBase}" data-testid="stepper">
|
||||
<!-- Header -->
|
||||
{#if $state.total}
|
||||
<header class="stepper-header {classesHeader}" transition:fade|local={{ duration: 100 }}>
|
||||
<header
|
||||
class="stepper-header {classesHeader}"
|
||||
in:dynamicTransition|local={{ transition: transitionIn, params: transitionInParams, enabled: transitions }}
|
||||
out:dynamicTransition|local={{ transition: transitionOut, params: transitionOutParams, enabled: transitions }}
|
||||
>
|
||||
{#each Array.from(Array($state.total).keys()) as step}
|
||||
<div class="stepper-header-step {classesHeaderStep}" class:flex-1={isActive(step)}>
|
||||
<span class="badge {classesBadge(step)}">{isActive(step) ? `${stepTerm} ${step + 1}` : step + 1}</span>
|
||||
|
||||
@@ -16,7 +16,6 @@ describe('Stepper.svelte', () => {
|
||||
props: {
|
||||
active: writable(0),
|
||||
length: 0,
|
||||
duration: 200,
|
||||
// Props (timeline)
|
||||
color: 'text-white',
|
||||
background: 'bg-secondary-500 text-white',
|
||||
|
||||
@@ -1,9 +1,16 @@
|
||||
<script lang="ts">
|
||||
import { onDestroy, onMount } from 'svelte';
|
||||
<script lang="ts" context="module">
|
||||
import { fade } from 'svelte/transition';
|
||||
import { type Transition, type TransitionParams, type CssClasses, prefersReducedMotionStore } from '../../index.js';
|
||||
|
||||
// Types
|
||||
import type { CssClasses } from '../../index.js';
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
type FadeTransition = typeof fade;
|
||||
type TransitionIn = Transition;
|
||||
type TransitionOut = Transition;
|
||||
</script>
|
||||
|
||||
<script lang="ts" generics="TransitionIn extends Transition = FadeTransition, TransitionOut extends Transition = FadeTransition">
|
||||
import { onDestroy, onMount } from 'svelte';
|
||||
import { dynamicTransition } from '../../internal/transitions.js';
|
||||
|
||||
// Props (settings)
|
||||
/** Query selector for the scrollable page element. */
|
||||
@@ -35,6 +42,33 @@
|
||||
/** Provide arbitrary styles for the list element. */
|
||||
export let regionList: CssClasses = '';
|
||||
|
||||
// Props (transition)
|
||||
/**
|
||||
* Enable/Disable transitions
|
||||
* @type {boolean}
|
||||
*/
|
||||
export let transitions = !$prefersReducedMotionStore;
|
||||
/**
|
||||
* Provide the transition to used on entry.
|
||||
* @type {TransitionIn}
|
||||
*/
|
||||
export let transitionIn: TransitionIn = fade as TransitionIn;
|
||||
/**
|
||||
* Transition params provided to `transitionIn`.
|
||||
* @type {TransitionParams}
|
||||
*/
|
||||
export let transitionInParams: TransitionParams<TransitionIn> = { duration: 100 };
|
||||
/**
|
||||
* Provide the transition to used on exit.
|
||||
* @type {TransitionOut}
|
||||
*/
|
||||
export let transitionOut: TransitionOut = fade as TransitionOut;
|
||||
/**
|
||||
* Transition params provided to `transitionOut`.
|
||||
* @type {TransitionParams}
|
||||
*/
|
||||
export let transitionOutParams: TransitionParams<TransitionOut> = { duration: 100 };
|
||||
|
||||
// Classes
|
||||
const cLabel = 'p-4 pt-0';
|
||||
const cList = 'list-none space-y-1';
|
||||
@@ -135,7 +169,11 @@
|
||||
<!-- @component Allows you to quickly navigate the hierarchy of headings for the current page. -->
|
||||
|
||||
{#if filteredHeadingsList.length > 0}
|
||||
<div class="toc {classesBase}" transition:fade|local={{ duration: 100 }}>
|
||||
<div
|
||||
class="toc {classesBase}"
|
||||
in:dynamicTransition|local={{ transition: transitionIn, params: transitionInParams, enabled: transitions }}
|
||||
out:dynamicTransition|local={{ transition: transitionOut, params: transitionOutParams, enabled: transitions }}
|
||||
>
|
||||
<nav class="toc-list {classesList}">
|
||||
<div class="toc-label {classesLabel}">{label}</div>
|
||||
{#each filteredHeadingsList as headingElem}
|
||||
|
||||
@@ -10,10 +10,8 @@ export type { ToastSettings } from './utilities/Toast/types.js';
|
||||
export type { TableSource } from './components/Table/types.js';
|
||||
export type { PaginationSettings } from './components/Paginator/types.js';
|
||||
export type { PopupSettings } from './utilities/Popup/types.js';
|
||||
|
||||
// This type alias is to identify CSS classes within component props, which enables Tailwind IntelliSense
|
||||
export type CssClasses = string;
|
||||
export type SvelteEvent<E extends Event = Event, T extends EventTarget = Element> = E & { currentTarget: EventTarget & T };
|
||||
export type { Transition, TransitionParams } from './internal/transitions.js';
|
||||
export type { CssClasses, SvelteEvent } from './types.js';
|
||||
|
||||
// Stores ---
|
||||
|
||||
@@ -54,6 +52,8 @@ export {
|
||||
} from './utilities/LightSwitch/lightswitch.js';
|
||||
// Local Storage Store
|
||||
export { localStorageStore } from './utilities/LocalStorageStore/LocalStorageStore.js';
|
||||
// Prefers-reduced-motion
|
||||
export { prefersReducedMotionStore } from './utilities/PrefersReducedMotion/PrefersReducedMotion.js';
|
||||
// Component Utilities
|
||||
export { tableSourceMapper, tableSourceValues, tableMapperValues } from './components/Table/utils.js';
|
||||
|
||||
|
||||
24
packages/skeleton/src/lib/internal/transitions.ts
Normal file
24
packages/skeleton/src/lib/internal/transitions.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import type { TransitionConfig } from 'svelte/transition';
|
||||
|
||||
// Transitions ---
|
||||
export function dynamicTransition<T extends Transition>(node: Element, dynParams: DynamicTransitionParams<T>): TransitionConfig {
|
||||
const { transition, params, enabled } = dynParams;
|
||||
|
||||
if (enabled) return transition(node, params);
|
||||
|
||||
// it's better to just set the `duration` to 0 to prevent flickering
|
||||
if ('duration' in params) return transition(node, { duration: 0 });
|
||||
|
||||
// if the transition doesn't provide a `duration` prop, then we'll just return this as a last resort
|
||||
return { duration: 0 };
|
||||
}
|
||||
|
||||
type DynamicTransitionParams<T extends Transition> = {
|
||||
transition: T;
|
||||
params: TransitionParams<T>;
|
||||
enabled: boolean;
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export type Transition = (node: Element, params?: any) => TransitionConfig;
|
||||
export type TransitionParams<T extends Transition> = Parameters<T>[1];
|
||||
24
packages/skeleton/src/lib/transitions/crossfade.ts
Normal file
24
packages/skeleton/src/lib/transitions/crossfade.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { cubicInOut } from 'svelte/easing';
|
||||
import { crossfade } from 'svelte/transition';
|
||||
|
||||
// TODO: convert to js transitions on revisit.
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
function swoop(duration: number, x: number, y: number): unknown {
|
||||
return crossfade({
|
||||
duration: (d) => Math.sqrt(d * duration),
|
||||
|
||||
fallback(node) {
|
||||
const style = getComputedStyle(node);
|
||||
const transform = style.transform === 'none' ? '' : style.transform;
|
||||
|
||||
return {
|
||||
duration,
|
||||
easing: cubicInOut,
|
||||
css: (t, u) => `
|
||||
transform: ${transform} scale(${t}) translate(${u * x}%, ${u * y}%);
|
||||
opacity: ${t}
|
||||
`
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
7
packages/skeleton/src/lib/types.ts
Normal file
7
packages/skeleton/src/lib/types.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
/**
|
||||
* This type alias is to identify CSS classes within component props,
|
||||
* which enables Tailwind IntelliSense
|
||||
*/
|
||||
export type CssClasses = string;
|
||||
|
||||
export type SvelteEvent<E extends Event = Event, T extends EventTarget = Element> = E & { currentTarget: EventTarget & T };
|
||||
@@ -1,5 +1,4 @@
|
||||
<script lang="ts">
|
||||
import { fade, fly } from 'svelte/transition';
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import { BROWSER } from 'esm-env';
|
||||
|
||||
@@ -11,7 +10,7 @@
|
||||
const dispatch = createEventDispatcher<DrawerEvent>();
|
||||
|
||||
// Types
|
||||
import type { CssClasses, SvelteEvent } from '../../index.js';
|
||||
import { type CssClasses, type SvelteEvent, prefersReducedMotionStore } from '../../index.js';
|
||||
|
||||
// Actions
|
||||
import { focusTrap } from '../../actions/FocusTrap/focusTrap.js';
|
||||
@@ -19,14 +18,14 @@
|
||||
// Drawer Utils
|
||||
import type { DrawerSettings } from './types.js';
|
||||
import { drawerStore } from './stores.js';
|
||||
import { fade, fly } from 'svelte/transition';
|
||||
import { dynamicTransition } from '../../internal/transitions.js';
|
||||
|
||||
// Props
|
||||
/** Set the anchor position.
|
||||
* @type {'left' | 'top' | 'right' | 'bottom'}
|
||||
*/
|
||||
export let position: 'left' | 'top' | 'right' | 'bottom' = 'left';
|
||||
/** Define the Svelte transition animation duration. */
|
||||
export let duration = 150;
|
||||
|
||||
// Props (backdrop)
|
||||
/** Backdrop - Provide classes to set the backdrop background color */
|
||||
@@ -64,6 +63,15 @@
|
||||
/** Provide an ID of the element describing the drawer. */
|
||||
export let describedby = '';
|
||||
|
||||
// Props (transition)
|
||||
/**
|
||||
* Enable/Disable transitions
|
||||
* @type {boolean}
|
||||
*/
|
||||
export let transitions = !$prefersReducedMotionStore;
|
||||
/** Drawer - Enable/Disable opacity transition */
|
||||
export let opacityTransition = true;
|
||||
|
||||
// Presets
|
||||
// prettier-ignore
|
||||
const presets = {
|
||||
@@ -73,23 +81,23 @@
|
||||
right: { alignment: 'justify-end', width: 'w-[90%]', height: 'h-full', rounded: 'rounded-tl-container-token rounded-bl-container-token' }
|
||||
};
|
||||
|
||||
// Classes
|
||||
const cBackdrop = 'fixed top-0 left-0 right-0 bottom-0 flex';
|
||||
const cDrawer = 'overflow-y-auto transition-transform';
|
||||
|
||||
// Local
|
||||
let elemBackdrop: HTMLElement;
|
||||
let elemDrawer: HTMLElement;
|
||||
let anim = { x: 0, y: 0 };
|
||||
|
||||
// Classes
|
||||
const cBackdrop = 'fixed top-0 left-0 right-0 bottom-0 flex';
|
||||
const cDrawer = 'overflow-y-auto transition-transform';
|
||||
|
||||
// Record a record of default props on init
|
||||
// NOTE: these must stay in sync with the props implemented above.
|
||||
// prettier-ignore
|
||||
const propDefaults = {
|
||||
position, duration,
|
||||
position,
|
||||
bgBackdrop, blur, padding,
|
||||
bgDrawer, border, rounded, shadow,
|
||||
width, height,
|
||||
width, height, opacityTransition,
|
||||
labelledby, describedby,
|
||||
regionBackdrop, regionDrawer
|
||||
};
|
||||
@@ -98,7 +106,6 @@
|
||||
// NOTE: these must stay in sync with the props implemented above.
|
||||
function applyPropSettings(settings: DrawerSettings): void {
|
||||
position = settings.position || propDefaults.position;
|
||||
duration = settings.duration || propDefaults.duration;
|
||||
// Backdrop
|
||||
bgBackdrop = settings.bgBackdrop || propDefaults.bgBackdrop;
|
||||
blur = settings.blur || propDefaults.blur;
|
||||
@@ -110,6 +117,7 @@
|
||||
shadow = settings.shadow || propDefaults.shadow;
|
||||
width = settings.width || propDefaults.width;
|
||||
height = settings.height || propDefaults.height;
|
||||
opacityTransition = settings.opacityTransition || propDefaults.opacityTransition;
|
||||
// Regions
|
||||
regionBackdrop = settings.regionBackdrop || propDefaults.regionBackdrop;
|
||||
regionDrawer = settings.regionDrawer || propDefaults.regionDrawer;
|
||||
@@ -179,7 +187,16 @@
|
||||
on:touchstart
|
||||
on:touchend
|
||||
on:keypress
|
||||
transition:fade|local={{ duration }}
|
||||
in:dynamicTransition|local={{
|
||||
transition: fade,
|
||||
params: { duration: 150 },
|
||||
enabled: transitions && opacityTransition
|
||||
}}
|
||||
out:dynamicTransition|local={{
|
||||
transition: fade,
|
||||
params: { duration: 150 },
|
||||
enabled: transitions && opacityTransition
|
||||
}}
|
||||
use:focusTrap={true}
|
||||
>
|
||||
<!-- Drawer -->
|
||||
@@ -192,8 +209,16 @@
|
||||
aria-modal="true"
|
||||
aria-labelledby={labelledby}
|
||||
aria-describedby={describedby}
|
||||
in:fly|local={{ x: anim.x, y: anim.y, duration }}
|
||||
out:fly|local={{ x: anim.x, y: anim.y, duration }}
|
||||
in:dynamicTransition|local={{
|
||||
transition: fly,
|
||||
params: { x: anim.x, y: anim.y, duration: 150, opacity: opacityTransition ? undefined : 1 },
|
||||
enabled: transitions
|
||||
}}
|
||||
out:dynamicTransition|local={{
|
||||
transition: fly,
|
||||
params: { x: anim.x, y: anim.y, duration: 150, opacity: opacityTransition ? undefined : 1 },
|
||||
enabled: transitions
|
||||
}}
|
||||
>
|
||||
<!-- Slot: Default -->
|
||||
<slot />
|
||||
|
||||
@@ -40,6 +40,8 @@ export interface DrawerSettings {
|
||||
width?: string;
|
||||
/** Drawer - Provide classes to override the height.*/
|
||||
height?: string;
|
||||
/** Drawer - Enable/Disable opacity transition */
|
||||
opacityTransition?: boolean;
|
||||
|
||||
// --- Regions ---
|
||||
/** Provide arbitrary classes to the backdrop region. */
|
||||
|
||||
@@ -1,6 +1,16 @@
|
||||
<script lang="ts">
|
||||
<script lang="ts" context="module">
|
||||
import { fly, fade } from 'svelte/transition';
|
||||
import { type Transition, type TransitionParams, prefersReducedMotionStore } from '../../index.js';
|
||||
import { dynamicTransition } from '../../internal/transitions.js';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
type FlyTransition = typeof fly;
|
||||
type TransitionIn = Transition;
|
||||
type TransitionOut = Transition;
|
||||
</script>
|
||||
|
||||
<script lang="ts" generics="TransitionIn extends Transition = FlyTransition, TransitionOut extends Transition = FlyTransition">
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import { fade, fly } from 'svelte/transition';
|
||||
|
||||
// Event Dispatcher
|
||||
type ModalEvent = {
|
||||
@@ -23,16 +33,6 @@
|
||||
/** Register a list of reusable component modals. */
|
||||
export let components: Record<string, ModalComponent> = {};
|
||||
|
||||
// Props (transitions)
|
||||
/** The open/close animation duration. Set '0' (zero) to disable. */
|
||||
export let duration = 150;
|
||||
/** Set the fly transition opacity. */
|
||||
export let flyOpacity = 0;
|
||||
/** Set the fly transition X axis value. */
|
||||
export let flyX = 0;
|
||||
/** Set the fly transition Y axis value. */
|
||||
export let flyY = 100;
|
||||
|
||||
// Props (modal)
|
||||
/** Provide classes to style the modal background. */
|
||||
export let background: CssClasses = 'bg-surface-100-800-token';
|
||||
@@ -73,6 +73,33 @@
|
||||
/** Provide arbitrary classes to modal footer region. */
|
||||
export let regionFooter: CssClasses = 'flex justify-end space-x-2';
|
||||
|
||||
// Props (transition)
|
||||
/**
|
||||
* Enable/Disable transitions
|
||||
* @type {boolean}
|
||||
*/
|
||||
export let transitions = !$prefersReducedMotionStore;
|
||||
/**
|
||||
* Provide the transition used on entry.
|
||||
* @type {ModalTransitionIn}
|
||||
*/
|
||||
export let transitionIn: TransitionIn = fly as TransitionIn;
|
||||
/**
|
||||
* Transition params provided to `TransitionIn`.
|
||||
* @type {TransitionParams}
|
||||
*/
|
||||
export let transitionInParams: TransitionParams<TransitionIn> = { duration: 150, opacity: 0, x: 0, y: 100 };
|
||||
/**
|
||||
* Provide the transition used on exit.
|
||||
* @type {TransitionOut}
|
||||
*/
|
||||
export let transitionOut: TransitionOut = fly as TransitionOut;
|
||||
/**
|
||||
* Transition params provided to `TransitionOut`.
|
||||
* @type {TransitionParams}
|
||||
*/
|
||||
export let transitionOutParams: TransitionParams<TransitionOut> = { duration: 150, opacity: 0, x: 0, y: 100 };
|
||||
|
||||
// Base Styles
|
||||
const cBackdrop = 'fixed top-0 left-0 right-0 bottom-0';
|
||||
const cTransitionLayer = 'w-full h-full p-4 overflow-y-auto flex justify-center';
|
||||
@@ -161,11 +188,6 @@
|
||||
$: parent = {
|
||||
position,
|
||||
// ---
|
||||
duration,
|
||||
flyOpacity,
|
||||
flyX,
|
||||
flyY,
|
||||
// ---
|
||||
background,
|
||||
width,
|
||||
height,
|
||||
@@ -203,21 +225,18 @@
|
||||
on:mouseup={onBackdropInteractionEnd}
|
||||
on:touchstart
|
||||
on:touchend
|
||||
transition:fade|global={{ duration }}
|
||||
transition:dynamicTransition|global={{ transition: fade, params: { duration: 150 }, enabled: transitions }}
|
||||
use:focusTrap={true}
|
||||
>
|
||||
<!-- Transition Layer -->
|
||||
<div class="modal-transition {classesTransitionLayer}" transition:fly|global={{ duration, opacity: flyOpacity, x: flyX, y: flyY }}>
|
||||
<div
|
||||
class="modal-transition {classesTransitionLayer}"
|
||||
in:dynamicTransition|global={{ transition: transitionIn, params: transitionInParams, enabled: transitions }}
|
||||
out:dynamicTransition|global={{ transition: transitionOut, params: transitionOutParams, enabled: transitions }}
|
||||
>
|
||||
{#if $modalStore[0].type !== 'component'}
|
||||
<!-- Modal: Presets -->
|
||||
<div
|
||||
class="modal {classesModal}"
|
||||
data-testid="modal"
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-label={$modalStore[0].title ?? ''}
|
||||
transition:fly|global={{ duration, opacity: 0, y: 100 }}
|
||||
>
|
||||
<div class="modal {classesModal}" data-testid="modal" role="dialog" aria-modal="true" aria-label={$modalStore[0].title ?? ''}>
|
||||
<!-- Header -->
|
||||
{#if $modalStore[0]?.title}
|
||||
<header class="modal-header {regionHeader}">{@html $modalStore[0].title}</header>
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import { readable } from 'svelte/store';
|
||||
import { BROWSER } from 'esm-env';
|
||||
|
||||
/** Prefers reduced motion */
|
||||
const reducedMotionQuery = '(prefers-reduced-motion: reduce)';
|
||||
|
||||
function prefersReducedMotion() {
|
||||
if (!BROWSER) return false;
|
||||
return window.matchMedia(reducedMotionQuery).matches;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates that the user has enabled reduced motion on their device.
|
||||
*
|
||||
* @see https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-reduced-motion
|
||||
*/
|
||||
export const prefersReducedMotionStore = readable(prefersReducedMotion(), (set) => {
|
||||
if (BROWSER) {
|
||||
const setReducedMotion = (event: MediaQueryListEvent) => {
|
||||
set(event.matches);
|
||||
};
|
||||
const mediaQueryList = window.matchMedia(reducedMotionQuery);
|
||||
mediaQueryList.addEventListener('change', setReducedMotion);
|
||||
|
||||
return () => {
|
||||
mediaQueryList.removeEventListener('change', setReducedMotion);
|
||||
};
|
||||
}
|
||||
});
|
||||
@@ -1,10 +1,16 @@
|
||||
<script lang="ts">
|
||||
import { crossfade } from 'svelte/transition';
|
||||
import { cubicInOut } from 'svelte/easing';
|
||||
import { flip } from 'svelte/animate';
|
||||
<script lang="ts" context="module">
|
||||
import { fly } from 'svelte/transition';
|
||||
import { type Transition, type TransitionParams, type CssClasses, prefersReducedMotionStore } from '../../index.js';
|
||||
import { dynamicTransition } from '../../internal/transitions.js';
|
||||
|
||||
// Types
|
||||
import type { CssClasses } from '../../index.js';
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
type FlyTransition = typeof fly;
|
||||
type TransitionIn = Transition;
|
||||
type TransitionOut = Transition;
|
||||
</script>
|
||||
|
||||
<script lang="ts" generics="TransitionIn extends Transition = FlyTransition, TransitionOut extends Transition = FlyTransition">
|
||||
import { flip } from 'svelte/animate';
|
||||
|
||||
// Stores
|
||||
import { toastStore } from './stores.js';
|
||||
@@ -16,8 +22,6 @@
|
||||
export let position: 't' | 'b' | 'l' | 'r' | 'tl' | 'tr' | 'bl' | 'br' = 'b';
|
||||
/** Maximum toasts that can show at once. */
|
||||
export let max = 3;
|
||||
/** The duration of the fly in/out animation. */
|
||||
export let duration = 250;
|
||||
|
||||
// Props (styles)
|
||||
/** Provide classes to set the background color. */
|
||||
@@ -45,6 +49,33 @@
|
||||
/** The button label text. */
|
||||
export let buttonDismissLabel = '✕';
|
||||
|
||||
// Props (transition)
|
||||
/**
|
||||
* Enable/Disable transitions
|
||||
* @type {boolean}
|
||||
*/
|
||||
export let transitions = !$prefersReducedMotionStore;
|
||||
/**
|
||||
* Provide the transition to used on entry.
|
||||
* @type {TransitionIn}
|
||||
*/
|
||||
export let transitionIn: TransitionIn = fly as TransitionIn;
|
||||
/**
|
||||
* Transition params provided to `transitionIn`.
|
||||
* @type {TransitionParams}
|
||||
*/
|
||||
export let transitionInParams: TransitionParams<TransitionIn> = { duration: 250 };
|
||||
/**
|
||||
* Provide the transition to used on exit.
|
||||
* @type {TransitionOut}
|
||||
*/
|
||||
export let transitionOut: TransitionOut = fly as TransitionOut;
|
||||
/**
|
||||
* Transition params provided to `transitionOut`.
|
||||
* @type {TransitionParams}
|
||||
*/
|
||||
export let transitionOutParams: TransitionParams<TransitionOut> = { duration: 250 };
|
||||
|
||||
// Base Classes
|
||||
const cWrapper = 'flex fixed top-0 left-0 right-0 bottom-0 pointer-events-none';
|
||||
const cSnackbar = 'flex flex-col gap-y-2';
|
||||
@@ -96,25 +127,6 @@
|
||||
$: classesToast = `${cToast} ${width} ${color} ${padding} ${spacing} ${rounded} ${shadow}`;
|
||||
// Filtered Toast Store
|
||||
$: filteredToasts = Array.from($toastStore).slice(0, max);
|
||||
|
||||
// Crossfade animation for Toasts
|
||||
const [send, receive] = crossfade({
|
||||
duration: (d) => Math.sqrt(d * duration),
|
||||
|
||||
fallback(node) {
|
||||
const style = getComputedStyle(node);
|
||||
const transform = style.transform === 'none' ? '' : style.transform;
|
||||
|
||||
return {
|
||||
duration,
|
||||
easing: cubicInOut,
|
||||
css: (t, u) => `
|
||||
transform: ${transform} scale(${t}) translate(${u * animAxis.x}%, ${u * animAxis.y}%);
|
||||
opacity: ${t}
|
||||
`
|
||||
};
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
{#if $toastStore.length}
|
||||
@@ -124,9 +136,17 @@
|
||||
<div class="snackbar {classesSnackbar}">
|
||||
{#each filteredToasts as t, i (t)}
|
||||
<div
|
||||
animate:flip={{ duration }}
|
||||
in:receive|global={{ key: t.id }}
|
||||
out:send|global={{ key: t.id }}
|
||||
animate:flip={{ duration: transitions ? 250 : 0 }}
|
||||
in:dynamicTransition|global={{
|
||||
transition: transitionIn,
|
||||
params: { x: animAxis.x, y: animAxis.y, ...transitionInParams },
|
||||
enabled: transitions
|
||||
}}
|
||||
out:dynamicTransition|global={{
|
||||
transition: transitionOut,
|
||||
params: { x: animAxis.x, y: animAxis.y, ...transitionOutParams },
|
||||
enabled: transitions
|
||||
}}
|
||||
on:mouseenter={() => onMouseEnter(i)}
|
||||
on:mouseleave={() => onMouseLeave(i)}
|
||||
role={t.hideDismiss ? 'alert' : 'alertdialog'}
|
||||
|
||||
@@ -77,6 +77,13 @@
|
||||
<span>WAI-ARIA</span>
|
||||
</a>
|
||||
{/if}
|
||||
<!-- Transitions -->
|
||||
{#if pageData.transitionIn || pageData.transitionOut}
|
||||
<a class={cChip} href="/docs/transitions" title={`In: ${pageData.transitionIn}, Out: ${pageData.transitionOut}`}>
|
||||
<i class="fa-solid fa-right-left text-[16px]" />
|
||||
<span>Transitions</span>
|
||||
</a>
|
||||
{/if}
|
||||
<!-- Dependencies -->
|
||||
{#if pageData.dependencies?.length}
|
||||
{#each pageData.dependencies as d}
|
||||
|
||||
@@ -79,6 +79,10 @@ export interface DocsShellSettings {
|
||||
classes?: [string, string, string][];
|
||||
/** Keyboard interaction table source [name, description]. */
|
||||
keyboard?: [string, string][];
|
||||
/** Indicates the transitionIn transition used (ex: fade) */
|
||||
transitionIn?: string;
|
||||
/** Indicates the transitionOut transition used (ex: fade) */
|
||||
transitionOut?: string;
|
||||
}
|
||||
|
||||
// NOTE: this will be removed alongside the move to the JSDocs documentation.
|
||||
|
||||
@@ -17,7 +17,13 @@ export const menuNavLinks: Record<string, Array<{ title: string; list: List }>>
|
||||
{ href: '/docs/colors', label: 'Colors', keywords: 'theme, colors, swatches' },
|
||||
{ href: '/docs/styling', label: 'Styling', keywords: 'styles, styling, props, classes, class, css' },
|
||||
{ href: '/docs/tokens', label: 'Design Tokens', keywords: 'theme, color, pairing, css, utility' },
|
||||
{ href: '/docs/variants', label: 'Variants', keywords: 'variant, variants, presets, backgrounds, classes' }
|
||||
{ href: '/docs/variants', label: 'Variants', keywords: 'variant, variants, presets, backgrounds, classes' },
|
||||
{
|
||||
href: '/docs/transitions',
|
||||
label: 'Transitions',
|
||||
keywords: 'transition, transitions, blur, fade, fly, slide, scale, draw, crossfade, prefers, reduced, motion',
|
||||
badge: 'New'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -30,7 +30,9 @@
|
||||
['<kbd class="kbd">Tab</kbd>', 'Focus the next accordion item.'],
|
||||
['<kbd class="kbd">Shift + Tab</kbd> ', 'Focus the previous accordion item.'],
|
||||
['<kbd class="kbd">Space</kbd> or <kbd class="kbd">Enter</kbd>', 'Toggles the item panel open or closed.']
|
||||
]
|
||||
],
|
||||
transitionIn: 'slide',
|
||||
transitionOut: 'slide'
|
||||
};
|
||||
|
||||
// Local
|
||||
|
||||
@@ -24,7 +24,9 @@
|
||||
['<kbd class="kbd">Tab</kbd>', 'Select the next autocomplete option.'],
|
||||
['<kbd class="kbd">Shift</kbd> + <kbd class="kbd">Tab</kbd>', 'Select the previous autocomplete option.'],
|
||||
['<kbd class="kbd">Space</kbd> or <kbd class="kbd">Enter</kbd>', 'Select the current autocomplete option.']
|
||||
]
|
||||
],
|
||||
transitionIn: 'slide',
|
||||
transitionOut: 'slide'
|
||||
};
|
||||
|
||||
// Local
|
||||
|
||||
@@ -18,7 +18,9 @@
|
||||
source: 'components/InputChip',
|
||||
components: [{ sveld: sveldInputChip }],
|
||||
// aria: 'https://www.w3.org/WAI/ARIA/apg/',
|
||||
restProps: 'input'
|
||||
restProps: 'input',
|
||||
transitionIn: 'list fly / chip scale',
|
||||
transitionOut: 'list fly / chip scale'
|
||||
};
|
||||
|
||||
// Local
|
||||
|
||||
@@ -32,7 +32,9 @@
|
||||
'buttonCompleteLabel'
|
||||
]
|
||||
}
|
||||
]
|
||||
],
|
||||
transitionIn: 'fade',
|
||||
transitionOut: 'fade'
|
||||
};
|
||||
|
||||
// Local
|
||||
|
||||
@@ -14,7 +14,9 @@
|
||||
description: 'Allows you to quickly navigate the hierarchy of headings for the current page.',
|
||||
imports: ['TableOfContents'],
|
||||
source: 'components/TableOfContents',
|
||||
components: [{ sveld: sveldTableOfContents }]
|
||||
components: [{ sveld: sveldTableOfContents }],
|
||||
transitionIn: 'fade',
|
||||
transitionOut: 'fade'
|
||||
};
|
||||
|
||||
// Local
|
||||
|
||||
@@ -212,6 +212,110 @@ $: classesLabel = \`\${cBaseLabel}\`; // child element
|
||||
|
||||
<hr />
|
||||
|
||||
<!-- Dynamic Transitions -->
|
||||
<section class="space-y-4">
|
||||
<h2 class="h2">Dynamic Transitions</h2>
|
||||
<p>
|
||||
Skeleton has a convention for implement dynamic transitions within components. Please follow the guidelines below to ensure you are
|
||||
following our standard for this process.
|
||||
</p>
|
||||
<blockquote class="blockquote">
|
||||
TIP: You may reference existing components to view this in practice: <a
|
||||
class="anchor"
|
||||
href="https://github.com/skeletonlabs/skeleton/blob/master/packages/skeleton/src/lib/components/Accordion/Accordion.svelte"
|
||||
target="_blank"
|
||||
>
|
||||
Accordion
|
||||
</a>
|
||||
</blockquote>
|
||||
<!-- Implementation -->
|
||||
<h3 class="h3">Implementation</h3>
|
||||
<p>
|
||||
Define transition types in a <code class="code">context="module"</code> script tag so you can use it as generic for the standard script
|
||||
tag attributes.
|
||||
</p>
|
||||
<CodeBlock
|
||||
language="ts"
|
||||
code={`
|
||||
${'<'}script context="module"${'>'}
|
||||
import { slide } from 'svelte/transition';
|
||||
import {
|
||||
type Transition,
|
||||
type TransitionParams,
|
||||
type CssClasses, prefersReducedMotionStore
|
||||
} from '../../index.js';\n
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
type SlideTransition = typeof slide; // switch with your transition
|
||||
type TransitionIn = Transition;
|
||||
type TransitionOut = Transition;
|
||||
${'<'}${'/script'}>
|
||||
`}
|
||||
/>
|
||||
<p>Supply the generics in the standard <code class="code">script</code> tag attributes.</p>
|
||||
<CodeBlock
|
||||
language="html"
|
||||
code={`
|
||||
${'<'}script
|
||||
lang="ts"
|
||||
generics="TransitionIn extends Transition = SlideTransition, TransitionOut extends Transition = SlideTransition"
|
||||
${'>'}
|
||||
// ...
|
||||
${'<'}${'/script'}>
|
||||
`}
|
||||
/>
|
||||
<p>Define the following properties:</p>
|
||||
<ul class="list-disc list-outside ml-4 space-y-1">
|
||||
<li>
|
||||
<code class="code">transition</code> - default to <code class="code">!$prefersReducedMotionStore</code> to adhere to the
|
||||
<a class="anchor" href="/docs/transitions#reduced-motion" target="_blank">Reduced motion rules</a>.
|
||||
</li>
|
||||
<li>
|
||||
<code class="code">transitionIn</code> - the transition on entry.
|
||||
</li>
|
||||
<li>
|
||||
<code class="code">transitionInParams</code> - the parameters for the entry transition.
|
||||
</li>
|
||||
<li>
|
||||
<code class="code">transitionOut</code> - the transition on exit.
|
||||
</li>
|
||||
<li>
|
||||
<code class="code">transitionOutParams</code> - the parameters for the entry transition.
|
||||
</li>
|
||||
</ul>
|
||||
<h3 class="h3">Implementing Transitions</h3>
|
||||
<p>
|
||||
See the dedicated <a class="anchor" href="/docs/transitions">Transitions</a> page for examples of how to implement custom transitions for
|
||||
a component.
|
||||
</p>
|
||||
<!-- Documentation -->
|
||||
<h3 class="h3">Documentation</h3>
|
||||
<blockquote class="blockquote">
|
||||
TIP: Review existing component pages to view how this is presented in practice: <a
|
||||
class="anchor"
|
||||
href="/components/accordions"
|
||||
target="_blank"
|
||||
>
|
||||
Accordions
|
||||
</a>
|
||||
</blockquote>
|
||||
<p>
|
||||
Add the <code class="code">transitionX</code> props to <code class="code">DocsShellSettings</code> to indicate that dynamic transitions
|
||||
are available.
|
||||
</p>
|
||||
<CodeBlock
|
||||
language="ts"
|
||||
code={`
|
||||
const settings: DocsShellSettings = {
|
||||
// ...
|
||||
transitionIn: 'slide',
|
||||
transitionOut: 'slide'
|
||||
};
|
||||
`}
|
||||
/>
|
||||
</section>
|
||||
|
||||
<hr />
|
||||
|
||||
<!-- Pitfalls -->
|
||||
<section class="space-y-4">
|
||||
<h2 class="h2">Pitfalls</h2>
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
<script lang="ts">
|
||||
import LayoutPage from '$lib/layouts/LayoutPage/LayoutPage.svelte';
|
||||
import { CodeBlock } from '@skeletonlabs/skeleton';
|
||||
</script>
|
||||
|
||||
<LayoutPage>
|
||||
<header class="space-y-4">
|
||||
<h1 class="h1">Transitions</h1>
|
||||
<!-- prettier-ignore -->
|
||||
<p>
|
||||
Skeleton provides a simple interface for modifying Svelte component transitions. This supports <a class="anchor" href="https://svelte.dev/docs#run-time-svelte-transition" target="_blank" rel="noreferrer">Svelte-provided transitions</a>, such as: <code class="code">fade</code>, <code class="code">blur</code>, <code class="code">fly</code>, <code class="code">slide</code>, and <code class="code">scale</code>. As well as custom <a class="anchor" href="https://svelte.dev/tutorial/custom-css-transitions" target="_blank" rel="noreferrer">CSS</a> and <a class="anchor" href="https://svelte.dev/tutorial/custom-js-transitions" target="_blank" rel="noreferrer">Javascript</a> transitions.
|
||||
</p>
|
||||
</header>
|
||||
|
||||
<hr />
|
||||
|
||||
<section class="space-y-4">
|
||||
<div class="card variant-glass p-4 text-center space-y-4">
|
||||
<span class="chip variant-soft">
|
||||
<i class="fa-solid fa-right-left text-[16px]" />
|
||||
<span>Transitions</span>
|
||||
</span>
|
||||
<p class="text-sm">Look for this indicator on each component page. If present, custom transitions are supported.</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="space-y-4">
|
||||
<h2 class="h2">Properties</h2>
|
||||
<p>Provide the transition and transition parameters as follows.</p>
|
||||
<h3 class="h3">Transition In</h3>
|
||||
<CodeBlock language="html" code={`<ExampleComponent transitionIn={fade} transitionInParams={{ duration: 200 }} />`} />
|
||||
<h3 class="h3">Transition Out</h3>
|
||||
<CodeBlock language="html" code={`<ExampleComponent transitionOut={fade} transitionOutParams={{ duration: 200 }} />`} />
|
||||
</section>
|
||||
|
||||
<section class="space-y-4">
|
||||
<h2 class="h2">Parameters</h2>
|
||||
<p>You can modify parameters for both the default <em>in</em> and <em>out</em> transitions.</p>
|
||||
<CodeBlock language="html" code={`<ExampleComponent transitionInParams={{ duration: 400 }} />`} />
|
||||
</section>
|
||||
|
||||
<section class="space-y-4">
|
||||
<h2 class="h2">Disable Transitions</h2>
|
||||
<p>
|
||||
To disable all transitions for a single component, set <code class="code">transitions</code> to <em>false</em>. This will affect both
|
||||
the
|
||||
<em>in</em>
|
||||
and <em>out</em> transitions.
|
||||
</p>
|
||||
<CodeBlock
|
||||
language="html"
|
||||
code={`
|
||||
<ExampleComponent transitions={false}/>
|
||||
`}
|
||||
/>
|
||||
</section>
|
||||
|
||||
<section class="space-y-4">
|
||||
<h2 class="h2">Non-Supported Transitions</h2>
|
||||
<p>
|
||||
Note that Svelte provides special <code class="code">crossfade</code> and <code class="code">draw</code> transitions. However these
|
||||
work and operate in a different manner than standard transition such as <em>fade</em> and <em>fly</em>. These are not supported within
|
||||
the dynamic transition system at this time.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
<section class="space-y-4">
|
||||
<h2 class="h2">Reduced Motion</h2>
|
||||
<p>
|
||||
To ensure a better experience for users who are sensitive to motion or have vestibular disorders, Skeleton's default
|
||||
behavior is to disable all transitions when users enable <a class="anchor" href="https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-reduced-motion" target="_blank" rel="noreferrer">prefers-reduced-motion</a> in their browser settings.
|
||||
</p>
|
||||
<h3 class="h3">Force Enable</h3>
|
||||
<p>
|
||||
For components with subtle transitions, you may choose to override this behavior by applying a property of <code class="code">{`transition={true}`}</code> to the component. We encourage you to use this setting with caution though.
|
||||
</p>
|
||||
<h3 class="h3">Store</h3>
|
||||
<p>
|
||||
You may utilize <code class="code">prefersReducedMotionStore</code> to access the user's preferred motion setting.
|
||||
</p>
|
||||
<CodeBlock
|
||||
language="ts"
|
||||
code={`
|
||||
import { prefersReducedMotionStore } from '@skeletonlabs/skeleton';\n
|
||||
const userMotionPreference = $prefersReducedMotionStore;
|
||||
`}
|
||||
/>
|
||||
</section>
|
||||
</LayoutPage>
|
||||
@@ -33,7 +33,9 @@
|
||||
['<code class="code">.w-modal</code>', '-', 'Sets a standard responsive width for modals.'],
|
||||
['<code class="code">.w-modal-slim</code>', '-', 'Create a slimmer modal. Great for small component modals.'],
|
||||
['<code class="code">.w-modal-wide</code>', '-', 'Create a wider modal. Great for full screen component modals.']
|
||||
]
|
||||
],
|
||||
transitionIn: 'fly',
|
||||
transitionOut: 'fly'
|
||||
};
|
||||
|
||||
// Demo ---
|
||||
|
||||
@@ -16,7 +16,9 @@
|
||||
imports: ['Toast', 'toastStore'],
|
||||
types: ['ToastSettings'],
|
||||
source: 'utilities/Toast',
|
||||
components: [{ sveld: sveldToast }]
|
||||
components: [{ sveld: sveldToast }],
|
||||
transitionIn: 'fly',
|
||||
transitionOut: 'fly'
|
||||
};
|
||||
|
||||
// Local
|
||||
|
||||
Reference in New Issue
Block a user