Skip to content

Commit 9864b86

Browse files
committed
feat(tabs): adds base tab bar component
1 parent 92c23bf commit 9864b86

23 files changed

+827
-222
lines changed

demo/App.svelte

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,35 +9,50 @@
99
1010
import { router } from './router/router.js';
1111
12-
import { Route } from './router/routes';
12+
import { routes } from './router/routes.js';
1313
14+
import type { Routes } from './router/routes.js';
1415
import type { TransitionProps } from '@dvcol/svelte-simple-router/models';
1516
1617
import NeoButton from '~/buttons/NeoButton.svelte';
1718
import NeoButtonGroup from '~/buttons/NeoButtonGroup.svelte';
1819
import IconMoon from '~/icons/IconMoon.svelte';
1920
import IconSun from '~/icons/IconSun.svelte';
21+
import NeoTab from '~/nav/NeoTab.svelte';
22+
import NeoTabs from '~/nav/NeoTabs.svelte';
2023
2124
const transition: TransitionProps = {
2225
in: fade,
2326
out: fade,
24-
params: { in: { delay: 200, duration: 200 }, out: { duration: 200 } },
27+
params: { in: { delay: 100, duration: 100 }, out: { duration: 100 } },
28+
props: { container: { style: 'display: flex; justify-content: center; align-items: center; overflow:hidden;' } },
2529
skipFirst: true,
2630
};
2731
2832
const active = $derived(router.route?.name);
2933
let transitioning = $state(false);
3034
35+
let first = true;
3136
const onChange = async () => {
37+
if (first) return;
3238
transitioning = true;
33-
await wait(150);
39+
await wait(200);
3440
};
3541
3642
const onLoaded = async () => {
37-
await wait(350);
43+
if (active && first) {
44+
first = false;
45+
return;
46+
}
47+
await wait(200);
3848
transitioning = false;
3949
};
4050
51+
const onClick = (id?: Routes) => {
52+
if (id === undefined || id === active) return;
53+
router.push({ name: id });
54+
};
55+
4156
const initial = localStorage.getItem('theme');
4257
let dark = $state(initial ? initial === 'dark' : window.matchMedia('(prefers-color-scheme: dark)').matches);
4358
let remember = $state(!!localStorage.getItem('theme'));
@@ -48,19 +63,15 @@
4863
if (remember) localStorage.setItem('theme', dark ? 'dark' : 'light');
4964
else localStorage.removeItem('theme');
5065
});
51-
52-
const routes = [Route.Buttons, Route.ButtonGroups];
5366
</script>
5467

