@nubitio/admin 0.5.13 → 0.5.15

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/dist/index.cjs CHANGED
@@ -383,8 +383,213 @@ const AdminShell = ({ title, menuItems, headerActions, renderUserMenu, renderThe
383
383
  });
384
384
  };
385
385
  //#endregion
386
+ //#region packages/admin/auth/SessionContext.tsx
387
+ const SessionContext = (0, react.createContext)(null);
388
+ function joinApiPath$1(apiBaseUrl, path) {
389
+ return `${apiBaseUrl.replace(/\/+$/, "")}/${path.replace(/^\/+/, "")}`;
390
+ }
391
+ function SessionProvider({ apiBaseUrl = "/api/", mePath = "me", logoutPath = "auth/logout", children }) {
392
+ const [session, setSession] = (0, react.useState)({ status: "loading" });
393
+ const refresh = (0, react.useCallback)(async () => {
394
+ try {
395
+ const response = await fetch(joinApiPath$1(apiBaseUrl, mePath), { credentials: "include" });
396
+ if (!response.ok) {
397
+ setSession({ status: "anonymous" });
398
+ return;
399
+ }
400
+ setSession({
401
+ status: "authenticated",
402
+ profile: await response.json()
403
+ });
404
+ } catch {
405
+ setSession({ status: "anonymous" });
406
+ }
407
+ }, [apiBaseUrl, mePath]);
408
+ (0, react.useEffect)(() => {
409
+ refresh();
410
+ }, [refresh]);
411
+ const logout = (0, react.useCallback)(async () => {
412
+ await fetch(joinApiPath$1(apiBaseUrl, logoutPath), {
413
+ method: "POST",
414
+ credentials: "include"
415
+ });
416
+ setSession({ status: "anonymous" });
417
+ }, [apiBaseUrl, logoutPath]);
418
+ const value = (0, react.useMemo)(() => ({
419
+ session,
420
+ refresh,
421
+ logout,
422
+ roles: session.status === "authenticated" ? session.profile.roles : [],
423
+ username: session.status === "authenticated" ? session.profile.username : null
424
+ }), [
425
+ logout,
426
+ refresh,
427
+ session
428
+ ]);
429
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SessionContext.Provider, {
430
+ value,
431
+ children
432
+ });
433
+ }
434
+ function useSession() {
435
+ const context = (0, react.useContext)(SessionContext);
436
+ if (!context) throw new Error("useSession must be used within a <SessionProvider>.");
437
+ return context;
438
+ }
439
+ //#endregion
440
+ //#region packages/admin/auth/LoginPage.tsx
441
+ function joinApiPath(apiBaseUrl, path) {
442
+ return `${apiBaseUrl.replace(/\/+$/, "")}/${path.replace(/^\/+/, "")}`;
443
+ }
444
+ function LoginPage({ onLoggedIn, apiBaseUrl = "/api/", loginPath = "auth/login", title = "Nubit Admin", hint, defaultUsername = "" }) {
445
+ const [username, setUsername] = (0, react.useState)(defaultUsername);
446
+ const [password, setPassword] = (0, react.useState)("");
447
+ const [error, setError] = (0, react.useState)(null);
448
+ const [busy, setBusy] = (0, react.useState)(false);
449
+ const submit = async (event) => {
450
+ event.preventDefault();
451
+ setBusy(true);
452
+ setError(null);
453
+ try {
454
+ const response = await fetch(joinApiPath(apiBaseUrl, loginPath), {
455
+ method: "POST",
456
+ headers: { "Content-Type": "application/json" },
457
+ credentials: "include",
458
+ body: JSON.stringify({
459
+ username,
460
+ password
461
+ })
462
+ });
463
+ if (!response.ok) {
464
+ setError((await response.json().catch(() => null))?.message ?? "Login failed");
465
+ return;
466
+ }
467
+ onLoggedIn();
468
+ } catch {
469
+ setError("Network error");
470
+ } finally {
471
+ setBusy(false);
472
+ }
473
+ };
474
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
475
+ style: {
476
+ display: "grid",
477
+ placeItems: "center",
478
+ minHeight: "100vh"
479
+ },
480
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_nubitio_ui.Card, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("form", {
481
+ onSubmit: submit,
482
+ style: {
483
+ display: "flex",
484
+ flexDirection: "column",
485
+ gap: 12,
486
+ width: 320,
487
+ padding: 8
488
+ },
489
+ children: [
490
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
491
+ style: { margin: 0 },
492
+ children: title
493
+ }),
494
+ hint && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
495
+ style: {
496
+ margin: 0,
497
+ color: "var(--text-secondary)"
498
+ },
499
+ children: hint
500
+ }),
501
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_nubitio_ui.TextField, {
502
+ placeholder: "Email",
503
+ value: username,
504
+ autoComplete: "username",
505
+ onChange: (e) => setUsername(e.target.value)
506
+ }),
507
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_nubitio_ui.TextField, {
508
+ placeholder: "Password",
509
+ type: "password",
510
+ value: password,
511
+ autoComplete: "current-password",
512
+ onChange: (e) => setPassword(e.target.value)
513
+ }),
514
+ error && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
515
+ style: {
516
+ margin: 0,
517
+ color: "var(--error-color, #dc2626)"
518
+ },
519
+ children: error
520
+ }),
521
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_nubitio_ui.Button, {
522
+ variant: "primary",
523
+ type: "submit",
524
+ disabled: busy,
525
+ children: busy ? "Signing in…" : "Sign in"
526
+ })
527
+ ]
528
+ }) })
529
+ });
530
+ }
531
+ //#endregion
532
+ //#region packages/admin/runtime/useAppRuntime.ts
533
+ function useAppRuntime() {
534
+ const [toasts, setToasts] = (0, react.useState)([]);
535
+ const dismiss = (0, react.useCallback)((id) => {
536
+ setToasts((current) => current.filter((toast) => toast.id !== id));
537
+ }, []);
538
+ const notify = (0, react.useCallback)((message, type = "info", durationMs = 4e3) => {
539
+ const id = Date.now() + Math.floor(Math.random() * 1e3);
540
+ setToasts((current) => [...current, {
541
+ id,
542
+ message,
543
+ type
544
+ }]);
545
+ window.setTimeout(() => dismiss(id), durationMs);
546
+ }, [dismiss]);
547
+ const confirm = (0, react.useCallback)((message) => {
548
+ if (typeof window === "undefined") return false;
549
+ return window.confirm(message);
550
+ }, []);
551
+ return {
552
+ runtime: (0, react.useMemo)(() => ({
553
+ notify,
554
+ confirm
555
+ }), [confirm, notify]),
556
+ toasts,
557
+ dismiss
558
+ };
559
+ }
560
+ //#endregion
561
+ //#region packages/admin/runtime/ToastHost.tsx
562
+ const TYPE_CLASS = {
563
+ success: "nb-toast--success",
564
+ error: "nb-toast--error",
565
+ warning: "nb-toast--warning",
566
+ info: "nb-toast--info"
567
+ };
568
+ function ToastHost({ toasts, onDismiss }) {
569
+ if (toasts.length === 0) return null;
570
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
571
+ className: "nb-toast-host",
572
+ "aria-live": "polite",
573
+ children: toasts.map((toast) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
574
+ className: `nb-toast ${TYPE_CLASS[toast.type]}`,
575
+ role: "status",
576
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: toast.message }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
577
+ type: "button",
578
+ className: "nb-toast__close",
579
+ onClick: () => onDismiss(toast.id),
580
+ children: "×"
581
+ })]
582
+ }, toast.id))
583
+ });
584
+ }
585
+ //#endregion
386
586
  exports.AdminHeader = AdminHeader;
