@sybilion/uilib 1.3.91 → 1.3.92

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.
@@ -12,6 +12,29 @@ import S from './WorkspaceAppSwitcher.styl.js';
12
12
  function entryKey(entry) {
13
13
  return entry.id;
14
14
  }
15
+ function subtitleClassName(entry) {
16
+ return cn(S.sub, entry.subtitleTone === 'experimental' && S.subExperimental);
17
+ }
18
+ /** Overlay native app labels from host registry onto cached localStorage entries. */
19
+ function mergeDefaultAppsMetadata(apps, defaultApps) {
20
+ if (!defaultApps?.length) {
21
+ return apps;
22
+ }
23
+ const defaultsById = new Map(defaultApps.map(entry => [entry.id, entry]));
24
+ return apps.map(entry => {
25
+ const defaults = defaultsById.get(entry.id);
26
+ if (!defaults) {
27
+ return entry;
28
+ }
29
+ return {
30
+ ...entry,
31
+ displayName: defaults.displayName,
32
+ subtitle: defaults.subtitle,
33
+ subtitleTone: defaults.subtitleTone,
34
+ icon: defaults.icon ?? entry.icon,
35
+ };
36
+ });
37
+ }
15
38
  function renderIconContent(icon, iconKey) {
16
39
  if (icon != null) {
17
40
  return (jsx("span", { className: S.icon, "aria-hidden": true, children: icon }));
@@ -37,7 +60,7 @@ function useResolvedApps(appsStorageKey, defaultApps) {
37
60
  appsStorageKey !== '') {
38
61
  const fromLs = readWorkspaceAppsFromLocalStorage(appsStorageKey);
39
62
  if (fromLs != null && fromLs.length > 0) {
40
- return fromLs;
63
+ return mergeDefaultAppsMetadata(fromLs, defaultApps);
41
64
  }
42
65
  }
43
66
  return defaultApps ?? [];
@@ -48,7 +71,9 @@ function useResolvedApps(appsStorageKey, defaultApps) {
48
71
  return;
49
72
  }
50
73
  const fromLs = readWorkspaceAppsFromLocalStorage(appsStorageKey);
51
- setApps(fromLs != null && fromLs.length > 0 ? fromLs : (defaultApps ?? []));
74
+ setApps(fromLs != null && fromLs.length > 0
75
+ ? mergeDefaultAppsMetadata(fromLs, defaultApps)
76
+ : (defaultApps ?? []));
52
77
  }, [appsStorageKey, defaultApps]);
53
78
  return apps;
54
79
  }
