Skip to content

Commit

Permalink
feat/dynamic-transitions (#1533)
Browse files Browse the repository at this point in the history
Co-authored-by: AdrianGonz97 <31664583+AdrianGonz97@users.noreply.github.com>
Co-authored-by: endigo9740 <gundamx9740@gmail.com>
  • Loading branch information
3 people committed Jul 14, 2023
1 parent dbd2ec0 commit 0ea5af0
Show file tree
Hide file tree
Showing 34 changed files with 793 additions and 120 deletions.
5 changes: 5 additions & 0 deletions .changeset/calm-dryers-peel.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@skeletonlabs/skeleton": minor
---

breaking: Introduced dynamic transitions for various components
1 change: 1 addition & 0 deletions cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
"cpath",
"Cruisin",
"csvg",
"customizability",
"datetime",
"delisted",
"describedby",
Expand Down
50 changes: 43 additions & 7 deletions packages/skeleton/src/lib/components/Accordion/Accordion.svelte
Original file line number Diff line number Diff line change
@@ -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. */
Expand Down Expand Up @@ -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);
Expand All @@ -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 ?? ''}`;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand All @@ -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. */
Expand All @@ -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');
Expand All @@ -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) {
Expand Down Expand Up @@ -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}"
Expand Down
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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>
Expand Down
80 changes: 75 additions & 5 deletions packages/skeleton/src/lib/components/InputChip/InputChip.svelte
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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;
Expand All @@ -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';
Expand Down Expand Up @@ -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 }}>
Expand All @@ -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>
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand Down
6 changes: 6 additions & 0 deletions packages/skeleton/src/lib/components/Ratings/Ratings.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
Loading

0 comments on commit 0ea5af0

Please sign in to comment.