@wells-nelson/interactions 1.0.0 → 1.0.1

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/README.md CHANGED
@@ -13,7 +13,8 @@ npm install @wells-nelson/displays @wells-nelson/interactions
13
13
  `TextField` (label, error, hint), `Checkbox` (optionally hidden label), `Toggle` (a switch for
14
14
  settings that apply at once), `Combobox` (type to filter, offers "Create …"; plus `sameName`),
15
15
  `AddRow` (inline "name + plus" that refocuses), `Segmented` (one of a few) and `SearchBar`
16
- (fixed at the bottom, with a clear button).
16
+ (fixed at the bottom, with a clear button; it sits above a `TabBar` via `--footer-offset`, as
17
+ does `UndoButton`).
17
18
 
18
19
  ```svelte
19
20
  <ActiveList
@@ -55,6 +56,17 @@ undo.push({ undo: () => setDone(task, false) });
55
56
  <UndoButton stack={undo} />
56
57
  ```
57
58
 
59
+ ## App version
60
+
61
+ The running version, with an Update button once a newer deploy is out (in SvelteKit, feed it
62
+ `updated` from `$app/state`; set `kit.version.name` to the release version and a
63
+ `pollInterval`). Without an update, `oncheck` adds "Check for updates". `onupdate` defaults to
64
+ reloading the page.
65
+
66
+ ```svelte
67
+ <AppVersion version={version} updateAvailable={updated.current} oncheck={() => updated.check()} />
68
+ ```
69
+
58
70
  ## Dev wipe
59
71
 
