@stnd/launcher 1.1.0 → 1.3.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/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # @stnd/launcher
2
2
 
3
+ ## 1.3.0
4
+
5
+ ### Minor Changes
6
+
7
+ - Auto-bumped @stnd/launcher due to modified code.
8
+
9
+ ## 1.2.0
10
+
11
+ ### Minor Changes
12
+
13
+ - Auto-bumped @stnd/launcher due to modified code.
14
+
3
15
  ## 1.1.0
4
16
 
5
17
  ### Minor Changes
package/Launcher.svelte CHANGED
@@ -5,7 +5,6 @@
5
5
  * Integrated with the Standard Garden OS aesthetic.
6
6
  */
7
7
  import Icon from "@stnd/icon/Icon.svelte";
8
- import Avatar from "@stnd/account/ui/Avatar.svelte";
9
8
  import {
10
9
  registry,
11
10
  palette,
@@ -34,22 +33,14 @@
34
33
  // --- PROPS (all optional — the Launcher is fully self-booting) ---
35
34
  // Apps wire services via provide/inject (the Standard OS runtime store).
36
35
  // Props exist only for backward compat or one-off overrides.
37
- let { WelcomeView = null, load = null, hideLogin = false } = $props();
38
-
39
- // Login only makes sense if an app actually wired up authentication —
40
- // otherwise this is the same assumption-bleed as a hardcoded "ask AI"
41
- // item: a button pointing at a "::login" view that doesn't exist for
42
- // apps with no account system at all (presence-of-data = mode).
43
- let hasAuthProvider = $derived(
44
- has("user") ||
45
- (typeof window !== "undefined" && !!window.__INITIAL_USER__),
46
- );
47
-
48
- // Check global window variable as fallback (set via window.__HIDE_LAUNCHER_LOGIN__)
49
- let shouldHideLogin = $derived(
50
- hideLogin ||
51
- (typeof window !== "undefined" && window.__HIDE_LAUNCHER_LOGIN__),
52
- );
36
+ let {
37
+ WelcomeView = null,
38
+ FooterView = null,
39
+ footer = null,
40
+ showFooter = false,
41
+ load = null,
42
+ hideLogin = false,
43
+ } = $props();
53
44
 
54
45
  // --- STATE (Synced with Engine) ---
55
46
  let pState = $state({
@@ -396,6 +387,21 @@
396
387
  return null;
397
388
  });
398
389
 
390
+ // --- FOOTER RESOLUTION (Optional) ---
391
+ let ActiveFooterComponent = $derived.by(() => {
392
+ if (FooterView) return FooterView;
393
+ if (registryState.views["::footer"]) return registryState.views["::footer"];
394
+ return inject("launcher:footer", null);
395
+ });
396
+
397
+ let shouldRenderFooter = $derived.by(() => {
398
+ if (footer || ActiveFooterComponent) return true;
399
+ const show = showFooter || inject("launcher:showFooter", false);
400
+ if (!show) return false;
401
+ if (ActiveViewComponent && show !== "always") return false;
402
+ return true;
403
+ });
404
+
399
405
  // Determine view size from registry meta
400
406
  let activeViewSize = $derived.by(() => {
401
407
  // 1. Dynamic Override
@@ -656,26 +662,22 @@
656
662
  {/if}
657
663
  </div>
658
664
 
659
- <!-- Footer (shown in search / palette mode, hidden in full view mode) -->
660
- {#if !ActiveViewComponent}
661
- <footer class="std-launcher-footer mt-0">
662
- <div>
663
- {#if currentUser && currentUser.isAuthenticated}
664
- <div
665
- class="std-launcher-user flex items-center gap-xs"
666
- role="button"
667
- tabindex="0"
668
- onclick={() => {
669
- if (registryState.views["::account-settings"]) {
670
- launcherView("::account-settings");
671
- } else if (registryState.views["::settings"]) {
672
- launcherView("::settings");
673
- } else {
674
- launcherView("::my-space");
675
- }
676
- }}
677
- onkeydown={(e) => {
678
- if (e.key === "Enter" || e.key === " ") {
665
+ <!-- Optional Launcher Footer -->
666
+ {#if shouldRenderFooter}
667
+ <footer class="std-launcher-footer">
668
+ {#if footer}
669
+ {@render footer({ currentUser, pState, close: launcherClose })}
670
+ {:else if ActiveFooterComponent}
671
+ <ActiveFooterComponent {currentUser} {pState} close={launcherClose} />
672
+ {:else}
673
+ <!-- Default Navigation Hints & Info -->
674
+ <div class="std-launcher-footer-left">
675
+ {#if currentUser && currentUser.isAuthenticated}
676
+ <div
677
+ class="std-launcher-user"
678
+ role="button"
679
+ tabindex="0"
680
+ onclick={() => {
679
681
  if (registryState.views["::account-settings"]) {
680
682
  launcherView("::account-settings");
681
683
  } else if (registryState.views["::settings"]) {
@@ -683,38 +685,31 @@
683
685
  } else {
684
686
  launcherView("::my-space");
685
687
  }
686
- }
687
- }}
688
- style="cursor: pointer;"
689
- >
690
- <Avatar
691
- src={currentUser.image}
692
- name={currentUser.username || currentUser.name}
693
- email={currentUser.email}
694
- size="18px"
695
- border={false}
696
- />
697
- <span class="smaller muted">
698
- {currentUser.name || currentUser.username || currentUser.email}
699
- </span>
700
- </div>
701
- {:else if hasAuthProvider && !shouldHideLogin}
702
- <button
703
- class="no-decoration smaller ghost text-subtle"
704
- onclick={() => launcherView("::login")}
705
- >
706
- Login
707
- </button>
708
- {/if}
709
- <div class="std-launcher-mode smaller inline hidden">
710
- COMMAND MODE
688
+ }}
689
+ onkeydown={(e) => {
690
+ if (e.key === "Enter" || e.key === " ") {
691
+ if (registryState.views["::account-settings"]) {
692
+ launcherView("::account-settings");
693
+ } else if (registryState.views["::settings"]) {
694
+ launcherView("::settings");
695
+ } else {
696
+ launcherView("::my-space");
697
+ }
698
+ }
699
+ }}
700
+ >
701
+ <span class="smaller muted">
702
+ {currentUser.name || currentUser.username || currentUser.email}
703
+ </span>
704
+ </div>
705
+ {/if}
711
706
  </div>
712
- </div>
713
707
 
714
- <div class="std-launcher-hints sm:hidden hidden">
715
- <span><kbd>↑</kbd> <kbd>↓</kbd> navigate</span>
716
- <span><kbd>esc</kbd> close</span>
717
- </div>
708
+ <div class="std-launcher-hints">
709
+ <span><kbd>↑</kbd> <kbd>↓</kbd> navigate</span>
710
+ <span><kbd>esc</kbd> close</span>
711
+ </div>
712
+ {/if}
718
713
  </footer>
719
714
  {/if}
720
715
  </div>
@@ -920,7 +915,7 @@
920
915
  Naming it is the point: views used to hardcode `var(--space)` to undo a
921
916
  padding they could only guess, so the two drifted apart and content got
922
917
  clipped at the frame. One name, one source of truth. */
923
- --launcher-pad: 1.25rem;
918
+ --launcher-pad: var(--space);
924
919
  }
925
920
 
926
921
  /* Full-bleed views (settings, welcome horizon) manage their own internal layout */
@@ -1062,29 +1057,36 @@
1062
1057
  font-size: var(--scale-d2);
1063
1058
  }
1064
1059
 
1065
- /* Footer (Refined & Compact) */
1060
+ /* Optional Footer */
1066
1061
  .std-launcher-footer {
1067
1062
  display: flex;
1068
1063
  justify-content: space-between;
1069
1064
  align-items: center;
1070
- padding: 3px var(--space-d2, 10px);
1065
+ padding: var(--space-d4) var(--space-d2);
1071
1066
  min-height: 28px;
1072
- font-size: var(--size-xs);
1067
+ font-size: var(--scale-d2, 0.75rem);
1073
1068
  color: var(--color-muted);
1074
1069
  border-top: var(--border);
1075
1070
  background-color: var(--color-surface-high);
1076
-
1077
1071
  box-shadow: var(--shadow-raised);
1072
+ flex-shrink: 0;
1073
+ }
1074
+
1075
+ .std-launcher-footer-left {
1076
+ display: flex;
1077
+ align-items: center;
1078
+ gap: var(--space-d3);
1078
1079
  }
1079
1080
 
1080
1081
  .std-launcher-user {
1081
1082
  display: flex;
1082
1083
  align-items: center;
1083
- gap: var(--space-d4, 4px);
1084
+ gap: var(--space-d4);
1084
1085
  color: var(--color-foreground-muted);
1085
- padding: 2px 6px;
1086
+ padding: var(--space-d5) var(--space-d3);
1086
1087
  border-radius: var(--radius);
1087
1088
  transition: all var(--transition-fast);
1089
+ cursor: pointer;
1088
1090
  }
1089
1091
 
1090
1092
  .std-launcher-user:hover {
@@ -1101,7 +1103,19 @@
1101
1103
 
1102
1104
  .std-launcher-hints {
1103
1105
  display: flex;
1104
- gap: 1rem;
1106
+ align-items: center;
1107
+ gap: var(--space-d1);
1108
+ font-size: var(--scale-d2, 0.75rem);
1109
+ }
1110
+
1111
+ .std-launcher-hints kbd {
1112
+ font-family: var(--font-mono, monospace);
1113
+ font-size: var(--scale-d3, 0.7rem);
1114
+ padding: 1px var(--space-d4);
1115
+ border-radius: var(--radius-sm, 3px);
1116
+ background: var(--color-surface-highest);
1117
+ border: var(--border);
1118
+ color: var(--color-foreground);
1105
1119
  }
1106
1120
 
1107
1121
  /* Mobile: Bottom Sheet */
package/README.md CHANGED
@@ -65,12 +65,15 @@ Import and mount the `<Launcher />` component in your layout:
65
65
 
66
66
  ### Props
67
67
 
68
- | Prop | Type | Default | Description |
69
- | :------------ | :------------------------------- | :------ | :---------------------------------------- |
70
- | `load` | `string \| string[] \| function` | `null` | Filter specific commands/views to load. |
71
- | `userStore` | `readable` | `null` | Svelte store containing the current user. |
72
- | `searchNotes` | `function` | `null` | Async function to search app content. |
73
- | `WelcomeView` | `Component` | `null` | Custom component for the empty state. |
68
+ | Prop | Type | Default | Description |
69
+ | :------------ | :------------------------------- | :------ | :----------------------------------------------------- |
70
+ | `load` | `string \| string[] \| function` | `null` | Filter specific commands/views to load. |
71
+ | `userStore` | `readable` | `null` | Svelte store containing the current user. |
72
+ | `searchNotes` | `function` | `null` | Async function to search app content. |
73
+ | `WelcomeView` | `Component` | `null` | Custom component for the empty state. |
74
+ | `FooterView` | `Component` | `null` | Custom component for the optional launcher footer. |
75
+ | `footer` | `Snippet` | `null` | Svelte 5 snippet for custom footer content. |
76
+ | `showFooter` | `boolean \| "always"` | `false` | Enable built-in footer (navigation hints & info). |
74
77
 
75
78
  ## Core Concepts
76
79
 
package/launcher.ts CHANGED
@@ -117,11 +117,15 @@ export function configureLauncher(
117
117
  searchNotes?: (query: string) => Promise<LauncherResult[]>;
118
118
  userStore?: any;
119
119
  filter?: (trigger: string, meta: CommandMeta) => boolean;
120
+ footer?: any;
121
+ showFooter?: boolean | "always";
120
122
  } = {},
121
123
  ) {
122
124
  // Legacy compat: if callers still pass these, push them into the runtime store
123
125
  if (options.searchNotes) provide("search", options.searchNotes);
124
126
  if (options.userStore) provide("user", options.userStore);
127
+ if (options.footer) provide("launcher:footer", options.footer);
128
+ if (options.showFooter !== undefined) provide("launcher:showFooter", options.showFooter);
125
129
  if (options.filter) appFilter = options.filter;
126
130
  }
127
131
 
@@ -135,6 +139,8 @@ export function initLauncher(
135
139
  searchNotes?: (query: string) => Promise<LauncherResult[]>;
136
140
  userStore?: any;
137
141
  filter?: (trigger: string, meta: CommandMeta) => boolean;
142
+ footer?: any;
143
+ showFooter?: boolean | "always";
138
144
  } = {},
139
145
  ) {
140
146
  configureLauncher(options);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stnd/launcher",
3
- "version": "1.1.0",
3
+ "version": "1.3.0",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -20,10 +20,10 @@
20
20
  "dependencies": {
21
21
  "svelte": "5.57.0",
22
22
  "wrangler": "^4.129.0",
23
+ "@stnd/account": "0.2.11",
23
24
  "@stnd/client": "0.7.0",
24
- "@stnd/log": "0.5.1",
25
25
  "@stnd/icon": "0.2.2",
26
- "@stnd/account": "0.2.11",
26
+ "@stnd/log": "0.5.1",
27
27
  "@stnd/store": "0.5.1",
28
28
  "@stnd/ui": "0.6.1"
29
29
  },