5568
<div class="container">
5669
<div class="row">
57-
<NeoButtonGroup>
70+
<NeoTabs {active} onchange={onClick}>
5871
{#each routes as route}
59-
<NeoButton checked={active === route} onclick={() => router.push({ name: route })}>
60-
{route}
61-
</NeoButton>
72+
<NeoTab tabId={route}>{route}</NeoTab>
6273
{/each}
63-
</NeoButtonGroup>
74+
</NeoTabs>
6475

6576
<NeoButtonGroup>
6677
<NeoButton toggle bind:checked={dark}>

demo/components/DemoButtonGroups.svelte

Lines changed: 42 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -3,112 +3,63 @@
33
44
import SphereBackdrop from '../utils/SphereBackdrop.svelte';
55
6+
import { useButtonState } from '../utils/use-button-state.svelte';
7+
8+
import type { NeoButtonGroupContext } from '~/buttons/neo-button-group.model';
9+
610
import NeoButton from '~/buttons/NeoButton.svelte';
711
import NeoButtonGroup from '~/buttons/NeoButtonGroup.svelte';
812
import IconAccount from '~/icons/IconAccount.svelte';
913
10-
const onClick = (...args: any) => {
11-
console.info(...args);
12-
};
13-
14-
let loading = $state(false);
15-
const onLoading = (e: MouseEvent, checked?: boolean, duration = 5000) => {
16-
loading = !loading;
17-
setTimeout(() => {
18-
loading = !loading;
19-
}, duration);
20-
onClick(e);
21-
};
22-
23-
let skeleton = $state(false);
24-
const onSkeleton = (e: MouseEvent, checked?: boolean, duration = 5000) => {
25-
skeleton = !skeleton;
26-
setTimeout(() => {
27-
skeleton = !skeleton;
28-
}, duration);
29-
onClick(e);
30-
};
14+
const { onClick, loading: loading$, onLoading, skeleton: skeleton$, onSkeleton } = useButtonState('DemoGroupClicked');
15+
const loading = $derived.by(loading$);
16+
const skeleton = $derived.by(skeleton$);
3117
3218
const { matches } = useWatchMedia('(max-width: 1550px)');
3319
const vertical = $derived.by(matches);
20+
21+
const columns = [
22+
{ label: 'Default' },
23+
{ label: 'Rounded', props: { rounded: true } },
24+
{ label: 'Flat', props: { flat: true } },
25+
{ label: 'Text', props: { text: true } },
26+
{ label: 'Glass', props: { glass: true } },
27+
];
3428
</script>
3529

3630
{#snippet icon()}
3731
<IconAccount />
3832
{/snippet}
3933

40-
<div class="row">
41-
<div class="column">
42-
<span class="label">Default</span>
43-
<NeoButtonGroup {vertical} {skeleton}>
44-
<NeoButton onclick={onClick}>Button</NeoButton>
45-
<NeoButton toggle onclick={onClick}>Toggle</NeoButton>
46-
<NeoButton disabled onclick={onClick}>Disabled</NeoButton>
47-
<NeoButton {loading} onclick={onLoading}>Loading</NeoButton>
48-
<NeoButton {loading} onclick={onLoading} {icon} />
49-
<NeoButton onclick={onClick} {icon}>Icon</NeoButton>
50-
<NeoButton reverse onclick={onClick} {icon}>Reversed</NeoButton>
51-
<NeoButton {skeleton} onclick={onSkeleton}>Skeleton</NeoButton>
52-
</NeoButtonGroup>
53-
</div>
54-
55-
<div class="column">
56-
<span class="label">Rounded</span>
57-
<NeoButtonGroup {vertical} {skeleton} rounded>
58-
<NeoButton onclick={onClick}>Button</NeoButton>
59-
<NeoButton toggle onclick={onClick}>Toggle</NeoButton>
60-
<NeoButton disabled onclick={onClick}>Disabled</NeoButton>
61-
<NeoButton {loading} onclick={onLoading}>Loading</NeoButton>
62-
<NeoButton {loading} onclick={onLoading} {icon} />
63-
<NeoButton onclick={onClick} {icon}>Icon</NeoButton>
64-
<NeoButton reverse onclick={onClick} {icon}>Reversed</NeoButton>
65-
<NeoButton {skeleton} onclick={onSkeleton}>Skeleton</NeoButton>
66-
</NeoButtonGroup>
67-
</div>
68-
69-
<div class="column">
70-
<span class="label">Flat</span>
71-
<NeoButtonGroup {vertical} {skeleton} flat>
72-
<NeoButton flat onclick={onClick}>Button</NeoButton>
73-
<NeoButton flat toggle onclick={onClick}>Toggle</NeoButton>
74-
<NeoButton flat disabled onclick={onClick}>Disabled</NeoButton>
75-
<NeoButton flat {loading} onclick={onLoading}>Loading</NeoButton>
76-
<NeoButton flat {loading} onclick={onLoading} {icon} />
77-
<NeoButton flat onclick={onClick} {icon}>Icon</NeoButton>
78-
<NeoButton flat reverse onclick={onClick} {icon}>Reversed</NeoButton>
79-
<NeoButton flat {skeleton} onclick={onSkeleton}>Skeleton</NeoButton>
80-
</NeoButtonGroup>
81-
</div>
34+
{#snippet buttons()}
35+
<NeoButton onclick={onClick}>Button</NeoButton>
36+
<NeoButton toggle onclick={onClick}>Toggle</NeoButton>
37+
<NeoButton disabled onclick={onClick}>Disabled</NeoButton>
38+
<NeoButton {loading} onclick={onLoading}>Loading</NeoButton>
39+
<NeoButton {loading} onclick={onLoading} {icon} />
40+
<NeoButton onclick={onClick} {icon}>Icon</NeoButton>
41+
<NeoButton reverse onclick={onClick} {icon}>Reversed</NeoButton>
42+
<NeoButton {skeleton} onclick={onSkeleton}>Skeleton</NeoButton>
43+
{/snippet}
8244

83-
<div class="column">
84-
<span class="label">Text</span>
85-
<NeoButtonGroup {vertical} {skeleton} text>
86-
<NeoButton text onclick={onClick}>Button</NeoButton>
87-
<NeoButton text toggle onclick={onClick}>Toggle</NeoButton>
88-
<NeoButton text disabled onclick={onClick}>Disabled</NeoButton>
89-
<NeoButton text {loading} onclick={onLoading}>Loading</NeoButton>
90-
<NeoButton text {loading} onclick={onLoading} {icon} />
91-
<NeoButton text onclick={onClick} {icon}>Icon</NeoButton>
92-
<NeoButton text reverse onclick={onClick} {icon}>Reversed</NeoButton>
93-
<NeoButton text {skeleton} onclick={onSkeleton}>Skeleton</NeoButton>
94-
</NeoButtonGroup>
95-
</div>
45+
{#snippet group(props: NeoButtonGroupContext = {})}
46+
<NeoButtonGroup {vertical} {skeleton} {...props}>
47+
{@render buttons()}
48+
</NeoButtonGroup>
49+
{/snippet}
9650

97-
<div class="column">
98-
<span class="label">Glass</span>
99-
<SphereBackdrop>
100-
<NeoButtonGroup {vertical} {skeleton} glass>
101-
<NeoButton onclick={onClick}>Button</NeoButton>
102-
<NeoButton toggle onclick={onClick}>Toggle</NeoButton>
103-
<NeoButton disabled onclick={onClick}>Disabled</NeoButton>
104-
<NeoButton {loading} onclick={onLoading}>Loading</NeoButton>
105-
<NeoButton {loading} onclick={onLoading} {icon} />
106-
<NeoButton onclick={onClick} {icon}>Icon</NeoButton>
107-
<NeoButton reverse onclick={onClick} {icon}>Reversed</NeoButton>
108-
<NeoButton {skeleton} onclick={onSkeleton}>Skeleton</NeoButton>
109-
</NeoButtonGroup>
110-
</SphereBackdrop>
111-
</div>
51+
<div class="row">
52+
{#each columns as { label, props }}
53+
<div class="column">
54+
<span class="label">{label}</span>
55+
56+
{#if props?.glass}
57+
<SphereBackdrop>{@render group(props)}</SphereBackdrop>
58+
{:else}
59+
{@render group(props)}
60+
{/if}
61+
</div>
62+
{/each}
11263

11364
<div class="column">
11465
<span class="label">Pulse</span>

demo/components/DemoButtons.svelte

Lines changed: 32 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -1,92 +1,49 @@
11
<script lang="ts">
22
import SphereBackdrop from '../utils/SphereBackdrop.svelte';
33
4+
import { useButtonState } from '../utils/use-button-state.svelte';
5+
6+
import type { NeoButtonProps } from '~/buttons/neo-button.model';
7+
48
import NeoButton from '~/buttons/NeoButton.svelte';
59
import IconAccount from '~/icons/IconAccount.svelte';
610
7-
const onClick = (...args: any) => {
8-
console.info(...args);
9-
};
11+
const { onClick, loading: loading$, onLoading, skeleton: skeleton$, onSkeleton } = useButtonState('DemoButtonClick');
12+
const loading = $derived.by(loading$);
13+
const skeleton = $derived.by(skeleton$);
1014
11-
let loading = $state(false);
12-
const onLoading = (e: MouseEvent, checked?: boolean, duration = 5000) => {
13-
loading = !loading;
14-
setTimeout(() => {
15-
loading = !loading;
16-
}, duration);
17-
onClick(e);
18-
};
19-
20-
let skeleton = $state(false);
21-
const onSkeleton = (e: MouseEvent, checked?: boolean, duration = 5000) => {
22-
skeleton = !skeleton;
23-
setTimeout(() => {
24-
skeleton = !skeleton;
25-
}, duration);
26-
onClick(e);
27-
};
15+
const columns = [
16+
{ label: 'Default' },
17+
{ label: 'Rounded', props: { rounded: true } },
18+
{ label: 'Flat', props: { flat: true } },
19+
{ label: 'Text', props: { text: true } },
20+
];
2821
</script>
2922

3023
{#snippet icon()}
3124
<IconAccount />
3225
{/snippet}
3326

34-
<div class="row">
35-
<div class="column">
36-
<span class="label">Default</span>
37-
<NeoButton onclick={onClick}>Button</NeoButton>
38-
<NeoButton toggle onclick={onClick}>Toggle</NeoButton>
39-
<NeoButton disabled onclick={onClick}>Disabled</NeoButton>
40-
<NeoButton {loading} onclick={onLoading}>Loading</NeoButton>
41-
<NeoButton {loading} onclick={onLoading} {icon} />
42-
<NeoButton onclick={onClick} {icon}>Icon</NeoButton>
43-
<NeoButton reverse onclick={onClick} {icon}>Reversed</NeoButton>
44-
<NeoButton {loading} pulse onclick={onLoading}>Pulse</NeoButton>
45-
<NeoButton coalesce onclick={onClick}>Coalesce</NeoButton>
46-
<NeoButton {skeleton} onclick={onSkeleton}>Skeleton</NeoButton>
47-
</div>
48-
49-
<div class="column">
50-
<span class="label">Rounded</span>
51-
<NeoButton rounded onclick={onClick}>Button</NeoButton>
52-
<NeoButton rounded toggle onclick={onClick}>Toggle</NeoButton>
53-
<NeoButton rounded disabled onclick={onClick}>Disabled</NeoButton>
54-
<NeoButton rounded {loading} onclick={onLoading}>Loading</NeoButton>
55-
<NeoButton rounded {loading} onclick={onLoading} {icon} />
56-
<NeoButton rounded onclick={onClick} {icon}>Icon</NeoButton>
57-
<NeoButton rounded reverse onclick={onClick} {icon}>Reversed</NeoButton>
58-
<NeoButton rounded {loading} pulse onclick={onLoading}>Pulse</NeoButton>
59-
<NeoButton rounded coalesce onclick={onClick}>Coalesce</NeoButton>
60-
<NeoButton {skeleton} onclick={onSkeleton}>Skeleton</NeoButton>
61-
</div>
62-
63-
<div class="column">
64-
<span class="label">Flat</span>
65-
<NeoButton flat onclick={onClick}>Button</NeoButton>
66-
<NeoButton flat toggle onclick={onClick}>Toggle</NeoButton>
67-
<NeoButton flat disabled onclick={onClick}>Disabled</NeoButton>
68-
<NeoButton flat {loading} onclick={onLoading}>Loading</NeoButton>
69-
<NeoButton flat {loading} onclick={onLoading} {icon} />
70-
<NeoButton flat onclick={onClick} {icon}>Icon</NeoButton>
71-
<NeoButton flat reverse onclick={onClick} {icon}>Reversed</NeoButton>
72-
<NeoButton flat {loading} pulse onclick={onLoading}>Pulse</NeoButton>
73-
<NeoButton flat coalesce onclick={onClick}>Coalesce</NeoButton>
74-
<NeoButton flat {skeleton} onclick={onSkeleton}>Skeleton</NeoButton>
75-
</div>
27+
{#snippet buttons(opts: NeoButtonProps = {})}
28+
<NeoButton {...opts} onclick={onClick}>Button</NeoButton>
29+
<NeoButton {...opts} toggle onclick={onClick}>Toggle</NeoButton>
30+
<NeoButton {...opts} disabled onclick={onClick}>Disabled</NeoButton>
31+
<NeoButton {...opts} {loading} onclick={onLoading}>Loading</NeoButton>
32+
<NeoButton {...opts} {loading} onclick={onLoading} {icon} />
33+
<NeoButton {...opts} onclick={onClick} {icon}>Icon</NeoButton>
34+
<NeoButton {...opts} reverse onclick={onClick} {icon}>Reversed</NeoButton>
35+
<NeoButton {...opts} {loading} pulse onclick={onLoading}>Pulse</NeoButton>
36+
<NeoButton {...opts} coalesce onclick={onClick}>Coalesce</NeoButton>
37+
<NeoButton {...opts} {skeleton} onclick={onSkeleton}>Skeleton</NeoButton>
38+
{/snippet}
7639

77-
<div class="column">
78-
<span class="label">Text</span>
79-
<NeoButton text onclick={onClick}>Button</NeoButton>
80-
<NeoButton text toggle onclick={onClick}>Toggle</NeoButton>
81-
<NeoButton text disabled onclick={onClick}>Disabled</NeoButton>
82-
<NeoButton text {loading} onclick={onLoading}>Loading</NeoButton>
83-
<NeoButton text {loading} onclick={onLoading} {icon} />
84-
<NeoButton text onclick={onClick} {icon}>Icon</NeoButton>
85-
<NeoButton text reverse onclick={onClick} {icon}>Reversed</NeoButton>
86-
<NeoButton text {loading} pulse onclick={onLoading}>Pulse</NeoButton>
87-
<NeoButton text coalesce onclick={onClick}>Coalesce</NeoButton>
88-
<NeoButton text {skeleton} onclick={onSkeleton}>Skeleton</NeoButton>
89-
</div>
40+
<div class="row">
41+
{#each columns as { label, props }}
42+
<div class="column">
43+
<span class="label">{label}</span>
44+
{@render buttons(props)}
45+
</div>
46+
{/each}
9047

9148
<div class="column">
9249
<span class="label">Glass</span>

0 commit comments

Comments
 (0)