Skip to content

Commit 9e7c524

Browse files
committed
feat(button): adds skeleton state
1 parent b0a9422 commit 9e7c524

File tree

9 files changed

+118
-31
lines changed

9 files changed

+118
-31
lines changed

demo/App.svelte

Lines changed: 40 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,26 @@
33
import '~/styles/theme.scss';
44
55
import DemoButtons from './components/DemoButtons.svelte';
6+
7+
import NeoButton from '~/buttons/NeoButton.svelte';
8+
9+
let transitionIn = $state(false);
10+
let transitionOut = $state(false);
11+
const onTransition = () => {
12+
transitionOut = true;
13+
setTimeout(() => {
14+
transitionOut = false;
15+
transitionIn = true;
16+
17+
setTimeout(() => {
18+
transitionIn = false;
19+
}, 750);
20+
}, 750);
21+
};
622
</script>
723

8-
<div class="container">
24+
<NeoButton onclick={onTransition}>transition</NeoButton>
25+
<div class="container" class:transition-in={transitionIn} class:transition-out={transitionOut}>
926
<DemoButtons />
1027
</div>
1128

@@ -18,14 +35,29 @@
1835
1936
padding: 1rem;
2037
21-
:global {
22-
.rotate {
23-
@include mixin.border-rotate;
24-
}
38+
&:global(.transition-out *),
39+
&:global(.transition-out *::before),
40+
&:global(.transition-out *::after) {
41+
box-shadow: var(--box-shadow-flat) !important;
42+
transition:
43+
all 0.5s ease,
44+
box-shadow 0.5s ease-in-out;
45+
}
46+
47+
&:global(.transition-in *),
48+
&:global(.transition-in *::before),
49+
&:global(.transition-in *::after) {
50+
transition:
51+
all 0.5s ease,
52+
box-shadow 0.5s ease-in-out;
53+
}
54+
55+
:global(.rotate) {
56+
@include mixin.border-rotate;
57+
}
2558
26-
.progress {
27-
@include mixin.border-progress;
28-
}
59+
:global(.progress) {
60+
@include mixin.border-progress;
2961
}
3062
}
3163
</style>

demo/components/DemoButtons.svelte

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,12 @@
77
};
88
99
let loading = $state(false);
10-
const onLoading = (...args: any) => {
10+
const onLoading = (e: MouseEvent, duration = 5000) => {
1111
loading = !loading;
1212
setTimeout(() => {
1313
loading = !loading;
14-
}, 3000);
15-
onClick(...args);
14+
}, duration);
15+
onClick(e);
1616
};
1717
</script>
1818

@@ -22,47 +22,51 @@
2222

2323
<div class="column">
2424
<div class="row">
25-
<NeoButton onclick={onClick}>Hello</NeoButton>
25+
<NeoButton onclick={onClick}>Button</NeoButton>
2626
<NeoButton disabled onclick={onClick}>Disabled</NeoButton>
2727
<NeoButton {loading} onclick={onLoading}>Loading</NeoButton>
2828
<NeoButton {loading} onclick={onLoading} {icon} />
2929
<NeoButton onclick={onClick} {icon}>Icon</NeoButton>
3030
<NeoButton onclick={onClick} {icon}>Reversed</NeoButton>
3131
<NeoButton {loading} pulse onclick={onLoading}>Pulse</NeoButton>
3232
<NeoButton coalesce onclick={onClick}>Coalesce</NeoButton>
33+
<NeoButton skeleton={loading} onclick={onLoading}>Skeleton</NeoButton>
3334
</div>
3435

3536
<div class="row">
36-
<NeoButton rounded onclick={onClick}>Hello</NeoButton>
37+
<NeoButton rounded onclick={onClick}>Button</NeoButton>
3738
<NeoButton rounded disabled onclick={onClick}>Disabled</NeoButton>
3839
<NeoButton rounded {loading} onclick={onLoading}>Loading</NeoButton>
3940
<NeoButton rounded {loading} onclick={onLoading} {icon} />
4041
<NeoButton rounded onclick={onClick} {icon}>Icon</NeoButton>
4142
<NeoButton rounded onclick={onClick} {icon}>Reversed</NeoButton>
4243
<NeoButton rounded {loading} pulse onclick={onLoading}>Pulse</NeoButton>
4344
<NeoButton rounded coalesce onclick={onClick}>Coalesce</NeoButton>
45+
<NeoButton skeleton={loading} onclick={onLoading}>Skeleton</NeoButton>
4446
</div>
4547