@@ -62,11 +87,11 @@ function WorkspaceAppSwitcher({ pathname, onNavigate, authenticated = true, defa
62
87
  if (!displayApp) {
63
88
  return null;
64
89
  }
65
- return (jsxs(DropdownMenu, { children: [jsx(DropdownMenuTrigger, { asChild: true, children: jsxs(Button, { variant: "ghost", className: S.trigger, "aria-label": "Select workspace app", children: [jsx(IconTile, { icon: displayApp.icon, iconKey: displayApp.iconKey, accentMuted: displayApp.accentMuted, accent: displayApp.accent }), jsxs("span", { className: S.textCol, children: [jsx("span", { className: S.name, children: displayApp.displayName }), jsx("span", { className: S.sub, children: displayApp.subtitle })] }), jsx(ChevronDown, { className: S.chevron, size: 12, "aria-hidden": true })] }) }), jsx(DropdownMenuContent, { className: S.menuContent, align: "start", sideOffset: 8, elevation: "md", children: apps.map(entry => {
90
+ return (jsxs(DropdownMenu, { children: [jsx(DropdownMenuTrigger, { asChild: true, children: jsxs(Button, { variant: "ghost", className: S.trigger, "aria-label": "Select workspace app", children: [jsx(IconTile, { icon: displayApp.icon, iconKey: displayApp.iconKey, accentMuted: displayApp.accentMuted, accent: displayApp.accent }), jsxs("span", { className: S.textCol, children: [jsx("span", { className: S.name, children: displayApp.displayName }), jsx("span", { className: subtitleClassName(displayApp), children: displayApp.subtitle })] }), jsx(ChevronDown, { className: S.chevron, size: 12, "aria-hidden": true })] }) }), jsx(DropdownMenuContent, { className: S.menuContent, align: "start", sideOffset: 8, elevation: "md", children: apps.map(entry => {
66
91
  const active = current != null ? entryKey(entry) === entryKey(current) : false;
67
92
  return (jsxs(DropdownMenuItem, { className: cn(S.item, active && S.itemActive), onSelect: () => {
68
93
  onNavigate(entry.href);
69
- }, children: [jsx(IconTile, { icon: entry.icon, iconKey: entry.iconKey, accentMuted: entry.accentMuted, accent: entry.accent }), jsxs("span", { className: S.textCol, children: [jsx("span", { className: S.name, children: entry.displayName }), jsx("span", { className: S.sub, children: entry.subtitle })] })] }, entry.id));
94
+ }, children: [jsx(IconTile, { icon: entry.icon, iconKey: entry.iconKey, accentMuted: entry.accentMuted, accent: entry.accent }), jsxs("span", { className: S.textCol, children: [jsx("span", { className: S.name, children: entry.displayName }), jsx("span", { className: subtitleClassName(entry), children: entry.subtitle })] })] }, entry.id));
70
95
  }) })] }));
71
96
  }
72
97
 
@@ -1,7 +1,7 @@
1
1
  import styleInject from 'style-inject';
2
2
 
3
- var css_248z = "@media (max-width:768px){:root{--page-x-padding:var(--p-6);--page-y-padding:var(--p-6)}}.WorkspaceAppSwitcher_trigger__s6qYT{align-items:center;background:transparent;border:none;border-radius:12px;color:inherit;cursor:pointer;display:flex;font:inherit;gap:var(--p-2);height:auto;margin-left:var(--p-3)!important;max-width:320px;padding:var(--p-1)!important;padding-right:var(--p-3)!important;text-align:left}.WorkspaceAppSwitcher_trigger__s6qYT:hover{background-color:var(--muted)}@media (max-width:768px){.WorkspaceAppSwitcher_trigger__s6qYT{gap:0;margin-left:0!important;max-width:none;padding-right:var(--p-1)!important}.WorkspaceAppSwitcher_trigger__s6qYT .WorkspaceAppSwitcher_chevron__7kAqO,.WorkspaceAppSwitcher_trigger__s6qYT .WorkspaceAppSwitcher_textCol__K1gfI{display:none}}.WorkspaceAppSwitcher_iconTile__tVDr8{align-items:center;border-radius:10px;color:var(--fg-color);display:flex;flex-shrink:0;height:40px;justify-content:center;position:relative;width:40px}.WorkspaceAppSwitcher_iconTile__tVDr8:after,.WorkspaceAppSwitcher_iconTile__tVDr8:before{border-radius:inherit;content:\"\";display:block;height:100%;position:absolute;width:100%}.WorkspaceAppSwitcher_iconTile__tVDr8:before{background-color:var(--background)}.WorkspaceAppSwitcher_iconTile__tVDr8:after{background-color:var(--bg-color)}.WorkspaceAppSwitcher_icon__Jgw14{align-items:center;display:flex;justify-content:center;z-index:1}.WorkspaceAppSwitcher_icon__Jgw14,.WorkspaceAppSwitcher_icon__Jgw14 svg{color:var(--fg-color)!important;height:22px!important;width:22px!important}.WorkspaceAppSwitcher_textCol__K1gfI{display:flex;flex:1;flex-direction:column;gap:2px;min-width:0}.WorkspaceAppSwitcher_name__ewMYP{color:var(--foreground);font-size:var(--text-sm);font-weight:600}.WorkspaceAppSwitcher_name__ewMYP,.WorkspaceAppSwitcher_sub__b7w1p{line-height:1.2;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.WorkspaceAppSwitcher_sub__b7w1p{color:var(--muted-foreground);font-size:var(--text-xs)}.WorkspaceAppSwitcher_menuContent__4-UNY{max-width:360px;min-width:280px}.WorkspaceAppSwitcher_item__nnufY{align-items:center;cursor:pointer;display:flex;gap:var(--p-3);outline:none;padding:var(--p-3)}.WorkspaceAppSwitcher_itemActive__3mPlO{background-color:var(--muted)}";
4
- var S = {"trigger":"WorkspaceAppSwitcher_trigger__s6qYT","textCol":"WorkspaceAppSwitcher_textCol__K1gfI","chevron":"WorkspaceAppSwitcher_chevron__7kAqO","iconTile":"WorkspaceAppSwitcher_iconTile__tVDr8","icon":"WorkspaceAppSwitcher_icon__Jgw14","name":"WorkspaceAppSwitcher_name__ewMYP","sub":"WorkspaceAppSwitcher_sub__b7w1p","menuContent":"WorkspaceAppSwitcher_menuContent__4-UNY","item":"WorkspaceAppSwitcher_item__nnufY","itemActive":"WorkspaceAppSwitcher_itemActive__3mPlO"};
3
+ var css_248z = "@media (max-width:768px){:root{--page-x-padding:var(--p-6);--page-y-padding:var(--p-6)}}.WorkspaceAppSwitcher_trigger__s6qYT{align-items:center;background:transparent;border:none;border-radius:12px;color:inherit;cursor:pointer;display:flex;font:inherit;gap:var(--p-2);height:auto;margin-left:var(--p-3)!important;max-width:320px;padding:var(--p-1)!important;padding-right:var(--p-3)!important;text-align:left}.WorkspaceAppSwitcher_trigger__s6qYT:hover{background-color:var(--muted)}@media (max-width:768px){.WorkspaceAppSwitcher_trigger__s6qYT{gap:0;margin-left:0!important;max-width:none;padding-right:var(--p-1)!important}.WorkspaceAppSwitcher_trigger__s6qYT .WorkspaceAppSwitcher_chevron__7kAqO,.WorkspaceAppSwitcher_trigger__s6qYT .WorkspaceAppSwitcher_textCol__K1gfI{display:none}}.WorkspaceAppSwitcher_iconTile__tVDr8{align-items:center;border-radius:10px;color:var(--fg-color);display:flex;flex-shrink:0;height:40px;justify-content:center;position:relative;width:40px}.WorkspaceAppSwitcher_iconTile__tVDr8:after,.WorkspaceAppSwitcher_iconTile__tVDr8:before{border-radius:inherit;content:\"\";display:block;height:100%;position:absolute;width:100%}.WorkspaceAppSwitcher_iconTile__tVDr8:before{background-color:var(--background)}.WorkspaceAppSwitcher_iconTile__tVDr8:after{background-color:var(--bg-color)}.WorkspaceAppSwitcher_icon__Jgw14{align-items:center;display:flex;justify-content:center;z-index:1}.WorkspaceAppSwitcher_icon__Jgw14,.WorkspaceAppSwitcher_icon__Jgw14 svg{color:var(--fg-color)!important;height:22px!important;width:22px!important}.WorkspaceAppSwitcher_textCol__K1gfI{display:flex;flex:1;flex-direction:column;gap:2px;min-width:0}.WorkspaceAppSwitcher_name__ewMYP{color:var(--foreground);font-size:var(--text-sm);font-weight:600}.WorkspaceAppSwitcher_name__ewMYP,.WorkspaceAppSwitcher_sub__b7w1p{line-height:1.2;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.WorkspaceAppSwitcher_sub__b7w1p{color:var(--muted-foreground);font-size:var(--text-xs)}.WorkspaceAppSwitcher_sub__b7w1p.WorkspaceAppSwitcher_subExperimental__-zHVr{color:var(--sb-red-400);opacity:.8}.dark .WorkspaceAppSwitcher_sub__b7w1p.WorkspaceAppSwitcher_subExperimental__-zHVr{color:var(--sb-red-400)}.WorkspaceAppSwitcher_menuContent__4-UNY{max-width:360px;min-width:280px}.WorkspaceAppSwitcher_item__nnufY{align-items:center;cursor:pointer;display:flex;gap:var(--p-3);outline:none;padding:var(--p-3)}.WorkspaceAppSwitcher_itemActive__3mPlO{background-color:var(--muted)}";
4
+ var S = {"trigger":"WorkspaceAppSwitcher_trigger__s6qYT","textCol":"WorkspaceAppSwitcher_textCol__K1gfI","chevron":"WorkspaceAppSwitcher_chevron__7kAqO","iconTile":"WorkspaceAppSwitcher_iconTile__tVDr8","icon":"WorkspaceAppSwitcher_icon__Jgw14","name":"WorkspaceAppSwitcher_name__ewMYP","sub":"WorkspaceAppSwitcher_sub__b7w1p","subExperimental":"WorkspaceAppSwitcher_subExperimental__-zHVr","menuContent":"WorkspaceAppSwitcher_menuContent__4-UNY","item":"WorkspaceAppSwitcher_item__nnufY","itemActive":"WorkspaceAppSwitcher_itemActive__3mPlO"};
5
5
  styleInject(css_248z);
6
6
 
7
7
  export { S as default };
@@ -1,5 +1,8 @@
1
1
  import { isWorkspaceAppIconKey } from './workspaceAppIcons.js';
2
2
 
3
+ function isWorkspaceAppSubtitleTone(v) {
4
+ return v === 'default' || v === 'experimental';
5
+ }
3
6
  function parseEntry(raw) {
4
7
  if (!raw || typeof raw !== 'object') {
5
8
  return null;
@@ -12,6 +15,7 @@ function parseEntry(raw) {
12
15
  const accent = o.accent;
13
16
  const accentMuted = o.accentMuted;
14
17
  const href = o.href;
18
+ const subtitleToneRaw = o.subtitleTone;
15
19
  const prefixesRaw = o.matchPathPrefixes;
16
20
  if (typeof idRaw !== 'string' ||
17
21
  !idRaw ||
@@ -35,7 +39,7 @@ function parseEntry(raw) {
35
39
  matchPathPrefixes = prefixes;
36
40
  }
37
41
  }
38
- return {
42
+ const entry = {
39
43
  id: idRaw,
40
44
  displayName,
41
45
  subtitle,
@@ -45,6 +49,10 @@ function parseEntry(raw) {
45
49
  href,
46
50
  matchPathPrefixes,
47
51
  };
52
+ if (subtitleToneRaw != null && isWorkspaceAppSubtitleTone(subtitleToneRaw)) {
53
+ entry.subtitleTone = subtitleToneRaw;
54
+ }
55
+ return entry;
48
56
  }
49
57
  /**
50
58
  * Read validated workspace apps JSON from localStorage; returns null if missing or invalid.
@@ -1,5 +1,5 @@
1
1
  export { WorkspaceAppSwitcher, type WorkspaceAppSwitcherProps, } from './WorkspaceAppSwitcher';
2
- export type { WorkspaceAppEntry } from './workspaceApp.types';
2
+ export type { WorkspaceAppEntry, WorkspaceAppSubtitleTone, } from './workspaceApp.types';
3
3
  export { WORKSPACE_APP_SLUG_BASE_PATH } from './workspaceApp.types';
4
4
  export { WORKSPACE_APPS_LS_KEY } from './workspaceAppsConstants';
5
5
  export { readWorkspaceAppsFromLocalStorage, writeWorkspaceAppsToLocalStorage, } from './workspaceAppsLocalStorage';
@@ -2,12 +2,15 @@ import type { ReactNode } from 'react';
2
2
  import type { WorkspaceAppIconKey } from './workspaceAppIcons';
3
3
  /** Path segment for slug apps: pathname matches `/apps/{id}` */
4
4
  export declare const WORKSPACE_APP_SLUG_BASE_PATH = "/apps";
5
+ export type WorkspaceAppSubtitleTone = 'default' | 'experimental';
5
6
  /** One surface in the workspace app switcher (serializable for localStorage). */
6
7
  export type WorkspaceAppEntry = {
7
8
  /** Slug (e.g. `my-custom-app` → `https://sybilion.io/apps/my-custom-app` via `href`). */
8
9
  id: string;
9
10
  displayName: string;
10
11
  subtitle: string;
12
+ /** Optional subtitle color tone; defaults to muted foreground. */
13
+ subtitleTone?: WorkspaceAppSubtitleTone;
11
14
  /** Custom icon for display; not persisted to localStorage. */
12
15
  icon?: ReactNode;
13
16
  /** Built-in icon lookup when `icon` is omitted (required for localStorage entries). */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sybilion/uilib",
3
- "version": "1.3.91",
3
+ "version": "1.3.92",
4
4
  "description": "Sybilion Design System — React UI components (Webpack + Stylus)",
5
5
  "publishConfig": {
6
6
  "access": "public",
@@ -95,6 +95,13 @@
95
95
  overflow hidden
96
96
  text-overflow ellipsis
97
97
 
98
+ &.subExperimental
99
+ opacity .8
100
+ color var(--sb-red-400)
101
+
102
+ :global(.dark) &
103
+ color var(--sb-red-400)
104
+
98
105
  .menuContent
99
106
  min-width 280px
100
107
  max-width 360px
@@ -9,6 +9,7 @@ interface CssExports {
9
9
  'menuContent': string;
10
10
  'name': string;
11
11
  'sub': string;
12
+ 'subExperimental': string;
12
13
  'textCol': string;
13
14
  'trigger': string;
14
15
  }
@@ -32,6 +32,34 @@ function entryKey(entry: WorkspaceAppEntry): string {
32
32
  return entry.id;
33
33
  }
34
34
 
35
+ function subtitleClassName(entry: WorkspaceAppEntry): string {
36
+ return cn(S.sub, entry.subtitleTone === 'experimental' && S.subExperimental);
37
+ }
38
+
39
+ /** Overlay native app labels from host registry onto cached localStorage entries. */
40
+ function mergeDefaultAppsMetadata(
41
+ apps: WorkspaceAppEntry[],
42
+ defaultApps: WorkspaceAppEntry[] | undefined,
43
+ ): WorkspaceAppEntry[] {
44
+ if (!defaultApps?.length) {
45
+ return apps;
46
+ }
47
+ const defaultsById = new Map(defaultApps.map(entry => [entry.id, entry]));
48
+ return apps.map(entry => {
49
+ const defaults = defaultsById.get(entry.id);
50
+ if (!defaults) {
51
+ return entry;
52
+ }
53
+ return {
54
+ ...entry,
55
+ displayName: defaults.displayName,
56
+ subtitle: defaults.subtitle,
57
+ subtitleTone: defaults.subtitleTone,
58
+ icon: defaults.icon ?? entry.icon,
59
+ };
60
+ });
61
+ }
62
+
35
63
  function renderIconContent(
36
64
  icon: ReactNode | undefined,
37
65
  iconKey: WorkspaceAppEntry['iconKey'],
@@ -90,7 +118,7 @@ function useResolvedApps(
90
118
  ) {
91
119
  const fromLs = readWorkspaceAppsFromLocalStorage(appsStorageKey);
92
120
  if (fromLs != null && fromLs.length > 0) {
93
- return fromLs;
121
+ return mergeDefaultAppsMetadata(fromLs, defaultApps);
94
122
  }
95
123
  }
96
124
  return defaultApps ?? [];
@@ -102,7 +130,11 @@ function useResolvedApps(
102
130
  return;
103
131
  }
104
132
  const fromLs = readWorkspaceAppsFromLocalStorage(appsStorageKey);
105
- setApps(fromLs != null && fromLs.length > 0 ? fromLs : (defaultApps ?? []));
133
+ setApps(
134
+ fromLs != null && fromLs.length > 0
135
+ ? mergeDefaultAppsMetadata(fromLs, defaultApps)
136
+ : (defaultApps ?? []),
137
+ );
106
138
  }, [appsStorageKey, defaultApps]);
107
139
 
108
140
  return apps;
@@ -144,7 +176,9 @@ export function WorkspaceAppSwitcher({
144
176
  />
145
177
  <span className={S.textCol}>
146
178
  <span className={S.name}>{displayApp.displayName}</span>
147
- <span className={S.sub}>{displayApp.subtitle}</span>
179
+ <span className={subtitleClassName(displayApp)}>
180
+ {displayApp.subtitle}
181
+ </span>
148
182
  </span>
149
183
  <ChevronDown className={S.chevron} size={12} aria-hidden />
150
184
  </Button>
@@ -175,7 +209,9 @@ export function WorkspaceAppSwitcher({
175
209
  />
176
210
  <span className={S.textCol}>
177
211
  <span className={S.name}>{entry.displayName}</span>
178
- <span className={S.sub}>{entry.subtitle}</span>
212
+ <span className={subtitleClassName(entry)}>
213
+ {entry.subtitle}
214
+ </span>
179
215
  </span>
180
216
  </DropdownMenuItem>
181
217
  );
@@ -2,7 +2,10 @@ export {
2
2
  WorkspaceAppSwitcher,
3
3
  type WorkspaceAppSwitcherProps,
4
4
  } from './WorkspaceAppSwitcher';
5
- export type { WorkspaceAppEntry } from './workspaceApp.types';
5
+ export type {
6
+ WorkspaceAppEntry,
7
+ WorkspaceAppSubtitleTone,
8
+ } from './workspaceApp.types';
6
9
  export { WORKSPACE_APP_SLUG_BASE_PATH } from './workspaceApp.types';
7
10
  export { WORKSPACE_APPS_LS_KEY } from './workspaceAppsConstants';
8
11
  export {
@@ -5,12 +5,16 @@ import type { WorkspaceAppIconKey } from './workspaceAppIcons';
5
5
  /** Path segment for slug apps: pathname matches `/apps/{id}` */
6
6
  export const WORKSPACE_APP_SLUG_BASE_PATH = '/apps';
7
7
 
8
+ export type WorkspaceAppSubtitleTone = 'default' | 'experimental';
9
+
8
10
  /** One surface in the workspace app switcher (serializable for localStorage). */
9
11
  export type WorkspaceAppEntry = {
10
12
  /** Slug (e.g. `my-custom-app` → `https://sybilion.io/apps/my-custom-app` via `href`). */
11
13
  id: string;
12
14
  displayName: string;
13
15
  subtitle: string;
16
+ /** Optional subtitle color tone; defaults to muted foreground. */
17
+ subtitleTone?: WorkspaceAppSubtitleTone;
14
18
  /** Custom icon for display; not persisted to localStorage. */
15
19
  icon?: ReactNode;
16
20
  /** Built-in icon lookup when `icon` is omitted (required for localStorage entries). */
@@ -1,6 +1,13 @@
1
- import type { WorkspaceAppEntry } from './workspaceApp.types';
1
+ import type {
2
+ WorkspaceAppEntry,
3
+ WorkspaceAppSubtitleTone,
4
+ } from './workspaceApp.types';
2
5
  import { isWorkspaceAppIconKey } from './workspaceAppIcons';
3
6
 
7
+ function isWorkspaceAppSubtitleTone(v: unknown): v is WorkspaceAppSubtitleTone {
8
+ return v === 'default' || v === 'experimental';
9
+ }
10
+
4
11
  function parseEntry(raw: unknown): WorkspaceAppEntry | null {
5
12
  if (!raw || typeof raw !== 'object') {
6
13
  return null;
@@ -13,6 +20,7 @@ function parseEntry(raw: unknown): WorkspaceAppEntry | null {
13
20
  const accent = o.accent;
14
21
  const accentMuted = o.accentMuted;
15
22
  const href = o.href;
23
+ const subtitleToneRaw = o.subtitleTone;
16
24
  const prefixesRaw = o.matchPathPrefixes;
17
25
 
18
26
  if (
@@ -43,7 +51,7 @@ function parseEntry(raw: unknown): WorkspaceAppEntry | null {
43
51
  }
44
52
  }
45
53
 
46
- return {
54
+ const entry: WorkspaceAppEntry = {
47
55
  id: idRaw,
48
56
  displayName,
49
57
  subtitle,
@@ -53,6 +61,12 @@ function parseEntry(raw: unknown): WorkspaceAppEntry | null {
53
61
  href,
54
62
  matchPathPrefixes,
55
63
  };
64
+
65
+ if (subtitleToneRaw != null && isWorkspaceAppSubtitleTone(subtitleToneRaw)) {
66
+ entry.subtitleTone = subtitleToneRaw;
67
+ }
68
+
69
+ return entry;
56
70
  }
57
71
 
58
72
  /**