Skip to content

Commit

Permalink
feat(tabs): adds animated tab card wrapper
Browse files Browse the repository at this point in the history
  • Loading branch information
dvcol committed Nov 12, 2024
1 parent 02cfac5 commit faaf689
Show file tree
Hide file tree
Showing 20 changed files with 350 additions and 129 deletions.
6 changes: 3 additions & 3 deletions demo/components/DemoTabs.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import { useButtonState } from '../utils/use-button-state.svelte';
import type { TabId } from '~/nav/neo-tab.model';
import type { NeoTabsProps } from '~/nav/neo-tabs.model.js';
import type { NeoTabContextValue, NeoTabsProps } from '~/nav/neo-tabs.model.js';
import NeoButton from '~/buttons/NeoButton.svelte';
import NeoButtonGroup from '~/buttons/NeoButtonGroup.svelte';
Expand Down Expand Up @@ -37,9 +37,9 @@
let active: unknown | undefined = $state('button');
let value: unknown | undefined = $state('button');
const onChange = (id?: TabId, _value?: unknown) => {
const onChange = (id?: TabId, context?: NeoTabContextValue) => {
active = id;
value = _value;
value = context?.value;
};
const onClear = () => onChange();
Expand Down
89 changes: 45 additions & 44 deletions demo/components/DemoTabsPanels.svelte
Original file line number Diff line number Diff line change
@@ -1,21 +1,18 @@
<script lang="ts">
import { randomHex } from '@dvcol/common-utils/common/crypto';
import { fade } from 'svelte/transition';
import SphereBackdrop from '../utils/SphereBackdrop.svelte';
import type { TabId } from '~/nav/neo-tab.model.js';
import type { NeoTabsProps } from '~/nav/neo-tabs.model.js';
import NeoButton from '~/buttons/NeoButton.svelte';
import NeoButtonGroup from '~/buttons/NeoButtonGroup.svelte';
import NeoCard from '~/cards/NeoCard.svelte';
import TransitionContainer from '~/container/TransitionContainer.svelte';
import IconAccount from '~/icons/IconAccount.svelte';
import NeoTab from '~/nav/NeoTab.svelte';
import NeoTabPane from '~/nav/NeoTabPane.svelte';
import NeoTabs from '~/nav/NeoTabs.svelte';
import NeoTabsCard from '~/nav/NeoTabsCard.svelte';
const added = $state([
{ text: `Added ${randomHex(1)}-0`, tabId: crypto.randomUUID() },
Expand All @@ -26,6 +23,7 @@
if (index === -1) return;
added.splice(index, 1);
};
const onadd = () => {
added.push({ text: `Added ${randomHex(1)}-${added.length + 1}`, tabId: crypto.randomUUID() });
};
Expand All @@ -40,6 +38,7 @@
close: true,
add: true,
slide: true,
inset: false,
shallow: false,
toggle: true,
before: false,
Expand All @@ -55,6 +54,7 @@
<div class="column">
<NeoButtonGroup>
<NeoButton toggle bind:checked={options.disabled}>Disabled</NeoButton>
<NeoButton toggle bind:checked={options.inset}>Inset</NeoButton>
<NeoButton toggle bind:checked={options.shallow}>Shallow</NeoButton>
<NeoButton toggle bind:checked={options.before}>Before</NeoButton>
<NeoButton toggle bind:checked={options.vertical}>Vertical</NeoButton>
Expand All @@ -79,43 +79,35 @@
{/snippet}

{#snippet content(word)}
<div class="panel" in:fade={{ delay: 400 }} out:fade>
<div>{Array.from({ length: 10 }, () => word).join(' ')}</div>
<div>{Array.from({ length: 10 }, () => word).join(' ')}</div>
<div>{Array.from({ length: 10 }, () => word).join(' ')}</div>
<div>{Array.from({ length: 10 }, () => word).join(' ')}</div>
<div>{Array.from({ length: 10 }, () => word).join(' ')}</div>
</div>
<p>{Array.from({ length: 50 }, () => word).join(' ')}</p>
{/snippet}

{#snippet panes()}
<NeoCard>
<TransitionContainer>
<NeoTabPane empty>
{@render content('Empty')}
</NeoTabPane>
<NeoTabPane tabId="button">
{@render content('Button')}
</NeoTabPane>
<NeoTabPane tabId="icon">
{@render content('Icon')}
</NeoTabPane>
<NeoTabPane tabId="reversed">
{@render content('Reversed')}
<NeoTabsCard class="panel">
<NeoTabPane empty>
{@render content('Empty')}
</NeoTabPane>
<NeoTabPane tabId="button">
{@render content('Button')}
</NeoTabPane>
<NeoTabPane tabId="icon">
{@render content('Icon')}
</NeoTabPane>
<NeoTabPane tabId="reversed">
{@render content('Reversed')}
</NeoTabPane>

{#each added as { text, tabId } (tabId)}
<NeoTabPane {tabId}>
{@render content(text)}
</NeoTabPane>

{#each added as { text, tabId } (tabId)}
<NeoTabPane {tabId}>
{@render content(text)}
</NeoTabPane>
{/each}
</TransitionContainer>
</NeoCard>
{/each}
</NeoTabsCard>
{/snippet}

{#snippet group(props: NeoTabsProps = {})}
<div class="column">
<NeoTabs {panes} {active} {onclose} {onadd} {...options} {...props}>
<div class="column" class:vertical={options.vertical}>
<NeoTabs {panes} {active} {onclose} {onadd} {onchange} {...options} {...props}>
{@render tabs()}
</NeoTabs>
</div>
Expand All @@ -138,8 +130,27 @@
<style lang="scss">
@use 'src/lib/styles/common/flex' as flex;
.content {
flex: 0 1 37.5rem;
max-width: 37.5rem;
}
:global(.panel) {
min-width: 34rem;
min-height: 20rem;
}
.column {
@include flex.column($center: true, $gap: var(--neo-gap-lg));
&.vertical {
flex-direction: row;
:global(.panel) {
min-width: 28.5rem;
min-height: 24rem;
}
}
}
.row {
Expand All @@ -149,14 +160,4 @@
justify-content: center;
margin: 2rem 0;
}
.content {
flex: 0 1 37.5rem;
max-width: 37.5rem;
}
.panel {
min-width: 37.5rem;
min-height: 15rem;
}
</style>
13 changes: 8 additions & 5 deletions src/lib/buttons/NeoButtonGroup.svelte
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<script lang="ts">
import type { NeoButtonGroup } from '~/buttons/neo-button-group.model.js';
import type { NeoButtonGroupProps } from '~/buttons/neo-button-group.model.js';
import { toAction, toActionProps, toTransition, toTransitionProps } from '~/utils/action.utils.js';
Expand Down Expand Up @@ -42,7 +42,7 @@
// Other props
...rest
}: NeoButtonGroup = $props();
}: NeoButtonGroupProps = $props();
/* eslint-enable prefer-const */
const inFn = $derived(toTransition(inAction ?? transitionAction));
Expand Down Expand Up @@ -96,11 +96,14 @@

<style lang="scss">
@use 'src/lib/styles/mixin' as mixin;
@use 'src/lib/styles/common/flex' as flex;
.neo-button-group {
@include flex.row($flex: 0 1 auto, $center: true, $gap: var(--neo-btn-grp-gap, 0.25rem));
display: inline-flex;
flex: 0 1 auto;
flex-flow: row wrap;
gap: var(--neo-btn-grp-gap, 0.25rem);
align-items: center;
justify-content: center;
box-sizing: border-box;
width: fit-content;
margin: var(--neo-shadow-margin, 0.25rem);
Expand Down
4 changes: 2 additions & 2 deletions src/lib/buttons/neo-button-group.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,9 @@ export type NeoButtonGroupContext = {
vertical?: boolean;
};

export type NeoButtonGroup = {
export type NeoButtonGroupProps = {
/**
* Optional snippet to display as the button content.
* Snippet to display as the button content.
*/
children?: Snippet<[NeoButtonGroupContext]>;

Expand Down
2 changes: 1 addition & 1 deletion src/lib/buttons/neo-button.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export type NeoButtonProps = {
// Snippets

/**
* Optional snippet to display as the button content.
* Snippet to display as the button content.
*/
children?: Snippet;
/**
Expand Down
3 changes: 1 addition & 2 deletions src/lib/cards/NeoCard.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,6 @@
<svelte:element
this={tag}
bind:this={ref}
class="neo-card"
class:borderless
class:glass
class:flat={!elevation}
Expand All @@ -70,14 +69,14 @@
in:inFn={inProps}
{...rest}
{style}
class={['neo-card', rest.class].filter(Boolean).join(' ')}
>
{@render children?.()}
</svelte:element>

<style lang="scss">
.neo-card {
display: flex;
flex-direction: column;
box-sizing: border-box;
width: calc(100% - var(--neo-shadow-margin, 0.25rem) * 2);
margin: var(--neo-shadow-margin, 0.25rem);
Expand Down
24 changes: 24 additions & 0 deletions src/lib/container/NeoTransitionContainer.model.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import type { Snippet } from 'svelte';
import type { HTMLNeoBaseElement } from '~/utils/html-element.utils.js';

export type NeoTransitionContainerProps = {
// Snippets
/**
* Snippet to display as the container content.
*/
children?: Snippet;

// States
/**
* The HTML tag to use for the container.
* @default 'div'
*/
tag?: keyof HTMLElementTagNameMap;

// Styles

/**
* Overflow style.
*/
overflow: CSSStyleDeclaration['overflow'];
} & HTMLNeoBaseElement;
32 changes: 32 additions & 0 deletions src/lib/container/NeoTransitionContainer.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<script lang="ts">
import type { NeoTransitionContainerProps } from '~/container/NeoTransitionContainer.model.js';
const {
// Snippets
children,
// States
tag = 'div',
// Styles
overflow = 'hidden',
// Other props
...rest
}: NeoTransitionContainerProps = $props();
</script>

<svelte:element this={tag} class="neo-transition-container" style:overflow {...rest}>
{@render children?.()}
</svelte:element>

<style lang="scss">
.neo-transition-container {
display: grid;
grid-template-areas: 'transition';
:global(> *) {
grid-area: transition;
}
}
</style>
20 changes: 0 additions & 20 deletions src/lib/container/TransitionContainer.svelte

This file was deleted.

18 changes: 13 additions & 5 deletions src/lib/nav/NeoTab.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
const context = getTabContext();
const active = $derived(context?.active === tabId);
const disabled = $derived(rest.disabled || (rest.disabled !== false && context?.disabled));
const closeable = $derived(close || (close !== false && context?.closeable));
const closeable = $derived(close || (close !== false && context?.close));
const transition = $derived(context?.vertical ? height : width);
const slide = $derived(context?.slide);
Expand All @@ -56,16 +56,18 @@
$effect(() => {
waitForTick();
if (!ref) return;
untrack(() => context?.register(tabId, ref!, value));
untrack(() => {
if (!ref) return;
context?.register(tabId, { ref, value });
});
return () => context?.remove(tabId);
});
const onClose = (e: MouseEvent) => {
e.preventDefault();
e.stopPropagation();
onclose?.(tabId, value, ref);
context?.onClose(tabId, value, ref);
onclose?.(tabId);
context?.onClose(tabId);
};
const useFn = $derived(toAction(tabProps?.use));
Expand Down Expand Up @@ -102,6 +104,8 @@

<style lang="scss">
.neo-tab {
display: flex;
:global(.neo-button:active),
:global(.neo-button.pressed),
:global(.neo-button:focus-visible),
Expand All @@ -112,6 +116,10 @@
}
}
:global(.neo-button .icon-close:focus-visible) {
transition: none;
}
&.slide {
:global(.neo-button:active .icon-close),
:global(.neo-button.pressed .icon-close) {
Expand Down
Loading

0 comments on commit faaf689

Please sign in to comment.