@streamoid/ui 0.6.17 → 0.6.19
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 +35 -18
- package/dist/docs/AGENTS.md +325 -0
- package/dist/docs/CreditWarningBanner.md +305 -0
- package/dist/docs/InvoiceHistoryMobile.md +222 -0
- package/dist/docs/ScAccess.md +259 -0
- package/dist/docs/ScAppCard.md +244 -0
- package/dist/docs/ScAppCardForCopilot.md +230 -0
- package/dist/docs/ScAppCardV3.md +273 -0
- package/dist/docs/ScAppField.md +308 -0
- package/dist/docs/ScAppListingCard.md +271 -0
- package/dist/docs/ScAppSwitchPanel.md +286 -0
- package/dist/docs/ScAppcardLogos.md +226 -0
- package/dist/docs/ScArtifaxInvite.md +262 -0
- package/dist/docs/ScArtifaxSidebar.md +330 -0
- package/dist/docs/ScAskAgentButton.md +307 -0
- package/dist/docs/ScBadges.md +261 -0
- package/dist/docs/ScBeacon.md +244 -0
- package/dist/docs/ScBillingHistoryHeader.md +210 -0
- package/dist/docs/ScBillingHistoryTableList.md +243 -0
- package/dist/docs/ScBillingLogsTableHeader.md +212 -0
- package/dist/docs/ScBillingLogsTableList.md +251 -0
- package/dist/docs/ScBriefCard.md +255 -0
- package/dist/docs/ScButton.md +251 -0
- package/dist/docs/ScCalendar.md +268 -0
- package/dist/docs/ScCalendarDateComps.md +264 -0
- package/dist/docs/ScCatalogixInvite.md +345 -0
- package/dist/docs/ScCatalogixSidebar.md +337 -0
- package/dist/docs/ScCatalogixStoreHeader.md +246 -0
- package/dist/docs/ScCatalogixStoreTableList.md +316 -0
- package/dist/docs/ScCheckField.md +233 -0
- package/dist/docs/ScCheckbox.md +272 -0
- package/dist/docs/ScCounter.md +235 -0
- package/dist/docs/ScCreditsUsageCard.md +247 -0
- package/dist/docs/ScCreditsUsageCardMobile.md +224 -0
- package/dist/docs/ScDefaultCard.md +269 -0
- package/dist/docs/ScDp.md +245 -0
- package/dist/docs/ScDrawer.md +318 -0
- package/dist/docs/ScFieldButton.md +255 -0
- package/dist/docs/ScFileField.md +268 -0
- package/dist/docs/ScGoogleSignIn.md +250 -0
- package/dist/docs/ScGuide.md +278 -0
- package/dist/docs/ScHDivider.md +210 -0
- package/dist/docs/ScHeader.md +222 -0
- package/dist/docs/ScImageField.md +253 -0
- package/dist/docs/ScInChatList.md +277 -0
- package/dist/docs/ScInChatMessage.md +205 -0
- package/dist/docs/ScInfoPopup.md +248 -0
- package/dist/docs/ScIntialProfileCover.md +233 -0
- package/dist/docs/ScInvoiceHistoryMobile.md +187 -0
- package/dist/docs/ScLogoUnit.md +232 -0
- package/dist/docs/ScMappingCard.md +241 -0
- package/dist/docs/ScMediaApproval.md +301 -0
- package/dist/docs/ScMediaSelect.md +310 -0
- package/dist/docs/ScMenuOptions.md +308 -0
- package/dist/docs/ScMobileBottomAction.md +252 -0
- package/dist/docs/ScMobileTopNav.md +279 -0
- package/dist/docs/ScModal.md +291 -0
- package/dist/docs/ScOnlyField.md +302 -0
- package/dist/docs/ScOnlyIcon.md +213 -0
- package/dist/docs/ScPagination.md +284 -0
- package/dist/docs/ScPairtext.md +287 -0
- package/dist/docs/ScPendingAction.md +238 -0
- package/dist/docs/ScPhtogenixInvite.md +275 -0
- package/dist/docs/ScPlanCard.md +302 -0
- package/dist/docs/ScPlanComparison.md +264 -0
- package/dist/docs/ScPlanDetailsCard.md +246 -0
- package/dist/docs/ScPlanDetailsCardMobile.md +240 -0
- package/dist/docs/ScPopUpMenu.md +224 -0
- package/dist/docs/ScProfile.md +234 -0
- package/dist/docs/ScProfileImageUpdate.md +261 -0
- package/dist/docs/ScProfileOptions.md +245 -0
- package/dist/docs/ScProfilePopup.md +396 -0
- package/dist/docs/ScProfileSettingsComp.md +250 -0
- package/dist/docs/ScProfileV2Mobile.md +216 -0
- package/dist/docs/ScProgressBar.md +267 -0
- package/dist/docs/ScQuickPrompt.md +277 -0
- package/dist/docs/ScRadio.md +228 -0
- package/dist/docs/ScReferralCardMobile.md +226 -0
- package/dist/docs/ScReferralTableHeader.md +260 -0
- package/dist/docs/ScReferralTableList.md +293 -0
- package/dist/docs/ScRole.md +226 -0
- package/dist/docs/ScRoleMobile.md +199 -0
- package/dist/docs/ScSelect.md +270 -0
- package/dist/docs/ScSelection.md +256 -0
- package/dist/docs/ScSelectionList.md +272 -0
- package/dist/docs/ScSelectionPill.md +240 -0
- package/dist/docs/ScSelectionPillGroup.md +302 -0
- package/dist/docs/ScSettingsNav.md +212 -0
- package/dist/docs/ScSettingsTabComp.md +260 -0
- package/dist/docs/ScSideBarLogoUnit.md +340 -0
- package/dist/docs/ScSidebar.md +243 -0
- package/dist/docs/ScSidebarIcons.md +232 -0
- package/dist/docs/ScSidebarMenu.md +283 -0
- package/dist/docs/ScSidebarProfile.md +231 -0
- package/dist/docs/ScSidebarSwitchMenu.md +258 -0
- package/dist/docs/ScSlider.md +194 -0
- package/dist/docs/ScStoreCard.md +252 -0
- package/dist/docs/ScStrLogo.md +253 -0
- package/dist/docs/ScStreamoidWordmark.md +302 -0
- package/dist/docs/ScSubAgent.md +226 -0
- package/dist/docs/ScTabComp.md +308 -0
- package/dist/docs/ScTabField.md +258 -0
- package/dist/docs/ScTabSwitcher.md +307 -0
- package/dist/docs/ScTableHeader.md +261 -0
- package/dist/docs/ScTableList.md +301 -0
- package/dist/docs/ScTableListMobile.md +282 -0
- package/dist/docs/ScTabs.md +268 -0
- package/dist/docs/ScTaxonomyPill.md +263 -0
- package/dist/docs/ScTextArea.md +259 -0
- package/dist/docs/ScTextField.md +324 -0
- package/dist/docs/ScThinkingStepIcon.md +249 -0
- package/dist/docs/ScTodoList.md +288 -0
- package/dist/docs/ScToggleSwitch.md +229 -0
- package/dist/docs/ScUsageHistoryMobile.md +194 -0
- package/dist/docs/ScVDivider.md +215 -0
- package/dist/docs/ScValueMappingL1.md +256 -0
- package/dist/docs/ScVersion.md +251 -0
- package/dist/docs/ScWorkspace.md +233 -0
- package/dist/docs/ScWorkspaceAccountMenu.md +115 -0
- package/dist/docs/ScWorkspaceCard.md +234 -0
- package/dist/docs/ScWorkspaceSettingsMobile.md +265 -0
- package/dist/docs/ScWorkspaceSwitchCard.md +312 -0
- package/dist/docs/ScWorkspaceSwitchMobile.md +241 -0
- package/dist/docs/ScWorkspaceSwitchMobileV2.md +278 -0
- package/dist/docs/StreamoidSidebar.md +413 -0
- package/dist/docs/StreamoidWorkspaceSwitcher.md +307 -0
- package/dist/docs/UsageHistoryMobile.md +235 -0
- package/dist/docs/components.json +4931 -0
- package/dist/index.css +361 -36
- package/dist/index.d.mts +213 -88
- package/dist/index.d.ts +213 -88
- package/dist/index.js +2486 -1629
- package/dist/index.mjs +2487 -1620
- package/package.json +5 -3
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
---
|
|
2
|
+
component: ScGoogleSignIn
|
|
3
|
+
package: "@streamoid/ui"
|
|
4
|
+
category: actions
|
|
5
|
+
status: legacy
|
|
6
|
+
renders: div
|
|
7
|
+
tags: [google, sign-in, oauth, sso, login, auth, social-login]
|
|
8
|
+
related: [ScButton, ScTextField]
|
|
9
|
+
do_not_confuse_with: [ScButton, ScAppSwitchPanel, ScAppCard]
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# ScGoogleSignIn
|
|
13
|
+
|
|
14
|
+
**The "Sign in with Google" row.** A raised 1rem-radius bar: 24px logo image on the
|
|
15
|
+
left, "Sign in with Google" in md/500 on the right, inverting to a white fill with
|
|
16
|
+
dark text on hover. That is the whole component — 30 lines, no auth logic.
|
|
17
|
+
|
|
18
|
+
> **status: legacy.** It ships a **broken default image path**
|
|
19
|
+
> (`googleLogoSrc = "google-logo0.png"`, an asset the package does not contain), has
|
|
20
|
+
> **no `cursor: pointer`**, and is not a button in any semantic sense. CXO's login
|
|
21
|
+
> screen hand-rolls the identical row rather than importing this. Use it only if you
|
|
22
|
+
> pass `googleLogoSrc` and add your own semantics — otherwise copy the CXO pattern
|
|
23
|
+
> (see "In the wild").
|
|
24
|
+
|
|
25
|
+
## TL;DR for agents
|
|
26
|
+
|
|
27
|
+
- **Reach for it when:** you want the Figma-exact Google row *and* you already have
|
|
28
|
+
the "G" asset in your app's bundle.
|
|
29
|
+
- **Don't reach for it when:** you want a real button (→ `ScButton`), or you want
|
|
30
|
+
Google's own rendered button (→ Google Identity Services' `renderButton`, which is
|
|
31
|
+
what Google's brand terms actually expect).
|
|
32
|
+
- **Four things that will bite you:**
|
|
33
|
+
1. ⚠️ `googleLogoSrc` defaults to **`"google-logo0.png"`** — a bare relative path.
|
|
34
|
+
The package ships no such file, so the default renders a broken image.
|
|
35
|
+
2. `hover` is the **string** `"true"` / `"false"`, not a boolean.
|
|
36
|
+
3. **No `cursor: pointer`, no `role`, no `tabIndex`, no keyboard.** It looks
|
|
37
|
+
clickable and behaves like a `div`.
|
|
38
|
+
4. The `<img>` has **no `alt`** at all.
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## 1. How to use it
|
|
43
|
+
|
|
44
|
+
### Import
|
|
45
|
+
|
|
46
|
+
```tsx
|
|
47
|
+
import { ScGoogleSignIn } from "@streamoid/ui";
|
|
48
|
+
import "@streamoid/ui/dist/index.css"; // once, at your app root
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Minimal usage
|
|
52
|
+
|
|
53
|
+
The minimum that isn't broken — your own asset, plus the semantics the component
|
|
54
|
+
omits:
|
|
55
|
+
|
|
56
|
+
```tsx
|
|
57
|
+
import googleLogo from "../assets/google-logo.png";
|
|
58
|
+
|
|
59
|
+
<ScGoogleSignIn
|
|
60
|
+
googleLogoSrc={googleLogo}
|
|
61
|
+
role="button"
|
|
62
|
+
tabIndex={0}
|
|
63
|
+
aria-label="Sign in with Google"
|
|
64
|
+
style={{ cursor: "pointer", width: "100%" }}
|
|
65
|
+
onClick={handleGoogleSignIn}
|
|
66
|
+
onKeyDown={(e) => { if (e.key === "Enter" || e.key === " ") handleGoogleSignIn(); }}
|
|
67
|
+
/>
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Props
|
|
71
|
+
|
|
72
|
+
| Prop | Type | Default | Notes |
|
|
73
|
+
|---|---|---|---|
|
|
74
|
+
| `googleLogoSrc` | `string` | `"google-logo0.png"` | ⚠️ **Broken default.** A relative path resolved against the current page URL; no such asset is published in `@streamoid/ui`. Always pass an imported asset or an absolute URL. |
|
|
75
|
+
| `signInText` | `string` | `"Sign in with Google"` | Rendered with a trailing space in the text node. Localise here if you must. |
|
|
76
|
+
| `hover` | `"false"` \| `"true"` | `"false"` | ⚠️ **String union, not boolean.** `"true"` force-renders the inverted (white fill / dark text) skin, for Figma parity and screenshots. Real `:hover` already works — don't wire this to mouse handlers. |
|
|
77
|
+
| `className` | `string` | – | Concatenated between the root class and the variant class. |
|
|
78
|
+
| `...props` | `HTMLAttributes<HTMLDivElement>` | – | Spread onto the root `div`. **This is how you attach `onClick`, `role`, `tabIndex`, `style` and `aria-label`** — there are no dedicated props for any of them. |
|
|
79
|
+
|
|
80
|
+
No `onClick` prop, no `loading`, no `disabled`, no `size`, no `variant`.
|
|
81
|
+
|
|
82
|
+
### Recipes
|
|
83
|
+
|
|
84
|
+
```tsx
|
|
85
|
+
// Under an email field, above/below a divider — the real login layout
|
|
86
|
+
<ScTextField label="Work email" value={email} onChange={(e) => setEmail(e.target.value)} />
|
|
87
|
+
<ScButton text="Continue" variant="mono" size="md" onClick={handleLogin} />
|
|
88
|
+
<span>or</span>
|
|
89
|
+
<ScGoogleSignIn
|
|
90
|
+
googleLogoSrc={googleLogo}
|
|
91
|
+
role="button"
|
|
92
|
+
tabIndex={0}
|
|
93
|
+
aria-label="Sign in with Google"
|
|
94
|
+
style={{ cursor: "pointer", width: "100%" }}
|
|
95
|
+
onClick={startGoogleFlow}
|
|
96
|
+
/>
|
|
97
|
+
|
|
98
|
+
// Screenshot / Figma-parity render of the hover skin
|
|
99
|
+
<ScGoogleSignIn googleLogoSrc={googleLogo} hover="true" />
|
|
100
|
+
|
|
101
|
+
// Blocking double-submits — the component has no loading/disabled state
|
|
102
|
+
<ScGoogleSignIn
|
|
103
|
+
googleLogoSrc={googleLogo}
|
|
104
|
+
signInText={busy ? "Signing in…" : "Sign in with Google"}
|
|
105
|
+
style={{ cursor: busy ? "default" : "pointer", opacity: busy ? 0.6 : 1 }}
|
|
106
|
+
onClick={busy ? undefined : startGoogleFlow}
|
|
107
|
+
/>
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
## 2. Where to use it
|
|
113
|
+
|
|
114
|
+
- **The login / sign-up screen**, as the social alternative under the email +
|
|
115
|
+
OTP path. That is the only surface it was drawn for.
|
|
116
|
+
- Potentially a **re-auth or "connect Google" row** in settings — but nothing renders
|
|
117
|
+
it there today.
|
|
118
|
+
|
|
119
|
+
It composes nothing (no `ScButton` inside — the fill, radius and typography are its
|
|
120
|
+
own CSS), so it does **not** inherit `ScButton`'s hover, focus ring or keyboard
|
|
121
|
+
behaviour.
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
## 3. When to use it
|
|
126
|
+
|
|
127
|
+
### Use it when
|
|
128
|
+
|
|
129
|
+
- You need the exact Figma Google row and you're supplying the logo asset yourself.
|
|
130
|
+
- You are comfortable adding `role`/`tabIndex`/`cursor` at the call site.
|
|
131
|
+
|
|
132
|
+
### Don't use it — reach for this instead
|
|
133
|
+
|
|
134
|
+
| Situation | Use instead |
|
|
135
|
+
|---|---|
|
|
136
|
+
| Any ordinary action, including "Continue with email" | `ScButton` (`variant="mono" \| "outline"`) |
|
|
137
|
+
| A button with an icon on the left | `ScButton styleVariant="icon-left" icon={…}` — gets you real keyboard activation, focus ring, `loading`, and `state="disabled"` |
|
|
138
|
+
| Google's *officially rendered* button (brand-compliant, handles One Tap) | `google.accounts.id.renderButton()` from Google Identity Services — CXO already loads that script for the credential flow |
|
|
139
|
+
| Any other SSO provider (Microsoft, Apple, SAML) | `ScButton` with your own icon — there is no `ScMicrosoftSignIn`, and this component's copy/logo are Google-specific |
|
|
140
|
+
| Switching between Streamoid apps | `ScAppSwitchPanel` / `ScAppSwitchRow` / `ScAppCard*` — unrelated, despite also being logo + label rows |
|
|
141
|
+
|
|
142
|
+
### Don't confuse with
|
|
143
|
+
|
|
144
|
+
| You may actually want | Not this |
|
|
145
|
+
|---|---|
|
|
146
|
+
| `ScButton` — the one button primitive: real keyboard activation, `variant` × `type` × `size` × `state`, `loading`, tokenised focus | `ScGoogleSignIn` is a styled `div` with a hover skin and nothing else |
|
|
147
|
+
| `hover={true}` (boolean) | It's `hover="true"` — a string. `hover={true}` is a type error. |
|
|
148
|
+
| `@streamoid/icons` for the G mark | There is **no** `SiconGoogle`. The logo must come from your app's assets. |
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
## 4. Why to use it
|
|
153
|
+
|
|
154
|
+
Thin, but real:
|
|
155
|
+
|
|
156
|
+
- **Token-correct inversion.** The rest state is `surface-raised` with
|
|
157
|
+
`text-and-icons-primary`; hover flips to `surface-inverse` + `text-and-icons-inverse`,
|
|
158
|
+
so it stays legible in light mode. The CXO hand-rolled copy instead hardcodes
|
|
159
|
+
`isDark ? "#f5f5f5" : "#ffffff"` for the label — exactly the bug the DS avoids.
|
|
160
|
+
- **Figma parity** on the numbers you'd otherwise guess: 1rem padding, 0.625rem gap,
|
|
161
|
+
`radius-3xl`, 24px square logo, md/500 label.
|
|
162
|
+
- **One place to change** if the auth row's look is ever revised.
|
|
163
|
+
|
|
164
|
+
What it does **not** give you: semantics, keyboard access, a cursor, an asset, a
|
|
165
|
+
loading state, or brand compliance. If you're not getting the token inversion for
|
|
166
|
+
free, `ScButton` is the better trade.
|
|
167
|
+
|
|
168
|
+
---
|
|
169
|
+
|
|
170
|
+
## Gotchas
|
|
171
|
+
|
|
172
|
+
**1. The default logo is a broken image.**
|
|
173
|
+
|
|
174
|
+
```tsx
|
|
175
|
+
// WRONG — <img src="google-logo0.png"> resolved against the current route; 404s
|
|
176
|
+
<ScGoogleSignIn onClick={signIn} />
|
|
177
|
+
|
|
178
|
+
// RIGHT — bundle your own asset
|
|
179
|
+
import googleLogo from "../assets/google-logo.png";
|
|
180
|
+
<ScGoogleSignIn googleLogoSrc={googleLogo} onClick={signIn} />
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
**2. `hover` is a string union.** `hover={true}` fails to compile; `hover` is for
|
|
184
|
+
design parity, not for reacting to the mouse. And `hover="false"` produces **no**
|
|
185
|
+
CSS class (there is no `.hover-false` rule), so the root class list contains the
|
|
186
|
+
literal `undefined` — as it also does when `className` is omitted, which is
|
|
187
|
+
concatenated unguarded. Don't assert on exact class strings.
|
|
188
|
+
|
|
189
|
+
**3. It is not a button.** Root is a bare `<div>`: no `role="button"`, no `tabIndex`,
|
|
190
|
+
no Enter/Space handling, no focus ring, and — because the CSS has no `cursor` rule —
|
|
191
|
+
**the pointer stays an arrow** even though the background changes on hover. All of
|
|
192
|
+
that has to be supplied through the DOM spread.
|
|
193
|
+
|
|
194
|
+
**4. The `<img>` has no `alt` attribute.** Screen readers get an unlabelled image
|
|
195
|
+
inside an unlabelled div. Pass `aria-label` on the root.
|
|
196
|
+
|
|
197
|
+
**5. No `loading` and no `disabled`.** OAuth redirects are slow and this will happily
|
|
198
|
+
fire twice. Gate `onClick` yourself.
|
|
199
|
+
|
|
200
|
+
**6. No width.** The root is `display: flex` with `justify-content: center` and no
|
|
201
|
+
`width`, so it shrinks to its content. Login screens want full width — pass
|
|
202
|
+
`style={{ width: "100%" }}` or a `className`.
|
|
203
|
+
|
|
204
|
+
**7. `signInText` renders with a trailing space** (`{signInText} `), so
|
|
205
|
+
`textContent` is `"Sign in with Google "` in tests.
|
|
206
|
+
|
|
207
|
+
**8. Google brand terms.** Google's identity guidelines constrain the mark, the
|
|
208
|
+
wording and the button's proportions. This component neither ships the asset nor
|
|
209
|
+
enforces the geometry, so compliance is entirely on the caller. If that matters,
|
|
210
|
+
render Google's own button via Google Identity Services instead.
|
|
211
|
+
|
|
212
|
+
---
|
|
213
|
+
|
|
214
|
+
## In the wild
|
|
215
|
+
|
|
216
|
+
_No host render site found — used by the agent runtime / composed internally._
|
|
217
|
+
|
|
218
|
+
Neither applies, so precisely: **nothing imports it.** Its only render is the DS
|
|
219
|
+
gallery, via the name→component map at `apps/docs/utils/componentMap.tsx:13`; the
|
|
220
|
+
gallery's usage snippet (`apps/docs/utils/componentData.ts:475`) reproduces the broken
|
|
221
|
+
`googleLogoSrc="google-logo0.png"` default. The surface it belongs to already exists
|
|
222
|
+
and hand-rolls the row — this is the pattern to copy or to migrate (abridged; the
|
|
223
|
+
real markup nests two more flex `div`s):
|
|
224
|
+
|
|
225
|
+
```tsx
|
|
226
|
+
// cxo-dashboard src/app/components/login-content.tsx:886
|
|
227
|
+
<div
|
|
228
|
+
className="w-full shrink-0 cursor-pointer"
|
|
229
|
+
style={googleButtonStyle}
|
|
230
|
+
onClick={handleGoogleSignIn}
|
|
231
|
+
>
|
|
232
|
+
{/* … flex wrappers … */}
|
|
233
|
+
<img alt="Google" src={imgGoogleLogo} /* 24×24 */ />
|
|
234
|
+
<p className="whitespace-nowrap">Sign in with Google</p>
|
|
235
|
+
</div>
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
Same geometry, same copy — plus the `cursor-pointer` and `alt` this component is
|
|
239
|
+
missing, and its own bundled PNG (`src/assets/e01edaaf…52.png`, imported at
|
|
240
|
+
`login-content.tsx:23`), which is where the logo has to come from either way.
|
|
241
|
+
|
|
242
|
+
---
|
|
243
|
+
|
|
244
|
+
## Related
|
|
245
|
+
|
|
246
|
+
- `ScButton` — the real button primitive; prefer it for every other action, and for
|
|
247
|
+
other SSO providers.
|
|
248
|
+
- `ScTextField` — the email field this row sits under on the login screen.
|
|
249
|
+
- `@streamoid/icons` — check `packages/icons/ICONS.md`; there is no Google mark there,
|
|
250
|
+
so the asset stays in your app.
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
---
|
|
2
|
+
component: ScGuide
|
|
3
|
+
package: "@streamoid/ui"
|
|
4
|
+
category: overlays
|
|
5
|
+
status: stable
|
|
6
|
+
renders: div
|
|
7
|
+
tags: [guide, tour, walkthrough, onboarding, coachmark, callout, popover-card, next, skip]
|
|
8
|
+
related: [ScHDivider, ScInfoPopup, ScBeacon, ScModal, ScButton]
|
|
9
|
+
do_not_confuse_with: [ScInfoPopup, ScModal, ScDrawer, ScBeacon, ScPopUpMenu]
|
|
10
|
+
used_by: [cxo]
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
# ScGuide
|
|
14
|
+
|
|
15
|
+
**The guided-tour step card.** A fixed-width 261px panel: title, description, a
|
|
16
|
+
divider, then a `Skip` / `Next →` action row with the CXO gradient on the Next label
|
|
17
|
+
and an optional centred "1 of 3" counter. It is **only the card** — positioning,
|
|
18
|
+
anchoring, the spotlight and the portal are the host's job.
|
|
19
|
+
|
|
20
|
+
## TL;DR for agents
|
|
21
|
+
|
|
22
|
+
- **Reach for it when:** you are building a product walkthrough / coachmark sequence
|
|
23
|
+
and need the step card.
|
|
24
|
+
- **Don't reach for it when:** you want a "?" tooltip (→ `ScInfoPopup`), a blocking
|
|
25
|
+
dialog (→ `ScModal`), a pulsing "new" dot (→ `ScBeacon`), or a menu (→ `ScPopUpMenu`).
|
|
26
|
+
- **Four things that will bite you:**
|
|
27
|
+
1. It is **not** a popover: no anchor, no arrow, no portal, no backdrop, and no
|
|
28
|
+
`position: fixed`/`absolute` of its own. You position it (CXO uses
|
|
29
|
+
`position: fixed` + `createPortal`).
|
|
30
|
+
2. **`title` defaults to `"Title"` and `description` to `"Description"`** — the
|
|
31
|
+
placeholders ship if you forget.
|
|
32
|
+
3. Width is a hardcoded **16.3125rem (261px)** with no `width` prop.
|
|
33
|
+
4. The Next label is **gradient-clipped text** (hardcoded `#d91536 → #ee5e3a`, not
|
|
34
|
+
tokens), so any colour you set on a custom `nextLabel` node is erased.
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## 1. How to use it
|
|
39
|
+
|
|
40
|
+
### Import
|
|
41
|
+
|
|
42
|
+
```tsx
|
|
43
|
+
import { ScGuide } from "@streamoid/ui";
|
|
44
|
+
import "@streamoid/ui/dist/index.css"; // once, at your app root
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Minimal usage
|
|
48
|
+
|
|
49
|
+
```tsx
|
|
50
|
+
<ScGuide
|
|
51
|
+
title="Your workspace"
|
|
52
|
+
description="Everything you create lives here."
|
|
53
|
+
onSkip={endTour}
|
|
54
|
+
onNext={goToNextStep}
|
|
55
|
+
/>
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Props
|
|
59
|
+
|
|
60
|
+
| Prop | Type | Default | Notes |
|
|
61
|
+
|---|---|---|---|
|
|
62
|
+
| `title` | `ReactNode` | `"Title"` | ⚠️ Real default. Rendered in a `<p>`, 16px/500, `word-break: break-word`. |
|
|
63
|
+
| `description` | `ReactNode` | `"Description"` | ⚠️ Real default. `<p>`, 14px/400, tertiary colour. |
|
|
64
|
+
| `skipLabel` | `ReactNode` | `"Skip"` | Left action's label. **Still used for width when `hideSkip`** — see Gotcha 5. |
|
|
65
|
+
| `nextLabel` | `ReactNode` | `"Next"` | Right action's label. Painted with the gradient via `background-clip: text`. |
|
|
66
|
+
| `nextIcon` | `ReactNode` | – (falls back to `<SiconRight size={16} strokeWidth={1.5} />` at render, via `nextIcon ?? …`) | ⚠️ Effectively defaulted, so you always get a chevron. Sits in a wrapper whose `color` is hardcoded `#ee5e3a`, so `currentColor` icons come out orange. |
|
|
67
|
+
| `stepLabel` | `ReactNode` | – | "1 of 3" counter. **Hidden entirely when omitted** (`!= null` check). Absolutely centred, `pointer-events: none`. |
|
|
68
|
+
| `onSkip` | `() => void` | – | Skip click. |
|
|
69
|
+
| `onNext` | `() => void` | – | Next click. |
|
|
70
|
+
| `hideSkip` | `boolean` | `false` | Replaces the Skip button with an `aria-hidden`, `visibility: hidden` spacer — the space is still reserved. |
|
|
71
|
+
| `hideNext` | `boolean` | `false` | Removes the Next button entirely (no spacer). |
|
|
72
|
+
| `disabled` | `boolean` | `false` | Sets `disabled` on both buttons and dims them to `opacity: .5`. No spinner. |
|
|
73
|
+
| `className` | `string` | – | Appended (guarded — no stray `"undefined"`). |
|
|
74
|
+
| `style` | `CSSProperties` | – | Applied to the root. Use it to override the fixed width. |
|
|
75
|
+
|
|
76
|
+
No other DOM props are accepted — the component does **not** spread `...props`, so
|
|
77
|
+
`onClick`, `data-*`, `aria-*` and `id` on the root are unavailable.
|
|
78
|
+
|
|
79
|
+
### What renders in each configuration
|
|
80
|
+
|
|
81
|
+
| Config | Left | Centre | Right |
|
|
82
|
+
|---|---|---|---|
|
|
83
|
+
| default | `<button>` Skip | — | `<button>` Next + chevron |
|
|
84
|
+
| `stepLabel="1 of 3"` | Skip | "1 of 3" (absolute, non-interactive) | Next |
|
|
85
|
+
| `hideSkip` | invisible spacer sized to `skipLabel` | `stepLabel` | Next |
|
|
86
|
+
| `hideNext` | Skip | `stepLabel` | — |
|
|
87
|
+
| `hideSkip hideNext` | invisible spacer | `stepLabel` | — (an empty-looking row) |
|
|
88
|
+
| `disabled` | Skip, dimmed + `disabled` | unchanged | Next, dimmed + `disabled` |
|
|
89
|
+
|
|
90
|
+
### Recipes
|
|
91
|
+
|
|
92
|
+
```tsx
|
|
93
|
+
// A step sequence with a counter, and "Done ✓" on the last step
|
|
94
|
+
<ScGuide
|
|
95
|
+
title={step.title}
|
|
96
|
+
description={step.description}
|
|
97
|
+
stepLabel={`${index + 1} of ${steps.length}`}
|
|
98
|
+
nextLabel={isLast ? "Done" : "Next"}
|
|
99
|
+
nextIcon={isLast ? <CheckIcon /> : undefined} // undefined → the default chevron
|
|
100
|
+
hideSkip={isLast}
|
|
101
|
+
onSkip={skipTour}
|
|
102
|
+
onNext={isLast ? completeTour : next}
|
|
103
|
+
/>
|
|
104
|
+
|
|
105
|
+
// Positioning it: the card has no anchoring of its own
|
|
106
|
+
createPortal(
|
|
107
|
+
<div style={{ position: "fixed", top: popoverTop, left: popoverLeft, zIndex: 10000 }}>
|
|
108
|
+
<ScGuide title={step.title} description={step.description} onSkip={skip} onNext={next} />
|
|
109
|
+
</div>,
|
|
110
|
+
document.body,
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
// Wider than 261px (there is no width prop)
|
|
114
|
+
<ScGuide style={{ width: 320 }} title="…" description="…" onNext={next} />
|
|
115
|
+
|
|
116
|
+
// Freeze the card while a step's side effect runs
|
|
117
|
+
<ScGuide title="…" description="…" disabled={isSaving} onNext={saveThenAdvance} />
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
## 2. Where to use it
|
|
123
|
+
|
|
124
|
+
- **CXO's onboarding walkthrough.** `src/app/components/guided-walkthrough.tsx` owns
|
|
125
|
+
the step list, resolves each step's target element, paints a spotlight cutout, and
|
|
126
|
+
portals a `ScGuide` next to the highlighted rect. That wrapper — not this card — is
|
|
127
|
+
where "Done + checkmark on the last step" logic lives.
|
|
128
|
+
- **Any first-run tour** over an existing screen: sidebar intro, new-feature callout,
|
|
129
|
+
multi-step "here's how this page works".
|
|
130
|
+
|
|
131
|
+
### What it composes
|
|
132
|
+
|
|
133
|
+
- `ScHDivider` between the header and the action row (always — no way to hide it).
|
|
134
|
+
- `SiconRight` as the default `nextIcon`.
|
|
135
|
+
- Two real `<button type="button">` elements (so it is safe inside a `<form>`).
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
## 3. When to use it
|
|
140
|
+
|
|
141
|
+
### Use it when
|
|
142
|
+
|
|
143
|
+
- The content is a **step in a sequence** with forward/skip affordances.
|
|
144
|
+
- You already have (or will build) the positioning + spotlight layer around it.
|
|
145
|
+
- The copy is short: a line of title and one or two lines of description at 261px wide.
|
|
146
|
+
|
|
147
|
+
### Don't use it — reach for this instead
|
|
148
|
+
|
|
149
|
+
| Situation | Use instead |
|
|
150
|
+
|---|---|
|
|
151
|
+
| Contextual "?" help on a field | `ScInfoPopup` |
|
|
152
|
+
| A blocking, focused task | `ScModal` |
|
|
153
|
+
| A side panel for create/edit | `ScDrawer` |
|
|
154
|
+
| A pulsing dot that says "new here" | `ScBeacon` (often paired *with* a guide) |
|
|
155
|
+
| A menu of choices | `ScPopUpMenu` + `ScMenuOptions` |
|
|
156
|
+
| A persistent page banner (e.g. low credits) | `CreditWarningBanner` (⚠️ exported without the `Sc` prefix) |
|
|
157
|
+
| An empty-state card with a CTA | your own card + `ScButton` — this one's actions are Skip/Next shaped |
|
|
158
|
+
| A confirm/cancel pair | `ScButton` ×2 — `ScGuide`'s actions are text links, not buttons visually |
|
|
159
|
+
|
|
160
|
+
### Don't confuse with
|
|
161
|
+
|
|
162
|
+
| You may actually want | Not this |
|
|
163
|
+
|---|---|
|
|
164
|
+
| `ScInfoPopup` — small anchored info popover | `ScGuide` is a 261px step card with a tour footer |
|
|
165
|
+
| `ScModal` — has a backdrop, traps focus, centres itself | `ScGuide` has none of that |
|
|
166
|
+
| `ScQuickPrompt` / `ScBriefCard` — chat-runtime suggestion cards | Different family (agent runtime) |
|
|
167
|
+
|
|
168
|
+
---
|
|
169
|
+
|
|
170
|
+
## 4. Why to use it
|
|
171
|
+
|
|
172
|
+
- **The action row is the fiddly part and it's already right.** Skip left, Next right,
|
|
173
|
+
counter dead-centre regardless of the two buttons' differing widths (absolute
|
|
174
|
+
centring), and the counter is `pointer-events: none` so it can't swallow a click
|
|
175
|
+
aimed at the row.
|
|
176
|
+
- **`hideSkip` keeps the layout stable.** The invisible spacer means the Next button
|
|
177
|
+
doesn't jump left on the final step — the single most common polish bug in a tour.
|
|
178
|
+
- **Real `<button type="button">` elements** with a working `disabled` state: keyboard
|
|
179
|
+
reachable, no accidental form submits (unlike `ScButton`, which is a `div`).
|
|
180
|
+
- **Token-styled surface**: `--alias-surface-subtleraised` + `--radius-3xl` + the DS
|
|
181
|
+
divider, so the card sits correctly on both the dark and light canvas.
|
|
182
|
+
- **The gradient is centralised.** The "CXO Linear 2" ramp appears identically here and
|
|
183
|
+
nowhere else at the call site, so a brand change is one file.
|
|
184
|
+
|
|
185
|
+
---
|
|
186
|
+
|
|
187
|
+
## Gotchas
|
|
188
|
+
|
|
189
|
+
**1. It is not a popover.** No anchor, arrow, portal, z-index or backdrop; the root's
|
|
190
|
+
only positioning is `position: relative` (a containing block for the absolutely-centred
|
|
191
|
+
`stepLabel`), never `fixed`/`absolute`. Dropped into normal flow it renders as a
|
|
192
|
+
261px-wide block where you put it.
|
|
193
|
+
|
|
194
|
+
```tsx
|
|
195
|
+
// WRONG — appears in the document flow, not next to the thing it describes
|
|
196
|
+
<ScGuide title="Your workspace" description="…" onNext={next} />
|
|
197
|
+
|
|
198
|
+
// RIGHT — you own positioning
|
|
199
|
+
createPortal(
|
|
200
|
+
<div style={{ position: "fixed", top, left, zIndex: 10000 }}>
|
|
201
|
+
<ScGuide title="Your workspace" description="…" onNext={next} />
|
|
202
|
+
</div>,
|
|
203
|
+
document.body,
|
|
204
|
+
)
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
**2. `title` / `description` default to `"Title"` / `"Description"`.** There is no
|
|
208
|
+
required prop, so an incomplete call renders placeholder copy instead of failing.
|
|
209
|
+
|
|
210
|
+
**3. The Next label is gradient-clipped, so its colour is not yours.**
|
|
211
|
+
`-webkit-text-fill-color: transparent` + `background-clip: text` means any `color` on a
|
|
212
|
+
custom `nextLabel` element is invisible. And the icon wrapper is hardcoded `#ee5e3a`,
|
|
213
|
+
so an icon drawn with `currentColor` comes out orange in both themes.
|
|
214
|
+
|
|
215
|
+
```tsx
|
|
216
|
+
// POINTLESS — colour is erased by the gradient clip
|
|
217
|
+
<ScGuide nextLabel={<span style={{ color: "var(--alias-text-and-icons-primary)" }}>Done</span>} />
|
|
218
|
+
|
|
219
|
+
// RIGHT — pass plain text (or an icon you're happy to see orange)
|
|
220
|
+
<ScGuide nextLabel="Done" nextIcon={<CheckIcon />} />
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
**4. Fixed 261px width, and the card centres its own children.** No `width`/`size`
|
|
224
|
+
prop; override via `style` or `className`. Long titles wrap (`word-break: break-word`)
|
|
225
|
+
rather than truncate, so the card grows *taller*, never wider.
|
|
226
|
+
|
|
227
|
+
**5. `hideSkip` still reserves the Skip's width.** The spacer renders `skipLabel` with
|
|
228
|
+
`visibility: hidden`, so a long `skipLabel` you thought was hidden still pushes the
|
|
229
|
+
layout. Shorten `skipLabel` if that matters.
|
|
230
|
+
|
|
231
|
+
**6. `stepLabel` is out of flow.** Absolutely centred with `pointer-events: none`, so a
|
|
232
|
+
long counter can overlap Skip or Next instead of pushing them apart. Keep it to
|
|
233
|
+
"N of M".
|
|
234
|
+
|
|
235
|
+
**7. `disabled` only dims.** Both buttons get `disabled` + `opacity: .5`; there is no
|
|
236
|
+
loading/spinner state. If the Next action is async, hold `disabled` yourself for the
|
|
237
|
+
duration.
|
|
238
|
+
|
|
239
|
+
**8. Root DOM props are not spread.** Only `className` and `style` reach the root — no
|
|
240
|
+
`onClick`, `data-testid`, `role="dialog"`, `aria-labelledby`. Wrap it if you need a
|
|
241
|
+
dialog role or a test hook.
|
|
242
|
+
|
|
243
|
+
**9. The divider is unconditional.** `ScHDivider` always renders between header and
|
|
244
|
+
actions, even with `hideSkip hideNext` (which leaves a rule above an empty row).
|
|
245
|
+
|
|
246
|
+
**10. Nothing dismisses it.** No close button, no Esc handling, no outside-click. The
|
|
247
|
+
host owns the tour lifecycle — CXO's wrapper wires Skip → `onSkip` and the last step's
|
|
248
|
+
Next → `onComplete`.
|
|
249
|
+
|
|
250
|
+
---
|
|
251
|
+
|
|
252
|
+
## In the wild
|
|
253
|
+
|
|
254
|
+
```tsx
|
|
255
|
+
// cxo-dashboard src/app/components/guided-walkthrough.tsx:225
|
|
256
|
+
<ScGuide
|
|
257
|
+
title={step.title}
|
|
258
|
+
description={step.description}
|
|
259
|
+
nextLabel={nextLabel} // step.nextLabel ?? (isLast ? "Done" : "Next")
|
|
260
|
+
nextIcon={nextIcon} // step.nextIcon ?? (isLast ? <DoneIcon /> : undefined)
|
|
261
|
+
onSkip={handleSkip}
|
|
262
|
+
onNext={handleNext}
|
|
263
|
+
/>
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
(That call site wraps the card in a `position: fixed` div inside a
|
|
267
|
+
`createPortal(…, document.body)` with `zIndex: 10000` — the canonical way to use it.)
|
|
268
|
+
|
|
269
|
+
---
|
|
270
|
+
|
|
271
|
+
## Related
|
|
272
|
+
|
|
273
|
+
- `ScHDivider` — rendered inside the card; don't add a second one.
|
|
274
|
+
- `ScInfoPopup` — anchored contextual help, for one-off "?" hints.
|
|
275
|
+
- `ScBeacon` — the pulsing dot that usually *precedes* a guide step.
|
|
276
|
+
- `ScModal` / `ScDrawer` — when the step should block or slide in instead.
|
|
277
|
+
- `ScButton` — for real CTAs; `ScGuide`'s Skip/Next are text-link buttons by design.
|
|
278
|
+
- `@streamoid/icons` — `SiconRight` is the default `nextIcon`; see `packages/icons/ICONS.md`.
|