@streamoid/ui 0.6.30 → 0.6.32

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.
@@ -166,7 +166,7 @@ theme-aware (dark on `:root`, light override) — never hardcode a colour.
166
166
 
167
167
  - **`CreditWarningBanner`** — use when you need the "you're nearly out of credits" nudge in a sidebar rail, in either expanded or collapsed form.
168
168
  - **`ScAppSwitchPanel`** — use when you need the "go to another Streamoid product" list — in `StreamoidSidebar`'s `switchPanel`, or in your own portalled flyout.
169
- - also documented here: `ScAppSwitchRow`, `createStreamoidAppSwitchUtilities`, `streamoidAppSwitchTagline`, `streamoidChangelogUrl`, `STREAMOID_CHANGELOG_URLS`, `STREAMOID_CHANGELOG_URL`, `STREAMOID_STATUS_PAGE_URL`
169
+ - also documented here: `ScAppSwitchRow`, `scAppSwitchOrder`, `createStreamoidAppSwitchUtilities`, `streamoidAppSwitchTagline`, `formatStreamoidAppVersion`, `streamoidChangelogUrl`, `STREAMOID_CHANGELOG_URLS`, `STREAMOID_CHANGELOG_URL`, `STREAMOID_STATUS_PAGE_URL`
170
170
  - **`ScArtifaxSidebar`** — use when you are working on the Artifax (or Tactix) shell, or you need a sidebar whose sections the user can collapse.
171
171
  - **`ScAskAgentButton`** — use when you need the assistant-panel CTA **outside** a DS sidebar. Inside one, use the sidebar's `assistantCta` prop instead.
172
172
  - also documented here: `ScAskAgentSlot`, `AssistantCta`
@@ -180,8 +180,8 @@ theme-aware (dark on `:root`, light override) — never hardcode a colour.
180
180
  - **`ScSidebarProfile`** _(legacy)_ — use when never. There is no way to pass a real user through it.
181
181
  - **`ScSidebarSwitchMenu`** — use when you're building a "switch to another product" list inside a sidebar and each entry needs a name **and** a subtitle.
182
182
  - **`ScVersion`** _(legacy)_ — use when never. `StreamoidSidebar` renders its own version footer inline from `versionText`.
183
- - **`ScWorkspaceAccountMenu`** — use when building the standard workspace menu opened from the top-left of a Streamoid application sidebar. - Use `ScSidebarWorkspaceTrigger`, `ScSidebarSearchTrigger`, and `ScSidebarAppIdentity` for the matching constant rail controls. - Use the two preference hooks so theme and expanded/collapsed state survive an app switch across Streamoid subdomains. - Use `useStreamoidSidebarPopoverPosition` for the upper-right workspace menu and lower-right app switcher instead of hard-coded host coordinates.
184
- - also documented here: `ScSidebarWorkspaceTrigger`, `ScSidebarSearchTrigger`, `ScSidebarAppIdentity`, `useStreamoidThemePreference`, `useStreamoidSidebarPreference`, `useStreamoidSidebarPopoverPosition`
183
+ - **`ScWorkspaceAccountMenu`** — use when building the standard workspace menu opened from the top-left of a Streamoid application sidebar. - Use `ScSidebarWorkspaceTrigger`, `ScSidebarSearchTrigger`, and `ScSidebarAppIdentity` for the matching constant rail controls. - Use the two preference hooks so theme and expanded/collapsed state survive an app switch across Streamoid subdomains. - Use `useStreamoidSidebarPopoverPosition` for a panel that belongs to the whole rail (the upper-right workspace menu) instead of hard-coded host coordinates. - Use `useStreamoidAnchoredPopoverPosition` for a panel that belongs to a BUTTON — the app switcher's grid trigger, a row's overflow menu. Pinning one of those to the sidebar card's edge leaves it floating mid-canvas with nothing connecting it to what was clicked; in Artifax it landed over the chat composer and its Send button.
184
+ - also documented here: `ScSidebarWorkspaceTrigger`, `ScSidebarSearchTrigger`, `ScSidebarAppIdentity`, `ScPopoverArrow`, `useStreamoidThemePreference`, `useStreamoidSidebarPreference`, `useStreamoidSidebarPopoverPosition`, `useStreamoidAnchoredPopover`, `useStreamoidAnchoredPopoverPosition`, `resolveAnchoredPopoverArrow`, `resolveAnchoredPopoverPosition`
185
185
  - **`StreamoidSidebar`** — use when you are building (or fixing) a desktop app's primary left navigation and you want the same chrome CXO, Photogenix and Catalogix use.
