@signal9/era-ui 1.18.0 → 1.19.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.
@@ -5,10 +5,12 @@
5
5
  import BellOff from '@lucide/svelte/icons/bell-off';
6
6
  import { cn } from '../utils/index.js';
7
7
  import { getNotifications, type AppNotification } from './notifications.svelte.js';
8
+ import { getWindowManager } from './wm.svelte.js';
8
9
  import { LAYER } from './layers.js';
9
10
 
10
11
  let { open = $bindable(false) }: { open?: boolean } = $props();
11
12
  const notif = getNotifications();
13
+ const wm = getWindowManager();
12
14
 
13
15
  const accent: Record<AppNotification['tone'], string> = {
14
16
  default: 'bg-(--era-highlight)',
@@ -40,9 +42,13 @@
40
42
  onclick={() => (open = false)}
41
43
  ></button>
42
44
 
45
+ <!-- Hangs off whichever edge(s) the chrome reserved — a top taskbar pushes
46
+ it down, a bottom one stops it short — so it never slides under a bar. -->
43
47
  <aside
44
48
  transition:eraFly={{ x: 320, duration: 200 }}
45
- class="absolute top-0 right-0 bottom-(--era-h-lg) flex w-80 flex-col border-l border-divider-faded bg-(--era-surface-bg) shadow-(--era-shadow-lg) glass-blur"
49
+ style:top="{wm.insets.top}px"
50
+ style:bottom="{wm.insets.bottom}px"
51
+ class="absolute right-0 flex w-80 flex-col border-l border-divider-faded bg-(--era-surface-bg) shadow-(--era-shadow-lg) glass-blur"
46
52
  >
47
53
  <header
48
54
  class="flex shrink-0 items-center gap-(--era-gap) border-b border-divider-faded px-(--era-pad-md) py-(--era-inset-sm)"
@@ -1,5 +1,5 @@
1
1
  <script lang="ts">
2
- import type { Component } from 'svelte';
2
+ import type { Component, Snippet } from 'svelte';
3
3
  import type { HTMLAttributes } from 'svelte/elements';
4
4
  import type { IconProps } from '@lucide/svelte';
5
5
  import { Button } from '../ui/button';
@@ -11,28 +11,40 @@
11
11
  import { LAYER } from './layers.js';
12
12
 
13
13
  let {
14
+ position = 'bottom',
14
15
  pinned = [],
15
16
  onLauncher,
16
17
  onNotifications,
18
+ start,
19
+ end,
17
20
  class: className,
18
21
  ...restProps
19
22
  }: HTMLAttributes<HTMLDivElement> & {
23
+ /** Which shell edge the bar docks to (default bottom). It registers its
24
+ * height as that edge's inset, so maximize / drag containment follow. */
25
+ position?: 'top' | 'bottom';
20
26
  /** Pinned launchers — click spawns that app even when it isn't running. */
21
27
  pinned?: { appId: string; title: string; icon?: Component<IconProps> | null }[];
22
28
  onLauncher?: () => void;
23
29
  onNotifications?: () => void;
30
+ /** Extra chrome before the launcher button (branding, menus…). */
31
+ start?: Snippet;
32
+ /** Extra chrome in the system tray, before the clock. */
33
+ end?: Snippet;
24
34
  } = $props();
25
35
 
26
36
  const wm = getWindowManager();
27
37
  const notif = getNotifications();
28
38
 
29
- // Register our height as reserved chrome: maximize stops above the bar, and
30
- // drag containment keeps window bars reachable above it (not hidden behind).
39
+ // Register our height as reserved chrome on whichever edge we dock to:
40
+ // maximize stops at the bar, and drag containment keeps window bars
41
+ // reachable beside it (not hidden behind).
31
42
  let barHeight = $state(0);
32
43
  $effect(() => {
33
- wm.insets.bottom = barHeight;
44
+ const edge = position === 'top' ? 'top' : 'bottom';
45
+ wm.insets[edge] = barHeight;
34
46
  return () => {
35
- wm.insets.bottom = 0;
47
+ wm.insets[edge] = 0;
36
48
  };
37
49
  });
38
50
 
@@ -45,18 +57,24 @@
45
57
  const time = $derived(now.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }));
46
58
  </script>
47
59
 
