@scalio-cloud/ui

中文文档
0.29.0发布于 2026/11/02SEE LICENSE IN README.mdCommonJS零依赖

Scalio Control Room design tokens, Tailwind preset, self-hosted fonts, the shared app header, sign-in layout, sidebar and page blocks

原始使用文档
文档目录16 节
  1. Use it in an app (CRA/craco or Vite)
  2. App header (React)
  3. Sidebar, switcher and page blocks (React)
  4. Settings page (React)
  5. Sign-in page and profile (React)
  6. User administration (React)
  7. Rollen und Microsoft Entra ( RoleAdmin , RoleSourceSettings…
  8. Setup wizard (React)
  9. Two-factor card and license panel (React)
  10. Gruppe System: Zurücksetzen and helpers (React)
  11. Benachrichtigungen: E-Mail-Versand und E-Mail-Vorlagen (Rea…
  12. Protokoll: Aktionen und Jobs (React)
  13. Command palette (React)
  14. Installing
  15. License
  16. Release

@scalio-cloud/ui

Shared look and feel for the Scalio apps (Nexus, Rosetta, Foundry, Citadel, Compass, later Pulse): the "Control Room" design tokens, a Tailwind preset and the fonts, served from the app itself. Spec: design/UISPEC.md in Scalio-Cloud/scalio-docs (single copy).

FileWhat it is
tokens.cssshadcn/ui CSS variables, light on :root, dark on .dark (Klein Blue #002FA7, radius 4px)
fonts.css@font-face for Chivo, IBM Plex Sans, JetBrains Mono (woff2, latin, SIL OFL 1.1). No Google Fonts request
base.cssbody / heading / code font families
shell.cssstyles of the app header, user menu, page header and version (scalio-* classes)
nav.csssidebar, phone drawer, switcher, context select, support drawer, page blocks
index.cssall of them in one import
react/shared app header for React (see below), no dependency except React
tailwind-preset.jscolors, fonts, radius, accordion animation, brand colors, h-switcher, h-header, w-sidebar
tokens.jsraw values for code without CSS (charts, e-mails, PDFs)

Use it in an app (CRA/craco or Vite)

// tailwind.config.js
module.exports = {
  presets: [require("@scalio-cloud/ui/tailwind-preset")],
  content: ["./src/**/*.{js,jsx,ts,tsx}", "./public/index.html"],
  plugins: [require("tailwindcss-animate")],
};
/* src/index.css, first line */
@import "@scalio-cloud/ui/index.css";
@tailwind base;
@tailwind components;
@tailwind utilities;

Then remove the app's own :root { --... } / .dark { --... } blocks and the Google Fonts @import / <link>. Classes like bg-primary, rounded-sm, font-display keep working unchanged.

App header (React)

Every app shows the same header on the right, always in this order: (app actions), status, language, theme, support, avatar. A part only appears when the app passes its handler (Compass has no dark mode, so no onThemeChange).

import { AppHeader, PageHeader, VersionTag, manualUrl } from "@scalio-cloud/ui/react";

<AppHeader
  brand={{ mark: "N", name: "Scalio Nexus", sub: "Dev" }}  // or your own element
  onMenu={() => setMobileNavOpen(true)}                     // hamburger below 768px
  left={<TenantSelect />}                                   // after the brand
  actions={<LocalePopover />}                               // right side, before the fixed group
  status={<ApiStatus />}                                    // hidden below 768px
  lang={lang} onLangChange={setLang}                        // one click, shows the other language
  theme={theme} themeModes={["light", "dark", "system"]} onThemeChange={setTheme}
  docsUrl={manualUrl("nexus", { stage, lang })}           // book icon, opens the manual in a new tab
  onSupport={() => setSupportOpen(true)}
  user={{ name, email, role }} onLogout={logout}
  menuItems={[{ label: "Profil", icon: User, onSelect: () => nav("/profile"), testId: "profile-link" }]}
  testIds={{ logout: "logout-button" }}                     // keep an app's existing test ids
/>
<PageHeader eyebrow="Verwaltung" title="Benutzer" subtitle="..." actions={<Button>Neu</Button>} />
<VersionTag version="1.2.4" commit="3f2a1bc" />              // bottom of the sidebar

Texts are built in for lang de and en and can be overridden with labels. Default test ids (language-toggle-btn, theme-toggle-btn, support-ticket-trigger, user-avatar-btn, logout-btn, ...) are in TEST_IDS. The components are plain CommonJS with React.createElement, so CRA, Vite and Jest need no extra config. Icons are inline (Lucide, ISC, see licenses/).

Sidebar, switcher and page blocks (React)

Every app has the same frame (UISPEC "Navigation"): switcher, header with the context select on the left, then the sidebar with search, at most one main action, "Übersicht", up to three sections and "Verwaltung" last (Protokoll, Konten und Rollen, Einstellungen). Profile and personal pages live only in the user menu. The app describes its navigation as data; the sidebar, the phone drawer and (later) the search read the same list.

import { NavLink } from "react-router-dom";
import { AppShell, Sidebar, ProductSwitcher, ContextSelect, SupportDrawer, useScalioApps,
  KpiTile, EmptyState, Loading, Notice, StatusBadge, LABELS } from "@scalio-cloud/ui/react";

const L = LABELS[lang];
const NAV = { sections: [
  { id: "main", items: [{ to: "/", label: L.overview, icon: LayoutGrid, end: true }] },
  { id: "compliance", label: t("Compliance"), items: [
    { to: "/drifts", label: t("Drifts"), icon: GitCompareArrows, badge: open || null, badgeTone: "error" },
  ] },
  { id: "admin", label: L.administration, items: [
    { to: "/audit", label: L.auditLog, icon: ScrollText },
    { to: "/accounts", label: L.accounts, icon: Users, hidden: !isAdmin },   // hidden items and empty sections disappear
    { to: "/settings", label: L.settings, icon: SlidersHorizontal },
  ] },
] };

const apps = useScalioApps(() => api.get("/apps").then((r) => r.data));   // once per page load
<AppShell
  switcher={apps.features.switcher && <ProductSwitcher current="citadel" links={apps.links} stage={apps.stage} sso={sso} lang={lang} />}
  header={<AppHeader ... onMenu={() => setDrawer(true)}
    left={<ContextSelect label={L.tenant} value={tenantId} options={tenants} onChange={select} hint={domain} testId="tenant-switcher" />} />}
  sidebar={<Sidebar nav={NAV} link={NavLink} lang={lang} version={VERSION} commit={COMMIT}
    search={{ onOpen: openPalette, shortcut: "⌘K" }} primaryAction={{ label: t("Scan starten"), icon: Play, onSelect: scan }} />}
  drawerOpen={drawer} onDrawerClose={() => setDrawer(false)}   // phones: same sidebar in a drawer (test ids with "m-")
  drawerTop={<ContextSelect ... testId="m-tenant-switcher" />}  // below 640px the header hides the select, the drawer shows it
>
  <Routes>...</Routes>
</AppShell>
<SupportDrawer open={supportOpen} onClose={...} lang={lang} client={supportApi(api)} page={location.pathname}
  context={[["URL", location.pathname], [L.tenant, current?.name]]} />

With client the drawer has the tabs "Neue Anfrage" (category, subject, description, technical details) and "Meine Tickets" (list, answers, reply, rating); tickets go to the central support desk in Compass (scalio-docs ADR 0022), the backend routes come from scalio-connect scalio_connect.support.web. supportApi(api) takes the app's axios instance. The settings card <SupportConnection client={supportApi(api)} lang={lang} /> connects the installation with a one-time code (admins). Without client the drawer keeps the old single form with onSubmit. supportApi(api, { ideas: true }) adds the tab "Ideen" (Scalio Ideen, ADR 0050; backend router scalio_connect.support.ideas_web.ideas_router, scalio-connect 0.8.0): ideas of the app with votes and comments and "Idee vorschlagen" with similar ideas; the ticket category "Wunsch" then moves there.

The settings card <BeaconConnection client={peersApi(api)} lang={lang} /> pairs the app with Scalio Beacon (address and one-time code from Beacon, Einstellungen -> Verbundene Apps); findings then go to Beacon. Backend: scalio-connect 0.8.0 peers.web.admin_router.

link is the router's NavLink (it gets to, end, a className function and onClick); without it the sidebar renders plain links and marks the item for activePath. Item test ids default to nav-<path> (nav-overview for /), or set testId. Page blocks: KpiTile (tone error = red number), EmptyState (children = next step), Loading, Notice (error red = Fehler, warn/important orange = wichtig, ok/success green = gute Meldung, info/action blue = Aktion; banner = full width), StatusBadge (tones neutral, running, ok, warn, error) and PageHeader with back for detail pages.

Pages that belong together under one menu item keep their own addresses: the item lists them in match ({ to: "/groups", label: "Gruppen", match: ["/teams", "/distribution-lists"] }, pass activePath with link) and each page shows <SubNav items={[{ to: "/groups", label: "Sicherheit", end: true }, ...]} link={NavLink} /> on top. An app with two work areas (Nexus Identity and HR) puts <WorkspaceSwitcher options value onSelect /> into the sidebar's top.

Page actions in the sidebar (from 0.18.0, UISPEC section 2): <Sidebar pageActions ... /> keeps Übersicht (the first item of a first section without heading) on top and adds a slot right below it. A page wraps its actions in <PageActions>; from 768px they move into the slot, on phones they stay in the page header. The slot takes no room on pages without actions; with actions it pushes the menu down.

<PageHeader title="Kontakte" actions={<PageActions><Button onClick={add}><Plus /> Kontakt</Button></PageActions>} />
<PageHeader title={doc.number} actions={<PageActions><Button variant="outline" asChild><Link to="/invoices"><ArrowLeft /> Zurück</Link></Button></PageActions>} />

Only actions that start something or lead away go there (Neu …, Import, Scannen, Zurück), at most two per page. Labels without "Neu"/"New": the + icon says it ("+ Kontakt", not "+ Neuer Kontakt"). Filters, search, tabs and Speichern stay on the page. Apps with their own sidebar place <ActionSlot /> themselves.

Settings page (React)

Every area of the settings has its own address (/settings/<area>); the app's router picks the area, the layout draws the sub-navigation, the crumb ("Einstellungen › Verbindungen") and the title. Groups general, connections, notifications, security and system get their names from LABELS and always sit in this order, the app's own groups (with label) come after "Allgemein". Hidden areas and empty groups disappear, below three areas (minItems) there is no sub-navigation. The search looks at label, hint, group and keywords; search={false} leaves it out, search={<OwnSearch />} puts the app's own search there.

import { SettingsLayout, SettingsSection } from "@scalio-cloud/ui/react";

const GROUPS = [
  { id: "general", items: [{ id: "general", to: "/settings/general", label: "Instanz", hint: "Name, Sprache, Zeitzone" }] },
  { id: "flows", label: "Abläufe", items: [{ id: "onboarding", to: "/settings/onboarding", label: "Onboarding" }] },
  { id: "connections", items: [{ id: "sync", to: "/settings/sync", label: "Synchronisierung", keywords: ["Intervall"] }] },
  { id: "system", items: [{ id: "updates", to: "/settings/updates", label: "Updates", hidden: saas }] },
];

<SettingsLayout groups={GROUPS} active={area} link={NavLink} onSelect={(i) => navigate(i.to)} lang={lang}>
  <SettingsSection id="cloud-sync" title="Cloud-Synchronisierung" description="Intervall und Umfang."
    footer={<Button onClick={save}>Speichern</Button>}>
    ...form fields (shadcn)...
  </SettingsSection>
</SettingsLayout>

Sign-in page and profile (React)

All sign-in pages use the same frame and order: Microsoft first, then "oder mit E-Mail", the password field with "Passwort vergessen?" below it, the submit button. Forgot password, reset, verify email and the two-factor step are their own pages in the same frame. Show only what the app can do (register only in Compass, demo accounts only in demo mode).

import { AuthLayout, AuthHeader, MicrosoftButton, AuthDivider, AuthMessage, ProfileSection } from "@scalio-cloud/ui/react";

<AuthLayout
  brand={{ mark: "N", name: "Scalio Nexus", claim: "Alle Konten aus einer Konsole verwalten.",
           features: ["Onboarding", "Ändern", "Offboarding"] }}
  lang={lang} onLangChange={setLang}                 // top right
  theme={theme} onThemeChange={setTheme}             // optional
  footer={<VersionTag version={v} />}                // optional
>
  <AuthHeader eyebrow="Zugang" title="Anmelden" subtitle="..." />
  <MicrosoftButton href="/api/auth/sso/login" lang={lang} disabled={!ssoReady} hint="..." />
  <AuthDivider lang={lang} />
  <form>...</form>
  <AuthMessage kind="error">{error}</AuthMessage>
</AuthLayout>

// Profile page: the same sections in every app, leave out what an app does not need.
<ProfileSection id="account" lang={lang}>...</ProfileSection>      // Konto
<ProfileSection id="appearance" lang={lang}>...</ProfileSection>   // Darstellung
<ProfileSection id="language" lang={lang}>...</ProfileSection>     // Sprache und Region
<ProfileSection id="security" lang={lang}>...</ProfileSection>     // Sicherheit
<ProfileSection id="connections" lang={lang}>...</ProfileSection>  // Verknüpfungen

Content of the sections (same in every app, reference Nexus): ProfileIdentity in Konto, ThemeChoice in Darstellung, LanguageChoice + TimezoneChoice in Sprache und Region, PasswordChange + SecurityFactors in Sicherheit. The app stores the time zone (POST /auth/preferences {timezone}), calls setUserTimezone() with the value from /auth/me and formats dates with timeZone: currentTz(). applyTheme(mode) sets the dark class for light, dark and system.

Other form fields and buttons (e.g. the name field) stay the app's own (shadcn), so they match the rest of the app.

User administration (React)

Konten und Rollen → Benutzer, the same in every app (ADR 0059). The backend follows scalio-docs interfaces/app-users.md; usersApi(api, {base: "/admins"}) for Nexus.

import { UserAdmin, usersApi, MicrosoftConnection } from "@scalio-cloud/ui/react";

const users = usersApi(api);   // once, outside the component
<UserAdmin client={users} me={user.id} lang={lang} roles={[{ value: "admin", label: t("Administrator") }, ...]}
  defaultRole="operator" signInModes={["microsoft", "password"]} microsoftEnabled={ssoOn} canLock
  accessColumn={{ label: t("Customers"), render: (u) => ... }} accessField={(value, onChange, role, u) => ...}
  notify={(text, tone) => toast[tone](text)} confirm={confirm} formatError={fmt} />

// Profile -> Verknüpfungen
<MicrosoftConnection linked={user.microsoft} canUnlink={user.has_password} onLink={...} onUnlink={...} lang={lang} />

Rollen und Microsoft Entra (RoleAdmin, RoleSourceSettings)

import { RoleAdmin, rolesApi, RoleSourceSettings } from "@scalio-cloud/ui/react";
const roles = rolesApi(api);                               // Nexus: rolesApi(api, { permissions: "/permissions" })
<RoleAdmin client={roles} lang={lang} notify={toast} confirm={confirm} onChange={reloadUsers} />
<RoleSourceSettings value={source} tenants onSave={({ source, tenants }) => api.put("/users/role-source", { source, tenant_ids: tenants })} lang={lang} />

The backend is scalio-platform roles_router (0.3.0). Built-in roles are read-only; own roles never get the permissions the app keeps for administrators.

Setup wizard (React)

Einrichtungsassistent after the first sign-in, the same in every app (ADR 0060, UISPEC section 3). The app writes only its steps; the backend follows scalio-docs interfaces/setup-wizard.md (GET/PUT /api/setup/wizard).

import { SetupWizard, SetupWizardButton, useSetupWizard, setupWizardApi } from "@scalio-cloud/ui/react";

const wizardClient = setupWizardApi(api);   // once, outside the component ({ base: "/wizard" } for another route)

function AppWizard() {
  const w = useSetupWizard({ client: wizardClient, enabled: isAdmin });   // opens by itself while needed
  const steps = [
    { id: "tenant", label: t("Tenant"), title: t("Connect Microsoft"), description: "...", content: <TenantsSection />,
      canNext: connected, onNext: async () => { /* save; a rejected promise keeps the step open */ } },
    { id: "mail", label: t("Email"), title: t("Email (SMTP)"), content: <SmtpForm />, optional: true },
  ];
  return <SetupWizard {...w.props} lang={lang} title={t("Set up Foundry")} subtitle={t("...")} steps={steps}
    success={{ title: t("Setup complete"), text: t("...") }} />;
}

// Einstellungen: open it again (admins only)
<SetupWizardButton lang={lang} />

Test IDs: setup-wizard, wizard-stepper(-item), wizard-step-{id}, wizard-next, wizard-back, wizard-skip, wizard-close, wizard-error, wizard-success, wizard-done, settings-open-wizard.

Two-factor card and license panel (React)

The same backend routes in every app, so the apps pass only their axios instance and a few helpers.

import { SecurityFactors, securityApi, LicensePanel, licenseApi } from "@scalio-cloud/ui/react";
import { QRCodeSVG } from "qrcode.react";
import { startRegistration, browserSupportsWebAuthn } from "@simplewebauthn/browser";

const security = securityApi(api, { startRegistration, browserSupportsWebAuthn });   // once, outside the component
const license = licenseApi(api);

// Profile -> Sicherheit: authenticator app, backup codes, passkeys. Hidden for accounts without a password.
<SecurityFactors client={security} lang={lang} product="Scalio Citadel" email={user.email}
  qr={(uri) => <QRCodeSVG value={uri} size={160} />} notify={(text, tone) => toast[tone](text)}
  confirm={confirm} onMfaChange={(on) => setUser({ ...user, mfa_enabled: on })} />

// Einstellungen -> System -> Lizenz (scalio-license backend: /license, /license/addons)
<LicensePanel client={license} lang={lang} product="Citadel" kindLabel={licenseKindLabel}
  featureLabel={licenseFeatureLabel} noFeatures={t("Drift detection only")} notify={...} confirm={confirm} />
// noFeatures only when the app has a better text than «Nur die Grundfunktionen»

securityApi reads snake_case and camelCase answers. notify(text, tone) gets "success" or "error"; without it the blocks show the message themselves. confirm is the app's dialog ({title, action, danger} -> Promise), window.confirm otherwise. formatError(err) lets the app phrase API errors (for example license limit codes). A license backend with other routes passes its own client and a map(license, {L}) that returns the view model described in react/license.js. Nexus does this: its map adds trial badges, seat hints, invalid add-ons and read-only notes (demo mode), and passes its trial notices as statusExtra and the reminder settings as children.

Since 0.27.0 (ADR 0069) the panel shows three areas in the settings form (title and hint left, card right): «Status und Nutzung», «Add-ons», «Lizenzschlüssel». children with extraTitle (and extraHint) becomes a fourth area. The panel passes its lang to every client call; licenseApi sends it as ?lang=, so scalio-license 0.4.0 renders warnings and errors in the language of the page.

Gruppe System: Zurücksetzen and helpers (React)

Every app shows Einstellungen → System in the same order: Lizenz, Updates, Sicherung, Zurücksetzen (settingsGroups sorts the group system that way). The reset block talks to scalio-platform factory_reset (GET/POST /api/admin/factory-reset):

import { FactoryReset, factoryResetApi, SettingsField, SettingsRow } from "@scalio-cloud/ui/react";

const reset = factoryResetApi(api);   // once, outside the component
<FactoryReset client={reset} lang={lang} tokenKey="citadel_token" backupTo="/settings/backup" link={Link} />

SettingsField (label, field, help) and SettingsRow (title and help left, a switch or button right; tone="danger") keep the fields inside a SettingsSection the same in every app.

Benachrichtigungen: E-Mail-Versand und E-Mail-Vorlagen (React)

Since 0.29.0 (ADR 0069 W5) every app sends mail the same way. The server side is scalio-platform 0.8.0 mail (Mailer.router, mounted at /api/mail). Both blocks go into the settings group «Benachrichtigungen».

import { MailTransport, MailTemplates, mailApi } from "@scalio-cloud/ui/react";
import { RichTextEditor } from "@/components/RichTextEditor";   // optional, the app's own editor (Compass: react-quill-new)

const mail = mailApi(api);   // once, outside the component; mailApi(api, "/admin/mail") for another prefix
<MailTransport client={mail} lang={lang} notify={toast} graphHint={t("...Mail.Send and Mail.ReadWrite...")}>
  <InboxSettings />           {/* optional blocks after «Testmail» */}
</MailTransport>
<MailTemplates client={mail} lang={lang} editor={RichTextEditor} />

MailTransport shows three blocks: «E-Mail-Versand» (off, SMTP or Microsoft 365, sender, language; secrets come back as a mask and the mask is sent back unchanged), «Testmail» (empty recipient: to me) and «Letzte Mails» (outbox={false} hides it). On a test instance the backend answers test_mailbox: true and the server fields are locked. MailTemplates picks mail and language, offers the placeholders as buttons, shows the preview of the real rendering in a sandboxed frame, and saves, tests («Test an mich») or resets to the built-in text. A switchable mail gets the checkbox «Diese Mail senden». The editor gets {value, onChange, testId}; without it a text field is used, so this package keeps no dependencies. With an editor, plain built-in texts are passed as paragraphs (textToHtml).

Protokoll: Aktionen und Jobs (React)

One menu item «Protokoll» with two tabs like Nexus (scalio-docs ADR 0070). The server side is scalio-platform audit_router and jobs_router (0.7.0).

import { AuditNav, AuditLog, JobsPanel, auditApi, jobsApi } from "@scalio-cloud/ui/react";

const audit = auditApi(api);   // GET /audit, /audit/export.csv
const jobs = jobsApi(api);     // GET /jobs, /jobs/runs, /jobs/queue, POST /jobs/{key}/run, queue actions

<PageHeader title={L.auditLog} nav={<AuditNav link={NavLink} lang={lang} />} />
<AuditLog client={audit} lang={lang} notify={toast} actionLabel={(a, lang) => ACTIONS[lang][a]} tenantLabel="Kunde" />
<JobsPanel client={jobs} lang={lang} notify={toast} canRun={can("jobs.run")} link={Link} confirm={confirm} />
  • AuditLog: note «unveränderlich», chips with counts (categories from the server), search, filters for action, status, tenant and time range, sortable columns, column picker (person, tenant, IP), CSV with the same filters, «Ältere laden» (cursor), view with «alt → neu», cards on phones. Extras: toolbar, rowActions(entry), renderDetail(entry) (Nexus KI, Citadel PDF, Nexus steps).
  • JobsPanel: tabs Warteschlange (only when the app has one), Läufe (filter by job and status) and Hintergrundjobs (schedule, last run, last result, «Jetzt ausführen» with canRun); reloads every 15 seconds (pollMs). Extras: queueActions(item, reload), renderQueueDetail(item, reload).
  • AuditNav: the tabs /audit and /jobs (base for another prefix, jobs={false} hides Jobs).

Command palette (React)

Reads the same navigation data as the sidebar. Put it once into the layout; the sidebar search button opens it.

import { CommandPalette, openCommandPalette, paletteShortcut } from "@scalio-cloud/ui/react";

<Sidebar nav={nav} search={{ onOpen: openCommandPalette, shortcut: paletteShortcut(lang) }} ... />
<CommandPalette nav={nav} activePath={location.pathname} onNavigate={navigate} lang={lang}
  pages={[{ to: "/profile", label: "Profil" }]}
  groups={(query) => [{ id: "ai", label: "KI", order: 10, items: [{ id: "ask", label: `Fragen: ${query}`, onSelect: ask, hidden: !query }] }]} />

Pages: every visible sidebar item plus pages (each path once), searched by label, keywords, section and path. Groups sort by order (default 60); recent pages (50) show only while the query is empty, pages come last (100). An item has label, icon, to or onSelect, and optional keywords, hint, tone (primary, danger), testId and alwaysShow. Without open the palette handles ⌘K / Ctrl+K and openCommandPalette() itself; pass open + onOpenChange to control it. recentKey sets the localStorage key (false: no recents).

Installing

Public on npmjs.com, no token needed: yarn add @scalio-cloud/ui (pin the exact version). The package holds only design values and fonts under the SIL OFL, nothing confidential.

License

Design tokens and preset: (c) Scalio Cloud LLC, free to use in Scalio apps. Fonts: SIL Open Font License 1.1, see fonts/licenses/. Icons in react/icons.js: Lucide, ISC license, see licenses/lucide-LICENSE.txt.

Release

  1. Change the files and bump version in package.json (semver; tag without v).
  2. Merge to main, then release it: in the Scalio Dev cockpit (row Scalio UI, "Freigeben" with the version) or in GitHub (Actions -> publish -> Run workflow on main, or create the tag/release yourself). The publish workflow tests and publishes that version to npmjs.com via trusted publishing (no token; npm org scalio-cloud, trusted publisher Scalio-Cloud / scalio-ui / publish.yml) and, when started by hand or from the cockpit, creates the tag and GitHub release with the CHANGELOG lines. The changelog lives in scalio-docs changelog/scalio-ui/ (ADR 0066): every change adds a file changelog.d/<name>.md there, and before the release a docs PR runs python3 tools/changelog.py --repo scalio-ui release <version>.
  3. Apps update with yarn upgrade @scalio-cloud/ui@<version> in their own PR.

npm test checks: light/dark variables match, the preset only uses defined variables, all font files exist, nothing is loaded from the network.

版本与发布记录

@scalio-cloud/ui 共发布 40 个版本,首次发布于 2026/10/25;过去 12 个月发布了 40 个版本。

版本发布日期距今
0.29.0latest2026/11/021 天前
0.28.02026/11/021 天前
0.27.12026/11/021 天前
0.27.02026/11/021 天前
0.26.02026/11/021 天前
0.25.12026/11/011 天前

关于 @scalio-cloud/ui 的常见问题

@scalio-cloud/ui 是什么?

Scalio控制室设计令牌,顺风预设,自托管字体,共享的应用程序标题,登录布局,侧边栏和页面块

如何安装 @scalio-cloud/ui?

在项目目录运行 npm install @scalio-cloud/ui,或使用 pnpm add / yarn add / bun add。

@scalio-cloud/ui 支持 TypeScript 吗?

该包的 package.json 没有声明类型入口。需要类型提示时,可以在 npm 上查找社区维护的 @types/scalio-cloud__ui 是否存在。

@scalio-cloud/ui 支持 ESM 还是 CommonJS?

以 CommonJS 形式发布,可以直接 require();在 ES Module 项目中也可以用 import 引入(Node.js 会把 module.exports 作为默认导出)。

@scalio-cloud/ui 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=18。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@scalio-cloud/ui 可以商用吗?

@scalio-cloud/ui 使用 SEE LICENSE IN README.md 许可。许可条款写在包内的独立文件中,请阅读该文件确认可否商用。以上为概括,不构成法律意见,请以许可原文为准。

@scalio-cloud/ui 还在维护吗?

最新版本 0.29.0 发布于 2026/11/02(1 天前);至今共发布 40 个版本,其中过去 12 个月发布了 40 个;首次发布于 2026/10/25。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。