186
186
  - also documented here: `ScSidebarResizeHandle`, `advanceSidebarResizeGesture`, `SIDEBAR_RESIZE_THRESHOLD_PX`
187
187
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  component: ScAppSwitchPanel
3
- also_exports: [ScAppSwitchRow, createStreamoidAppSwitchUtilities, streamoidAppSwitchTagline, streamoidChangelogUrl, STREAMOID_CHANGELOG_URLS, STREAMOID_CHANGELOG_URL, STREAMOID_STATUS_PAGE_URL]
3
+ also_exports: [ScAppSwitchRow, scAppSwitchOrder, createStreamoidAppSwitchUtilities, streamoidAppSwitchTagline, formatStreamoidAppVersion, streamoidChangelogUrl, STREAMOID_CHANGELOG_URLS, STREAMOID_CHANGELOG_URL, STREAMOID_STATUS_PAGE_URL]
4
4
  package: "@streamoid/ui"
5
5
  category: sidebar
6
6
  status: stable
@@ -16,8 +16,13 @@ required_props: [apps]
16
16
 
17
17
  **The cross-product "switch app" list — one identical implementation for all four
18
18
  apps.** A bare column of 60px rows, each showing the target product's wordmark
19
- (gray, brightening to white on hover) and a `>` chevron that fades in. It has no
20
- card, no border and no background: the surrounding sidebar or flyout supplies those.
19
+ (gray, brightening to white on hover) under an **Apps** group label, with the
20
+ utility rows under a **Help** one. It has no card, no border and no background:
21
+ the surrounding sidebar or flyout supplies those.
22
+
23
+ No chevrons: every product row navigates, so an arrow on each of them
24
+ distinguishes nothing. The only trailing glyph is the check on the row you are
25
+ already on.
21
26
 
22
27
  ## TL;DR for agents
23
28
 
@@ -30,13 +35,16 @@ card, no border and no background: the surrounding sidebar or flyout supplies th
30
35
  - **Four things that will bite you:**
31
36
  1. It **re-sorts your array** into a canonical product order. Your ordering is
32
37
  discarded.
