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:
Mahmoud Zino
2023-07-14 19:07:17 +02:00
committed by GitHub
parent dbd2ec03c2
commit 0ea5af00d8
34 changed files with 793 additions and 120 deletions

View File

@@ -0,0 +1,5 @@
---
"@skeletonlabs/skeleton": minor
---
breaking: Introduced dynamic transitions for various components

View File

@@ -37,6 +37,7 @@
"cpath",
"Cruisin",
"csvg",
"customizability",
"datetime",
"delisted",
"describedby",

View File

@@ -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 ?? ''}`;

View File

@@ -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',

View File

@@ -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}"

View File

@@ -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>

View File

@@ -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>

View File

@@ -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

View File

@@ -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>

View File

@@ -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">

View File

@@ -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>

View File

@@ -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',

View File

@@ -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}

View File

@@ -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';

View 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];

View 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}
`
};
}
});
}

View 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 };

View File

@@ -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 />

View File

@@ -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. */

View File

@@ -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>

View File

@@ -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);
};
}
});

View File

@@ -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'}

View File

@@ -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}

View File

@@ -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.

View File

@@ -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'
}
]
},
{

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -32,7 +32,9 @@
'buttonCompleteLabel'
]
}
]
],
transitionIn: 'fade',
transitionOut: 'fade'
};
// Local

View File

@@ -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

View File

@@ -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>

View File

@@ -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>

View File

@@ -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 ---

View File

@@ -16,7 +16,9 @@
imports: ['Toast', 'toastStore'],
types: ['ToastSettings'],
source: 'utilities/Toast',
components: [{ sveld: sveldToast }]
components: [{ sveld: sveldToast }],
transitionIn: 'fly',
transitionOut: 'fly'
};
// Local