4648
<div class="row">
47-
<NeoButton flat onclick={onClick}>Hello</NeoButton>
49+
<NeoButton flat onclick={onClick}>Button</NeoButton>
4850
<NeoButton flat disabled onclick={onClick}>Disabled</NeoButton>
4951
<NeoButton flat {loading} onclick={onLoading}>Loading</NeoButton>
5052
<NeoButton flat {loading} onclick={onLoading} {icon} />
5153
<NeoButton flat onclick={onClick} {icon}>Icon</NeoButton>
5254
<NeoButton flat onclick={onClick} {icon}>Reversed</NeoButton>
5355
<NeoButton flat {loading} pulse onclick={onLoading}>Pulse</NeoButton>
5456
<NeoButton flat coalesce onclick={onClick}>Coalesce</NeoButton>
57+
<NeoButton flat skeleton={loading} onclick={onLoading}>Skeleton</NeoButton>
5558
</div>
5659

5760
<div class="row">
58-
<NeoButton text onclick={onClick}>Hello</NeoButton>
61+
<NeoButton text onclick={onClick}>Button</NeoButton>
5962
<NeoButton text disabled onclick={onClick}>Disabled</NeoButton>
6063
<NeoButton text {loading} onclick={onLoading}>Loading</NeoButton>
6164
<NeoButton text {loading} onclick={onLoading} {icon} />
6265
<NeoButton text onclick={onClick} {icon}>Icon</NeoButton>
6366
<NeoButton text onclick={onClick} {icon}>Reversed</NeoButton>
6467
<NeoButton text {loading} pulse onclick={onLoading}>Pulse</NeoButton>
6568
<NeoButton text coalesce onclick={onClick}>Coalesce</NeoButton>
69+
<NeoButton text skeleton={loading} onclick={onLoading}>Skeleton</NeoButton>
6670
</div>
6771
</div>
6872

favicon.svg

Lines changed: 7 additions & 0 deletions
Loading

index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
<html lang="en">
33
<head>
44
<meta charset="utf-8" />
5-
<link rel="icon" type="image/svg+xml" href="/favicon.png" />
5+
<link rel="icon" type="image/svg+xml" href="./favicon.svg" />
66
<meta name="viewport" content="width=device-width, initial-scale=1" />
77
<title>Neo Svelte</title>
88
</head>

src/lib/buttons/NeoButton.svelte

Lines changed: 26 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@
1212
icon,
1313
// States
1414
loading,
15-
skeleton, // todo
15+
skeleton,
16+
disabled,
1617
// Styles
1718
class: classNames,
1819
text,
@@ -71,7 +72,7 @@
7172
active = true;
7273
timeout = setTimeout(() => {
7374
active = false;
74-
}, 100);
75+
}, 300);
7576
};
7677
7778
const onClick = (e: MouseEvent) => {
@@ -87,12 +88,14 @@
8788
class:coalesce
8889
class:pressed
8990
class:loading
91+
class:skeleton
9092
class:text
9193
class:flat
9294
class:rounded
9395
onkeydown={onKeydownEnter}
9496
onkeyup={onKeyUpEnter}
9597
onclick={onClick}
98+
disabled={disabled || skeleton}
9699
{...rest}
97100
>
98101
<span class="content" class:reverse>
@@ -126,11 +129,11 @@
126129
box-shadow: var(--box-shadow-raised-2);
127130
cursor: pointer;
128131
transition:
129-
opacity 0.4s ease,
130-
color 0.4s ease,
131-
background-color 0.4s ease,
132-
border-color 0.4s ease,
133-
box-shadow 0.2s ease-in;
132+
opacity 0.3s ease,
133+
color 0.3s ease,
134+
background-color 0.3s ease,
135+
border-color 0.3s ease,
136+
box-shadow 0.3s ease-out;
134137
135138
&:focus-visible {
136139
color: var(--neo-btn-text-color-focused, var(--text-color-focused));
@@ -141,15 +144,14 @@
141144
&.pressed,
142145
&:active {
143146
box-shadow: var(--box-shadow-inset-2);
144-
transition: all 0.1s ease-out;
145147
}
146148
147149
&.loading {
148150
cursor: wait;
149151
}
150152
151153
&.text {
152-
border: none;
154+
border: 1px solid transparent !important;
153155
}
154156
155157
&.flat,
@@ -163,6 +165,11 @@
163165
color: var(--neo-btn-text-color-hover, var(--text-color-hover));
164166
}
165167
168+
@starting-style {
169+
border-color: var(--neo-btn-border-color-hover, var(--border-color));
170+
box-shadow: var(--box-shadow-flat);
171+
}
172+
166173
&.text:not(:active, &.pressed),
167174
&.flat:not(:active, &.pressed),
168175
&.loading:active,
@@ -182,8 +189,16 @@
182189
}
183190
}
184191
185-
&[disabled]:not([disabled='false']) {
186-
color: var(--neo-btn-text-color-disabled, var(--text-color-disabled)) !important;
192+
&.skeleton {
193+
@include mixin.skeleton;
194+
195+
box-shadow: var(--box-shadow-flat);
196+
opacity: 1;
197+
pointer-events: none;
198+
}
199+
200+
&[disabled]:not([disabled='false'], .skeleton) {
201+
color: var(--neo-btn-text-color-disabled, var(--text-color-disabled));
187202
border-color: var(--neo-btn-border-color-disabled, var(--border-color-disabled)) !important;
188203
box-shadow: var(--box-shadow-flat);
189204
cursor: not-allowed;

src/lib/styles/animation.scss

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,3 +45,11 @@
4545
}
4646
}
4747
}
48+
49+
@mixin skeleton {
50+
@keyframes skeleton {
51+
50% {
52+
opacity: 0.5;
53+
}
54+
}
55+
}

