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.
@@ -3,16 +3,22 @@
3
3
 
4
4
  ### ShareButton
5
5
 
6
- Uses the navigator api to share or copy a url link depending on browser support.
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
- - `text` - prefixed text in share message
14
- - `title` - title of share message and `button` attribute, defaults to end of url
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
- <ShareButton
33
- class="btn"
34
- text="Check out this page: "
35
- title="drab"
36
- url="https://drab.robino.dev"
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 text = "";
48
- export let url;
49
- export let title = url.split("/").splice(-1)[0];
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
- try {
55
- if (navigator.canShare) {
56
- await navigator.share({ text, url, title });
57
- } else {
58
- await navigator.clipboard.writeText(url);
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
- } catch (error) {
63
- console.log(error);
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
- <noscript><span class={classNoscript}>{url}</span></noscript>
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
- /** prefixed text in share message */ text?: string | undefined;
7
- /** url to be shared */ url: string;
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 navigator api to share or copy a url link depending on browser support.
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
- * - `text` - prefixed text in share message
33
- * - `title` - title of share message and `button` attribute, defaults to end of url
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
- * class="btn"
53
- * text="Check out this page: "
54
- * title="drab"
55
- * url="https://drab.robino.dev"
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
- - `onClickClose` - close on click, defaults to `false` - only closes if clicked outside
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` - fades the entire component, set to `false` to remove
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
- type="button"
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
- fade,
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 = "right";
83
- export let size = 488;
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 = size;
86
+ transitionSheet.y = maxSize;
100
87
  } else if (position === "top") {
101
- transitionSheet.y = -size;
88
+ transitionSheet.y = -maxSize;
102
89
  } else if (position === "right") {
103
- transitionSheet.x = size;
90
+ transitionSheet.x = maxSize;
104
91
  } else {
105
- transitionSheet.x = -size;
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
- role="button"
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: ${size}px;`
136
- : `max-width: ${size}px`}
137
- class="d-sheet {classSheet}"
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: grid;
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-top {
168
- margin-top: 0;
169
- width: 100%;
145
+ .d-backdrop-bottom {
146
+ flex-direction: column-reverse;
170
147
  }
171
- .d-right {
172
- margin-right: 0;
173
- height: 100%;
148
+ .d-backdrop-top {
149
+ flex-direction: column;
174
150
  }
175
- .d-left {
176
- margin-left: 0;
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 FadeParams, type FlyParams } from "svelte/transition";
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
- /** fades the entire component, set to `false` to remove */ transition?: false | FadeParams | undefined;
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 */ size?: number | undefined;
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
- * - `onClickClose` - close on click, defaults to `false` - only closes if clicked outside
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` - fades the entire component, set to `false` to remove
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
- * type="button"
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 active tab's content.
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 tabs
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 | purpose | default value | slot props |
24
- | --------- | --------------------- | ------------------ | --------------- |
25
- | `default` | active item's content | `activeItem.panel` | `activeItem` |
26
- | `tab` | title of each tab | `item.tab` | `item`, `index` |
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 rounded-sm p-1"
40
- classTabActive="bg-white text-neutral-950"
41
- classTabInactive="bg-neutral-200 text-neutral-500"
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 rounded-sm p-1"
52
- classTabActive="bg-white text-neutral-950"
53
- classTabInactive="bg-neutral-200 text-neutral-500"
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:item let:index>
66
- {item.tab}
65
+ <svelte:fragment slot="tab" let:tab let:index>
66
+ {tab.tab}
67
67
  {index + 1}
68
68
  </svelte:fragment>
69
- <div class="mb-2">{selectedTab.panel}</div>
70
- {#if selectedTab.data?.component}
71
- <svelte:component this={selectedTab.data.component} class="btn" />
72
- {/if}
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
- selectedTab = tabs[selectedIndex];
95
- onMount(() => clientJs = true);
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 item, index}
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="tabpanel"
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" {item} {index}>{item.tab}</slot>
123
+ <slot name="tab" {tab} {index}>{tab.tab}</slot>
113
124
  </button>
114
125
  {/each}
115
126
  </div>
116
- <div class={classTabPanel} role="tabpanel" id="tabpanel">
117
- <slot {selectedTab}>{selectedTab.panel}</slot>
118
- </div>
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>