@scalio-cloud/ui 0.11.0 → 0.12.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,11 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.12.0
4
+ - New manual button in `AppHeader` (prop `docsUrl`): a book icon between theme and support that opens the product
5
+ manual in a new tab; `DocsButton` on its own, `manualUrl(product, { stage, lang })` builds the address
6
+ (`devdocs.<domain>` on dev and test, `docs.<domain>` on production, demo and self-hosted, German under `/de/`).
7
+ **Visible change** only in apps that pass `docsUrl`; no token changes.
8
+
3
9
  ## 0.11.0
4
10
  - Self-hosted support (ADR 0022, T11; needs scalio-connect 0.7.0 in the backend): `supportApi` gets `status()` and
5
11
  `setButtonHidden(hidden)`. The support drawer asks `status()` first; a self-hosted installation without a
package/README.md CHANGED
@@ -45,7 +45,7 @@ support, avatar.
45
45
  A part only appears when the app passes its handler (Compass has no dark mode, so no `onThemeChange`).
46
46
 
47
47
  ```jsx
48
- import { AppHeader, PageHeader, VersionTag } from "@scalio-cloud/ui/react";
48
+ import { AppHeader, PageHeader, VersionTag, manualUrl } from "@scalio-cloud/ui/react";
49
49
 
50
50
  <AppHeader
51
51
  brand={{ mark: "N", name: "Scalio Nexus", sub: "Dev" }} // or your own element
@@ -55,6 +55,7 @@ import { AppHeader, PageHeader, VersionTag } from "@scalio-cloud/ui/react";
55
55
  status={<ApiStatus />} // hidden below 768px
56
56
  lang={lang} onLangChange={setLang} // one click, shows the other language
57
57
  theme={theme} themeModes={["light", "dark", "system"]} onThemeChange={setTheme}
58
+ docsUrl={manualUrl("nexus", { stage, lang })} // book icon, opens the manual in a new tab
58
59
  onSupport={() => setSupportOpen(true)}
59
60
  user={{ name, email, role }} onLogout={logout}
60
61
  menuItems={[{ label: "Profil", icon: User, onSelect: () => nav("/profile"), testId: "profile-link" }]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scalio-cloud/ui",
3
- "version": "0.11.0",
3
+ "version": "0.12.0",
4
4
  "description": "Scalio Control Room design tokens, Tailwind preset, self-hosted fonts, the shared app header, sign-in layout, sidebar and page blocks",
5
5
  "license": "SEE LICENSE IN README.md",
6
6
  "private": false,
package/react/icons.js CHANGED
@@ -32,6 +32,7 @@ const LifeBuoy = icon([
32
32
  ["circle", { cx: 12, cy: 12, r: 4 }],
33
33
  ["path", { d: "m4.93 4.93 4.24 4.24M14.83 9.17l4.24-4.24M14.83 14.83l4.24 4.24M9.17 14.83l-4.24 4.24" }],
34
34
  ]);
35
+ const BookOpen = icon([["path", { d: "M12 7v14" }], ["path", { d: "M3 18a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h5a4 4 0 0 1 4 4 4 4 0 0 1 4-4h5a1 1 0 0 1 1 1v13a1 1 0 0 1-1 1h-6a3 3 0 0 0-3 3 3 3 0 0 0-3-3z" }]]);
35
36
  const LogOut = icon([["path", { d: "M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4M16 17l5-5-5-5M21 12H9" }]]);
36
37
  const Menu = icon([["path", { d: "M4 6h16M4 12h16M4 18h16" }]]);
37
38
  const Check = icon([["path", { d: "M20 6 9 17l-5-5" }]]);
@@ -99,5 +100,5 @@ const History = icon([
99
100
  ]);
100
101
  const CornerDownLeft = icon([["path", { d: "M20 4v7a4 4 0 0 1-4 4H4" }], ["path", { d: "m9 10-5 5 5 5" }]]);
101
102
 
102
- module.exports = { Sun, Moon, Monitor, LifeBuoy, LogOut, Menu, Check, X, Search, ChevronDown, Fingerprint, FileStack, ShieldCheck, Hammer, Activity,
103
+ module.exports = { Sun, Moon, Monitor, LifeBuoy, BookOpen, LogOut, Menu, Check, X, Search, ChevronDown, Fingerprint, FileStack, ShieldCheck, Hammer, Activity,
103
104
  Smartphone, KeyRound, Trash, Copy, Puzzle, Plus, Alert, History, CornerDownLeft };
package/react/index.js CHANGED
@@ -10,6 +10,7 @@ const TEST_IDS = {
10
10
  menu: "mobile-menu-btn",
11
11
  language: "language-toggle-btn",
12
12
  theme: "theme-toggle-btn",
13
+ docs: "docs-link-btn",
13
14
  support: "support-ticket-trigger",
14
15
  avatar: "user-avatar-btn",
15
16
  userName: "user-name",
@@ -233,6 +234,43 @@ function SupportButton({ onClick, lang, labels, testId }) {
233
234
  );
234
235
  }
235
236
 
237
+ // The customer manual of a product: devdocs.<domain> on our dev and test instances, docs.<domain> everywhere
238
+ // else (production, demo, self-hosted). German readers get the German version under /de/.
239
+ const DOCS_DOMAINS = {
240
+ nexus: "scalionexus.com",
241
+ citadel: "scaliocitadel.com",
242
+ foundry: "scaliofoundry.com",
243
+ rosetta: "scaliorosetta.com",
244
+ };
245
+
246
+ function manualUrl(product, opts) {
247
+ const o = opts || {};
248
+ const domain = DOCS_DOMAINS[product] || product;
249
+ if (!domain || domain.indexOf(".") < 0) return null;
250
+ const stage = String(o.stage || "").toLowerCase();
251
+ const host = (stage === "dev" || stage === "test" ? "devdocs." : "docs.") + domain;
252
+ return `https://${host}/${o.lang === "de" ? "de/" : ""}`;
253
+ }
254
+
255
+ // Opens the manual in a new tab.
256
+ function DocsButton({ href, lang, labels, testId }) {
257
+ if (!href) return null;
258
+ const L = labelsFor(lang, labels);
259
+ return h(
260
+ "a",
261
+ {
262
+ className: "scalio-btn scalio-btn-icon",
263
+ href,
264
+ target: "_blank",
265
+ rel: "noopener noreferrer",
266
+ title: L.docs,
267
+ "aria-label": L.docs,
268
+ "data-testid": testId || TEST_IDS.docs,
269
+ },
270
+ h(Icons.BookOpen, { className: "scalio-icon" })
271
+ );
272
+ }
273
+
236
274
  function Brand({ brand }) {
237
275
  if (!brand) return null;
238
276
  if (React.isValidElement(brand)) return brand;
@@ -249,7 +287,7 @@ function Brand({ brand }) {
249
287
  return h("div", { className: "scalio-brand" }, inner);
250
288
  }
251
289
 
252
- // Header: [menu] brand, left slot ... app actions, status, language, theme, support, avatar.
290
+ // Header: [menu] brand, left slot ... app actions, status, language, theme, manual, support, avatar.
253
291
  // The part from status to avatar has the same order in every app.
254
292
  function AppHeader(props) {
255
293
  const L = labelsFor(props.lang, props.labels);
@@ -284,6 +322,7 @@ function AppHeader(props) {
284
322
  labels: props.labels,
285
323
  testId: T.theme,
286
324
  }),
325
+ h(DocsButton, { href: props.docsUrl, lang: props.lang, labels: props.labels, testId: T.docs }),
287
326
  h(SupportButton, { onClick: props.onSupport, lang: props.lang, labels: props.labels, testId: T.support }),
288
327
  props.user
289
328
  ? h(UserMenu, {
@@ -326,6 +365,9 @@ module.exports = {
326
365
  LanguageToggle,
327
366
  ThemeToggle,
328
367
  SupportButton,
368
+ DocsButton,
369
+ manualUrl,
370
+ DOCS_DOMAINS,
329
371
  PageHeader,
330
372
  VersionTag,
331
373
  Icons,
package/react/labels.js CHANGED
@@ -8,6 +8,7 @@ const LABELS = {
8
8
  dark: "Dunkel",
9
9
  system: "System",
10
10
  support: "Support",
11
+ docs: "Handbuch (öffnet in neuem Tab)",
11
12
  logout: "Abmelden",
12
13
  userMenu: "Benutzermenü",
13
14
  menu: "Menü",
@@ -208,6 +209,7 @@ const LABELS = {
208
209
  dark: "Dark",
209
210
  system: "System",
210
211
  support: "Support",
212
+ docs: "Manual (opens in a new tab)",
211
213
  logout: "Sign out",
212
214
  userMenu: "User menu",
213
215
  menu: "Menu",
package/shell.css CHANGED
@@ -45,6 +45,7 @@
45
45
  }
46
46
  @media (min-width: 640px) { .scalio-btn { padding: 0 1rem; } }
47
47
  .scalio-btn:hover { background: hsl(var(--secondary)); }
48
+ a.scalio-btn { text-decoration: none; }
48
49
  .scalio-btn:focus-visible, .scalio-avatar:focus-visible, .scalio-menu-item:focus-visible {
49
50
  outline: 2px solid hsl(var(--ring)); outline-offset: 1px;
50
51
  }