src/lib/styles/common/colors.scss

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,9 @@
1111
--black-soft-strong: oklch(from var(--black-soft) calc(l - 0.075) c h);
1212
--grey: oklch(85.9% 0.033 270.413deg);
1313
--grey-50: oklch(from var(--grey) l c h / 50%);
14-
--grey-soft: oklch(from var(--grey) calc(l - 0.4) c h);
15-
--grey-soft-20: oklch(from var(--grey-soft) l c h / 20%);
14+
--grey-soft: oklch(from var(--grey) calc(l - 0.05) c h);
15+
--grey-dark: oklch(from var(--grey) calc(l - 0.4) c h);
16+
--grey-dark-20: oklch(from var(--grey-soft) l c h / 20%);
1617
--purple: oklch(46.4% 0.305 269.594deg);
1718
--purple-50: oklch(from var(--purple) l c h / 50%);
1819
--purple-80: oklch(from var(--purple) l c h / 80%);
@@ -21,17 +22,18 @@
2122
--purple-soft-80: oklch(from var(--purple-soft) l c h / 80%);
2223

2324
/** semantic colors */
24-
--text-color: var(--grey-soft);
25+
--text-color: var(--grey-dark);
2526
--text-color-focused: var(--purple-80);
2627
--text-color-hover: var(--purple);
2728
--text-color-disabled: var(--text-color);
28-
--border-color: var(--grey-soft-20);
29+
--border-color: var(--grey-dark-20);
2930
--border-color-focused: var(--purple-50);
3031
--border-color-hover: var(--purple-80);
3132
--border-color-disabled: var(--border-color);
3233
--background-color: var(--white-soft);
3334
--shadow-color-light: var(--white);
3435
--shadow-color-dark: var(--grey);
36+
--skeleton-color: var(--grey-soft);
3537

3638
/** dark mode semantic colors */
3739
--dark-text-color: var(--white-80);
@@ -45,6 +47,7 @@
4547
--dark-background-color: var(--black-soft);
4648
--dark-shadow-color-light: var(--black-soft-light);
4749
--dark-shadow-color-dark: var(--black-soft-strong);
50+
--dark-skeleton-color: var(--grey-dark);
4851

4952
/* touch highlight */
5053
-webkit-tap-highlight-color: transparent;
@@ -66,6 +69,7 @@
6669
--background-color: var(--dark-background-color);
6770
--shadow-color-light: var(--dark-shadow-color-light);
6871
--shadow-color-dark: var(--dark-shadow-color-dark);
72+
--skeleton-color: var(--dark-skeleton-color);
6973
}
7074

7175
/* override semantic color if dark mode is enabled */

src/lib/styles/common/utils.scss

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
:host {
33
/* Transforms */
44
--transition-bezier: cubic-bezier(0.4, 0, 0.2, 1);
5+
--transition-skeleton: cubic-bezier(0.4, 0, 0.6, 1);
56

67
/* Opacity */
78
--opacity-disabled: 0.3;

src/lib/styles/mixin.scss

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,3 +126,19 @@
126126
border: $border-width solid transparent;
127127
transition: $transition $speed $easing;
128128
}
129+
130+
@mixin skeleton(
131+
$background-color: var(--skeleton-color),
132+
$border-color: var(--skeleton-color),
133+
$text-color: var(--skeleton-color),
134+
$timing: 2s,
135+
$easing: var(--transition-skeleton)
136+
) {
137+
color: $text-color !important;
138+
background-color: $background-color !important;
139+
border-color: $border-color !important;
140+
transition: none;
141+
animation: skeleton $timing $easing infinite;
142+
143+
@include animation.skeleton;
144+
}

0 commit comments

Comments
 (0)