48
- <!-- Flush left/right/bottom; the exposed OUTER corners are the top two, rounded
49
- at the bar's own tier (h-lg → rd-lg). The corners axis gates it for free:
50
- data-corners="square" zeroes --era-roundness-scale and with it rd-lg. -->
60
+ <!-- Flush on three edges; the exposed OUTER corners are the two facing the
61
+ desktop, rounded at the bar's own tier (h-lg → rd-lg). The corners axis
62
+ gates it for free: data-corners="square" zeroes --era-roundness-scale and
63
+ with it rd-lg. -->
51
64
  <div
52
65
  bind:clientHeight={barHeight}
53
66
  class={cn(
54
- 'absolute inset-x-0 bottom-0 flex h-(--era-h-lg) items-center gap-(--era-gap) rounded-t-(--era-rd-lg) border-t border-divider-faded bg-(--era-surface-bg-elevated) px-(--era-inset-sm) shadow-(--era-shadow-lg) glass-blur',
67
+ 'absolute inset-x-0 flex h-(--era-h-lg) items-center gap-(--era-gap) border-divider-faded bg-(--era-surface-bg-elevated) px-(--era-inset-sm) shadow-(--era-shadow-lg) glass-blur',
68
+ position === 'top'
69
+ ? 'top-0 rounded-b-(--era-rd-lg) border-b'
70
+ : 'bottom-0 rounded-t-(--era-rd-lg) border-t',
55
71
  LAYER.taskbar,
56
72
  className
57
73
  )}
58
74
  {...restProps}
59
75
  >
76
+ {@render start?.()}
77
+
60
78
  <Button icon size="default" aria-label="Open launcher" onclick={onLauncher}>
61
79
  <LayoutGrid class="size-(--era-h-xs)" />
62
80
  </Button>
@@ -89,6 +107,7 @@
89
107
 
90
108
  <!-- System tray -->
91
109
  <div class="ml-auto flex shrink-0 items-center gap-(--era-gap)">
110
+ {@render end?.()}
92
111
  <span class="px-(--era-inset-sm) text-body text-muted tabular-nums">{time}</span>
93
112
  <div class="relative">
94
113
  <Button icon size="default" aria-label="Notifications" onclick={onNotifications}>
@@ -1,7 +1,10 @@
1
- import type { Component } from 'svelte';
1
+ import type { Component, Snippet } from 'svelte';
2
2
  import type { HTMLAttributes } from 'svelte/elements';
3
3
  import type { IconProps } from '@lucide/svelte';
4
4
  type $$ComponentProps = HTMLAttributes<HTMLDivElement> & {
5
+ /** Which shell edge the bar docks to (default bottom). It registers its
6
+ * height as that edge's inset, so maximize / drag containment follow. */
7
+ position?: 'top' | 'bottom';
5
8
  /** Pinned launchers — click spawns that app even when it isn't running. */
6
9
  pinned?: {
7
10
  appId: string;
@@ -10,6 +13,10 @@ type $$ComponentProps = HTMLAttributes<HTMLDivElement> & {
10
13
  }[];
11
14
  onLauncher?: () => void;
12
15
  onNotifications?: () => void;
16
+ /** Extra chrome before the launcher button (branding, menus…). */
17
+ start?: Snippet;
18
+ /** Extra chrome in the system tray, before the clock. */
19
+ end?: Snippet;
13
20
  };
14
21
  declare const Taskbar: Component<$$ComponentProps, {}, "">;
15
22
  type Taskbar = ReturnType<typeof Taskbar>;
@@ -78,9 +78,12 @@
78
78
  {/if}
79
79
  <span class={cn('truncate text-body', focused ? 'text-bright' : 'text-muted')}>{win.title}</span
80
80
  >
81
- <!-- Close / minimize / maximize. data-pane-control so it clicks instead of
82
- dragging the pane. -->
83
- <div class="ml-auto shrink-0" data-pane-control>
81
+ <!-- Close / minimize / maximize. Deliberately NOT data-pane-control (same as
82
+ the raw Pane.Close): a left press-and-HOLD drags the window — neodrag only
83
+ drags the primary button and suppresses the trailing click after a real
84
+ drag, so a clean click still closes, and right/middle arming is a
85
+ different button entirely. -->
86
+ <div class="ml-auto shrink-0">
84
87
  <Button
85
88
  icon
86
89
  size="icon"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@signal9/era-ui",
3
- "version": "1.18.0",
3
+ "version": "1.19.0",
4
4
  "scripts": {
5
5
  "dev": "vite dev --host",
6
6
  "build": "vite build && npm run prepack",