60
72
  ```svelte
@@ -82,4 +94,5 @@ shape. Wording comes from `nouns` (`{ singular: 'project', plural: 'projects' }`
82
94
 
83
95
  With `roles`, `ShareModal` shows a role picker and protects the last owner, and
84
96
  `ContainerEditModal` offers "Leave" instead of "Delete" while other owners remain. Without roles
85
- every member counts as an owner. The sandbox's Account page drives every state with a mock.
97
+ every member counts as an owner. `localOnlyReason` keeps a container local-only (e.g. built-in
98
+ sample data): both modals show that text instead of offering to make it cloud-enabled. The sandbox's Account page drives every state with a mock.
@@ -0,0 +1,79 @@
1
+ <script lang="ts">
2
+ import { Button } from '@wells-nelson/displays';
3
+
4
+ /**
5
+ * The running version, with an Update button once a newer deploy is out. Feed it the app's
6
+ * update signal (in SvelteKit: `updated.current` from `$app/state`, with `updated.check` as
7
+ * `oncheck`). Without an update, `oncheck` adds a "Check for updates" button.
8
+ */
9
+ let {
10
+ version,
11
+ updateAvailable = false,
12
+ onupdate = () => location.reload(),
13
+ oncheck
14
+ }: {
15
+ version: string;
16
+ updateAvailable?: boolean;
17
+ /** Load the new version. Defaults to reloading the page. */
18
+ onupdate?: () => void;
19
+ /** Look for a new version now; resolves true when there is one. */
20
+ oncheck?: () => Promise<boolean>;
21
+ } = $props();
22
+
23
+ let checking = $state(false);
24
+ let found = $state(false);
25
+ let status = $state<'latest' | 'failed' | null>(null);
26
+ const available = $derived(updateAvailable || found);
27
+
28
+ async function check() {
29
+ if (!oncheck) return;
30
+ checking = true;
31
+ status = null;
32
+ try {
33
+ found = await oncheck();
34
+ if (!found) status = 'latest';
35
+ } catch {
36
+ status = 'failed';
37
+ } finally {
38
+ checking = false;
39
+ }
40
+ }
41
+ </script>
42
+
43
+ <section class="app-version" aria-label="Version">
44
+ <p class="line">
45
+ Version {version}{#if available}<span class="new"> · A new version is available.</span>{/if}
46
+ </p>
47
+ {#if available}
48
+ <Button variant="primary" icon="refresh" onclick={() => onupdate()}>Update</Button>
49
+ {:else if oncheck}
50
+ <Button variant="ghost" icon="refresh" onclick={check} disabled={checking}>
51
+ {checking ? 'Checking…' : 'Check for updates'}
52
+ </Button>
53
+ {#if status === 'latest'}
54
+ <p class="status" role="status">You have the latest version.</p>
55
+ {:else if status === 'failed'}
56
+ <p class="status" role="status">Couldn't check for updates. Try again when you're online.</p>
57
+ {/if}
58
+ {/if}
59
+ </section>
60
+
61
+ <style>
62
+ .app-version {
63
+ display: flex;
64
+ flex-direction: column;
65
+ align-items: center;
66
+ gap: 0.4rem;
67
+ text-align: center;
68
+ }
69
+ .line,
70
+ .status {
71
+ margin: 0;
72
+ color: var(--muted);
73
+ font-size: 0.85rem;
74
+ }
75
+ .new {
76
+ color: var(--text);
77
+ font-weight: 600;
78
+ }
79
+ </style>
@@ -0,0 +1,11 @@
1
+ type $$ComponentProps = {
2
+ version: string;
3
+ updateAvailable?: boolean;
4
+ /** Load the new version. Defaults to reloading the page. */
5
+ onupdate?: () => void;
6
+ /** Look for a new version now; resolves true when there is one. */
7
+ oncheck?: () => Promise<boolean>;
8
+ };
9
+ declare const AppVersion: import("svelte").Component<$$ComponentProps, {}, "">;
10
+ type AppVersion = ReturnType<typeof AppVersion>;
11
+ export default AppVersion;
@@ -21,9 +21,10 @@
21
21
  position: fixed;
22
22
  left: 0;
23
23
  right: 0;
24
- bottom: 0;
24
+ /* Above a bottom tab bar when there is one (--footer-offset includes the safe area then). */
25
+ bottom: var(--footer-offset, 0px);
25
26
  z-index: 40;
26
- padding: 0.5rem 1rem calc(0.5rem + env(safe-area-inset-bottom, 0px));
27
+ padding: 0.5rem 1rem calc(0.5rem + max(0px, env(safe-area-inset-bottom, 0px) - var(--footer-offset, 0px)));
27
28
  background: linear-gradient(to top, var(--bg) 70%, transparent);
28
29
  }
29
30
  .inner {
@@ -55,6 +56,6 @@
55
56
  display: none;
56
57
  }
57
58
  .spacer {
58
- height: calc(4.5rem + env(safe-area-inset-bottom, 0px));
59
+ height: calc(4.5rem + max(0px, env(safe-area-inset-bottom, 0px) - var(--footer-offset, 0px)));
59
60
  }
60
61
  </style>
@@ -2,7 +2,7 @@
2
2
  import { Icon } from '@wells-nelson/displays';
3
3
  import type { UndoStack } from './undo.svelte.js';
4
4
 
5
- /** Floating Undo button, bottom left (above a `SearchBar`), shown while `stack.visible`. */
5
+ /** Floating Undo button, bottom left (above a `SearchBar` and any `TabBar`), shown while `stack.visible`. */
6
6
  let { stack, label = 'Undo' }: { stack: UndoStack; label?: string } = $props();
7
7
  </script>
8
8
 
@@ -17,7 +17,7 @@
17
17
  .undo {
18
18
  position: fixed;
19
19
  left: max(1rem, calc(50vw - var(--content-w) / 2 + 1rem));
20
- bottom: calc(4.75rem + env(safe-area-inset-bottom, 0px));
20
+ bottom: calc(4.75rem + max(env(safe-area-inset-bottom, 0px), var(--footer-offset, 0px)));
21
21
  z-index: 45;
22
22
  display: inline-flex;
23
23
  align-items: center;
@@ -31,6 +31,7 @@
31
31
  nouns,
32
32
  signInHref,
33
33
  contentsLabel = 'everything in it',
34
+ localOnlyReason,
34
35
  isDefault,
35
36
  ondefaultchange,
36
37
  onrename,
@@ -53,6 +54,11 @@
53
54
  signInHref: string;
54
55
  /** What gets deleted along with it, e.g. "all its tasks and notes". */
55
56
  contentsLabel?: string;
57
+ /**
58
+ * Why this container has to stay local-only (e.g. built-in sample data). The cloud option
59
+ * is replaced by this text while it is local.
60
+ */
61
+ localOnlyReason?: string;
56
62
  isDefault: boolean;
57
63
  ondefaultchange: (isDefault: boolean) => void;
58
64
  onrename: (name: string) => void;
@@ -172,7 +178,9 @@
172
178
 
173
179
  <section class="group">
174
180
  <h3>Sync</h3>
175
- {#if signedIn}
181
+ {#if localOnlyReason && !isCloud}
182
+ <p class="muted small"><Icon name="wifi-off" size={16} /> {localOnlyReason}</p>
183
+ {:else if signedIn}
176
184
  <Toggle
177
185
  label="Cloud-enabled"
178
186
  bind:checked={cloudToggle}
@@ -13,6 +13,11 @@ type $$ComponentProps = {
13
13
  signInHref: string;
14
14
  /** What gets deleted along with it, e.g. "all its tasks and notes". */
15
15
  contentsLabel?: string;
16
+ /**
17
+ * Why this container has to stay local-only (e.g. built-in sample data). The cloud option
18
+ * is replaced by this text while it is local.
19
+ */
20
+ localOnlyReason?: string;
16
21
  isDefault: boolean;
17
22
  ondefaultchange: (isDefault: boolean) => void;
18
23
  onrename: (name: string) => void;
@@ -33,6 +33,7 @@
33
33
  signInHref,
34
34
  signInPitch,
35
35
  description,
36
+ localOnlyReason,
36
37
  onmakecloud,
37
38
  onclose
38
39
  }: {
@@ -53,6 +54,11 @@
53
54
  signInPitch?: string;
54
55
  /** Who can do what, shown above the invite form. */
55
56
  description?: string;
57
+ /**
58
+ * Why this container has to stay local-only (e.g. built-in sample data). The cloud option
59
+ * is replaced by this text while it is local.
60
+ */
61
+ localOnlyReason?: string;
56
62
  onmakecloud: () => Promise<unknown>;
57
63
  onclose: () => void;
58
64
  } = $props();
@@ -141,7 +147,9 @@
141
147
  </script>
142
148
 
143
149
  <Modal bind:open title={`Share “${name}”`} {onclose}>
144
- {#if !cloud.configured}
150
+ {#if localOnlyReason && !isCloud}
151
+ <p>{localOnlyReason}</p>
152
+ {:else if !cloud.configured}
145
153
  <p class="muted">Sharing isn't available because cloud sync isn't set up for this copy of {appName}.</p>
146
154
  {:else if !signedIn}
147
155
  <p>{pitch}</p>
@@ -19,6 +19,11 @@ type $$ComponentProps = {
19
19
  signInPitch?: string;
20
20
  /** Who can do what, shown above the invite form. */
21
21
  description?: string;
22
+ /**
23
+ * Why this container has to stay local-only (e.g. built-in sample data). The cloud option
24
+ * is replaced by this text while it is local.
25
+ */
26
+ localOnlyReason?: string;
22
27
  onmakecloud: () => Promise<unknown>;
23
28
  onclose: () => void;
24
29
  };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  export { default as ActiveList } from './ActiveList.svelte';
2
2
  export { default as AddRow } from './AddRow.svelte';
3
+ export { default as AppVersion } from './AppVersion.svelte';
3
4
  export { default as Checkbox } from './Checkbox.svelte';
4
5
  export { default as CollapsibleSection } from './CollapsibleSection.svelte';
5
6
  export { default as Combobox, sameName, type ComboChoice, type ComboOption } from './Combobox.svelte';
package/dist/index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  export { default as ActiveList } from './ActiveList.svelte';
2
2
  export { default as AddRow } from './AddRow.svelte';
3
+ export { default as AppVersion } from './AppVersion.svelte';
3
4
  export { default as Checkbox } from './Checkbox.svelte';
4
5
  export { default as CollapsibleSection } from './CollapsibleSection.svelte';
5
6
  export { default as Combobox, sameName } from './Combobox.svelte';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wells-nelson/interactions",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "Svelte 5 inputs, actions (long-press, drag and drop), undo, and account/sharing UI shared by Wells Nelson apps.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -38,7 +38,7 @@
38
38
  "lint:package": "publint"
39
39
  },
40
40
  "peerDependencies": {
41
- "@wells-nelson/displays": ">=1.0.0 <2.0.0",
41
+ "@wells-nelson/displays": ">=1.0.2 <2.0.0",
42
42
  "svelte": "^5.40.0"
43
43
  },
44
44
  "devDependencies": {