Skip to content

Commit a42adba

Browse files
committed
Delayed placeholders
1 parent cad19a0 commit a42adba

File tree

5 files changed

+130
-6
lines changed

5 files changed

+130
-6
lines changed

src/demos/placeholders-demo.tsx

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,11 @@ import {
77
} from '~/shared/components/card';
88
import {
99
ChatPlaceholder,
10+
DelayedPlaceholder,
1011
ImgPlaceholder,
12+
InlineDelayedPlaceholder,
1113
InlineMissingPlaceholder,
14+
MissingPlaceholder,
1215
ParagraphPlaceholder,
1316
} from '~/shared/components/placeholder';
1417

@@ -26,7 +29,17 @@ export function PlaceholdersDemo() {
2629
<ImgPlaceholder aspectRatio={3 / 4} />
2730
<ImgPlaceholder aspectRatio={16 / 9} />
2831
</div>
29-
<InlineMissingPlaceholder />
32+
<div class="o-group">
33+
<div>
34+
<InlineMissingPlaceholder />
35+
</div>
36+
<InlineDelayedPlaceholder delay={5000}>
37+
<InlineMissingPlaceholder />
38+
</InlineDelayedPlaceholder>
39+
</div>
40+
<DelayedPlaceholder>
41+
<MissingPlaceholder />
42+
</DelayedPlaceholder>
3043
<ParagraphPlaceholder />
3144
<ImgPlaceholder height={150} width={300} />
3245
</div>

src/shared/components/callbacks/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import * as menu from '~/shared/components/callbacks/menu';
1313
import * as modal from '~/shared/components/callbacks/modal';
1414
import * as modalForm from '~/shared/components/callbacks/modal-form';
1515
import * as optionList from '~/shared/components/callbacks/option-list';
16+
import * as placeholder from '~/shared/components/callbacks/placeholder';
1617
import * as scroll from '~/shared/components/callbacks/scroll';
1718
import * as select from '~/shared/components/callbacks/select';
1819
import * as sidebar from '~/shared/components/callbacks/sidebar';
@@ -37,6 +38,7 @@ loadCallbacks(
3738
modal,
3839
modalForm,
3940
optionList,
41+
placeholder,
4042
scroll,
4143
select,
4244
sidebar,
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { createMounter } from '~/shared/utility/callback-attrs/mount';
2+
import { parseIntOrNull } from '~/shared/utility/parse';
3+
4+
export const PLACEHOLDER_DELAY_ATTR = 'data-delay';
5+
6+
export const placeholderDelayedShow = createMounter('$p-placeholder__delayed-show', async (el) => {
7+
el.classList.add('t-hidden');
8+
const delay = parseIntOrNull(el.getAttribute(PLACEHOLDER_DELAY_ATTR) ?? '0');
9+
if (delay && delay > 0) {
10+
await new Promise((resolve) => setTimeout(resolve, delay));
11+
}
12+
el.classList.remove('t-hidden');
13+
});
14+
15+
export const placeholderDelayedHide = createMounter('$p-placeholder__delayed-hide', async (el) => {
16+
el.classList.remove('t-hidden');
17+
const delay = parseIntOrNull(el.getAttribute(PLACEHOLDER_DELAY_ATTR) ?? '0');
18+
if (delay && delay > 0) {
19+
await new Promise((resolve) => setTimeout(resolve, delay));
20+
}
21+
el.classList.add('t-hidden');
22+
});

src/shared/components/placeholder.css

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,29 @@
77
.c-placeholder--circle {
88
@mixin v-colors-secondary;
99
@mixin t-shimmer;
10+
}
1011

12+
.c-placeholder::after,
13+
.c-placeholder--img::after,
14+
.c-placeholder--inline,
15+
.c-placeholder--circle,
16+
.c-placeholder--missing::after,
17+
.c-placeholder--inline-missing {
1118
position: relative;
1219
overflow: hidden;
1320
}
1421

1522
.c-placeholder::after,
1623
.c-placeholder--inline,
17-
.c-placeholder--missing {
24+
.c-placeholder--missing::after,
25+
.c-placeholder--inline-missing {
1826
border-radius: 1lh;
1927
height: 0.85em;
28+
min-height: auto;
2029
}
2130

22-
.c-placeholder {
31+
.c-placeholder,
32+
.c-placeholder--missing {
2333
display: flex;
2434
align-items: center;
2535

@@ -35,7 +45,7 @@
3545
}
3646

3747
.c-placeholder--inline,
38-
.c-placeholder--missing {
48+
.c-placeholder--inline-missing {
3949
display: inline-block;
4050
width: 9em;
4151
vertical-align: baseline;
@@ -44,7 +54,8 @@
4454
top: 2px;
4555
}
4656

47-
.c-placeholder--missing {
57+
.c-placeholder--missing::after,
58+
.c-placeholder--inline-missing {
4859
@mixin v-colors-card;
4960
@mixin t-border;
5061
}

src/shared/components/placeholder.tsx

Lines changed: 77 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@ import cx from 'classix';
44
import { For, type JSX, splitProps, Suspense } from 'solid-js';
55
import { isServer } from 'solid-js/web';
66

7+
import {
8+
PLACEHOLDER_DELAY_ATTR,
9+
placeholderDelayedHide,
10+
placeholderDelayedShow,
11+
} from '~/shared/components/callbacks/placeholder';
12+
import { callbackAttrs } from '~/shared/utility/callback-attrs/callback-registry';
713
import { randInt } from '~/shared/utility/random';
814
import { useT } from '~/shared/utility/solid/locale-context';
915

@@ -33,6 +39,45 @@ export function Placeholder(props: PlaceholderProps) {
3339
);
3440
}
3541

42+
/**
43+
* Fixed width pill placeholder, used for inline elements in text
44+
*/
45+
export function MissingPlaceholder(props: JSX.HTMLAttributes<HTMLSpanElement>) {
46+
const [local, rest] = splitProps(props, ['class']);
47+
const t = useT();
48+
49+
return (
50+
<span class={cx('c-placeholder--missing', local.class)} aria-label={t`Missing`} {...rest} />
51+
);
52+
}
53+
54+
/**
55+
* Show placeholder for a given amount of time before rendering content
56+
*/
57+
export function DelayedPlaceholder(props: {
58+
delay?: number | undefined;
59+
fallback?: JSX.Element;
60+
children: JSX.Element;
61+
}) {
62+
return (
63+
<>
64+
<div
65+
{...{ [PLACEHOLDER_DELAY_ATTR]: String(props.delay ?? 3000) }}
66+
{...callbackAttrs(placeholderDelayedHide)}
67+
>
68+
{props.fallback ?? <Placeholder />}
69+
</div>
70+
<div
71+
class="t-hidden"
72+
{...{ [PLACEHOLDER_DELAY_ATTR]: String(props.delay ?? 3000) }}
73+
{...callbackAttrs(placeholderDelayedShow)}
74+
>
75+
{props.children}
76+
</div>
77+
</>
78+
);
79+
}
80+
3681
/**
3782
* Fixed width pill placeholder, used for inline elements in text
3883
*/
@@ -53,7 +98,38 @@ export function InlineMissingPlaceholder(props: JSX.HTMLAttributes<HTMLSpanEleme
5398
const t = useT();
5499

55100
return (
56-
<span class={cx('c-placeholder--missing', local.class)} aria-label={t`Missing`} {...rest} />
101+
<span
102+
class={cx('c-placeholder--inline-missing', local.class)}
103+
aria-label={t`Missing`}
104+
{...rest}
105+
/>
106+
);
107+
}
108+
109+
/**
110+
* Show placeholder for a given amount of time before rendering content
111+
*/
112+
export function InlineDelayedPlaceholder(props: {
113+
delay?: number | undefined;
114+
fallback?: JSX.Element;
115+
children: JSX.Element;
116+
}) {
117+
return (
118+
<>
119+
<span
120+
{...{ [PLACEHOLDER_DELAY_ATTR]: String(props.delay ?? 3000) }}
121+
{...callbackAttrs(placeholderDelayedHide)}
122+
>
123+
{props.fallback ?? <InlinePlaceholder />}
124+
</span>
125+
<span
126+
class="t-hidden"
127+
{...{ [PLACEHOLDER_DELAY_ATTR]: String(props.delay ?? 3000) }}
128+
{...callbackAttrs(placeholderDelayedShow)}
129+
>
130+
{props.children}
131+
</span>
132+
</>
57133
);
58134
}
59135

0 commit comments

Comments
 (0)