387
587
  exports.AdminShell = AdminShell;
388
588
  exports.AdminSidebarMenu = AdminSidebarMenu;
589
+ exports.LoginPage = LoginPage;
590
+ exports.SessionProvider = SessionProvider;
591
+ exports.ToastHost = ToastHost;
592
+ exports.useAppRuntime = useAppRuntime;
389
593
  exports.useScreenSize = useScreenSize;
390
594
  exports.useScreenSizeClass = useScreenSizeClass;
595
+ exports.useSession = useSession;
package/dist/index.d.cts CHANGED
@@ -1,4 +1,5 @@
1
1
  import React from "react";
2
+ import { CoreRuntime } from "@nubitio/core";
2
3
 
3
4
  //#region packages/admin/AdminHeader.d.ts
4
5
  interface AdminHeaderAction {
@@ -95,4 +96,83 @@ declare const useScreenSize: () => {
95
96
  };
96
97
  declare const useScreenSizeClass: () => "screen-large" | "screen-medium" | "screen-small" | "screen-x-small";
97
98
  //#endregion
98
- export { AdminHeader, type AdminHeaderAction, type AdminHeaderProps, type AdminMenuItem, type AdminMenuSubItem, AdminShell, type AdminShellProps, AdminSidebarMenu, type AdminSidebarMenuProps, type AdminSidebarMenuSelectEvent, useScreenSize, useScreenSizeClass };
99
+ //#region packages/admin/auth/SessionContext.d.ts
100
+ type SessionProfile = {
101
+ username: string;
102
+ roles: string[];
103
+ };
104
+ type SessionState = {
105
+ status: 'loading';
106
+ } | {
107
+ status: 'anonymous';
108
+ } | {
109
+ status: 'authenticated';
110
+ profile: SessionProfile;
111
+ };
112
+ interface SessionProviderConfig {
113
+ /** API base URL, e.g. `/api/`. */
114
+ apiBaseUrl?: string;
115
+ /** Profile endpoint relative to apiBaseUrl. @default `me` */
116
+ mePath?: string;
117
+ /** Logout endpoint relative to apiBaseUrl. @default `auth/logout` */
118
+ logoutPath?: string;
119
+ }
120
+ interface SessionContextValue {
121
+ session: SessionState;
122
+ refresh: () => Promise<void>;
123
+ logout: () => Promise<void>;
124
+ roles: string[];
125
+ username: string | null;
126
+ }
127
+ declare function SessionProvider({
128
+ apiBaseUrl,
129
+ mePath,
130
+ logoutPath,
131
+ children
132
+ }: SessionProviderConfig & {
133
+ children: React.ReactNode;
134
+ }): React.JSX.Element;
135
+ declare function useSession(): SessionContextValue;
136
+ //#endregion
137
+ //#region packages/admin/auth/LoginPage.d.ts
138
+ interface LoginPageProps {
139
+ onLoggedIn: () => void;
140
+ apiBaseUrl?: string;
141
+ loginPath?: string;
142
+ title?: string;
143
+ hint?: string;
144
+ defaultUsername?: string;
145
+ }
146
+ declare function LoginPage({
147
+ onLoggedIn,
148
+ apiBaseUrl,
149
+ loginPath,
150
+ title,
151
+ hint,
152
+ defaultUsername
153
+ }: LoginPageProps): import("react").JSX.Element;
154
+ //#endregion
155
+ //#region packages/admin/runtime/useAppRuntime.d.ts
156
+ type NotificationType = 'success' | 'error' | 'warning' | 'info';
157
+ type ToastItem = {
158
+ id: number;
159
+ message: string;
160
+ type: NotificationType;
161
+ };
162
+ declare function useAppRuntime(): {
163
+ runtime: CoreRuntime;
164
+ toasts: ToastItem[];
165
+ dismiss: (id: number) => void;
166
+ };
167
+ //#endregion
168
+ //#region packages/admin/runtime/ToastHost.d.ts
169
+ interface ToastHostProps {
170
+ toasts: ToastItem[];
171
+ onDismiss: (id: number) => void;
172
+ }
173
+ declare function ToastHost({
174
+ toasts,
175
+ onDismiss
176
+ }: ToastHostProps): import("react").JSX.Element | null;
177
+ //#endregion
178
+ export { AdminHeader, type AdminHeaderAction, type AdminHeaderProps, type AdminMenuItem, type AdminMenuSubItem, AdminShell, type AdminShellProps, AdminSidebarMenu, type AdminSidebarMenuProps, type AdminSidebarMenuSelectEvent, LoginPage, type LoginPageProps, type NotificationType, type SessionContextValue, type SessionProfile, SessionProvider, type SessionProviderConfig, type SessionState, ToastHost, type ToastHostProps, type ToastItem, useAppRuntime, useScreenSize, useScreenSizeClass, useSession };
package/dist/index.d.mts CHANGED
@@ -1,4 +1,5 @@
1
1
  import React from "react";
2
+ import { CoreRuntime } from "@nubitio/core";
2
3
 
3
4
  //#region packages/admin/AdminHeader.d.ts
4
5
  interface AdminHeaderAction {
@@ -95,4 +96,83 @@ declare const useScreenSize: () => {
95
96
  };
96
97
  declare const useScreenSizeClass: () => "screen-large" | "screen-medium" | "screen-small" | "screen-x-small";
97
98
  //#endregion
98
- export { AdminHeader, type AdminHeaderAction, type AdminHeaderProps, type AdminMenuItem, type AdminMenuSubItem, AdminShell, type AdminShellProps, AdminSidebarMenu, type AdminSidebarMenuProps, type AdminSidebarMenuSelectEvent, useScreenSize, useScreenSizeClass };
99
+ //#region packages/admin/auth/SessionContext.d.ts
100
+ type SessionProfile = {
101
+ username: string;
102
+ roles: string[];
103
+ };
104
+ type SessionState = {
105
+ status: 'loading';
106
+ } | {
107
+ status: 'anonymous';
108
+ } | {
109
+ status: 'authenticated';
110
+ profile: SessionProfile;
111
+ };
112
+ interface SessionProviderConfig {
113
+ /** API base URL, e.g. `/api/`. */
114
+ apiBaseUrl?: string;
115
+ /** Profile endpoint relative to apiBaseUrl. @default `me` */
116
+ mePath?: string;
117
+ /** Logout endpoint relative to apiBaseUrl. @default `auth/logout` */
118
+ logoutPath?: string;
119
+ }
120
+ interface SessionContextValue {
121
+ session: SessionState;
122
+ refresh: () => Promise<void>;
123
+ logout: () => Promise<void>;
124
+ roles: string[];
125
+ username: string | null;
126
+ }
127
+ declare function SessionProvider({
128
+ apiBaseUrl,
129
+ mePath,
130
+ logoutPath,
131
+ children
132
+ }: SessionProviderConfig & {
133
+ children: React.ReactNode;
134
+ }): React.JSX.Element;
135
+ declare function useSession(): SessionContextValue;
136
+ //#endregion
137
+ //#region packages/admin/auth/LoginPage.d.ts
138
+ interface LoginPageProps {
139
+ onLoggedIn: () => void;
140
+ apiBaseUrl?: string;
141
+ loginPath?: string;
142
+ title?: string;
143
+ hint?: string;
144
+ defaultUsername?: string;
145
+ }
146
+ declare function LoginPage({
147
+ onLoggedIn,
148
+ apiBaseUrl,
149
+ loginPath,
150
+ title,
151
+ hint,
152
+ defaultUsername
153
+ }: LoginPageProps): import("react").JSX.Element;
154
+ //#endregion
155
+ //#region packages/admin/runtime/useAppRuntime.d.ts
156
+ type NotificationType = 'success' | 'error' | 'warning' | 'info';
157
+ type ToastItem = {
158
+ id: number;
159
+ message: string;
160
+ type: NotificationType;
161
+ };
162
+ declare function useAppRuntime(): {
163
+ runtime: CoreRuntime;
164
+ toasts: ToastItem[];
165
+ dismiss: (id: number) => void;
166
+ };
167
+ //#endregion
168
+ //#region packages/admin/runtime/ToastHost.d.ts
169
+ interface ToastHostProps {
170
+ toasts: ToastItem[];
171
+ onDismiss: (id: number) => void;
172
+ }
173
+ declare function ToastHost({
174
+ toasts,
175
+ onDismiss
176
+ }: ToastHostProps): import("react").JSX.Element | null;
177
+ //#endregion
178
+ export { AdminHeader, type AdminHeaderAction, type AdminHeaderProps, type AdminMenuItem, type AdminMenuSubItem, AdminShell, type AdminShellProps, AdminSidebarMenu, type AdminSidebarMenuProps, type AdminSidebarMenuSelectEvent, LoginPage, type LoginPageProps, type NotificationType, type SessionContextValue, type SessionProfile, SessionProvider, type SessionProviderConfig, type SessionState, ToastHost, type ToastHostProps, type ToastItem, useAppRuntime, useScreenSize, useScreenSizeClass, useSession };
package/dist/index.mjs CHANGED
@@ -1,6 +1,6 @@
1
- import { useCallback, useEffect, useMemo, useState } from "react";
1
+ import { createContext, useCallback, useContext, useEffect, useMemo, useState } from "react";
2
2
  import { useLocation, useNavigate } from "react-router-dom";
3
- import { Badge, IconButton, useFloatingPanel } from "@nubitio/ui";
3
+ import { Badge, Button, Card, IconButton, TextField, useFloatingPanel } from "@nubitio/ui";
4
4
  import { jsx, jsxs } from "react/jsx-runtime";
5
5
  //#region packages/admin/AdminHeader.tsx
6
6
  function ActionPopover({ action }) {
@@ -359,4 +359,204 @@ const AdminShell = ({ title, menuItems, headerActions, renderUserMenu, renderThe
359
359
  });
360
360
  };
361
361
  //#endregion
362
- export { AdminHeader, AdminShell, AdminSidebarMenu, useScreenSize, useScreenSizeClass };
362
+ //#region packages/admin/auth/SessionContext.tsx
363
+ const SessionContext = createContext(null);
364
+ function joinApiPath$1(apiBaseUrl, path) {
365
+ return `${apiBaseUrl.replace(/\/+$/, "")}/${path.replace(/^\/+/, "")}`;
366
+ }
367
+ function SessionProvider({ apiBaseUrl = "/api/", mePath = "me", logoutPath = "auth/logout", children }) {
368
+ const [session, setSession] = useState({ status: "loading" });
369
+ const refresh = useCallback(async () => {
370
+ try {
371
+ const response = await fetch(joinApiPath$1(apiBaseUrl, mePath), { credentials: "include" });
372
+ if (!response.ok) {
373
+ setSession({ status: "anonymous" });
374
+ return;
375
+ }
376
+ setSession({
377
+ status: "authenticated",
378
+ profile: await response.json()
379
+ });
380
+ } catch {
381
+ setSession({ status: "anonymous" });
382
+ }
383
+ }, [apiBaseUrl, mePath]);
384
+ useEffect(() => {
385
+ refresh();
386
+ }, [refresh]);
387
+ const logout = useCallback(async () => {
388
+ await fetch(joinApiPath$1(apiBaseUrl, logoutPath), {
389
+ method: "POST",
390
+ credentials: "include"
391
+ });
392
+ setSession({ status: "anonymous" });
393
+ }, [apiBaseUrl, logoutPath]);
394
+ const value = useMemo(() => ({
395
+ session,
396
+ refresh,
397
+ logout,
398
+ roles: session.status === "authenticated" ? session.profile.roles : [],
399
+ username: session.status === "authenticated" ? session.profile.username : null
400
+ }), [
401
+ logout,
402
+ refresh,
403
+ session
404
+ ]);
405
+ return /* @__PURE__ */ jsx(SessionContext.Provider, {
406
+ value,
407
+ children
408
+ });
409
+ }
410
+ function useSession() {
411
+ const context = useContext(SessionContext);
412
+ if (!context) throw new Error("useSession must be used within a <SessionProvider>.");
413
+ return context;
414
+ }
415
+ //#endregion
416
+ //#region packages/admin/auth/LoginPage.tsx
417
+ function joinApiPath(apiBaseUrl, path) {
418
+ return `${apiBaseUrl.replace(/\/+$/, "")}/${path.replace(/^\/+/, "")}`;
419
+ }
420
+ function LoginPage({ onLoggedIn, apiBaseUrl = "/api/", loginPath = "auth/login", title = "Nubit Admin", hint, defaultUsername = "" }) {
421
+ const [username, setUsername] = useState(defaultUsername);
422
+ const [password, setPassword] = useState("");
423
+ const [error, setError] = useState(null);
424
+ const [busy, setBusy] = useState(false);
425
+ const submit = async (event) => {
426
+ event.preventDefault();
427
+ setBusy(true);
428
+ setError(null);
429
+ try {
430
+ const response = await fetch(joinApiPath(apiBaseUrl, loginPath), {
431
+ method: "POST",
432
+ headers: { "Content-Type": "application/json" },
433
+ credentials: "include",
434
+ body: JSON.stringify({
435
+ username,
436
+ password
437
+ })
438
+ });
439
+ if (!response.ok) {
440
+ setError((await response.json().catch(() => null))?.message ?? "Login failed");
441
+ return;
442
+ }
443
+ onLoggedIn();
444
+ } catch {
445
+ setError("Network error");
446
+ } finally {
447
+ setBusy(false);
448
+ }
449
+ };
450
+ return /* @__PURE__ */ jsx("div", {
451
+ style: {
452
+ display: "grid",
453
+ placeItems: "center",
454
+ minHeight: "100vh"
455
+ },
456
+ children: /* @__PURE__ */ jsx(Card, { children: /* @__PURE__ */ jsxs("form", {
457
+ onSubmit: submit,
458
+ style: {
459
+ display: "flex",
460
+ flexDirection: "column",
461
+ gap: 12,
462
+ width: 320,
463
+ padding: 8
464
+ },
465
+ children: [
466
+ /* @__PURE__ */ jsx("h2", {
467
+ style: { margin: 0 },
468
+ children: title
469
+ }),
470
+ hint && /* @__PURE__ */ jsx("p", {
471
+ style: {
472
+ margin: 0,
473
+ color: "var(--text-secondary)"
474
+ },
475
+ children: hint
476
+ }),
477
+ /* @__PURE__ */ jsx(TextField, {
478
+ placeholder: "Email",
479
+ value: username,
480
+ autoComplete: "username",
481
+ onChange: (e) => setUsername(e.target.value)
482
+ }),
483
+ /* @__PURE__ */ jsx(TextField, {
484
+ placeholder: "Password",
485
+ type: "password",
486
+ value: password,
487
+ autoComplete: "current-password",
488
+ onChange: (e) => setPassword(e.target.value)
489
+ }),
490
+ error && /* @__PURE__ */ jsx("p", {
491
+ style: {
492
+ margin: 0,
493
+ color: "var(--error-color, #dc2626)"
494
+ },
495
+ children: error
496
+ }),
497
+ /* @__PURE__ */ jsx(Button, {
498
+ variant: "primary",
499
+ type: "submit",
500
+ disabled: busy,
501
+ children: busy ? "Signing in…" : "Sign in"
502
+ })
503
+ ]
504
+ }) })
505
+ });
506
+ }
507
+ //#endregion
508
+ //#region packages/admin/runtime/useAppRuntime.ts
509
+ function useAppRuntime() {
510
+ const [toasts, setToasts] = useState([]);
511
+ const dismiss = useCallback((id) => {
512
+ setToasts((current) => current.filter((toast) => toast.id !== id));
513
+ }, []);
514
+ const notify = useCallback((message, type = "info", durationMs = 4e3) => {
515
+ const id = Date.now() + Math.floor(Math.random() * 1e3);
516
+ setToasts((current) => [...current, {
517
+ id,
518
+ message,
519
+ type
520
+ }]);
521
+ window.setTimeout(() => dismiss(id), durationMs);
522
+ }, [dismiss]);
523
+ const confirm = useCallback((message) => {
524
+ if (typeof window === "undefined") return false;
525
+ return window.confirm(message);
526
+ }, []);
527
+ return {
528
+ runtime: useMemo(() => ({
529
+ notify,
530
+ confirm
531
+ }), [confirm, notify]),
532
+ toasts,
533
+ dismiss
534
+ };
535
+ }
536
+ //#endregion
537
+ //#region packages/admin/runtime/ToastHost.tsx
538
+ const TYPE_CLASS = {
539
+ success: "nb-toast--success",
540
+ error: "nb-toast--error",
541
+ warning: "nb-toast--warning",
542
+ info: "nb-toast--info"
543
+ };
544
+ function ToastHost({ toasts, onDismiss }) {
545
+ if (toasts.length === 0) return null;
546
+ return /* @__PURE__ */ jsx("div", {
547
+ className: "nb-toast-host",
548
+ "aria-live": "polite",
549
+ children: toasts.map((toast) => /* @__PURE__ */ jsxs("div", {
550
+ className: `nb-toast ${TYPE_CLASS[toast.type]}`,
551
+ role: "status",
552
+ children: [/* @__PURE__ */ jsx("span", { children: toast.message }), /* @__PURE__ */ jsx("button", {
553
+ type: "button",
554
+ className: "nb-toast__close",
555
+ onClick: () => onDismiss(toast.id),
556
+ children: "×"
557
+ })]
558
+ }, toast.id))
559
+ });
560
+ }
561
+ //#endregion
562
+ export { AdminHeader, AdminShell, AdminSidebarMenu, LoginPage, SessionProvider, ToastHost, useAppRuntime, useScreenSize, useScreenSizeClass, useSession };
package/dist/style.css CHANGED
@@ -369,3 +369,49 @@
369
369
  opacity: 0;
370
370
  pointer-events: none;
371
371
  }
372
+ .nb-toast-host {
373
+ position: fixed;
374
+ right: 1rem;
375
+ bottom: 1rem;
376
+ z-index: 1000;
377
+ display: flex;
378
+ flex-direction: column;
379
+ gap: 0.5rem;
380
+ max-width: min(24rem, 100vw - 2rem);
381
+ }
382
+
383
+ .nb-toast {
384
+ display: flex;
385
+ align-items: flex-start;
386
+ justify-content: space-between;
387
+ gap: 0.75rem;
388
+ padding: 0.75rem 1rem;
389
+ border-radius: var(--radius-md, 0.5rem);
390
+ border: 1px solid var(--border-subtle);
391
+ background: var(--surface-2);
392
+ color: var(--text-primary);
393
+ box-shadow: var(--shadow-md, 0 8px 24px rgba(0, 0, 0, 0.12));
394
+ font-size: 0.875rem;
395
+ }
396
+
397
+ .nb-toast--success {
398
+ border-color: color-mix(in srgb, var(--success-color, #16a34a) 35%, var(--border-subtle));
399
+ }
400
+
401
+ .nb-toast--error {
402
+ border-color: color-mix(in srgb, var(--error-color, #dc2626) 35%, var(--border-subtle));
403
+ }
404
+
405
+ .nb-toast--warning {
406
+ border-color: color-mix(in srgb, var(--warning-color, #d97706) 35%, var(--border-subtle));
407
+ }
408
+
409
+ .nb-toast__close {
410
+ border: 0;
411
+ background: transparent;
412
+ color: var(--text-secondary);
413
+ cursor: pointer;
414
+ font-size: 1.125rem;
415
+ line-height: 1;
416
+ padding: 0;
417
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nubitio/admin",
3
- "version": "0.5.13",
3
+ "version": "0.5.15",
4
4
  "type": "module",
5
5
  "description": "Admin shell layout components: responsive sidebar, header, and screen size utilities for Nubit apps.",
6
6
  "license": "MIT",
@@ -52,6 +52,7 @@
52
52
  "react": "^19.0.0",
53
53
  "react-dom": "^19.0.0",
54
54
  "react-router-dom": "^6.0.0",
55
- "@nubitio/ui": "^0.5.13"
55
+ "@nubitio/ui": "^0.5.15",
56
+ "@nubitio/core": "^0.5.15"
56
57
  }
57
58
  }