33
- 2. Known products render the **canonical tagline** (e.g. "for buyers and
38
+ 2. Known products render the **canonical tagline** (e.g. "Buyers and
34
39
  planners"), not `description` and not the product `name`. Do not copy that
35
- map into a host.
40
+ map into a host. Taglines name what the product is; none of them start with
41
+ "for".
36
42
  3. Matching is a **substring** test over `` `${key} ${name}`.toLowerCase() ``. A key
37
43
  containing `"cxo"` or `"streamoid"` gets the **CXO** wordmark — but the four
38
44
  product names are matched first, so `"Streamoid Catalogix"` is Catalogix.
39
- 4. It does **not** exclude the current app. That's the host's job.
45
+ 4. **Pass every product, including the one you are in**, marked `current`.
46
+ Omitting it (which hosts used to do) changes the list's length per surface,
47
+ so rows move between apps and the hub has no row in its own switcher.
40
48
 
41
49
  ---
42
50
 
@@ -59,8 +67,13 @@ import "@streamoid/ui/dist/index.css"; // once, at your app root
59
67
  ```tsx
60
68
  <ScAppSwitchPanel
61
69
  apps={[
62
- { key: "artifax", name: "Artifax", onSelect: () => open(artifaxUrl) },
70
+ // Every product, always, in every app. `current` marks where you are; its
71
+ // `onSelect` is never called, so a no-op is fine.
72
+ { key: "cxo", name: "CXO", onSelect: () => open(cxoUrl) },
73
+ { key: "tactix", name: "Tactix", onSelect: () => open(tactixUrl) },
74
+ { key: "artifax", name: "Artifax", current: true, onSelect: () => {} },
63
75
  { key: "photogenix", name: "Photogenix", onSelect: () => open(photogenixUrl) },
76
+ { key: "catalogix", name: "Catalogix", onSelect: () => open(catalogixUrl) },
64
77
  ]}
65
78
  utilities={{
66
79
  appVersion: APP_VERSION,
@@ -75,8 +88,11 @@ import "@streamoid/ui/dist/index.css"; // once, at your app root
75
88
  | Prop | Type | Default | Notes |
76
89
  |---|---|---|---|
77
90
  | `apps` | `ScAppSwitchItem[]` | — | **Required.** ⚠️ **Re-sorted internally** into canonical order; unknown products keep their relative order and go last. |
78
- | `utilities` | `ScAppSwitchUtilities` | – | Adds the standard Changelog + app version, Status Page, and Help and Support rows. |
91
+ | `utilities` | `ScAppSwitchUtilities` | – | Adds the standard **What's new** + version, **Status**, and **Help and Support** rows. |
79
92
  | `systemItems` | `ScAppSwitchSystemItem[]` | – | Fully custom utility rows. Overrides `utilities` when supplied. |
93
+ | `title` | `string \| null` | `null` | Off by default. It used to read "Switch Apps" above a list of apps, inside a panel opened by a button of the same name. |
94
+ | `appsLabel` | `string \| null` | `"Apps"` | Group label above the products. |
95
+ | `systemLabel` | `string \| null` | `"Help"` | Group label above the utility rows. |
80
96
  | `className` | `string` | – | On the root column. |
81
97
  | `style` | `CSSProperties` | – | Merged **after** the base inline styles, so it can override `paddingBottom`. |
82
98
 
@@ -86,6 +102,19 @@ import "@streamoid/ui/dist/index.css"; // once, at your app root
86
102
  |---|---|---|---|
87
103
  | `item` | `ScAppSwitchItem` | — | **Required.** The only prop — no `className`, no `style`. Rarely used directly; the panel renders one per app. |
88
104
 
105
+ ### `formatStreamoidAppVersion(raw)`
106
+
107
+ What the **What's new** row shows, from whatever the host's build stamped.
108
+ Returns `v2.4` for `2.4`/`v2.4`, `v0.1.1` for `v0.1.1-dev`, and **`undefined`**
109
+ for anything that is not a version — a commit hash (Artifax was showing
110
+ `7f79e00`) or a bare `dev` (Tactix, `Vdev`). `utilities` applies it; hosts do
111
+ not need to.
112
+
113
+ ### `scAppSwitchOrder(apps)`
114
+
115
+ The canonical ordering, exported so a host (or a test) can assert the row order
116
+ without rendering. The panel applies it already — hosts do not need to sort.
117
+
89
118
  ### `ScAppSwitchItem`
90
119
 
91
120
  | Field | Type | Notes |
@@ -93,7 +122,8 @@ import "@streamoid/ui/dist/index.css"; // once, at your app root
93
122
  | `key` | `string` | React key **and** half of the wordmark-matching string. |
94
123
  | `name` | `string` | Matching input, and the fallback label. ⚠️ **Not rendered** when a wordmark matches. Always used for the row's `aria-label` (`"Switch to {name}"`). |
95
124
  | `description` | `string` | Optional. Ignored for known products (the panel uses the canonical tagline). Shown only for unknown products. |
96
- | `onSelect` | `() => void` | Row click. |
125
+ | `current` | `boolean` | Optional. This is the app you are in: the row is filled (`--alias-fill-neutral-neutralselected`), carries a **check** instead of the chevron, gets `aria-current="true"` + `aria-disabled`, and does **not** call `onSelect`. It stays focusable on purpose — it is the answer to "where am I". |
126
+ | `onSelect` | `() => void` | Row click. Never called on the `current` row. |
97
127
 
98
128
  ### Wordmark matching + canonical order
99
129
 
@@ -9,7 +9,7 @@ related: [StreamoidSidebar, ScArtifaxSidebar, ScAppSwitchPanel, ScProfilePopup]
9
9
  do_not_confuse_with: [ScProfilePopup, ScWorkspaceSwitcher, ScAppSwitchPanel]
10
10
  used_by: [cxo, artifax, catalogix, photogenix]
11
11
  required_props: [workspaceName, accountName]
12
- also_exports: [ScSidebarWorkspaceTrigger, ScSidebarSearchTrigger, ScSidebarAppIdentity, useStreamoidThemePreference, useStreamoidSidebarPreference, useStreamoidSidebarPopoverPosition]
12
+ also_exports: [ScSidebarWorkspaceTrigger, ScSidebarSearchTrigger, ScSidebarAppIdentity, ScPopoverArrow, useStreamoidThemePreference, useStreamoidSidebarPreference, useStreamoidSidebarPopoverPosition, useStreamoidAnchoredPopover, useStreamoidAnchoredPopoverPosition, resolveAnchoredPopoverArrow, resolveAnchoredPopoverPosition]
13
13
  ---
14
14
 
15
15
  # Shared Streamoid sidebar shell
@@ -27,8 +27,14 @@ shared popover-position hook keeps the desktop anchors consistent.
27
27
  `ScSidebarAppIdentity` for the matching constant rail controls.
28
28
  - Use the two preference hooks so theme and expanded/collapsed state survive an
29
29
  app switch across Streamoid subdomains.
30
- - Use `useStreamoidSidebarPopoverPosition` for the upper-right workspace menu
31
- and lower-right app switcher instead of hard-coded host coordinates.
30
+ - Use `useStreamoidSidebarPopoverPosition` for a panel that belongs to the
31
+ whole rail (the upper-right workspace menu) instead of hard-coded host
32
+ coordinates.
33
+ - Use `useStreamoidAnchoredPopoverPosition` for a panel that belongs to a
34
+ BUTTON — the app switcher's grid trigger, a row's overflow menu. Pinning one
35
+ of those to the sidebar card's edge leaves it floating mid-canvas with nothing
36
+ connecting it to what was clicked; in Artifax it landed over the chat
37
+ composer and its Send button.
32
38
 
33
39
  ## 1. How to use it
34
40
 
@@ -50,12 +56,33 @@ const workspaceMenuPosition = useStreamoidSidebarPopoverPosition(sidebarRef, {
50
56
  placement: "top-right",
51
57
  width: 320,
52
58
  });
53
- const appSwitcherPosition = useStreamoidSidebarPopoverPosition(sidebarRef, {
54
- placement: "bottom-right",
55
- width: 240,
56
- });
59
+ // Tethered to the trigger, flipping only to stay on screen: beside the button
60
+ // and running down from its top edge, to the button's other side when that side
61
+ // has no room, and upward (bottom aligned to the trigger) when there is no room
62
+ // below — which is what a footer grid button gets.
63
+ const appSwitcherPosition = useStreamoidAnchoredPopoverPosition(
64
+ triggerRef,
65
+ panelRef,
66
+ { width: 240 },
67
+ );
57
68
  ```
58
69
 
70
+ ### `useStreamoidAnchoredPopoverPosition(anchorRef, panelRef, options)`
71
+
72
+ | Option | Type | Default | Purpose |
73
+ |---|---|---|---|
74
+ | `width` | `number` | — | **Required.** Panel width; narrowed to the viewport when it cannot fit. |
75
+ | `gap` | `number` | `8` | Distance between the trigger and the panel. |
76
+ | `inset` | `number` | `16` | Keep-out margin from the viewport edges. |
77
+
78
+ Pass a ref to the panel as well as the trigger: the vertical flip needs the
79
+ panel's measured height, so the first frame opens downward and settles once
80
+ measured. Re-measures on resize, on scroll (capture phase — a rail can scroll
81
+ without bubbling), and while either element changes size.
82
+
83
+ `resolveAnchoredPopoverPosition(geometry)` is the same rules as a pure
84
+ function, exported for hosts that position their own portal and for tests.
85
+
59
86
  | Prop | Type | Default | Purpose |
60
87
  |---|---|---|---|
61
88
  | `workspaceName` | `string` | required | Current workspace label. |
@@ -132,3 +159,44 @@ CXO dashboard sidebar integration shipped with this release.
132
159
  - `ScAppSwitchPanel` — the app list opened from the bottom app identity.
133
160
  - `ScProfilePopup` — the previous profile-first menu; do not use it for the new
134
161
  workspace-first shell.
162
+
163
+ ## `useStreamoidAnchoredPopover` + `ScPopoverArrow`
164
+
165
+ A popover tethered to the control that opened it, **with the caret that proves
166
+ it**. Same placement rules as `useStreamoidAnchoredPopoverPosition` (beside the
167
+ trigger, collision-flip only) and it additionally returns where the caret goes:
168
+
169
+ ```tsx
170
+ const { style, arrow } = useStreamoidAnchoredPopover(anchorRef, panelRef, {
171
+ width: 260,
172
+ });
173
+
174
+ return createPortal(
175
+ <div
176
+ ref={panelRef}
177
+ style={{ ...style, background: SURFACE, border: `1px solid ${BORDER}`, borderRadius: 16 }}
178
+ >
179
+ <ScPopoverArrow arrow={arrow} background={SURFACE} borderColor={BORDER} />
180
+ <div style={{ borderRadius: 16, overflow: "hidden" }}>{children}</div>
181
+ </div>,
182
+ document.body,
183
+ );
184
+ ```
185
+
186
+ Two things will bite you:
187
+
188
+ 1. **`overflow: hidden` on the card clips the caret off.** The caret hangs
189
+ half outside the panel by design. Round and clip an INNER box instead, as
190
+ above.
191
+ 2. **The caret is not decoration.** A tall panel opened from a button at the
192
+ foot of a rail flips upward — its bottom edge level with the button — and
193
+ without the caret it reads as a card floating mid-canvas that happens to be
194
+ nearby. That was reported twice as "the popover still isn't tethered".
195
+
196
+ `arrow` is `null` until the panel has been measured; `ScPopoverArrow` renders
197
+ nothing for it, so there is no flash of a caret in the wrong place.
198
+
199
+ Use the same hook for **every** popover a rail owns. Mixing it with
200
+ `useStreamoidSidebarPopoverPosition` (which pins to the sidebar CARD's edge)
201
+ puts two menus from the same rail in visibly different places — one tethered to
202
+ its button, one parked against a container edge.
@@ -162,8 +162,10 @@
162
162
  ],
163
163
  "alsoExports": [
164
164
  "ScAppSwitchRow",
165
+ "scAppSwitchOrder",
165
166
  "createStreamoidAppSwitchUtilities",
166
167
  "streamoidAppSwitchTagline",
168
+ "formatStreamoidAppVersion",
167
169
  "streamoidChangelogUrl",
168
170
  "STREAMOID_CHANGELOG_URLS",
169
171
  "STREAMOID_CHANGELOG_URL",
@@ -2757,7 +2759,7 @@
2757
2759
  "status": "stable",
2758
2760
  "renders": "div",
2759
2761
  "summary": "Reach for it when:",
2760
- "reachForWhen": "building the standard workspace menu opened from the top-left of a Streamoid application sidebar. - Use `ScSidebarWorkspaceTrigger`, `ScSidebarSearchTrigger`, and `ScSidebarAppIdentity` for the matching constant rail controls. - Use the two preference hooks so theme and expanded/collapsed state survive an app switch across Streamoid subdomains. - Use `useStreamoidSidebarPopoverPosition` for the upper-right workspace menu and lower-right app switcher instead of hard-coded host coordinates.",
2762
+ "reachForWhen": "building the standard workspace menu opened from the top-left of a Streamoid application sidebar. - Use `ScSidebarWorkspaceTrigger`, `ScSidebarSearchTrigger`, and `ScSidebarAppIdentity` for the matching constant rail controls. - Use the two preference hooks so theme and expanded/collapsed state survive an app switch across Streamoid subdomains. - Use `useStreamoidSidebarPopoverPosition` for a panel that belongs to the whole rail (the upper-right workspace menu) instead of hard-coded host coordinates. - Use `useStreamoidAnchoredPopoverPosition` for a panel that belongs to a BUTTON — the app switcher's grid trigger, a row's overflow menu. Pinning one of those to the sidebar card's edge leaves it floating mid-canvas with nothing connecting it to what was clicked; in Artifax it landed over the chat composer and its Send button.",
2761
2763
  "tags": [
2762
2764
  "sidebar",
2763
2765
  "workspace",
@@ -2791,9 +2793,14 @@
2791
2793
  "ScSidebarWorkspaceTrigger",
2792
2794
  "ScSidebarSearchTrigger",
2793
2795
  "ScSidebarAppIdentity",
2796
+ "ScPopoverArrow",
2794
2797
  "useStreamoidThemePreference",
2795
2798
  "useStreamoidSidebarPreference",
2796
- "useStreamoidSidebarPopoverPosition"
2799
+ "useStreamoidSidebarPopoverPosition",
2800
+ "useStreamoidAnchoredPopover",
2801
+ "useStreamoidAnchoredPopoverPosition",
2802
+ "resolveAnchoredPopoverArrow",
2803
+ "resolveAnchoredPopoverPosition"
2797
2804
  ],
2798
2805
  "exported": true
2799
2806
  },
@@ -4912,6 +4919,10 @@
4912
4919
  "documentedIn": "ScAppSwitchPanel",
4913
4920
  "doc": "ScAppSwitchPanel.md"
4914
4921
  },
4922
+ "scAppSwitchOrder": {
4923
+ "documentedIn": "ScAppSwitchPanel",
4924
+ "doc": "ScAppSwitchPanel.md"
4925
+ },
4915
4926
  "createStreamoidAppSwitchUtilities": {
4916
4927
  "documentedIn": "ScAppSwitchPanel",
4917
4928
  "doc": "ScAppSwitchPanel.md"
@@ -4920,6 +4931,10 @@
4920
4931
  "documentedIn": "ScAppSwitchPanel",
4921
4932
  "doc": "ScAppSwitchPanel.md"
4922
4933
  },
4934
+ "formatStreamoidAppVersion": {
4935
+ "documentedIn": "ScAppSwitchPanel",
4936
+ "doc": "ScAppSwitchPanel.md"
4937
+ },
4923
4938
  "streamoidChangelogUrl": {
4924
4939
  "documentedIn": "ScAppSwitchPanel",
4925
4940
  "doc": "ScAppSwitchPanel.md"
@@ -5012,6 +5027,10 @@
5012
5027
  "documentedIn": "ScWorkspaceAccountMenu",
5013
5028
  "doc": "ScWorkspaceAccountMenu.md"
5014
5029
  },
5030
+ "ScPopoverArrow": {
5031
+ "documentedIn": "ScWorkspaceAccountMenu",
5032
+ "doc": "ScWorkspaceAccountMenu.md"
5033
+ },
5015
5034
  "useStreamoidThemePreference": {
5016
5035
  "documentedIn": "ScWorkspaceAccountMenu",
5017
5036
  "doc": "ScWorkspaceAccountMenu.md"
@@ -5024,6 +5043,22 @@
5024
5043
  "documentedIn": "ScWorkspaceAccountMenu",
5025
5044
  "doc": "ScWorkspaceAccountMenu.md"
5026
5045
  },
5046
+ "useStreamoidAnchoredPopover": {
5047
+ "documentedIn": "ScWorkspaceAccountMenu",
5048
+ "doc": "ScWorkspaceAccountMenu.md"
5049
+ },
5050
+ "useStreamoidAnchoredPopoverPosition": {
5051
+ "documentedIn": "ScWorkspaceAccountMenu",
5052
+ "doc": "ScWorkspaceAccountMenu.md"
5053
+ },
5054
+ "resolveAnchoredPopoverArrow": {
5055
+ "documentedIn": "ScWorkspaceAccountMenu",
5056
+ "doc": "ScWorkspaceAccountMenu.md"
5057
+ },
5058
+ "resolveAnchoredPopoverPosition": {
5059
+ "documentedIn": "ScWorkspaceAccountMenu",
5060
+ "doc": "ScWorkspaceAccountMenu.md"
5061
+ },
5027
5062
  "formatSubAgentLabel": {
5028
5063
  "documentedIn": "ScSubAgent",
5029
5064
  "doc": "ScSubAgent.md"