drab 2.6.1 → 2.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/Accordion.svelte +37 -11
- package/dist/components/Accordion.svelte.d.ts +35 -9
- package/dist/components/Breakpoint.svelte +1 -11
- package/dist/components/Breakpoint.svelte.d.ts +1 -11
- package/dist/components/ContextMenu.svelte +0 -4
- package/dist/components/ContextMenu.svelte.d.ts +0 -2
- package/dist/components/CopyButton.svelte +1 -1
- package/dist/components/CopyButton.svelte.d.ts +1 -1
- package/dist/components/DataTable.svelte +89 -30
- package/dist/components/DataTable.svelte.d.ts +70 -14
- package/dist/components/FullscreenButton.svelte +2 -3
- package/dist/components/FullscreenButton.svelte.d.ts +2 -3
- package/dist/components/Popover.svelte +3 -3
- package/dist/components/Popover.svelte.d.ts +1 -1
- package/dist/components/ShareButton.svelte +72 -22
- package/dist/components/ShareButton.svelte.d.ts +44 -13
- package/dist/components/Sheet.svelte +29 -54
- package/dist/components/Sheet.svelte.d.ts +7 -15
- package/dist/components/Tabs.svelte +48 -31
- package/dist/components/Tabs.svelte.d.ts +31 -26
- package/package.json +1 -1
@@ -3,16 +3,22 @@
|
|
3
3
|
|
4
4
|
### ShareButton
|
5
5
|
|
6
|
-
Uses the
|
6
|
+
Uses the [Navigator API](https://developer.mozilla.org/en-US/docs/Web/API/Navigator/share) to share data. Progressively enhances according to browser support.
|
7
|
+
|
8
|
+
- If the browser cannot share the provided data:
|
9
|
+
- `url` - uses the the [Clipboard API](https://developer.mozilla.org/en-US/docs/Web/API/Clipboard/writeText) to copy
|
10
|
+
- `files` - uses a hidden `anchor` element to download the first file in the `files` array
|
11
|
+
- If no JavaScript:
|
12
|
+
- `button` is disabled
|
13
|
+
- `url` - displayed after the button
|
7
14
|
|
8
15
|
@props
|
9
16
|
|
10
17
|
- `classNoscript` - `noscript` class
|
11
18
|
- `class`
|
12
19
|
- `id`
|
13
|
-
- `
|
14
|
-
- `title`
|
15
|
-
- `url` - url to be shared
|
20
|
+
- `shareData` - [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/API/Navigator/share)
|
21
|
+
- `title`
|
16
22
|
|
17
23
|
@slots
|
18
24
|
|
@@ -24,43 +30,81 @@ Uses the navigator api to share or copy a url link depending on browser support.
|
|
24
30
|
@example
|
25
31
|
|
26
32
|
```svelte
|
27
|
-
<script>
|
33
|
+
<script lang="ts">
|
28
34
|
import { ShareButton } from "drab";
|
35
|
+
|
36
|
+
let fileInput: HTMLInputElement;
|
37
|
+
|
38
|
+
let fileShareData: ShareData;
|
39
|
+
|
40
|
+
const onInput = () => {
|
41
|
+
if (fileInput.files) {
|
42
|
+
fileShareData.files = [fileInput.files[0]];
|
43
|
+
}
|
44
|
+
};
|
29
45
|
</script>
|
30
46
|
|
47
|
+
<ShareButton
|
48
|
+
class="btn mb-8"
|
49
|
+
shareData={{
|
50
|
+
text: "Check out this page: ",
|
51
|
+
title: "drab",
|
52
|
+
url: "https://drab.robino.dev",
|
53
|
+
}}
|
54
|
+
>
|
55
|
+
Share URL
|
56
|
+
</ShareButton>
|
57
|
+
|
31
58
|
<div>
|
32
|
-
<
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
59
|
+
<label class="label" for="fileInput">Upload File</label>
|
60
|
+
<input
|
61
|
+
type="file"
|
62
|
+
id="fileInput"
|
63
|
+
class="input mb-4"
|
64
|
+
bind:this={fileInput}
|
65
|
+
on:input={onInput}
|
37
66
|
/>
|
67
|
+
<ShareButton class="btn" bind:shareData={fileShareData}>
|
68
|
+
Share File
|
69
|
+
</ShareButton>
|
38
70
|
</div>
|
39
71
|
```
|
40
72
|
-->
|
41
73
|
|
42
74
|
<script>import { onMount } from "svelte";
|
43
75
|
import { delay } from "../util/delay";
|
76
|
+
import { messageNoScript } from "../util/messages";
|
44
77
|
let className = "";
|
45
78
|
export { className as class };
|
46
79
|
export let id = "";
|
47
|
-
export let
|
48
|
-
export let
|
49
|
-
|
80
|
+
export let title = "";
|
81
|
+
export let shareData = {};
|
82
|
+
let downloadAnchor;
|
50
83
|
export let classNoscript = "";
|
51
84
|
let clientJs = false;
|
52
85
|
let complete = false;
|
53
86
|
const onClick = async () => {
|
54
|
-
|
55
|
-
|
56
|
-
await navigator.share(
|
57
|
-
}
|
58
|
-
|
87
|
+
if (navigator.canShare && navigator.canShare(shareData)) {
|
88
|
+
try {
|
89
|
+
await navigator.share(shareData);
|
90
|
+
} catch (error) {
|
91
|
+
if (error.name !== "AbortError") {
|
92
|
+
console.error(error);
|
93
|
+
}
|
94
|
+
}
|
95
|
+
} else if (shareData.url) {
|
96
|
+
try {
|
97
|
+
await navigator.clipboard.writeText(shareData.url);
|
59
98
|
complete = true;
|
60
99
|
setTimeout(() => complete = false, delay);
|
100
|
+
} catch (error) {
|
101
|
+
console.error(error);
|
61
102
|
}
|
62
|
-
}
|
63
|
-
|
103
|
+
} else if (shareData.files) {
|
104
|
+
const file = shareData.files[0];
|
105
|
+
downloadAnchor.download = file.name;
|
106
|
+
downloadAnchor.href = URL.createObjectURL(file);
|
107
|
+
downloadAnchor.click();
|
64
108
|
}
|
65
109
|
};
|
66
110
|
onMount(() => clientJs = true);
|
@@ -68,7 +112,7 @@ onMount(() => clientJs = true);
|
|
68
112
|
|
69
113
|
<button
|
70
114
|
type="button"
|
71
|
-
disabled={!clientJs}
|
115
|
+
disabled={!clientJs || (!shareData.url && !shareData.files)}
|
72
116
|
on:click={onClick}
|
73
117
|
class={className}
|
74
118
|
{id}
|
@@ -81,4 +125,10 @@ onMount(() => clientJs = true);
|
|
81
125
|
{/if}
|
82
126
|
</button>
|
83
127
|
|
84
|
-
<
|
128
|
+
<a href="/" bind:this={downloadAnchor} style:display="none">Download</a>
|
129
|
+
|
130
|
+
<noscript>
|
131
|
+
<span class={classNoscript}>
|
132
|
+
{shareData.url ? shareData.url : messageNoScript}
|
133
|
+
</span>
|
134
|
+
</noscript>
|
@@ -3,9 +3,8 @@ declare const __propDef: {
|
|
3
3
|
props: {
|
4
4
|
class?: string | undefined;
|
5
5
|
id?: string | undefined;
|
6
|
-
|
7
|
-
/**
|
8
|
-
/** title of share message and `button` attribute, defaults to end of url */ title?: string | undefined;
|
6
|
+
title?: string | undefined;
|
7
|
+
/** [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/API/Navigator/share) */ shareData?: ShareData | undefined;
|
9
8
|
/** `noscript` class */ classNoscript?: string | undefined;
|
10
9
|
};
|
11
10
|
events: {
|
@@ -22,16 +21,22 @@ export type ShareButtonSlots = typeof __propDef.slots;
|
|
22
21
|
/**
|
23
22
|
* ### ShareButton
|
24
23
|
*
|
25
|
-
* Uses the
|
24
|
+
* Uses the [Navigator API](https://developer.mozilla.org/en-US/docs/Web/API/Navigator/share) to share data. Progressively enhances according to browser support.
|
25
|
+
*
|
26
|
+
* - If the browser cannot share the provided data:
|
27
|
+
* - `url` - uses the the [Clipboard API](https://developer.mozilla.org/en-US/docs/Web/API/Clipboard/writeText) to copy
|
28
|
+
* - `files` - uses a hidden `anchor` element to download the first file in the `files` array
|
29
|
+
* - If no JavaScript:
|
30
|
+
* - `button` is disabled
|
31
|
+
* - `url` - displayed after the button
|
26
32
|
*
|
27
33
|
* @props
|
28
34
|
*
|
29
35
|
* - `classNoscript` - `noscript` class
|
30
36
|
* - `class`
|
31
37
|
* - `id`
|
32
|
-
* - `
|
33
|
-
* - `title`
|
34
|
-
* - `url` - url to be shared
|
38
|
+
* - `shareData` - [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/API/Navigator/share)
|
39
|
+
* - `title`
|
35
40
|
*
|
36
41
|
* @slots
|
37
42
|
*
|
@@ -43,17 +48,43 @@ export type ShareButtonSlots = typeof __propDef.slots;
|
|
43
48
|
* @example
|
44
49
|
*
|
45
50
|
* ```svelte
|
46
|
-
* <script>
|
51
|
+
* <script lang="ts">
|
47
52
|
* import { ShareButton } from "drab";
|
53
|
+
*
|
54
|
+
* let fileInput: HTMLInputElement;
|
55
|
+
*
|
56
|
+
* let fileShareData: ShareData;
|
57
|
+
*
|
58
|
+
* const onInput = () => {
|
59
|
+
* if (fileInput.files) {
|
60
|
+
* fileShareData.files = [fileInput.files[0]];
|
61
|
+
* }
|
62
|
+
* };
|
48
63
|
* </script>
|
49
64
|
*
|
50
|
-
* <div>
|
51
65
|
* <ShareButton
|
52
|
-
*
|
53
|
-
*
|
54
|
-
*
|
55
|
-
*
|
66
|
+
* class="btn mb-8"
|
67
|
+
* shareData={{
|
68
|
+
* text: "Check out this page: ",
|
69
|
+
* title: "drab",
|
70
|
+
* url: "https://drab.robino.dev",
|
71
|
+
* }}
|
72
|
+
* >
|
73
|
+
* Share URL
|
74
|
+
* </ShareButton>
|
75
|
+
*
|
76
|
+
* <div>
|
77
|
+
* <label class="label" for="fileInput">Upload File</label>
|
78
|
+
* <input
|
79
|
+
* type="file"
|
80
|
+
* id="fileInput"
|
81
|
+
* class="input mb-4"
|
82
|
+
* bind:this={fileInput}
|
83
|
+
* on:input={onInput}
|
56
84
|
* />
|
85
|
+
* <ShareButton class="btn" bind:shareData={fileShareData}>
|
86
|
+
* Share File
|
87
|
+
* </ShareButton>
|
57
88
|
* </div>
|
58
89
|
* ```
|
59
90
|
*/
|
@@ -11,11 +11,10 @@ Creates a sheet element based on the `position` provided.
|
|
11
11
|
- `class`
|
12
12
|
- `display` - controls whether the sheet is displayed
|
13
13
|
- `id`
|
14
|
-
- `
|
14
|
+
- `maxSize` - max width/height of sheet based on the `side` - can also use css instead
|
15
15
|
- `position` - determines the position of sheet
|
16
|
-
- `size` - width/height of sheet based on the `side` - can also use css instead
|
17
16
|
- `transitionSheet` - slides the sheet, set to `false` to remove
|
18
|
-
- `transition` -
|
17
|
+
- `transition` - blurs the entire component, set to `false` to remove
|
19
18
|
|
20
19
|
@slots
|
21
20
|
|
@@ -28,7 +27,6 @@ Creates a sheet element based on the `position` provided.
|
|
28
27
|
```svelte
|
29
28
|
<script lang="ts">
|
30
29
|
import { Sheet } from "drab";
|
31
|
-
import { X } from "../../site/svg/X.svelte";
|
32
30
|
|
33
31
|
let display = false;
|
34
32
|
</script>
|
@@ -46,13 +44,8 @@ Creates a sheet element based on the `position` provided.
|
|
46
44
|
>
|
47
45
|
<div class="mb-4 flex items-center justify-between">
|
48
46
|
<h2 class="my-0">Sheet</h2>
|
49
|
-
<button
|
50
|
-
|
51
|
-
title="Close"
|
52
|
-
class="btn btn-s"
|
53
|
-
on:click={() => (display = false)}
|
54
|
-
>
|
55
|
-
<X />
|
47
|
+
<button type="button" class="btn btn-s" on:click={() => (display = false)}>
|
48
|
+
Close
|
56
49
|
</button>
|
57
50
|
</div>
|
58
51
|
<div>
|
@@ -68,7 +61,7 @@ Creates a sheet element based on the `position` provided.
|
|
68
61
|
|
69
62
|
<script>import { onMount } from "svelte";
|
70
63
|
import {
|
71
|
-
|
64
|
+
blur,
|
72
65
|
fly
|
73
66
|
} from "svelte/transition";
|
74
67
|
import { prefersReducedMotion } from "../util/accessibility";
|
@@ -79,16 +72,10 @@ export let id = "";
|
|
79
72
|
export let classSheet = "";
|
80
73
|
export let display = false;
|
81
74
|
export let transition = { duration };
|
82
|
-
export let position = "
|
83
|
-
export let
|
75
|
+
export let position = "left";
|
76
|
+
export let maxSize = 488;
|
84
77
|
export let transitionSheet = { duration };
|
85
|
-
export let onClickClose = false;
|
86
78
|
let sheet;
|
87
|
-
const clickOutside = (e) => {
|
88
|
-
if (e.target instanceof Node && !sheet.contains(e.target) || onClickClose) {
|
89
|
-
display = false;
|
90
|
-
}
|
91
|
-
};
|
92
79
|
const onKeyDown = (e) => {
|
93
80
|
if (e.key === "Escape") {
|
94
81
|
display = false;
|
@@ -96,13 +83,13 @@ const onKeyDown = (e) => {
|
|
96
83
|
};
|
97
84
|
if (transitionSheet && !transitionSheet.x && !transitionSheet.y) {
|
98
85
|
if (position === "bottom") {
|
99
|
-
transitionSheet.y =
|
86
|
+
transitionSheet.y = maxSize;
|
100
87
|
} else if (position === "top") {
|
101
|
-
transitionSheet.y = -
|
88
|
+
transitionSheet.y = -maxSize;
|
102
89
|
} else if (position === "right") {
|
103
|
-
transitionSheet.x =
|
90
|
+
transitionSheet.x = maxSize;
|
104
91
|
} else {
|
105
|
-
transitionSheet.x = -
|
92
|
+
transitionSheet.x = -maxSize;
|
106
93
|
}
|
107
94
|
}
|
108
95
|
onMount(() => {
|
@@ -119,12 +106,11 @@ onMount(() => {
|
|
119
106
|
|
120
107
|
{#if display}
|
121
108
|
<div
|
122
|
-
|
123
|
-
tabindex="0"
|
124
|
-
on:click={clickOutside}
|
125
|
-
on:keydown={onKeyDown}
|
126
|
-
transition:fade={transition ? transition : { duration: 0 }}
|
109
|
+
transition:blur={transition ? transition : { duration: 0 }}
|
127
110
|
class="d-backdrop {className}"
|
111
|
+
class:d-backdrop-bottom={position === "bottom"}
|
112
|
+
class:d-backdrop-top={position === "top"}
|
113
|
+
class:d-backdrop-right={position === "right"}
|
128
114
|
{id}
|
129
115
|
>
|
130
116
|
<div
|
@@ -132,48 +118,37 @@ onMount(() => {
|
|
132
118
|
bind:this={sheet}
|
133
119
|
transition:fly={transitionSheet ? transitionSheet : { duration: 0 }}
|
134
120
|
style={position === "top" || position === "bottom"
|
135
|
-
? `max-height: ${
|
136
|
-
: `max-width: ${
|
137
|
-
class=
|
138
|
-
class:d-bottom={position === "bottom"}
|
139
|
-
class:d-top={position === "top"}
|
140
|
-
class:d-left={position === "left"}
|
141
|
-
class:d-right={position === "right"}
|
121
|
+
? `max-height: ${maxSize}px;`
|
122
|
+
: `max-width: ${maxSize}px`}
|
123
|
+
class={classSheet}
|
142
124
|
>
|
143
125
|
<slot>Content</slot>
|
144
126
|
</div>
|
127
|
+
<button title="Close" on:click={() => (display = false)}></button>
|
145
128
|
</div>
|
146
129
|
{/if}
|
147
130
|
|
148
131
|
<style>
|
132
|
+
button {
|
133
|
+
flex-grow: 1;
|
134
|
+
}
|
149
135
|
.d-backdrop {
|
150
136
|
position: fixed;
|
151
|
-
display:
|
137
|
+
display: flex;
|
152
138
|
z-index: 40;
|
153
139
|
top: 0;
|
154
140
|
bottom: 0;
|
155
141
|
left: 0;
|
156
142
|
right: 0;
|
157
143
|
overflow: hidden;
|
158
|
-
cursor: default;
|
159
|
-
}
|
160
|
-
.d-sheet {
|
161
|
-
margin: auto;
|
162
|
-
}
|
163
|
-
.d-bottom {
|
164
|
-
margin-bottom: 0;
|
165
|
-
width: 100%;
|
166
144
|
}
|
167
|
-
.d-
|
168
|
-
|
169
|
-
width: 100%;
|
145
|
+
.d-backdrop-bottom {
|
146
|
+
flex-direction: column-reverse;
|
170
147
|
}
|
171
|
-
.d-
|
172
|
-
|
173
|
-
height: 100%;
|
148
|
+
.d-backdrop-top {
|
149
|
+
flex-direction: column;
|
174
150
|
}
|
175
|
-
.d-
|
176
|
-
|
177
|
-
height: 100%;
|
151
|
+
.d-backdrop-right {
|
152
|
+
flex-direction: row-reverse;
|
178
153
|
}
|
179
154
|
</style>
|
@@ -1,16 +1,15 @@
|
|
1
1
|
import { SvelteComponent } from "svelte";
|
2
|
-
import { type
|
2
|
+
import { type BlurParams, type FlyParams } from "svelte/transition";
|
3
3
|
declare const __propDef: {
|
4
4
|
props: {
|
5
5
|
class?: string | undefined;
|
6
6
|
id?: string | undefined;
|
7
7
|
/** sheet class - not the backdrop */ classSheet?: string | undefined;
|
8
8
|
/** controls whether the sheet is displayed*/ display?: boolean | undefined;
|
9
|
-
/**
|
9
|
+
/** blurs the entire component, set to `false` to remove */ transition?: false | BlurParams | undefined;
|
10
10
|
/** determines the position of sheet */ position?: "top" | "bottom" | "left" | "right" | undefined;
|
11
|
-
/** width/height of sheet based on the `side` - can also use css instead */
|
11
|
+
/** max width/height of sheet based on the `side` - can also use css instead */ maxSize?: number | undefined;
|
12
12
|
/** slides the sheet, set to `false` to remove */ transitionSheet?: false | FlyParams | undefined;
|
13
|
-
/** close on click, defaults to `false` - only closes if clicked outside */ onClickClose?: boolean | undefined;
|
14
13
|
};
|
15
14
|
events: {
|
16
15
|
[evt: string]: CustomEvent<any>;
|
@@ -33,11 +32,10 @@ export type SheetSlots = typeof __propDef.slots;
|
|
33
32
|
* - `class`
|
34
33
|
* - `display` - controls whether the sheet is displayed
|
35
34
|
* - `id`
|
36
|
-
* - `
|
35
|
+
* - `maxSize` - max width/height of sheet based on the `side` - can also use css instead
|
37
36
|
* - `position` - determines the position of sheet
|
38
|
-
* - `size` - width/height of sheet based on the `side` - can also use css instead
|
39
37
|
* - `transitionSheet` - slides the sheet, set to `false` to remove
|
40
|
-
* - `transition` -
|
38
|
+
* - `transition` - blurs the entire component, set to `false` to remove
|
41
39
|
*
|
42
40
|
* @slots
|
43
41
|
*
|
@@ -50,7 +48,6 @@ export type SheetSlots = typeof __propDef.slots;
|
|
50
48
|
* ```svelte
|
51
49
|
* <script lang="ts">
|
52
50
|
* import { Sheet } from "drab";
|
53
|
-
* import { X } from "../../site/svg/X.svelte";
|
54
51
|
*
|
55
52
|
* let display = false;
|
56
53
|
* </script>
|
@@ -68,13 +65,8 @@ export type SheetSlots = typeof __propDef.slots;
|
|
68
65
|
* >
|
69
66
|
* <div class="mb-4 flex items-center justify-between">
|
70
67
|
* <h2 class="my-0">Sheet</h2>
|
71
|
-
* <button
|
72
|
-
*
|
73
|
-
* title="Close"
|
74
|
-
* class="btn btn-s"
|
75
|
-
* on:click={() => (display = false)}
|
76
|
-
* >
|
77
|
-
* <X />
|
68
|
+
* <button type="button" class="btn btn-s" on:click={() => (display = false)}>
|
69
|
+
* Close
|
78
70
|
* </button>
|
79
71
|
* </div>
|
80
72
|
* <div>
|
@@ -3,7 +3,7 @@
|
|
3
3
|
|
4
4
|
### Tabs
|
5
5
|
|
6
|
-
Displays tabs and the
|
6
|
+
Displays tabs and the selected tab's content.
|
7
7
|
|
8
8
|
@props
|
9
9
|
|
@@ -15,15 +15,16 @@ Displays tabs and the active tab's content.
|
|
15
15
|
- `classTab` - class of all title `button`s
|
16
16
|
- `class`
|
17
17
|
- `id`
|
18
|
-
- `selectedIndex` - index of selected tab, defaults to 0
|
19
|
-
- `tabs` - array of
|
18
|
+
- `selectedIndex` - index of selected tab, defaults to `0`
|
19
|
+
- `tabs` - array of `TabsTab` objects
|
20
|
+
- `transition` - fades the panel content, set to `false` to remove
|
20
21
|
|
21
22
|
@slots
|
22
23
|
|
23
|
-
| name
|
24
|
-
|
|
25
|
-
| `
|
26
|
-
| `tab`
|
24
|
+
| name | purpose | default value | slot props |
|
25
|
+
| ------- | ------------------ | ------------------- | ---------------------- |
|
26
|
+
| `panel` | active tab's panel | `selectedTab.panel` | `selectedTab`, `index` |
|
27
|
+
| `tab` | title of each tab | `tab.tab` | `tab`, `index` |
|
27
28
|
|
28
29
|
@example
|
29
30
|
|
@@ -35,10 +36,10 @@ Displays tabs and the active tab's content.
|
|
35
36
|
|
36
37
|
<Tabs
|
37
38
|
class="mb-4"
|
38
|
-
classTabList="grid grid-flow-col grid-rows-1 gap-1 rounded bg-neutral-200 p-1"
|
39
|
-
classTab="btn btn-s
|
40
|
-
classTabActive="bg-white text-neutral-950"
|
41
|
-
classTabInactive="bg-neutral-200 text-neutral-
|
39
|
+
classTabList="grid grid-flow-col grid-rows-1 gap-1 rounded-md bg-neutral-200 p-1"
|
40
|
+
classTab="btn btn-s p-2"
|
41
|
+
classTabActive="bg-white text-neutral-950 shadow"
|
42
|
+
classTabInactive="bg-neutral-200 text-neutral-600"
|
42
43
|
classTabPanel="py-2"
|
43
44
|
tabs={[
|
44
45
|
{ tab: "Tab", panel: "Content" },
|
@@ -47,10 +48,10 @@ Displays tabs and the active tab's content.
|
|
47
48
|
/>
|
48
49
|
|
49
50
|
<Tabs
|
50
|
-
classTabList="grid grid-flow-col grid-rows-1 gap-1 rounded bg-neutral-200 p-1"
|
51
|
-
classTab="btn btn-s
|
52
|
-
classTabActive="bg-white text-neutral-950"
|
53
|
-
classTabInactive="bg-neutral-200 text-neutral-
|
51
|
+
classTabList="grid grid-flow-col grid-rows-1 gap-1 rounded-md bg-neutral-200 p-1"
|
52
|
+
classTab="btn btn-s p-2"
|
53
|
+
classTabActive="bg-white text-neutral-950 shadow"
|
54
|
+
classTabInactive="bg-neutral-200 text-neutral-600"
|
54
55
|
classTabPanel="py-2"
|
55
56
|
tabs={[
|
56
57
|
{ tab: "Tab", panel: "Generated indexes" },
|
@@ -60,16 +61,17 @@ Displays tabs and the active tab's content.
|
|
60
61
|
data: { component: FullscreenButton },
|
61
62
|
},
|
62
63
|
]}
|
63
|
-
let:selectedTab
|
64
64
|
>
|
65
|
-
<svelte:fragment slot="tab" let:
|
66
|
-
{
|
65
|
+
<svelte:fragment slot="tab" let:tab let:index>
|
66
|
+
{tab.tab}
|
67
67
|
{index + 1}
|
68
68
|
</svelte:fragment>
|
69
|
-
<
|
70
|
-
|
71
|
-
|
72
|
-
|
69
|
+
<svelte:fragment slot="panel" let:selectedTab>
|
70
|
+
<div class="mb-2">{selectedTab.panel}</div>
|
71
|
+
{#if selectedTab.data?.component}
|
72
|
+
<svelte:component this={selectedTab.data.component} class="btn" />
|
73
|
+
{/if}
|
74
|
+
</svelte:fragment>
|
73
75
|
</Tabs>
|
74
76
|
```
|
75
77
|
-->
|
@@ -78,6 +80,9 @@ Displays tabs and the active tab's content.
|
|
78
80
|
|
79
81
|
<script>import { onMount } from "svelte";
|
80
82
|
import { messageNoScript } from "../util/messages";
|
83
|
+
import { fade } from "svelte/transition";
|
84
|
+
import { duration } from "../util/transition";
|
85
|
+
import { prefersReducedMotion } from "../util/accessibility";
|
81
86
|
let className = "";
|
82
87
|
export { className as class };
|
83
88
|
export let id = "";
|
@@ -89,33 +94,45 @@ export let classNoscript = "";
|
|
89
94
|
export let classTabPanel = "";
|
90
95
|
export let tabs;
|
91
96
|
export let selectedIndex = 0;
|
97
|
+
export let transition = { duration };
|
92
98
|
let clientJs = false;
|
93
|
-
|
94
|
-
|
95
|
-
|
99
|
+
onMount(() => {
|
100
|
+
if (prefersReducedMotion()) {
|
101
|
+
if (transition)
|
102
|
+
transition.duration = 0;
|
103
|
+
}
|
104
|
+
clientJs = true;
|
105
|
+
});
|
106
|
+
const panelId = "tabPanel-" + Math.random().toString().substring(2, 8);
|
96
107
|
</script>
|
97
108
|
|
98
109
|
<div class={className} {id}>
|
99
110
|
<div class={classTabList} role="tablist">
|
100
|
-
{#each tabs as
|
111
|
+
{#each tabs as tab, index}
|
101
112
|
<button
|
102
113
|
role="tab"
|
103
114
|
tabindex={index === selectedIndex ? 0 : -1}
|
104
115
|
aria-selected={index === selectedIndex}
|
105
|
-
aria-controls=
|
116
|
+
aria-controls={panelId}
|
106
117
|
disabled={!clientJs}
|
107
118
|
class="{classTab} {selectedIndex === index
|
108
119
|
? classTabActive
|
109
120
|
: ''} {selectedIndex !== index ? classTabInactive : ''}"
|
110
121
|
on:click={() => (selectedIndex = index)}
|
111
122
|
>
|
112
|
-
<slot name="tab" {
|
123
|
+
<slot name="tab" {tab} {index}>{tab.tab}</slot>
|
113
124
|
</button>
|
114
125
|
{/each}
|
115
126
|
</div>
|
116
|
-
|
117
|
-
|
118
|
-
|
127
|
+
{#each tabs as tab, index}
|
128
|
+
{#if index === selectedIndex}
|
129
|
+
<div class={classTabPanel} role="tabpanel" id={panelId}>
|
130
|
+
<div in:fade={transition ? transition : { duration: 0 }}>
|
131
|
+
<slot name="panel" selectedTab={tab} {index}>{tab.panel}</slot>
|
132
|
+
</div>
|
133
|
+
</div>
|
134
|
+
{/if}
|
135
|
+
{/each}
|
119
136
|
<noscript>
|
120
137
|
<div class={classNoscript}>{messageNoScript}</div>
|
121
138
|
</noscript>
|