@scalio-cloud/ui 0.36.0 → 0.38.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/README.md CHANGED
@@ -13,6 +13,7 @@ Spec: `design/UISPEC.md` in `Scalio-Cloud/scalio-docs` (single copy).
13
13
  | `nav.css` | sidebar, phone drawer, switcher, context select, support drawer, page blocks |
14
14
  | `index.css` | all of them in one import |
15
15
  | `react/` | shared app header for React (see below), no dependency except React |
16
+ | `shadcn/` | shadcn/ui components on Radix UI (from 0.38.0, see below); the app installs the Radix peers it uses |
16
17
  | `tailwind-preset.js` | colors, fonts, radius, accordion animation, `brand` colors, `h-switcher`, `h-header`, `w-sidebar` |
17
18
  | `tokens.js` | raw values for code without CSS (charts, e-mails, PDFs) |
18
19
 
@@ -53,6 +54,22 @@ COPY --from=build /app/node_modules/@scalio-cloud/ui/nginx/security-headers.conf
53
54
  include /etc/nginx/snippets/security-headers.conf;
54
55
  ```
55
56
 
57
+ ## Sign-in state (React, from 0.37.0)
58
+
59
+ `createAuth({ api, formatError, signInWithPasskey, passkeyCancelled, legacyTokenKey })` gives `{ AuthProvider,
60
+ useAuth }`, the one version of the apps' `AuthContext.jsx` (code-quality plan QA5). The session lives only in the
61
+ HttpOnly cookie of the backend; `useAuth()` returns `user` (null while checking, false when signed out), `ready`,
62
+ `login`, `loginMfa`, `loginPasskey`, `loginScalio`, `register`, `setup`, `logout`, `updateUser`. The app keeps a
63
+ three-line `context/AuthContext.jsx`:
64
+
65
+ ```js
66
+ import { createAuth } from "@scalio-cloud/ui/react";
67
+ import { api, formatApiErrorDetail } from "@/lib/api";
68
+ import { passkeyCancelled, signInWithPasskey } from "@/lib/passkeys";
69
+ export const { AuthProvider, useAuth } = createAuth({ api, formatError: formatApiErrorDetail, signInWithPasskey,
70
+ passkeyCancelled, legacyTokenKey: "pulse_token" });
71
+ ```
72
+
56
73
  ## App header (React)
57
74
 
58
75
  Every app shows the same header on the right, always in this order: (app actions), status, language, theme,
@@ -473,6 +490,25 @@ const jobs = jobsApi(api); // GET /jobs, /jobs/runs, /jobs/queue, POST /jobs
473
490
  `queueActions(item, reload)`, `renderQueueDetail(item, reload)`.
474
491
  - `AuditNav`: the tabs `/audit` and `/jobs` (`base` for another prefix, `jobs={false}` hides Jobs).
475
492
 
493
+ ## shadcn/ui components (from 0.38.0)
494
+
495
+ One copy of the shadcn/ui components the apps used to keep in `src/components/ui` (code-quality plan QA6, decision
496
+ Markus 2026-11-05: Radix as peer dependency). Control Room style: buttons upper case, bold, `rounded-sm` (UISPEC A7).
497
+ Import each component by its own path, so an app only needs the Radix packages of the components it uses:
498
+
499
+ ```js
500
+ import { Button } from "@scalio-cloud/ui/shadcn/button";
501
+ import { AlertDialog, AlertDialogContent } from "@scalio-cloud/ui/shadcn/alert-dialog";
502
+ import { cn } from "@scalio-cloud/ui/shadcn/utils";
503
+ ```
504
+
505
+ Components: `alert-dialog`, `badge`, `button`, `checkbox`, `dialog` (`closeLabel` for the hidden close text),
506
+ `dropdown-menu`, `input`, `label`, `popover`, `progress`, `select`, `sonner` (`Toaster theme`, no next-themes),
507
+ `switch`, `table`, `tabs`, `textarea`, `utils` (`cn`). Always needed: `clsx`, `tailwind-merge`; per component the
508
+ `@radix-ui/react-*` package, `class-variance-authority` (button, badge), `lucide-react` (icons), `sonner`. Tailwind
509
+ must scan the files: add `"./node_modules/@scalio-cloud/ui/shadcn/*.mjs"` to `content` in `tailwind.config.js`.
510
+ An app keeps a component in `src/components/ui` only when no other app uses it.
511
+
476
512
  ## Command palette (React)
477
513
 
478
514
  Reads the same navigation data as the sidebar. Put it once into the layout; the sidebar search button opens it.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scalio-cloud/ui",
3
- "version": "0.36.0",
3
+ "version": "0.38.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,
@@ -23,7 +23,8 @@
23
23
  "./shell.css": "./shell.css",
24
24
  "./auth.css": "./auth.css",
25
25
  "./nav.css": "./nav.css",
26
- "./nginx/*": "./nginx/*"
26
+ "./nginx/*": "./nginx/*",
27
+ "./shadcn/*": "./shadcn/*.mjs"
27
28
  },
28
29
  "files": [
29
30
  "tokens.js",
@@ -36,6 +37,7 @@
36
37
  "auth.css",
37
38
  "nav.css",
38
39
  "react/",
40
+ "shadcn/",
39
41
  "nginx/",
40
42
  "licenses/",
41
43
  "fonts/",
@@ -47,12 +49,67 @@
47
49
  "access": "public"
48
50
  },
49
51
  "peerDependencies": {
52
+ "@radix-ui/react-alert-dialog": "^1.1.0",
53
+ "@radix-ui/react-checkbox": "^1.1.0",
54
+ "@radix-ui/react-dialog": "^1.1.0",
55
+ "@radix-ui/react-dropdown-menu": "^2.1.0",
56
+ "@radix-ui/react-label": "^2.1.0",
57
+ "@radix-ui/react-popover": "^1.1.0",
58
+ "@radix-ui/react-progress": "^1.1.0",
59
+ "@radix-ui/react-select": "^2.1.0",
60
+ "@radix-ui/react-slot": "^1.1.0",
61
+ "@radix-ui/react-switch": "^1.1.0",
62
+ "@radix-ui/react-tabs": "^1.1.0",
63
+ "class-variance-authority": "^0.7.0",
64
+ "clsx": "^2.1.0",
65
+ "lucide-react": ">=0.400.0",
50
66
  "react": ">=18",
51
- "tailwindcss": ">=3.3 <4",
52
- "react-dom": ">=18"
67
+ "react-dom": ">=18",
68
+ "sonner": "^2.0.0",
69
+ "tailwind-merge": "^3.0.0",
70
+ "tailwindcss": ">=3.3 <4"
53
71
  },
54
72
  "peerDependenciesMeta": {
55
- "tailwindcss": {
73
+ "@radix-ui/react-alert-dialog": {
74
+ "optional": true
75
+ },
76
+ "@radix-ui/react-checkbox": {
77
+ "optional": true
78
+ },
79
+ "@radix-ui/react-dialog": {
80
+ "optional": true
81
+ },
82
+ "@radix-ui/react-dropdown-menu": {
83
+ "optional": true
84
+ },
85
+ "@radix-ui/react-label": {
86
+ "optional": true
87
+ },
88
+ "@radix-ui/react-popover": {
89
+ "optional": true
90
+ },
91
+ "@radix-ui/react-progress": {
92
+ "optional": true
93
+ },
94
+ "@radix-ui/react-select": {
95
+ "optional": true
96
+ },
97
+ "@radix-ui/react-slot": {
98
+ "optional": true
99
+ },
100
+ "@radix-ui/react-switch": {
101
+ "optional": true
102
+ },
103
+ "@radix-ui/react-tabs": {
104
+ "optional": true
105
+ },
106
+ "class-variance-authority": {
107
+ "optional": true
108
+ },
109
+ "clsx": {
110
+ "optional": true
111
+ },
112
+ "lucide-react": {
56
113
  "optional": true
57
114
  },
58
115
  "react": {
@@ -60,6 +117,15 @@
60
117
  },
61
118
  "react-dom": {
62
119
  "optional": true
120
+ },
121
+ "sonner": {
122
+ "optional": true
123
+ },
124
+ "tailwind-merge": {
125
+ "optional": true
126
+ },
127
+ "tailwindcss": {
128
+ "optional": true
63
129
  }
64
130
  },
65
131
  "scripts": {
@@ -69,8 +135,24 @@
69
135
  "node": ">=18"
70
136
  },
71
137
  "devDependencies": {
138
+ "@radix-ui/react-alert-dialog": "1.2.1",
139
+ "@radix-ui/react-checkbox": "1.4.1",
140
+ "@radix-ui/react-dialog": "1.3.1",
141
+ "@radix-ui/react-dropdown-menu": "2.2.1",
142
+ "@radix-ui/react-label": "2.1.17",
143
+ "@radix-ui/react-popover": "1.3.1",
144
+ "@radix-ui/react-progress": "1.2.1",
145
+ "@radix-ui/react-select": "2.4.1",
146
+ "@radix-ui/react-slot": "1.5.0",
147
+ "@radix-ui/react-switch": "1.4.1",
148
+ "@radix-ui/react-tabs": "1.2.1",
149
+ "class-variance-authority": "0.7.1",
150
+ "clsx": "2.1.1",
72
151
  "jsdom": "30.1.3",
152
+ "lucide-react": "1.65.0",
73
153
  "react": "19.3.1",
74
- "react-dom": "19.3.1"
154
+ "react-dom": "19.3.1",
155
+ "sonner": "2.0.8",
156
+ "tailwind-merge": "3.7.0"
75
157
  }
76
158
  }
package/react/index.js CHANGED
@@ -380,4 +380,4 @@ module.exports = {
380
380
  };
381
381
  Object.assign(module.exports, Nav, Blocks, require("./settings"), require("./support"), require("./beacon"), require("./security"), require("./license"), require("./palette"), require("./unify"), require("./profile"), require("./actions"), require("./selftest"), require("./users"), require("./roles"), require("./wizard"), require("./maintenance"), require("./backup"), require("./audit"), require("./mail"), require("./saas_updates"), require("./pages"));
382
382
  // After the exports above: auth.js uses LanguageToggle and ThemeToggle from this module.
383
- Object.assign(module.exports, require("./auth"));
383
+ Object.assign(module.exports, require("./auth"), require("./session"));
package/react/labels.js CHANGED
@@ -45,7 +45,7 @@ const LABELS = {
45
45
  mfaCode: "Code aus der Authenticator-App",
46
46
  // Navigation (same words in every app, see the glossary in UISPEC)
47
47
  overview: "Übersicht",
48
- // UI alignment round 1 (ADR 0041)
48
+ // UI alignment round 1 (ADR 0042)
49
49
  later: "Später",
50
50
  apiOffline: "API nicht erreichbar",
51
51
  apiOfflineTip: "Die Anwendung erreicht ihr Backend nicht. Änderungen werden nicht gespeichert.",
@@ -559,7 +559,7 @@ const LABELS = {
559
559
  twoFactor: "Two-factor sign-in",
560
560
  mfaCode: "Code from the authenticator app",
561
561
  overview: "Overview",
562
- // UI alignment round 1 (ADR 0041)
562
+ // UI alignment round 1 (ADR 0042)
563
563
  later: "Later",
564
564
  apiOffline: "API unreachable",
565
565
  apiOfflineTip: "The app cannot reach its backend. Changes are not saved.",
@@ -0,0 +1,98 @@
1
+ "use strict";
2
+ // Sign-in state of an app (QA5 step 3, Grundregel 8): Beacon, Pulse and Citadel had the same AuthContext.jsx.
3
+ // The session lives only in the HttpOnly cookie of the backend (scalio-identity localauth); the browser keeps no
4
+ // token. createAuth({ api, formatError, signInWithPasskey, passkeyCancelled, legacyTokenKey }) gives
5
+ // { AuthProvider, useAuth }. useAuth() returns
6
+ // { user, ready, login, loginMfa, loginPasskey, loginScalio, register, setup, logout, updateUser }:
7
+ // user is null while checking, false when signed out, else /api/auth/me. Every action answers { ok, error?, status? };
8
+ // login with an authenticator app answers { ok: false, mfaToken } and the page asks for the code.
9
+ const React = require("react");
10
+ const { setUserTimezone } = require("./profile");
11
+
12
+ function createAuth(opts) {
13
+ const api = opts.api;
14
+ const formatError = opts.formatError || ((d) => (typeof d === "string" ? d : ""));
15
+ const AuthContext = React.createContext(null);
16
+
17
+ function AuthProvider(props) {
18
+ const [user, setUser] = React.useState(null); // null = checking
19
+ const [ready, setReady] = React.useState(false);
20
+
21
+ React.useEffect(() => {
22
+ if (opts.legacyTokenKey) {
23
+ try {
24
+ localStorage.removeItem(opts.legacyTokenKey); // token of older versions
25
+ } catch (e) {
26
+ /* private mode */
27
+ }
28
+ }
29
+ api
30
+ .get("/auth/me")
31
+ .then((r) => setUser(r.data))
32
+ .catch(() => setUser(false))
33
+ .finally(() => setReady(true));
34
+ }, []);
35
+
36
+ const signedIn = (data) => {
37
+ setUser(data.user);
38
+ // /auth/me adds the permissions of the role (own roles).
39
+ api.get("/auth/me").then((r) => setUser(r.data)).catch(() => null);
40
+ return { ok: true };
41
+ };
42
+ const failed = (e) => ({
43
+ ok: false,
44
+ status: e.response && e.response.status,
45
+ error: formatError(e.response && e.response.data && e.response.data.detail) || e.message,
46
+ });
47
+ const attempt = async (fn) => {
48
+ try {
49
+ return await fn();
50
+ } catch (e) {
51
+ return failed(e);
52
+ }
53
+ };
54
+
55
+ const login = (email, password) =>
56
+ attempt(async () => {
57
+ const { data } = await api.post("/auth/login", { email, password });
58
+ return data.mfa_required ? { ok: false, mfaToken: data.mfa_token } : signedIn(data);
59
+ });
60
+ const loginMfa = (mfaToken, code) =>
61
+ attempt(async () => signedIn((await api.post("/auth/login/mfa", { mfa_token: mfaToken, code })).data));
62
+ const loginPasskey = async () => {
63
+ try {
64
+ return signedIn(await opts.signInWithPasskey());
65
+ } catch (e) {
66
+ return opts.passkeyCancelled && opts.passkeyCancelled(e) ? { ok: false } : failed(e);
67
+ }
68
+ };
69
+ // Microsoft through Scalio ID: the backend callback hands over a one-time code (URL fragment).
70
+ const loginScalio = (code) => attempt(async () => signedIn((await api.post("/auth/scalio/finish", { code })).data));
71
+ const register = (name, email, password) =>
72
+ attempt(async () => signedIn((await api.post("/auth/register", { name, email, password })).data));
73
+ const setup = (name, email, password, setup_code) =>
74
+ attempt(async () => signedIn((await api.post("/auth/setup", { name, email, password, setup_code })).data));
75
+ // Profile page: new name. After a password change the backend sets the new cookie itself.
76
+ const updateUser = (u) => setUser(u);
77
+ // Signs out at the server (the cookie is removed there), then locally even if the call fails.
78
+ const logout = async () => {
79
+ try {
80
+ await api.post("/auth/logout");
81
+ } catch (e) {
82
+ /* already signed out */
83
+ }
84
+ setUser(false);
85
+ };
86
+
87
+ // Times everywhere follow the profile time zone; set before the children render.
88
+ setUserTimezone(user ? user.timezone : "");
89
+
90
+ const value = { user, ready, login, loginMfa, loginPasskey, loginScalio, register, setup, logout, updateUser };
91
+ return React.createElement(AuthContext.Provider, { value }, props.children);
92
+ }
93
+
94
+ const useAuth = () => React.useContext(AuthContext);
95
+ return { AuthProvider, useAuth, AuthContext };
96
+ }
97
+
98
+ module.exports = { createAuth };
package/react/unify.js CHANGED
@@ -1,5 +1,5 @@
1
1
  "use strict";
2
- // UI alignment round 1 (scalio-docs ADR 0041): edition badges next to the brand, the fixed "Verwaltung"
2
+ // UI alignment round 1 (scalio-docs ADR 0042): edition badges next to the brand, the fixed "Verwaltung"
3
3
  // section, the API status in the header, the tabs of "Konten und Rollen" and the password sign-in choice.
4
4
  const React = require("react");
5
5
  const h = React.createElement;
@@ -0,0 +1,90 @@
1
+ // shadcn/ui component shared by the Scalio apps (code-quality plan QA6, Grundregel 8). Import it as
2
+ // "@scalio-cloud/ui/shadcn/alert-dialog"; the app installs the Radix peer dependencies it uses (README).
3
+ import * as React from "react";
4
+ import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog";
5
+ import { cn } from "./utils.mjs";
6
+ import { buttonVariants } from "./button.mjs";
7
+ const AlertDialog = AlertDialogPrimitive.Root;
8
+ const AlertDialogTrigger = AlertDialogPrimitive.Trigger;
9
+ const AlertDialogPortal = AlertDialogPrimitive.Portal;
10
+ const AlertDialogOverlay = React.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ React.createElement(
11
+ AlertDialogPrimitive.Overlay,
12
+ {
13
+ className: cn(
14
+ "fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
15
+ className
16
+ ),
17
+ ...props,
18
+ ref
19
+ }
20
+ ));
21
+ AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName;
22
+ const AlertDialogContent = React.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ React.createElement(AlertDialogPortal, null, /* @__PURE__ */ React.createElement(AlertDialogOverlay, null), /* @__PURE__ */ React.createElement(
23
+ AlertDialogPrimitive.Content,
24
+ {
25
+ ref,
26
+ className: cn(
27
+ "fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
28
+ className
29
+ ),
30
+ ...props
31
+ }
32
+ )));
33
+ AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName;
34
+ const AlertDialogHeader = ({
35
+ className,
36
+ ...props
37
+ }) => /* @__PURE__ */ React.createElement(
38
+ "div",
39
+ {
40
+ className: cn("flex flex-col space-y-2 text-center sm:text-left", className),
41
+ ...props
42
+ }
43
+ );
44
+ AlertDialogHeader.displayName = "AlertDialogHeader";
45
+ const AlertDialogFooter = ({
46
+ className,
47
+ ...props
48
+ }) => /* @__PURE__ */ React.createElement(
49
+ "div",
50
+ {
51
+ className: cn("flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2", className),
52
+ ...props
53
+ }
54
+ );
55
+ AlertDialogFooter.displayName = "AlertDialogFooter";
56
+ const AlertDialogTitle = React.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ React.createElement(AlertDialogPrimitive.Title, { ref, className: cn("text-lg font-semibold", className), ...props }));
57
+ AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName;
58
+ const AlertDialogDescription = React.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ React.createElement(
59
+ AlertDialogPrimitive.Description,
60
+ {
61
+ ref,
62
+ className: cn("text-sm text-muted-foreground", className),
63
+ ...props
64
+ }
65
+ ));
66
+ AlertDialogDescription.displayName = AlertDialogPrimitive.Description.displayName;
67
+ const AlertDialogAction = React.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ React.createElement(AlertDialogPrimitive.Action, { ref, className: cn(buttonVariants(), className), ...props }));
68
+ AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName;
69
+ const AlertDialogCancel = React.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ React.createElement(
70
+ AlertDialogPrimitive.Cancel,
71
+ {
72
+ ref,
73
+ className: cn(buttonVariants({ variant: "outline" }), "mt-2 sm:mt-0", className),
74
+ ...props
75
+ }
76
+ ));
77
+ AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName;
78
+ export {
79
+ AlertDialog,
80
+ AlertDialogAction,
81
+ AlertDialogCancel,
82
+ AlertDialogContent,
83
+ AlertDialogDescription,
84
+ AlertDialogFooter,
85
+ AlertDialogHeader,
86
+ AlertDialogOverlay,
87
+ AlertDialogPortal,
88
+ AlertDialogTitle,
89
+ AlertDialogTrigger
90
+ };
@@ -0,0 +1,32 @@
1
+ // shadcn/ui component shared by the Scalio apps (code-quality plan QA6, Grundregel 8). Import it as
2
+ // "@scalio-cloud/ui/shadcn/badge"; the app installs the Radix peer dependencies it uses (README).
3
+ import * as React from "react";
4
+ import { cva } from "class-variance-authority";
5
+ import { cn } from "./utils.mjs";
6
+ const badgeVariants = cva(
7
+ "inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
8
+ {
9
+ variants: {
10
+ variant: {
11
+ default: "border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80",
12
+ secondary: "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
13
+ destructive: "border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80",
14
+ outline: "text-foreground"
15
+ }
16
+ },
17
+ defaultVariants: {
18
+ variant: "default"
19
+ }
20
+ }
21
+ );
22
+ function Badge({
23
+ className,
24
+ variant,
25
+ ...props
26
+ }) {
27
+ return /* @__PURE__ */ React.createElement("div", { className: cn(badgeVariants({ variant }), className), ...props });
28
+ }
29
+ export {
30
+ Badge,
31
+ badgeVariants
32
+ };
@@ -0,0 +1,47 @@
1
+ // shadcn/ui component shared by the Scalio apps (code-quality plan QA6, Grundregel 8). Import it as
2
+ // "@scalio-cloud/ui/shadcn/button"; the app installs the Radix peer dependencies it uses (README).
3
+ import * as React from "react";
4
+ import { Slot } from "@radix-ui/react-slot";
5
+ import { cva } from "class-variance-authority";
6
+ import { cn } from "./utils.mjs";
7
+ const buttonVariants = cva(
8
+ "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-sm text-xs font-bold uppercase tracking-wider transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
9
+ {
10
+ variants: {
11
+ variant: {
12
+ default: "bg-primary text-primary-foreground shadow hover:bg-primary/90",
13
+ destructive: "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
14
+ outline: "border border-input shadow-sm hover:bg-accent hover:text-accent-foreground",
15
+ secondary: "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
16
+ ghost: "hover:bg-accent hover:text-accent-foreground",
17
+ link: "text-primary underline-offset-4 hover:underline"
18
+ },
19
+ size: {
20
+ default: "h-9 px-4 py-2",
21
+ sm: "h-8 rounded-sm px-3 text-xs",
22
+ lg: "h-10 rounded-sm px-8",
23
+ icon: "h-9 w-9"
24
+ }
25
+ },
26
+ defaultVariants: {
27
+ variant: "default",
28
+ size: "default"
29
+ }
30
+ }
31
+ );
32
+ const Button = React.forwardRef(({ className, variant, size, asChild = false, ...props }, ref) => {
33
+ const Comp = asChild ? Slot : "button";
34
+ return /* @__PURE__ */ React.createElement(
35
+ Comp,
36
+ {
37
+ className: cn(buttonVariants({ variant, size, className })),
38
+ ref,
39
+ ...props
40
+ }
41
+ );
42
+ });
43
+ Button.displayName = "Button";
44
+ export {
45
+ Button,
46
+ buttonVariants
47
+ };
@@ -0,0 +1,22 @@
1
+ // shadcn/ui component shared by the Scalio apps (code-quality plan QA6, Grundregel 8). Import it as
2
+ // "@scalio-cloud/ui/shadcn/checkbox"; the app installs the Radix peer dependencies it uses (README).
3
+ import * as React from "react";
4
+ import * as CheckboxPrimitive from "@radix-ui/react-checkbox";
5
+ import { Check } from "lucide-react";
6
+ import { cn } from "./utils.mjs";
7
+ const Checkbox = React.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ React.createElement(
8
+ CheckboxPrimitive.Root,
9
+ {
10
+ ref,
11
+ className: cn(
12
+ "peer h-4 w-4 shrink-0 rounded-sm border border-primary shadow focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground",
13
+ className
14
+ ),
15
+ ...props
16
+ },
17
+ /* @__PURE__ */ React.createElement(CheckboxPrimitive.Indicator, { className: cn("flex items-center justify-center text-current") }, /* @__PURE__ */ React.createElement(Check, { className: "h-4 w-4" }))
18
+ ));
19
+ Checkbox.displayName = CheckboxPrimitive.Root.displayName;
20
+ export {
21
+ Checkbox
22
+ };
@@ -0,0 +1,95 @@
1
+ // shadcn/ui component shared by the Scalio apps (code-quality plan QA6, Grundregel 8). Import it as
2
+ // "@scalio-cloud/ui/shadcn/dialog"; the app installs the Radix peer dependencies it uses (README).
3
+ import * as React from "react";
4
+ import * as DialogPrimitive from "@radix-ui/react-dialog";
5
+ import { X } from "lucide-react";
6
+ import { cn } from "./utils.mjs";
7
+ const Dialog = DialogPrimitive.Root;
8
+ const DialogTrigger = DialogPrimitive.Trigger;
9
+ const DialogPortal = DialogPrimitive.Portal;
10
+ const DialogClose = DialogPrimitive.Close;
11
+ const DialogOverlay = React.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ React.createElement(
12
+ DialogPrimitive.Overlay,
13
+ {
14
+ ref,
15
+ className: cn(
16
+ "fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
17
+ className
18
+ ),
19
+ ...props
20
+ }
21
+ ));
22
+ DialogOverlay.displayName = DialogPrimitive.Overlay.displayName;
23
+ const DialogContent = React.forwardRef(({ className, children, closeLabel = "Close", ...props }, ref) => /* @__PURE__ */ React.createElement(DialogPortal, null, /* @__PURE__ */ React.createElement(DialogOverlay, null), /* @__PURE__ */ React.createElement(
24
+ DialogPrimitive.Content,
25
+ {
26
+ ref,
27
+ className: cn(
28
+ "fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
29
+ className
30
+ ),
31
+ ...props
32
+ },
33
+ children,
34
+ /* @__PURE__ */ React.createElement(
35
+ DialogPrimitive.Close,
36
+ {
37
+ className: "absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground"
38
+ },
39
+ /* @__PURE__ */ React.createElement(X, { className: "h-4 w-4" }),
40
+ /* @__PURE__ */ React.createElement("span", { className: "sr-only" }, closeLabel)
41
+ )
42
+ )));
43
+ DialogContent.displayName = DialogPrimitive.Content.displayName;
44
+ const DialogHeader = ({
45
+ className,
46
+ ...props
47
+ }) => /* @__PURE__ */ React.createElement(
48
+ "div",
49
+ {
50
+ className: cn("flex flex-col space-y-1.5 text-center sm:text-left", className),
51
+ ...props
52
+ }
53
+ );
54
+ DialogHeader.displayName = "DialogHeader";
55
+ const DialogFooter = ({
56
+ className,
57
+ ...props
58
+ }) => /* @__PURE__ */ React.createElement(
59
+ "div",
60
+ {
61
+ className: cn("flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2", className),
62
+ ...props
63
+ }
64
+ );
65
+ DialogFooter.displayName = "DialogFooter";
66
+ const DialogTitle = React.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ React.createElement(
67
+ DialogPrimitive.Title,
68
+ {
69
+ ref,
70
+ className: cn("text-lg font-semibold leading-none tracking-tight", className),
71
+ ...props
72
+ }
73
+ ));
74
+ DialogTitle.displayName = DialogPrimitive.Title.displayName;
75
+ const DialogDescription = React.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ React.createElement(
76
+ DialogPrimitive.Description,
77
+ {
78
+ ref,
79
+ className: cn("text-sm text-muted-foreground", className),
80
+ ...props
81
+ }
82
+ ));
83
+ DialogDescription.displayName = DialogPrimitive.Description.displayName;
84
+ export {
85
+ Dialog,
86
+ DialogClose,
87
+ DialogContent,
88
+ DialogDescription,
89
+ DialogFooter,
90
+ DialogHeader,
91
+ DialogOverlay,
92
+ DialogPortal,
93
+ DialogTitle,
94
+ DialogTrigger
95
+ };
@@ -0,0 +1,143 @@
1
+ // shadcn/ui component shared by the Scalio apps (code-quality plan QA6, Grundregel 8). Import it as
2
+ // "@scalio-cloud/ui/shadcn/dropdown-menu"; the app installs the Radix peer dependencies it uses (README).
3
+ import * as React from "react";
4
+ import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
5
+ import { Check, ChevronRight, Circle } from "lucide-react";
6
+ import { cn } from "./utils.mjs";
7
+ const DropdownMenu = DropdownMenuPrimitive.Root;
8
+ const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger;
9
+ const DropdownMenuGroup = DropdownMenuPrimitive.Group;
10
+ const DropdownMenuPortal = DropdownMenuPrimitive.Portal;
11
+ const DropdownMenuSub = DropdownMenuPrimitive.Sub;
12
+ const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup;
13
+ const DropdownMenuSubTrigger = React.forwardRef(({ className, inset, children, ...props }, ref) => /* @__PURE__ */ React.createElement(
14
+ DropdownMenuPrimitive.SubTrigger,
15
+ {
16
+ ref,
17
+ className: cn(
18
+ "flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
19
+ inset && "pl-8",
20
+ className
21
+ ),
22
+ ...props
23
+ },
24
+ children,
25
+ /* @__PURE__ */ React.createElement(ChevronRight, { className: "ml-auto" })
26
+ ));
27
+ DropdownMenuSubTrigger.displayName = DropdownMenuPrimitive.SubTrigger.displayName;
28
+ const DropdownMenuSubContent = React.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ React.createElement(
29
+ DropdownMenuPrimitive.SubContent,
30
+ {
31
+ ref,
32
+ className: cn(
33
+ "z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-dropdown-menu-content-transform-origin]",
34
+ className
35
+ ),
36
+ ...props
37
+ }
38
+ ));
39
+ DropdownMenuSubContent.displayName = DropdownMenuPrimitive.SubContent.displayName;
40
+ const DropdownMenuContent = React.forwardRef(({ className, sideOffset = 4, ...props }, ref) => /* @__PURE__ */ React.createElement(DropdownMenuPrimitive.Portal, null, /* @__PURE__ */ React.createElement(
41
+ DropdownMenuPrimitive.Content,
42
+ {
43
+ ref,
44
+ sideOffset,
45
+ className: cn(
46
+ "z-50 max-h-[var(--radix-dropdown-menu-content-available-height)] min-w-[8rem] overflow-y-auto overflow-x-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md",
47
+ "data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-dropdown-menu-content-transform-origin]",
48
+ className
49
+ ),
50
+ ...props
51
+ }
52
+ )));
53
+ DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName;
54
+ const DropdownMenuItem = React.forwardRef(({ className, inset, ...props }, ref) => /* @__PURE__ */ React.createElement(
55
+ DropdownMenuPrimitive.Item,
56
+ {
57
+ ref,
58
+ className: cn(
59
+ "relative flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&>svg]:size-4 [&>svg]:shrink-0",
60
+ inset && "pl-8",
61
+ className
62
+ ),
63
+ ...props
64
+ }
65
+ ));
66
+ DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName;
67
+ const DropdownMenuCheckboxItem = React.forwardRef(({ className, children, checked, ...props }, ref) => /* @__PURE__ */ React.createElement(
68
+ DropdownMenuPrimitive.CheckboxItem,
69
+ {
70
+ ref,
71
+ className: cn(
72
+ "relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
73
+ className
74
+ ),
75
+ checked,
76
+ ...props
77
+ },
78
+ /* @__PURE__ */ React.createElement("span", { className: "absolute left-2 flex h-3.5 w-3.5 items-center justify-center" }, /* @__PURE__ */ React.createElement(DropdownMenuPrimitive.ItemIndicator, null, /* @__PURE__ */ React.createElement(Check, { className: "h-4 w-4" }))),
79
+ children
80
+ ));
81
+ DropdownMenuCheckboxItem.displayName = DropdownMenuPrimitive.CheckboxItem.displayName;
82
+ const DropdownMenuRadioItem = React.forwardRef(({ className, children, ...props }, ref) => /* @__PURE__ */ React.createElement(
83
+ DropdownMenuPrimitive.RadioItem,
84
+ {
85
+ ref,
86
+ className: cn(
87
+ "relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
88
+ className
89
+ ),
90
+ ...props
91
+ },
92
+ /* @__PURE__ */ React.createElement("span", { className: "absolute left-2 flex h-3.5 w-3.5 items-center justify-center" }, /* @__PURE__ */ React.createElement(DropdownMenuPrimitive.ItemIndicator, null, /* @__PURE__ */ React.createElement(Circle, { className: "h-2 w-2 fill-current" }))),
93
+ children
94
+ ));
95
+ DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName;
96
+ const DropdownMenuLabel = React.forwardRef(({ className, inset, ...props }, ref) => /* @__PURE__ */ React.createElement(
97
+ DropdownMenuPrimitive.Label,
98
+ {
99
+ ref,
100
+ className: cn("px-2 py-1.5 text-sm font-semibold", inset && "pl-8", className),
101
+ ...props
102
+ }
103
+ ));
104
+ DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName;
105
+ const DropdownMenuSeparator = React.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ React.createElement(
106
+ DropdownMenuPrimitive.Separator,
107
+ {
108
+ ref,
109
+ className: cn("-mx-1 my-1 h-px bg-muted", className),
110
+ ...props
111
+ }
112
+ ));
113
+ DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName;
114
+ const DropdownMenuShortcut = ({
115
+ className,
116
+ ...props
117
+ }) => {
118
+ return /* @__PURE__ */ React.createElement(
119
+ "span",
120
+ {
121
+ className: cn("ml-auto text-xs tracking-widest opacity-60", className),
122
+ ...props
123
+ }
124
+ );
125
+ };
126
+ DropdownMenuShortcut.displayName = "DropdownMenuShortcut";
127
+ export {
128
+ DropdownMenu,
129
+ DropdownMenuCheckboxItem,
130
+ DropdownMenuContent,
131
+ DropdownMenuGroup,
132
+ DropdownMenuItem,
133
+ DropdownMenuLabel,
134
+ DropdownMenuPortal,
135
+ DropdownMenuRadioGroup,
136
+ DropdownMenuRadioItem,
137
+ DropdownMenuSeparator,
138
+ DropdownMenuShortcut,
139
+ DropdownMenuSub,
140
+ DropdownMenuSubContent,
141
+ DropdownMenuSubTrigger,
142
+ DropdownMenuTrigger
143
+ };
@@ -0,0 +1,22 @@
1
+ // shadcn/ui component shared by the Scalio apps (code-quality plan QA6, Grundregel 8). Import it as
2
+ // "@scalio-cloud/ui/shadcn/input"; the app installs the Radix peer dependencies it uses (README).
3
+ import * as React from "react";
4
+ import { cn } from "./utils.mjs";
5
+ const Input = React.forwardRef(({ className, type, ...props }, ref) => {
6
+ return /* @__PURE__ */ React.createElement(
7
+ "input",
8
+ {
9
+ type,
10
+ className: cn(
11
+ "flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
12
+ className
13
+ ),
14
+ ref,
15
+ ...props
16
+ }
17
+ );
18
+ });
19
+ Input.displayName = "Input";
20
+ export {
21
+ Input
22
+ };
@@ -0,0 +1,14 @@
1
+ // shadcn/ui component shared by the Scalio apps (code-quality plan QA6, Grundregel 8). Import it as
2
+ // "@scalio-cloud/ui/shadcn/label"; the app installs the Radix peer dependencies it uses (README).
3
+ import * as React from "react";
4
+ import * as LabelPrimitive from "@radix-ui/react-label";
5
+ import { cva } from "class-variance-authority";
6
+ import { cn } from "./utils.mjs";
7
+ const labelVariants = cva(
8
+ "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
9
+ );
10
+ const Label = React.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ React.createElement(LabelPrimitive.Root, { ref, className: cn(labelVariants(), className), ...props }));
11
+ Label.displayName = LabelPrimitive.Root.displayName;
12
+ export {
13
+ Label
14
+ };
@@ -0,0 +1,28 @@
1
+ // shadcn/ui component shared by the Scalio apps (code-quality plan QA6, Grundregel 8). Import it as
2
+ // "@scalio-cloud/ui/shadcn/popover"; the app installs the Radix peer dependencies it uses (README).
3
+ import * as React from "react";
4
+ import * as PopoverPrimitive from "@radix-ui/react-popover";
5
+ import { cn } from "./utils.mjs";
6
+ const Popover = PopoverPrimitive.Root;
7
+ const PopoverTrigger = PopoverPrimitive.Trigger;
8
+ const PopoverAnchor = PopoverPrimitive.Anchor;
9
+ const PopoverContent = React.forwardRef(({ className, align = "center", sideOffset = 4, ...props }, ref) => /* @__PURE__ */ React.createElement(PopoverPrimitive.Portal, null, /* @__PURE__ */ React.createElement(
10
+ PopoverPrimitive.Content,
11
+ {
12
+ ref,
13
+ align,
14
+ sideOffset,
15
+ className: cn(
16
+ "z-50 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-popover-content-transform-origin]",
17
+ className
18
+ ),
19
+ ...props
20
+ }
21
+ )));
22
+ PopoverContent.displayName = PopoverPrimitive.Content.displayName;
23
+ export {
24
+ Popover,
25
+ PopoverAnchor,
26
+ PopoverContent,
27
+ PopoverTrigger
28
+ };
@@ -0,0 +1,27 @@
1
+ // shadcn/ui component shared by the Scalio apps (code-quality plan QA6, Grundregel 8). Import it as
2
+ // "@scalio-cloud/ui/shadcn/progress"; the app installs the Radix peer dependencies it uses (README).
3
+ import * as React from "react";
4
+ import * as ProgressPrimitive from "@radix-ui/react-progress";
5
+ import { cn } from "./utils.mjs";
6
+ const Progress = React.forwardRef(({ className, value, ...props }, ref) => /* @__PURE__ */ React.createElement(
7
+ ProgressPrimitive.Root,
8
+ {
9
+ ref,
10
+ className: cn(
11
+ "relative h-2 w-full overflow-hidden rounded-full bg-primary/20",
12
+ className
13
+ ),
14
+ ...props
15
+ },
16
+ /* @__PURE__ */ React.createElement(
17
+ ProgressPrimitive.Indicator,
18
+ {
19
+ className: "h-full w-full flex-1 bg-primary transition-all",
20
+ style: { transform: `translateX(-${100 - (value || 0)}%)` }
21
+ }
22
+ )
23
+ ));
24
+ Progress.displayName = ProgressPrimitive.Root.displayName;
25
+ export {
26
+ Progress
27
+ };
@@ -0,0 +1,110 @@
1
+ // shadcn/ui component shared by the Scalio apps (code-quality plan QA6, Grundregel 8). Import it as
2
+ // "@scalio-cloud/ui/shadcn/select"; the app installs the Radix peer dependencies it uses (README).
3
+ import * as React from "react";
4
+ import * as SelectPrimitive from "@radix-ui/react-select";
5
+ import { Check, ChevronDown, ChevronUp } from "lucide-react";
6
+ import { cn } from "./utils.mjs";
7
+ const Select = SelectPrimitive.Root;
8
+ const SelectGroup = SelectPrimitive.Group;
9
+ const SelectValue = SelectPrimitive.Value;
10
+ const SelectTrigger = React.forwardRef(({ className, children, ...props }, ref) => /* @__PURE__ */ React.createElement(
11
+ SelectPrimitive.Trigger,
12
+ {
13
+ ref,
14
+ className: cn(
15
+ "flex h-9 w-full items-center justify-between whitespace-nowrap rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm ring-offset-background data-[placeholder]:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1",
16
+ className
17
+ ),
18
+ ...props
19
+ },
20
+ children,
21
+ /* @__PURE__ */ React.createElement(SelectPrimitive.Icon, { asChild: true }, /* @__PURE__ */ React.createElement(ChevronDown, { className: "h-4 w-4 opacity-50" }))
22
+ ));
23
+ SelectTrigger.displayName = SelectPrimitive.Trigger.displayName;
24
+ const SelectScrollUpButton = React.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ React.createElement(
25
+ SelectPrimitive.ScrollUpButton,
26
+ {
27
+ ref,
28
+ className: cn("flex cursor-default items-center justify-center py-1", className),
29
+ ...props
30
+ },
31
+ /* @__PURE__ */ React.createElement(ChevronUp, { className: "h-4 w-4" })
32
+ ));
33
+ SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName;
34
+ const SelectScrollDownButton = React.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ React.createElement(
35
+ SelectPrimitive.ScrollDownButton,
36
+ {
37
+ ref,
38
+ className: cn("flex cursor-default items-center justify-center py-1", className),
39
+ ...props
40
+ },
41
+ /* @__PURE__ */ React.createElement(ChevronDown, { className: "h-4 w-4" })
42
+ ));
43
+ SelectScrollDownButton.displayName = SelectPrimitive.ScrollDownButton.displayName;
44
+ const SelectContent = React.forwardRef(({ className, children, position = "popper", ...props }, ref) => /* @__PURE__ */ React.createElement(SelectPrimitive.Portal, null, /* @__PURE__ */ React.createElement(
45
+ SelectPrimitive.Content,
46
+ {
47
+ ref,
48
+ className: cn(
49
+ "relative z-50 max-h-[--radix-select-content-available-height] min-w-[8rem] overflow-y-auto overflow-x-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-select-content-transform-origin]",
50
+ position === "popper" && "data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
51
+ className
52
+ ),
53
+ position,
54
+ ...props
55
+ },
56
+ /* @__PURE__ */ React.createElement(SelectScrollUpButton, null),
57
+ /* @__PURE__ */ React.createElement(
58
+ SelectPrimitive.Viewport,
59
+ {
60
+ className: cn("p-1", position === "popper" && "h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]")
61
+ },
62
+ children
63
+ ),
64
+ /* @__PURE__ */ React.createElement(SelectScrollDownButton, null)
65
+ )));
66
+ SelectContent.displayName = SelectPrimitive.Content.displayName;
67
+ const SelectLabel = React.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ React.createElement(
68
+ SelectPrimitive.Label,
69
+ {
70
+ ref,
71
+ className: cn("px-2 py-1.5 text-sm font-semibold", className),
72
+ ...props
73
+ }
74
+ ));
75
+ SelectLabel.displayName = SelectPrimitive.Label.displayName;
76
+ const SelectItem = React.forwardRef(({ className, children, ...props }, ref) => /* @__PURE__ */ React.createElement(
77
+ SelectPrimitive.Item,
78
+ {
79
+ ref,
80
+ className: cn(
81
+ "relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-2 pr-8 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
82
+ className
83
+ ),
84
+ ...props
85
+ },
86
+ /* @__PURE__ */ React.createElement("span", { className: "absolute right-2 flex h-3.5 w-3.5 items-center justify-center" }, /* @__PURE__ */ React.createElement(SelectPrimitive.ItemIndicator, null, /* @__PURE__ */ React.createElement(Check, { className: "h-4 w-4" }))),
87
+ /* @__PURE__ */ React.createElement(SelectPrimitive.ItemText, null, children)
88
+ ));
89
+ SelectItem.displayName = SelectPrimitive.Item.displayName;
90
+ const SelectSeparator = React.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ React.createElement(
91
+ SelectPrimitive.Separator,
92
+ {
93
+ ref,
94
+ className: cn("-mx-1 my-1 h-px bg-muted", className),
95
+ ...props
96
+ }
97
+ ));
98
+ SelectSeparator.displayName = SelectPrimitive.Separator.displayName;
99
+ export {
100
+ Select,
101
+ SelectContent,
102
+ SelectGroup,
103
+ SelectItem,
104
+ SelectLabel,
105
+ SelectScrollDownButton,
106
+ SelectScrollUpButton,
107
+ SelectSeparator,
108
+ SelectTrigger,
109
+ SelectValue
110
+ };
@@ -0,0 +1,28 @@
1
+ // shadcn/ui component shared by the Scalio apps (code-quality plan QA6, Grundregel 8). Import it as
2
+ // "@scalio-cloud/ui/shadcn/sonner"; the app installs the Radix peer dependencies it uses (README).
3
+ import { Toaster as Sonner, toast } from "sonner";
4
+ const Toaster = ({
5
+ theme = "system",
6
+ ...props
7
+ }) => {
8
+ return /* @__PURE__ */ React.createElement(
9
+ Sonner,
10
+ {
11
+ theme,
12
+ className: "toaster group",
13
+ toastOptions: {
14
+ classNames: {
15
+ toast: "group toast group-[.toaster]:bg-background group-[.toaster]:text-foreground group-[.toaster]:border-border group-[.toaster]:shadow-lg",
16
+ description: "group-[.toast]:text-muted-foreground",
17
+ actionButton: "group-[.toast]:bg-primary group-[.toast]:text-primary-foreground",
18
+ cancelButton: "group-[.toast]:bg-muted group-[.toast]:text-muted-foreground"
19
+ }
20
+ },
21
+ ...props
22
+ }
23
+ );
24
+ };
25
+ export {
26
+ Toaster,
27
+ toast
28
+ };
@@ -0,0 +1,28 @@
1
+ // shadcn/ui component shared by the Scalio apps (code-quality plan QA6, Grundregel 8). Import it as
2
+ // "@scalio-cloud/ui/shadcn/switch"; the app installs the Radix peer dependencies it uses (README).
3
+ import * as React from "react";
4
+ import * as SwitchPrimitives from "@radix-ui/react-switch";
5
+ import { cn } from "./utils.mjs";
6
+ const Switch = React.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ React.createElement(
7
+ SwitchPrimitives.Root,
8
+ {
9
+ className: cn(
10
+ "peer inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input",
11
+ className
12
+ ),
13
+ ...props,
14
+ ref
15
+ },
16
+ /* @__PURE__ */ React.createElement(
17
+ SwitchPrimitives.Thumb,
18
+ {
19
+ className: cn(
20
+ "pointer-events-none block h-4 w-4 rounded-full bg-background shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-4 data-[state=unchecked]:translate-x-0"
21
+ )
22
+ }
23
+ )
24
+ ));
25
+ Switch.displayName = SwitchPrimitives.Root.displayName;
26
+ export {
27
+ Switch
28
+ };
@@ -0,0 +1,88 @@
1
+ // shadcn/ui component shared by the Scalio apps (code-quality plan QA6, Grundregel 8). Import it as
2
+ // "@scalio-cloud/ui/shadcn/table"; the app installs the Radix peer dependencies it uses (README).
3
+ import * as React from "react";
4
+ import { cn } from "./utils.mjs";
5
+ const Table = React.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ React.createElement("div", { className: "relative w-full overflow-auto" }, /* @__PURE__ */ React.createElement(
6
+ "table",
7
+ {
8
+ ref,
9
+ className: cn("w-full caption-bottom text-sm", className),
10
+ ...props
11
+ }
12
+ )));
13
+ Table.displayName = "Table";
14
+ const TableHeader = React.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ React.createElement("thead", { ref, className: cn("[&_tr]:border-b", className), ...props }));
15
+ TableHeader.displayName = "TableHeader";
16
+ const TableBody = React.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ React.createElement(
17
+ "tbody",
18
+ {
19
+ ref,
20
+ className: cn("[&_tr:last-child]:border-0", className),
21
+ ...props
22
+ }
23
+ ));
24
+ TableBody.displayName = "TableBody";
25
+ const TableFooter = React.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ React.createElement(
26
+ "tfoot",
27
+ {
28
+ ref,
29
+ className: cn("border-t bg-muted/50 font-medium [&>tr]:last:border-b-0", className),
30
+ ...props
31
+ }
32
+ ));
33
+ TableFooter.displayName = "TableFooter";
34
+ const TableRow = React.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ React.createElement(
35
+ "tr",
36
+ {
37
+ ref,
38
+ className: cn(
39
+ "border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted",
40
+ className
41
+ ),
42
+ ...props
43
+ }
44
+ ));
45
+ TableRow.displayName = "TableRow";
46
+ const TableHead = React.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ React.createElement(
47
+ "th",
48
+ {
49
+ ref,
50
+ className: cn(
51
+ "h-10 px-2 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
52
+ className
53
+ ),
54
+ ...props
55
+ }
56
+ ));
57
+ TableHead.displayName = "TableHead";
58
+ const TableCell = React.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ React.createElement(
59
+ "td",
60
+ {
61
+ ref,
62
+ className: cn(
63
+ "p-2 align-middle [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
64
+ className
65
+ ),
66
+ ...props
67
+ }
68
+ ));
69
+ TableCell.displayName = "TableCell";
70
+ const TableCaption = React.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ React.createElement(
71
+ "caption",
72
+ {
73
+ ref,
74
+ className: cn("mt-4 text-sm text-muted-foreground", className),
75
+ ...props
76
+ }
77
+ ));
78
+ TableCaption.displayName = "TableCaption";
79
+ export {
80
+ Table,
81
+ TableBody,
82
+ TableCaption,
83
+ TableCell,
84
+ TableFooter,
85
+ TableHead,
86
+ TableHeader,
87
+ TableRow
88
+ };
@@ -0,0 +1,48 @@
1
+ // shadcn/ui component shared by the Scalio apps (code-quality plan QA6, Grundregel 8). Import it as
2
+ // "@scalio-cloud/ui/shadcn/tabs"; the app installs the Radix peer dependencies it uses (README).
3
+ import * as React from "react";
4
+ import * as TabsPrimitive from "@radix-ui/react-tabs";
5
+ import { cn } from "./utils.mjs";
6
+ const Tabs = TabsPrimitive.Root;
7
+ const TabsList = React.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ React.createElement(
8
+ TabsPrimitive.List,
9
+ {
10
+ ref,
11
+ className: cn(
12
+ "scalio-tabs",
13
+ className
14
+ ),
15
+ ...props
16
+ }
17
+ ));
18
+ TabsList.displayName = TabsPrimitive.List.displayName;
19
+ const TabsTrigger = React.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ React.createElement(
20
+ TabsPrimitive.Trigger,
21
+ {
22
+ ref,
23
+ className: cn(
24
+ "scalio-tab",
25
+ className
26
+ ),
27
+ ...props
28
+ }
29
+ ));
30
+ TabsTrigger.displayName = TabsPrimitive.Trigger.displayName;
31
+ const TabsContent = React.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ React.createElement(
32
+ TabsPrimitive.Content,
33
+ {
34
+ ref,
35
+ className: cn(
36
+ "mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
37
+ className
38
+ ),
39
+ ...props
40
+ }
41
+ ));
42
+ TabsContent.displayName = TabsPrimitive.Content.displayName;
43
+ export {
44
+ Tabs,
45
+ TabsContent,
46
+ TabsList,
47
+ TabsTrigger
48
+ };
@@ -0,0 +1,21 @@
1
+ // shadcn/ui component shared by the Scalio apps (code-quality plan QA6, Grundregel 8). Import it as
2
+ // "@scalio-cloud/ui/shadcn/textarea"; the app installs the Radix peer dependencies it uses (README).
3
+ import * as React from "react";
4
+ import { cn } from "./utils.mjs";
5
+ const Textarea = React.forwardRef(({ className, ...props }, ref) => {
6
+ return /* @__PURE__ */ React.createElement(
7
+ "textarea",
8
+ {
9
+ className: cn(
10
+ "flex min-h-[60px] w-full rounded-md border border-input bg-transparent px-3 py-2 text-base shadow-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
11
+ className
12
+ ),
13
+ ref,
14
+ ...props
15
+ }
16
+ );
17
+ });
18
+ Textarea.displayName = "Textarea";
19
+ export {
20
+ Textarea
21
+ };
@@ -0,0 +1,10 @@
1
+ // shadcn/ui component shared by the Scalio apps (code-quality plan QA6, Grundregel 8). Import it as
2
+ // "@scalio-cloud/ui/shadcn/utils"; the app installs the Radix peer dependencies it uses (README).
3
+ import { clsx } from "clsx";
4
+ import { twMerge } from "tailwind-merge";
5
+ function cn(...inputs) {
6
+ return twMerge(clsx(inputs));
7
+ }
8
+ export {
9
+ cn
10
+ };
package/shell.css CHANGED
@@ -29,7 +29,7 @@
29
29
  }
30
30
  .scalio-brand-text { display: none; flex-direction: column; line-height: 1.2; }
31
31
  @media (min-width: 1024px) { .scalio-brand-text { display: flex; } }
32
- /* Product name and its edition badges on one line (ADR 0041 A1). */
32
+ /* Product name and its edition badges on one line (ADR 0042 A1). */
33
33
  .scalio-brand-line { display: flex; align-items: center; gap: 0.375rem; }
34
34
  .scalio-edition-badges { display: inline-flex; align-items: center; gap: 0.25rem; }
35
35
  .scalio-edition-badge { font-size: 9px; padding: 0.0625rem 0.3125rem; }