Skip to content

Commit

Permalink
fix(Media): forward all events including child components
Browse files Browse the repository at this point in the history
fix(Media.Avatar): added graceful error fallback
  • Loading branch information
N00nDay committed Oct 14, 2022
1 parent 4bd164b commit 1833ae5
Show file tree
Hide file tree
Showing 7 changed files with 294 additions and 14 deletions.
125 changes: 115 additions & 10 deletions src/lib/components/media/Avatar.svelte
Original file line number Diff line number Diff line change
@@ -1,29 +1,134 @@
<script lang="ts" context="module">
export const MEDIA_AVATAR_CONTEXT_ID = 'media-avatar-context-id';
</script>

<script lang="ts">
import { MEDIA_CONTEXT_ID } from './Media.svelte';
import { useContext } from '../../utils/useContext';
import Avatar from '../avatar';
import { setContext, onMount } from 'svelte/internal';
import { twMerge } from 'tailwind-merge';
import Placeholder from './Placeholder.svelte';
import { get_current_component } from 'svelte/internal';
import { forwardEventsBuilder, useActions, type ActionArray } from '../../actions';
export let use: ActionArray = [];
import { exclude } from '../../utils/exclude';
const forwardEvents = forwardEventsBuilder(get_current_component());
export let size: 'xs' | 'sm' | 'md' | 'lg' | 'xl' = 'md';
export let align: 'top' | 'center' | 'bottom' = 'top';
export let src: string;
export let src: string | undefined = undefined;
export let alt = 'avatar';
export let size: 'xs' | 'sm' | 'md' | 'lg' | 'xl' = 'md';
export let shape: 'circle' | 'rounded' | 'square' = 'circle';
export let initials: string | undefined = undefined;
let loaded = false;
let failed = false;
let loading = true;
useContext({
context_id: MEDIA_CONTEXT_ID,
parent: 'Media',
component: 'Media.Avatar'
});
let defaultClass = 'first:mr-4 last:ml-4 flex-shrink-0';
$: if (align === 'center') {
defaultClass = defaultClass + ' self-center';
} else if (align === 'bottom') {
defaultClass = defaultClass + ' self-end';
setContext(MEDIA_AVATAR_CONTEXT_ID, {
avatar: true,
src,
alt,
shape,
size
});
let defaultClass = '';
let containerDefaultClass = '';
if (src) {
defaultClass = 'inline-block absolute';
containerDefaultClass = 'inline-block h-8 w-8 relative align-middle';
} else if (initials) {
defaultClass =
'inline-flex h-8 w-8 items-center justify-center align-middle bg-light-icon-background dark:bg-dark-icon-background text-light-content dark:text-dark-content';
}
if (size === 'xs') {
defaultClass += ' h-6 w-6';
containerDefaultClass += ' h-6 w-6';
} else if (size === 'sm') {
defaultClass += ' h-8 w-8';
containerDefaultClass += ' h-8 w-8';
} else if (size === 'md') {
defaultClass += ' h-10 w-10';
containerDefaultClass += ' h-10 w-10';
} else if (size === 'lg') {
defaultClass += ' h-12 w-12';
containerDefaultClass += ' h-12 w-12';
} else if (size === 'xl') {
defaultClass += ' h-16 w-16';
containerDefaultClass += ' h-16 w-16';
}
if (shape === 'circle') {
defaultClass += ' rounded-full';
containerDefaultClass += ' rounded-full';
} else if (shape === 'rounded') {
defaultClass += ' rounded-md';
containerDefaultClass += ' rounded-md';
}
$: finalClass = twMerge(defaultClass, $$props.class);
$: finalContainerClass = twMerge(containerDefaultClass, $$props.class);
onMount(() => {
if (src) {
const image = new Image();
image.src = src;
loading = true;
image.onload = () => {
loading = false;
loaded = true;
};
image.onerror = () => {
loading = false;
failed = true;
};
}
});
</script>

<div class={finalClass} style={$$props.style}>
<Avatar {src} {alt} {size} />
<div
class="first:mr-4 last:ml-4 flex-shrink-0"
class:self-center={align === 'center'}
class:self-end={align === 'bottom'}
>
{#if src}
<span
class={finalContainerClass}
use:useActions={use}
use:forwardEvents
{...exclude($$props, ['use', 'class'])}
>
{#if loaded}
<img class={finalClass} style={$$props.style} src={src || ''} {alt} />
{:else if failed}
{#if $$slots.placeholder}
<slot name="placeholder" />
{:else}
<Placeholder />
{/if}
{:else if loading}
<Placeholder loading />
{/if}

<slot name="indicator" />
</span>
{:else if initials}
<span
class={finalClass}
use:useActions={use}
use:forwardEvents
{...exclude($$props, ['use', 'class'])}
>
<span class="font-bold leading-none text-current text-xl">{initials}</span>
</span>
{/if}
</div>
12 changes: 11 additions & 1 deletion src/lib/components/media/Content.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@
import { setContext } from 'svelte';
import { MEDIA_CONTEXT_ID } from './Media.svelte';
import { useContext } from '../../utils/useContext';
import { get_current_component } from 'svelte/internal';
import { forwardEventsBuilder, useActions, type ActionArray } from '../../actions';
export let use: ActionArray = [];
import { exclude } from '../../utils/exclude';
const forwardEvents = forwardEventsBuilder(get_current_component());
useContext({
context_id: MEDIA_CONTEXT_ID,
Expand All @@ -17,7 +22,12 @@
});
</script>

<div class={$$props.class} style={$$props.style}>
<div
class={$$props.class}
use:useActions={use}
use:forwardEvents
{...exclude($$props, ['use', 'class'])}
>
<slot name="title" />
<slot name="description" />
<slot />
Expand Down
12 changes: 11 additions & 1 deletion src/lib/components/media/Description.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@
import { MEDIA_CONTENT_CONTEXT_ID } from './Content.svelte';
import { useContext } from '../../utils/useContext';
import { twMerge } from 'tailwind-merge';
import { get_current_component } from 'svelte/internal';
import { forwardEventsBuilder, useActions, type ActionArray } from '../../actions';
export let use: ActionArray = [];
import { exclude } from '../../utils/exclude';
const forwardEvents = forwardEventsBuilder(get_current_component());
useContext({
context_id: MEDIA_CONTEXT_ID,
Expand All @@ -19,6 +24,11 @@
$: finalClass = twMerge(defaultClass, $$props.class);
</script>

<p class={finalClass} style={$$props.style}>
<p
class={finalClass}
use:useActions={use}
use:forwardEvents
{...exclude($$props, ['use', 'class'])}
>
<slot />
</p>
52 changes: 52 additions & 0 deletions src/lib/components/media/Icon.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<script lang="ts">
import { twMerge } from 'tailwind-merge';
import { MEDIA_AVATAR_CONTEXT_ID } from './Avatar.svelte';
import { MEDIA_AVATAR_PLACEHOLDER_ID } from './Placeholder.svelte';
import { getContext } from 'svelte/internal';
import { useContext } from '../../utils/useContext';
import type { MaterialIcon } from '../../types';
import { get_current_component } from 'svelte/internal';
import { forwardEventsBuilder, useActions, type ActionArray } from '../../actions';
export let use: ActionArray = [];
import { exclude } from '../../utils/exclude';
const forwardEvents = forwardEventsBuilder(get_current_component());
export let icon: MaterialIcon = 'person';
useContext({
context_id: MEDIA_AVATAR_CONTEXT_ID,
parent: 'Media.Avatar',
component: 'Media.Avatar.Placeholder.Icon'
});
useContext({
context_id: MEDIA_AVATAR_PLACEHOLDER_ID,
parent: 'Media.Avatar.Placeholder',
component: 'Media.Avatar.Placeholder.Icon'
});
const { size }: { size: 'xs' | 'sm' | 'md' | 'lg' | 'xl' } = getContext(MEDIA_AVATAR_CONTEXT_ID);
let defaultClass = 'material-icons absolute text-light-icon dark:text-dark-icon';
if (size === 'xs') {
defaultClass += ' text-2xl bottom-[-0.5rem]';
} else if (size === 'sm') {
defaultClass += ' text-4xl bottom-[-0.5rem]';
} else if (size === 'md') {
defaultClass += ' text-5xl bottom-[-0.5rem]';
} else if (size === 'lg') {
defaultClass += ' text-6xl bottom-[-0.75rem]';
} else if (size === 'xl') {
defaultClass += ' text-7xl bottom-[-0.75rem]';
}
$: finalClass = twMerge(defaultClass, $$props.class);
</script>

<span
class={finalClass}
use:useActions={use}
use:forwardEvents
{...exclude($$props, ['use', 'class'])}
>
{icon}
</span>
12 changes: 11 additions & 1 deletion src/lib/components/media/Media.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@
<script lang="ts">
import { setContext } from 'svelte';
import { twMerge } from 'tailwind-merge';
import { get_current_component } from 'svelte/internal';
import { forwardEventsBuilder, useActions, type ActionArray } from '../../actions';
export let use: ActionArray = [];
import { exclude } from '../../utils/exclude';
const forwardEvents = forwardEventsBuilder(get_current_component());
setContext(MEDIA_CONTEXT_ID, {
media: true
Expand All @@ -14,7 +19,12 @@
$: finalClass = twMerge(defaultClass, $$props.class);
</script>

<div class={finalClass} style={$$props.style}>
<div
class={finalClass}
use:useActions={use}
use:forwardEvents
{...exclude($$props, ['use', 'class'])}
>
<slot name="avatar" />
<slot name="content" />
<slot />
Expand Down
83 changes: 83 additions & 0 deletions src/lib/components/media/Placeholder.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
<script lang="ts" context="module">
export const MEDIA_AVATAR_PLACEHOLDER_ID = 'media-avatar-placeholder-id';
</script>

<script lang="ts">
import { fade } from 'svelte/transition';
import { MEDIA_AVATAR_CONTEXT_ID } from './Avatar.svelte';
import { useContext } from '../../utils/useContext';
import { getContext, setContext } from 'svelte/internal';
import { twMerge } from 'tailwind-merge';
import Icon from './Icon.svelte';
import { get_current_component } from 'svelte/internal';
import { forwardEventsBuilder, useActions, type ActionArray } from '../../actions';
export let use: ActionArray = [];
import { exclude } from '../../utils/exclude';
const forwardEvents = forwardEventsBuilder(get_current_component());
export let loading = false;
useContext({
context_id: MEDIA_AVATAR_CONTEXT_ID,
parent: 'Media.Avatar',
component: 'Media.Avatar.Placeholder'
});
setContext(MEDIA_AVATAR_PLACEHOLDER_ID, {
placeholder: true
});
const { shape }: { shape: 'circle' | 'rounded' | 'square' } = getContext(MEDIA_AVATAR_CONTEXT_ID);
let defaultClass =
'absolute inset-0 h-full w-full flex items-center justify-center overflow-hidden bg-light-icon-background dark:bg-dark-icon-background';
if (shape === 'circle') {
defaultClass += ' rounded-full';
} else if (shape === 'rounded') {
defaultClass += ' rounded-md';
}
if (loading) {
defaultClass += ' loading';
}
$: finalClass = twMerge(defaultClass, $$props.class);
</script>

<div
transition:fade|local
class={finalClass}
use:useActions={use}
use:forwardEvents
{...exclude($$props, ['use', 'class'])}
>
{#if $$slots.icon || $$slots.default}
<slot name="icon" />
<slot />
{:else}
<Icon class="text-light-icon dark:text-dark-icon" icon="person" />
{/if}
</div>

<style>
.loading::after {
position: absolute;
transform: translateX(-100%);
background: linear-gradient(
90deg,
rgba(190, 190, 190, 0.2) 25%,
rgba(129, 129, 129, 0.24) 37%,
rgba(190, 190, 190, 0.2) 63%
);
inset: 0 -150%;
animation: shimmer 2s infinite;
content: '';
}
@keyframes shimmer {
0% {
transform: translate(-37.5%);
}
to {
transform: translate(37.5%);
}
}
</style>
12 changes: 11 additions & 1 deletion src/lib/components/media/Title.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@
import { MEDIA_CONTENT_CONTEXT_ID } from './Content.svelte';
import { useContext } from '../../utils/useContext';
import { twMerge } from 'tailwind-merge';
import { get_current_component } from 'svelte/internal';
import { forwardEventsBuilder, useActions, type ActionArray } from '../../actions';
export let use: ActionArray = [];
import { exclude } from '../../utils/exclude';
const forwardEvents = forwardEventsBuilder(get_current_component());
useContext({
context_id: MEDIA_CONTEXT_ID,
Expand All @@ -19,6 +24,11 @@
$: finalClass = twMerge(defaultClass, $$props.class);
</script>

<h4 class={finalClass} style={$$props.style}>
<h4
class={finalClass}
use:useActions={use}
use:forwardEvents
{...exclude($$props, ['use', 'class'])}
>
<slot />
</h4>

1 comment on commit 1833ae5

@vercel
Copy link

@vercel vercel bot commented on 1833ae5 Oct 14, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

stwui – ./

stwui-n00nday.vercel.app
stwui.vercel.app
stwui-git-main-n00nday.vercel.app

Please sign in to comment.