@object-ui/app-shell 3.3.0 → 3.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +45 -0
- package/README.md +22 -2
- package/dist/chrome/CommandPalette.d.ts +16 -0
- package/dist/chrome/CommandPalette.js +88 -0
- package/dist/chrome/ConditionalAuthWrapper.d.ts +23 -0
- package/dist/chrome/ConditionalAuthWrapper.js +135 -0
- package/dist/chrome/ConsoleToaster.d.ts +11 -0
- package/dist/chrome/ConsoleToaster.js +28 -0
- package/dist/chrome/ErrorBoundary.d.ts +39 -0
- package/dist/chrome/ErrorBoundary.js +64 -0
- package/dist/chrome/KeyboardShortcutsDialog.d.ts +7 -0
- package/dist/chrome/KeyboardShortcutsDialog.js +63 -0
- package/dist/chrome/LoadingScreen.d.ts +12 -0
- package/dist/chrome/LoadingScreen.js +44 -0
- package/dist/chrome/OnboardingWalkthrough.d.ts +9 -0
- package/dist/chrome/OnboardingWalkthrough.js +11 -0
- package/dist/chrome/ThemeProvider.d.ts +13 -0
- package/dist/chrome/ThemeProvider.js +37 -0
- package/dist/chrome/index.d.ts +8 -0
- package/dist/chrome/index.js +8 -0
- package/dist/console/AppContent.d.ts +26 -0
- package/dist/console/AppContent.js +266 -0
- package/dist/console/ConsoleShell.d.ts +66 -0
- package/dist/console/ConsoleShell.js +97 -0
- package/dist/console/auth/AuthPageLayout.d.ts +9 -0
- package/dist/console/auth/AuthPageLayout.js +4 -0
- package/dist/console/auth/ForgotPasswordPage.d.ts +4 -0
- package/dist/console/auth/ForgotPasswordPage.js +23 -0
- package/dist/console/auth/LoginPage.d.ts +4 -0
- package/dist/console/auth/LoginPage.js +24 -0
- package/dist/console/auth/RegisterPage.d.ts +4 -0
- package/dist/console/auth/RegisterPage.js +29 -0
- package/dist/console/auth/index.d.ts +4 -0
- package/dist/console/auth/index.js +4 -0
- package/dist/console/home/AppCard.d.ts +14 -0
- package/dist/console/home/AppCard.js +36 -0
- package/dist/console/home/HomeLayout.d.ts +15 -0
- package/dist/console/home/HomeLayout.js +20 -0
- package/dist/console/home/HomePage.d.ts +18 -0
- package/dist/console/home/HomePage.js +54 -0
- package/dist/console/home/QuickActions.d.ts +9 -0
- package/dist/console/home/QuickActions.js +53 -0
- package/dist/console/home/RecentApps.d.ts +13 -0
- package/dist/console/home/RecentApps.js +32 -0
- package/dist/console/home/StarredApps.d.ts +13 -0
- package/dist/console/home/StarredApps.js +29 -0
- package/dist/console/home/index.d.ts +11 -0
- package/dist/console/home/index.js +11 -0
- package/dist/console/organizations/CreateWorkspaceDialog.d.ts +16 -0
- package/dist/console/organizations/CreateWorkspaceDialog.js +71 -0
- package/dist/console/organizations/OrganizationsLayout.d.ts +15 -0
- package/dist/console/organizations/OrganizationsLayout.js +20 -0
- package/dist/console/organizations/OrganizationsPage.d.ts +9 -0
- package/dist/console/organizations/OrganizationsPage.js +76 -0
- package/dist/console/organizations/index.d.ts +8 -0
- package/dist/console/organizations/index.js +8 -0
- package/dist/console/organizations/manage/AcceptInvitationPage.d.ts +7 -0
- package/dist/console/organizations/manage/AcceptInvitationPage.js +100 -0
- package/dist/console/organizations/manage/InvitationsPage.d.ts +6 -0
- package/dist/console/organizations/manage/InvitationsPage.js +107 -0
- package/dist/console/organizations/manage/InviteMemberDialog.d.ts +16 -0
- package/dist/console/organizations/manage/InviteMemberDialog.js +83 -0
- package/dist/console/organizations/manage/MembersPage.d.ts +6 -0
- package/dist/console/organizations/manage/MembersPage.js +89 -0
- package/dist/console/organizations/manage/OrganizationLayout.d.ts +8 -0
- package/dist/console/organizations/manage/OrganizationLayout.js +51 -0
- package/dist/console/organizations/manage/SettingsPage.d.ts +6 -0
- package/dist/console/organizations/manage/SettingsPage.js +141 -0
- package/dist/console/organizations/manage/orgContext.d.ts +5 -0
- package/dist/console/organizations/manage/orgContext.js +4 -0
- package/dist/context/FavoritesProvider.d.ts +50 -0
- package/dist/context/FavoritesProvider.js +129 -0
- package/dist/context/NavigationContext.d.ts +46 -0
- package/dist/context/NavigationContext.js +59 -0
- package/dist/context/index.d.ts +3 -0
- package/dist/context/index.js +2 -0
- package/dist/hooks/index.d.ts +7 -0
- package/dist/hooks/index.js +7 -0
- package/dist/hooks/useFavorites.d.ts +13 -0
- package/dist/hooks/useFavorites.js +13 -0
- package/dist/hooks/useMetadataService.d.ts +13 -0
- package/dist/hooks/useMetadataService.js +18 -0
- package/dist/hooks/useNavPins.d.ts +16 -0
- package/dist/hooks/useNavPins.js +72 -0
- package/dist/hooks/useNavigationSync.d.ts +82 -0
- package/dist/hooks/useNavigationSync.js +495 -0
- package/dist/hooks/useObjectActions.d.ts +38 -0
- package/dist/hooks/useObjectActions.js +92 -0
- package/dist/hooks/useRecentItems.d.ts +21 -0
- package/dist/hooks/useRecentItems.js +50 -0
- package/dist/hooks/useResponsiveSidebar.d.ts +8 -0
- package/dist/hooks/useResponsiveSidebar.js +28 -0
- package/dist/index.d.ts +29 -0
- package/dist/index.js +35 -0
- package/dist/layout/ActivityFeed.d.ts +22 -0
- package/dist/layout/ActivityFeed.js +57 -0
- package/dist/layout/AppHeader.d.ts +33 -0
- package/dist/layout/AppHeader.js +182 -0
- package/dist/layout/AppSidebar.d.ts +14 -0
- package/dist/layout/AppSidebar.js +217 -0
- package/dist/layout/AppSwitcher.d.ts +12 -0
- package/dist/layout/AppSwitcher.js +39 -0
- package/dist/layout/AuthPageLayout.d.ts +9 -0
- package/dist/layout/AuthPageLayout.js +4 -0
- package/dist/layout/ConnectionStatus.d.ts +14 -0
- package/dist/layout/ConnectionStatus.js +36 -0
- package/dist/layout/ConsoleLayout.d.ts +21 -0
- package/dist/layout/ConsoleLayout.js +71 -0
- package/dist/layout/LocaleSwitcher.d.ts +7 -0
- package/dist/layout/LocaleSwitcher.js +27 -0
- package/dist/layout/ModeToggle.d.ts +1 -0
- package/dist/layout/ModeToggle.js +10 -0
- package/dist/layout/UnifiedSidebar.d.ts +22 -0
- package/dist/layout/UnifiedSidebar.js +210 -0
- package/dist/layout/index.d.ts +11 -0
- package/dist/layout/index.js +10 -0
- package/dist/providers/AdapterProvider.d.ts +21 -0
- package/dist/providers/AdapterProvider.js +52 -0
- package/dist/providers/ExpressionProvider.d.ts +50 -0
- package/dist/providers/ExpressionProvider.js +67 -0
- package/dist/providers/MetadataProvider.d.ts +18 -0
- package/dist/providers/MetadataProvider.js +324 -0
- package/dist/services/MetadataService.d.ts +119 -0
- package/dist/services/MetadataService.js +211 -0
- package/dist/skeletons/SkeletonDashboard.d.ts +13 -0
- package/dist/skeletons/SkeletonDashboard.js +12 -0
- package/dist/skeletons/SkeletonDetail.d.ts +15 -0
- package/dist/skeletons/SkeletonDetail.js +12 -0
- package/dist/skeletons/SkeletonGrid.d.ts +15 -0
- package/dist/skeletons/SkeletonGrid.js +12 -0
- package/dist/skeletons/index.d.ts +3 -0
- package/dist/skeletons/index.js +3 -0
- package/dist/utils/getIcon.d.ts +10 -0
- package/dist/utils/getIcon.js +24 -0
- package/dist/utils/index.d.ts +33 -0
- package/dist/utils/index.js +62 -0
- package/dist/utils/metadataConverters.d.ts +78 -0
- package/dist/utils/metadataConverters.js +74 -0
- package/dist/views/ActionConfirmDialog.d.ts +22 -0
- package/dist/views/ActionConfirmDialog.js +22 -0
- package/dist/views/ActionParamDialog.d.ts +23 -0
- package/dist/views/ActionParamDialog.js +67 -0
- package/dist/views/DashboardView.d.ts +9 -0
- package/dist/views/DashboardView.js +357 -0
- package/dist/views/DesignDrawer.d.ts +28 -0
- package/dist/views/DesignDrawer.js +51 -0
- package/dist/views/MetadataInspector.d.ts +40 -0
- package/dist/views/MetadataInspector.js +69 -0
- package/dist/views/ObjectView.d.ts +14 -0
- package/dist/views/ObjectView.js +770 -0
- package/dist/views/PageView.d.ts +6 -0
- package/dist/views/PageView.js +49 -0
- package/dist/views/RecordDetailView.d.ts +14 -0
- package/dist/views/RecordDetailView.js +508 -0
- package/dist/views/ReportView.d.ts +4 -0
- package/dist/views/ReportView.js +284 -0
- package/dist/views/SearchResultsPage.d.ts +9 -0
- package/dist/views/SearchResultsPage.js +107 -0
- package/dist/views/ViewConfigPanel.d.ts +56 -0
- package/dist/views/ViewConfigPanel.js +91 -0
- package/dist/views/index.d.ts +11 -0
- package/dist/views/index.js +11 -0
- package/package.json +59 -11
- package/.turbo/turbo-build.log +0 -4
- package/src/components/AppShell.tsx +0 -31
- package/src/components/DashboardRenderer.tsx +0 -36
- package/src/components/FormRenderer.tsx +0 -71
- package/src/components/ObjectRenderer.tsx +0 -122
- package/src/components/PageRenderer.tsx +0 -28
- package/src/index.ts +0 -20
- package/src/types.ts +0 -78
- package/tsconfig.json +0 -20
- package/tsconfig.tsbuildinfo +0 -1
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useObjectActions Hook
|
|
3
|
+
*
|
|
4
|
+
* Provides action handlers for CRUD operations on an object, backed by
|
|
5
|
+
* the ActionRunner from @object-ui/core via the useActionRunner hook.
|
|
6
|
+
*
|
|
7
|
+
* Supports:
|
|
8
|
+
* - create: Open create dialog
|
|
9
|
+
* - delete: Delete a record with confirmation
|
|
10
|
+
* - navigate: Route to a specific view or record
|
|
11
|
+
* - refresh: Trigger a data refresh
|
|
12
|
+
*/
|
|
13
|
+
import { useCallback, useEffect } from 'react';
|
|
14
|
+
import { useNavigate, useParams } from 'react-router-dom';
|
|
15
|
+
import { useActionRunner } from '@object-ui/react';
|
|
16
|
+
import { toast } from 'sonner';
|
|
17
|
+
export function useObjectActions({ objectName, objectLabel, dataSource, onEdit, onRefresh, }) {
|
|
18
|
+
const navigate = useNavigate();
|
|
19
|
+
const { appName } = useParams();
|
|
20
|
+
const baseUrl = `/apps/${appName}`;
|
|
21
|
+
const { execute, loading, error, runner } = useActionRunner({
|
|
22
|
+
context: {
|
|
23
|
+
objectName,
|
|
24
|
+
objectLabel: objectLabel || objectName,
|
|
25
|
+
baseUrl,
|
|
26
|
+
},
|
|
27
|
+
});
|
|
28
|
+
// Register custom handlers
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
// Handler: create
|
|
31
|
+
runner.registerHandler('create', async () => {
|
|
32
|
+
onEdit?.(null);
|
|
33
|
+
return { success: true };
|
|
34
|
+
});
|
|
35
|
+
// Handler: delete
|
|
36
|
+
runner.registerHandler('delete', async (action) => {
|
|
37
|
+
const recordId = action.params?.recordId || action.recordId;
|
|
38
|
+
if (!recordId)
|
|
39
|
+
return { success: false, error: 'No record ID provided' };
|
|
40
|
+
try {
|
|
41
|
+
await dataSource.delete(objectName, recordId);
|
|
42
|
+
onRefresh?.();
|
|
43
|
+
toast.success(`${objectLabel || objectName} deleted successfully`);
|
|
44
|
+
return { success: true, reload: true };
|
|
45
|
+
}
|
|
46
|
+
catch (err) {
|
|
47
|
+
toast.error(`Failed to delete ${objectLabel || objectName}`, {
|
|
48
|
+
description: err.message,
|
|
49
|
+
});
|
|
50
|
+
return { success: false, error: err.message };
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
// Handler: navigate
|
|
54
|
+
runner.registerHandler('navigate', async (action) => {
|
|
55
|
+
const url = action.params?.url || action.url;
|
|
56
|
+
if (url) {
|
|
57
|
+
navigate(url.startsWith('/') ? url : `${baseUrl}/${url}`);
|
|
58
|
+
}
|
|
59
|
+
return { success: true };
|
|
60
|
+
});
|
|
61
|
+
// Handler: refresh
|
|
62
|
+
runner.registerHandler('refresh', async () => {
|
|
63
|
+
onRefresh?.();
|
|
64
|
+
return { success: true, reload: true };
|
|
65
|
+
});
|
|
66
|
+
}, [runner, objectName, dataSource, onEdit, onRefresh, navigate, baseUrl]);
|
|
67
|
+
const create = useCallback(() => {
|
|
68
|
+
onEdit?.(null);
|
|
69
|
+
}, [onEdit]);
|
|
70
|
+
const deleteRecord = useCallback(async (recordId) => {
|
|
71
|
+
return execute({
|
|
72
|
+
type: 'delete',
|
|
73
|
+
confirmText: `Are you sure you want to delete this record?`,
|
|
74
|
+
params: { recordId },
|
|
75
|
+
});
|
|
76
|
+
}, [execute]);
|
|
77
|
+
const navigateToView = useCallback((viewId) => {
|
|
78
|
+
navigate(`${baseUrl}/${objectName}/view/${viewId}`);
|
|
79
|
+
}, [navigate, baseUrl, objectName]);
|
|
80
|
+
const navigateToRecord = useCallback((recordId) => {
|
|
81
|
+
navigate(`${baseUrl}/${objectName}/record/${encodeURIComponent(recordId)}`);
|
|
82
|
+
}, [navigate, baseUrl, objectName]);
|
|
83
|
+
return {
|
|
84
|
+
execute,
|
|
85
|
+
create,
|
|
86
|
+
deleteRecord,
|
|
87
|
+
navigateToView,
|
|
88
|
+
navigateToRecord,
|
|
89
|
+
loading,
|
|
90
|
+
error,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useRecentItems
|
|
3
|
+
*
|
|
4
|
+
* Tracks recently visited items (objects, dashboards, pages) with
|
|
5
|
+
* localStorage persistence. Exposes helpers to add and retrieve items.
|
|
6
|
+
* @module
|
|
7
|
+
*/
|
|
8
|
+
export interface RecentItem {
|
|
9
|
+
/** Unique key, e.g. "object:contact" or "dashboard:sales_overview" */
|
|
10
|
+
id: string;
|
|
11
|
+
label: string;
|
|
12
|
+
href: string;
|
|
13
|
+
type: 'object' | 'dashboard' | 'page' | 'report' | 'record';
|
|
14
|
+
/** ISO timestamp of last visit */
|
|
15
|
+
visitedAt: string;
|
|
16
|
+
}
|
|
17
|
+
export declare function useRecentItems(): {
|
|
18
|
+
recentItems: RecentItem[];
|
|
19
|
+
addRecentItem: (item: Omit<RecentItem, "visitedAt">) => void;
|
|
20
|
+
clearRecentItems: () => void;
|
|
21
|
+
};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useRecentItems
|
|
3
|
+
*
|
|
4
|
+
* Tracks recently visited items (objects, dashboards, pages) with
|
|
5
|
+
* localStorage persistence. Exposes helpers to add and retrieve items.
|
|
6
|
+
* @module
|
|
7
|
+
*/
|
|
8
|
+
import { useState, useCallback, useEffect } from 'react';
|
|
9
|
+
const STORAGE_KEY = 'objectui-recent-items';
|
|
10
|
+
const MAX_RECENT = 8;
|
|
11
|
+
function loadRecent() {
|
|
12
|
+
try {
|
|
13
|
+
const raw = localStorage.getItem(STORAGE_KEY);
|
|
14
|
+
return raw ? JSON.parse(raw) : [];
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return [];
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
function saveRecent(items) {
|
|
21
|
+
try {
|
|
22
|
+
localStorage.setItem(STORAGE_KEY, JSON.stringify(items));
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
// Storage full — silently ignore
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
export function useRecentItems() {
|
|
29
|
+
const [recentItems, setRecentItems] = useState(loadRecent);
|
|
30
|
+
// Sync from storage on mount
|
|
31
|
+
useEffect(() => {
|
|
32
|
+
setRecentItems(loadRecent());
|
|
33
|
+
}, []);
|
|
34
|
+
const addRecentItem = useCallback((item) => {
|
|
35
|
+
setRecentItems(prev => {
|
|
36
|
+
const filtered = prev.filter(r => r.id !== item.id);
|
|
37
|
+
const updated = [
|
|
38
|
+
{ ...item, visitedAt: new Date().toISOString() },
|
|
39
|
+
...filtered,
|
|
40
|
+
].slice(0, MAX_RECENT);
|
|
41
|
+
saveRecent(updated);
|
|
42
|
+
return updated;
|
|
43
|
+
});
|
|
44
|
+
}, []);
|
|
45
|
+
const clearRecentItems = useCallback(() => {
|
|
46
|
+
setRecentItems([]);
|
|
47
|
+
saveRecent([]);
|
|
48
|
+
}, []);
|
|
49
|
+
return { recentItems, addRecentItem, clearRecentItems };
|
|
50
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useResponsiveSidebar
|
|
3
|
+
*
|
|
4
|
+
* Auto-collapses the sidebar on tablet-width viewports (768px–1023px).
|
|
5
|
+
* Must be called inside a SidebarProvider context.
|
|
6
|
+
* @module
|
|
7
|
+
*/
|
|
8
|
+
import { useEffect } from 'react';
|
|
9
|
+
import { useSidebar } from '@object-ui/components';
|
|
10
|
+
/** Tablet breakpoint range: 768px <= width < 1024px */
|
|
11
|
+
const TABLET_MIN = 768;
|
|
12
|
+
const TABLET_MAX = 1024;
|
|
13
|
+
export function useResponsiveSidebar() {
|
|
14
|
+
const { setOpen, isMobile } = useSidebar();
|
|
15
|
+
useEffect(() => {
|
|
16
|
+
function handleResize() {
|
|
17
|
+
const width = window.innerWidth;
|
|
18
|
+
if (width >= TABLET_MIN && width < TABLET_MAX) {
|
|
19
|
+
// Tablet: auto-collapse sidebar
|
|
20
|
+
setOpen(false);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
// Run on mount to set initial state
|
|
24
|
+
handleResize();
|
|
25
|
+
window.addEventListener('resize', handleResize);
|
|
26
|
+
return () => window.removeEventListener('resize', handleResize);
|
|
27
|
+
}, [setOpen, isMobile]);
|
|
28
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -9,4 +9,33 @@ export { ObjectRenderer } from './components/ObjectRenderer';
|
|
|
9
9
|
export { DashboardRenderer } from './components/DashboardRenderer';
|
|
10
10
|
export { PageRenderer } from './components/PageRenderer';
|
|
11
11
|
export { FormRenderer } from './components/FormRenderer';
|
|
12
|
+
export { AdapterProvider, useAdapter } from './providers/AdapterProvider';
|
|
13
|
+
export { MetadataProvider, useMetadata, useMetadataItem } from './providers/MetadataProvider';
|
|
14
|
+
export { ExpressionProvider, useExpressionContext, evaluateVisibility } from './providers/ExpressionProvider';
|
|
15
|
+
export { useObjectActions } from './hooks/useObjectActions';
|
|
16
|
+
export { useRecentItems } from './hooks/useRecentItems';
|
|
12
17
|
export type { AppShellProps, ObjectRendererProps, DashboardRendererProps, PageRendererProps, FormRendererProps, } from './types';
|
|
18
|
+
export type { MetadataState, MetadataContextValue, MetadataTypeStatus, } from './providers/MetadataProvider';
|
|
19
|
+
export type { ExpressionContextValue, } from './providers/ExpressionProvider';
|
|
20
|
+
export type { RecentItem, } from './hooks/useRecentItems';
|
|
21
|
+
export { ConsoleShell, ConnectedShell, RequireOrganization, AuthenticatedRoute, RootRedirect, SystemRedirect, LoadingFallback, } from './console/ConsoleShell';
|
|
22
|
+
export { ConsoleLayout, AppHeader, AppSidebar, UnifiedSidebar, AppSwitcher, ConnectionStatus, ActivityFeed, LocaleSwitcher, ModeToggle, AuthPageLayout, } from './layout';
|
|
23
|
+
export type { ActivityItem } from './layout';
|
|
24
|
+
export { CommandPalette, KeyboardShortcutsDialog, OnboardingWalkthrough, ConditionalAuthWrapper, ConsoleToaster, ErrorBoundary, LoadingScreen, ThemeProvider, useTheme, } from './chrome';
|
|
25
|
+
export { ObjectView, RecordDetailView, DashboardView, PageView, ReportView, SearchResultsPage, ViewConfigPanel, } from './views';
|
|
26
|
+
export { useFavorites, useMetadataService, useNavPins, useNavigationSync, NavigationSyncEffect, addNavigationItem, removeNavigationItems, renameNavigationItems, navigationEqual, generateNavId, useResponsiveSidebar, } from './hooks';
|
|
27
|
+
export type { FavoriteItem } from './hooks';
|
|
28
|
+
export { NavigationProvider, useNavigationContext, FavoritesProvider } from './context';
|
|
29
|
+
export { AppContent as DefaultAppContent } from './console/AppContent';
|
|
30
|
+
export { LoginPage as DefaultLoginPage } from './console/auth/LoginPage';
|
|
31
|
+
export { RegisterPage as DefaultRegisterPage } from './console/auth/RegisterPage';
|
|
32
|
+
export { ForgotPasswordPage as DefaultForgotPasswordPage } from './console/auth/ForgotPasswordPage';
|
|
33
|
+
export { HomeLayout as DefaultHomeLayout, HomeLayout } from './console/home/HomeLayout';
|
|
34
|
+
export { HomePage as DefaultHomePage, HomePage } from './console/home/HomePage';
|
|
35
|
+
export { OrganizationsLayout as DefaultOrganizationsLayout } from './console/organizations/OrganizationsLayout';
|
|
36
|
+
export { OrganizationsPage as DefaultOrganizationsPage } from './console/organizations/OrganizationsPage';
|
|
37
|
+
export { OrganizationLayout as DefaultOrganizationLayout } from './console/organizations/manage/OrganizationLayout';
|
|
38
|
+
export { MembersPage as DefaultMembersPage } from './console/organizations/manage/MembersPage';
|
|
39
|
+
export { InvitationsPage as DefaultInvitationsPage } from './console/organizations/manage/InvitationsPage';
|
|
40
|
+
export { SettingsPage as DefaultSettingsPage } from './console/organizations/manage/SettingsPage';
|
|
41
|
+
export { AcceptInvitationPage as DefaultAcceptInvitationPage } from './console/organizations/manage/AcceptInvitationPage';
|
package/dist/index.js
CHANGED
|
@@ -4,8 +4,43 @@
|
|
|
4
4
|
* Minimal Application Shell for ObjectUI
|
|
5
5
|
* Framework-agnostic rendering engine for third-party integration
|
|
6
6
|
*/
|
|
7
|
+
// Components
|
|
7
8
|
export { AppShell } from './components/AppShell';
|
|
8
9
|
export { ObjectRenderer } from './components/ObjectRenderer';
|
|
9
10
|
export { DashboardRenderer } from './components/DashboardRenderer';
|
|
10
11
|
export { PageRenderer } from './components/PageRenderer';
|
|
11
12
|
export { FormRenderer } from './components/FormRenderer';
|
|
13
|
+
// Providers
|
|
14
|
+
export { AdapterProvider, useAdapter } from './providers/AdapterProvider';
|
|
15
|
+
export { MetadataProvider, useMetadata, useMetadataItem } from './providers/MetadataProvider';
|
|
16
|
+
export { ExpressionProvider, useExpressionContext, evaluateVisibility } from './providers/ExpressionProvider';
|
|
17
|
+
// Hooks
|
|
18
|
+
export { useObjectActions } from './hooks/useObjectActions';
|
|
19
|
+
export { useRecentItems } from './hooks/useRecentItems';
|
|
20
|
+
// Console building blocks — compose these in your App.tsx to build the console
|
|
21
|
+
// routing tree. See examples/console-starter/src/App.tsx for a minimal example.
|
|
22
|
+
export { ConsoleShell, ConnectedShell, RequireOrganization, AuthenticatedRoute, RootRedirect, SystemRedirect, LoadingFallback, } from './console/ConsoleShell';
|
|
23
|
+
// Layout chrome
|
|
24
|
+
export { ConsoleLayout, AppHeader, AppSidebar, UnifiedSidebar, AppSwitcher, ConnectionStatus, ActivityFeed, LocaleSwitcher, ModeToggle, AuthPageLayout, } from './layout';
|
|
25
|
+
// Top-level chrome (dialogs, providers, error boundaries)
|
|
26
|
+
export { CommandPalette, KeyboardShortcutsDialog, OnboardingWalkthrough, ConditionalAuthWrapper, ConsoleToaster, ErrorBoundary, LoadingScreen, ThemeProvider, useTheme, } from './chrome';
|
|
27
|
+
// Standard inner-SPA views
|
|
28
|
+
export { ObjectView, RecordDetailView, DashboardView, PageView, ReportView, SearchResultsPage, ViewConfigPanel, } from './views';
|
|
29
|
+
// Hooks
|
|
30
|
+
export { useFavorites, useMetadataService, useNavPins, useNavigationSync, NavigationSyncEffect, addNavigationItem, removeNavigationItems, renameNavigationItems, navigationEqual, generateNavId, useResponsiveSidebar, } from './hooks';
|
|
31
|
+
// Context providers
|
|
32
|
+
export { NavigationProvider, useNavigationContext, FavoritesProvider } from './context';
|
|
33
|
+
// Default page implementations (consumers can partial-override slots)
|
|
34
|
+
export { AppContent as DefaultAppContent } from './console/AppContent';
|
|
35
|
+
export { LoginPage as DefaultLoginPage } from './console/auth/LoginPage';
|
|
36
|
+
export { RegisterPage as DefaultRegisterPage } from './console/auth/RegisterPage';
|
|
37
|
+
export { ForgotPasswordPage as DefaultForgotPasswordPage } from './console/auth/ForgotPasswordPage';
|
|
38
|
+
export { HomeLayout as DefaultHomeLayout, HomeLayout } from './console/home/HomeLayout';
|
|
39
|
+
export { HomePage as DefaultHomePage, HomePage } from './console/home/HomePage';
|
|
40
|
+
export { OrganizationsLayout as DefaultOrganizationsLayout } from './console/organizations/OrganizationsLayout';
|
|
41
|
+
export { OrganizationsPage as DefaultOrganizationsPage } from './console/organizations/OrganizationsPage';
|
|
42
|
+
export { OrganizationLayout as DefaultOrganizationLayout } from './console/organizations/manage/OrganizationLayout';
|
|
43
|
+
export { MembersPage as DefaultMembersPage } from './console/organizations/manage/MembersPage';
|
|
44
|
+
export { InvitationsPage as DefaultInvitationsPage } from './console/organizations/manage/InvitationsPage';
|
|
45
|
+
export { SettingsPage as DefaultSettingsPage } from './console/organizations/manage/SettingsPage';
|
|
46
|
+
export { AcceptInvitationPage as DefaultAcceptInvitationPage } from './console/organizations/manage/AcceptInvitationPage';
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ActivityFeed
|
|
3
|
+
*
|
|
4
|
+
* Sidebar panel that displays recent activity items (create, update, delete,
|
|
5
|
+
* comment). Opens as a slide-out Sheet triggered by a bell icon button.
|
|
6
|
+
* Phase 17 L1 – local state only, no server integration.
|
|
7
|
+
* @module
|
|
8
|
+
*/
|
|
9
|
+
export interface ActivityItem {
|
|
10
|
+
id: string;
|
|
11
|
+
type: 'create' | 'update' | 'delete' | 'comment';
|
|
12
|
+
objectName: string;
|
|
13
|
+
recordId?: string;
|
|
14
|
+
user: string;
|
|
15
|
+
description: string;
|
|
16
|
+
timestamp: string;
|
|
17
|
+
}
|
|
18
|
+
export interface ActivityFeedProps {
|
|
19
|
+
activities?: ActivityItem[];
|
|
20
|
+
className?: string;
|
|
21
|
+
}
|
|
22
|
+
export declare function ActivityFeed({ activities, className }: ActivityFeedProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* ActivityFeed
|
|
4
|
+
*
|
|
5
|
+
* Sidebar panel that displays recent activity items (create, update, delete,
|
|
6
|
+
* comment). Opens as a slide-out Sheet triggered by a bell icon button.
|
|
7
|
+
* Phase 17 L1 – local state only, no server integration.
|
|
8
|
+
* @module
|
|
9
|
+
*/
|
|
10
|
+
import { useState } from 'react';
|
|
11
|
+
import { Button, Badge, Sheet, SheetContent, SheetHeader, SheetTitle, SheetTrigger, } from '@object-ui/components';
|
|
12
|
+
import { Bell, Plus, Pencil, Trash2, MessageSquare, Filter } from 'lucide-react';
|
|
13
|
+
const typeConfig = {
|
|
14
|
+
create: { icon: Plus, color: 'text-green-500' },
|
|
15
|
+
update: { icon: Pencil, color: 'text-blue-500' },
|
|
16
|
+
delete: { icon: Trash2, color: 'text-red-500' },
|
|
17
|
+
comment: { icon: MessageSquare, color: 'text-amber-500' },
|
|
18
|
+
};
|
|
19
|
+
/** Format an ISO timestamp as a relative string (e.g. "2m ago"). */
|
|
20
|
+
function formatRelativeTime(iso) {
|
|
21
|
+
const ms = new Date(iso).getTime();
|
|
22
|
+
if (Number.isNaN(ms))
|
|
23
|
+
return '';
|
|
24
|
+
const seconds = Math.floor((Date.now() - ms) / 1000);
|
|
25
|
+
if (seconds < 60)
|
|
26
|
+
return `${Math.max(seconds, 0)}s ago`;
|
|
27
|
+
const minutes = Math.floor(seconds / 60);
|
|
28
|
+
if (minutes < 60)
|
|
29
|
+
return `${minutes}m ago`;
|
|
30
|
+
const hours = Math.floor(minutes / 60);
|
|
31
|
+
if (hours < 24)
|
|
32
|
+
return `${hours}h ago`;
|
|
33
|
+
const days = Math.floor(hours / 24);
|
|
34
|
+
return `${days}d ago`;
|
|
35
|
+
}
|
|
36
|
+
export function ActivityFeed({ activities = [], className }) {
|
|
37
|
+
const [open, setOpen] = useState(false);
|
|
38
|
+
const [showFilters, setShowFilters] = useState(false);
|
|
39
|
+
const [notificationPreferences, setNotificationPreferences] = useState({
|
|
40
|
+
create: true,
|
|
41
|
+
update: true,
|
|
42
|
+
delete: true,
|
|
43
|
+
comment: true,
|
|
44
|
+
});
|
|
45
|
+
const togglePreference = (type) => {
|
|
46
|
+
setNotificationPreferences(prev => ({ ...prev, [type]: !prev[type] }));
|
|
47
|
+
};
|
|
48
|
+
const filteredActivities = activities.filter(a => notificationPreferences[a.type]);
|
|
49
|
+
return (_jsxs(Sheet, { open: open, onOpenChange: setOpen, children: [_jsx(SheetTrigger, { asChild: true, children: _jsxs(Button, { variant: "ghost", size: "icon", className: className ?? 'h-8 w-8', "aria-label": "Activity feed", children: [_jsx(Bell, { className: "h-4 w-4" }), activities.length > 0 && (_jsx("span", { className: "absolute -top-0.5 -right-0.5 h-3.5 w-3.5 rounded-full bg-primary text-[9px] font-bold text-primary-foreground flex items-center justify-center", children: activities.length > 9 ? '9+' : activities.length }))] }) }), _jsxs(SheetContent, { side: "right", className: "w-80 sm:w-96", children: [_jsx(SheetHeader, { children: _jsxs(SheetTitle, { className: "flex items-center justify-between", children: ["Recent Activity", _jsxs(Button, { variant: showFilters ? 'secondary' : 'ghost', size: "sm", className: "h-7 px-2", onClick: () => setShowFilters(!showFilters), children: [_jsx(Filter, { className: "h-3.5 w-3.5 mr-1" }), "Filter"] })] }) }), showFilters && (_jsx("div", { className: "flex flex-wrap gap-1.5 mt-3 px-1", children: Object.keys(typeConfig).map(type => {
|
|
50
|
+
const { icon: Icon, color } = typeConfig[type];
|
|
51
|
+
const active = notificationPreferences[type];
|
|
52
|
+
return (_jsxs(Badge, { variant: active ? 'default' : 'outline', className: "cursor-pointer select-none gap-1 capitalize", onClick: () => togglePreference(type), children: [_jsx(Icon, { className: `h-3 w-3 ${active ? '' : color}` }), type] }, type));
|
|
53
|
+
}) })), filteredActivities.length === 0 ? (_jsxs("div", { className: "flex flex-col items-center justify-center gap-2 py-16 text-muted-foreground", children: [_jsx(Bell, { className: "h-8 w-8 opacity-40" }), _jsx("p", { className: "text-sm", children: "No recent activity" })] })) : (_jsx("ul", { className: "mt-4 space-y-1 overflow-y-auto max-h-[calc(100vh-8rem)]", children: filteredActivities.map((item) => {
|
|
54
|
+
const { icon: Icon, color } = typeConfig[item.type];
|
|
55
|
+
return (_jsxs("li", { className: "flex items-start gap-3 rounded-md px-2 py-2 hover:bg-muted/50 transition-colors", children: [_jsx("span", { className: `mt-0.5 shrink-0 ${color}`, children: _jsx(Icon, { className: "h-4 w-4" }) }), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsx("p", { className: "text-sm leading-snug", children: item.description }), _jsxs("p", { className: "mt-0.5 text-xs text-muted-foreground", children: [item.user, " \u00B7 ", formatRelativeTime(item.timestamp)] })] })] }, item.id));
|
|
56
|
+
}) }))] })] }));
|
|
57
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AppHeader — unified top bar
|
|
3
|
+
*
|
|
4
|
+
* Supabase-style top bar used across the whole console:
|
|
5
|
+
* [Logo] [/ App ▾ / Object ▾ ...] [actions] [user ▾]
|
|
6
|
+
*
|
|
7
|
+
* Variants:
|
|
8
|
+
* - `app` (default when `appName` is present): sidebar trigger + AppSwitcher
|
|
9
|
+
* + breadcrumb path. Used by `ConsoleLayout` inside `/apps/:appName/*`.
|
|
10
|
+
* - `home` : no breadcrumb; displays the "ObjectStack" wordmark next to the
|
|
11
|
+
* logo. Used by `/home`.
|
|
12
|
+
* - `orgs` : no breadcrumb; logo + "Organizations" title. Used by the
|
|
13
|
+
* `/organizations` landing page.
|
|
14
|
+
*
|
|
15
|
+
* The user avatar dropdown includes the organization (workspace) switcher at
|
|
16
|
+
* the top so the same chrome lets users change orgs from any page.
|
|
17
|
+
* @module
|
|
18
|
+
*/
|
|
19
|
+
import { type PresenceUser } from '@object-ui/collaboration';
|
|
20
|
+
import { type ActivityItem } from './ActivityFeed';
|
|
21
|
+
import type { ConnectionState } from '@object-ui/data-objectstack';
|
|
22
|
+
export type AppHeaderVariant = 'app' | 'home' | 'orgs';
|
|
23
|
+
export interface AppHeaderProps {
|
|
24
|
+
variant?: AppHeaderVariant;
|
|
25
|
+
appName?: string;
|
|
26
|
+
objects?: any[];
|
|
27
|
+
connectionState?: ConnectionState;
|
|
28
|
+
presenceUsers?: PresenceUser[];
|
|
29
|
+
activities?: ActivityItem[];
|
|
30
|
+
activeAppName?: string;
|
|
31
|
+
onAppChange?: (name: string) => void;
|
|
32
|
+
}
|
|
33
|
+
export declare function AppHeader({ variant, appName, objects, connectionState, presenceUsers, activities, activeAppName, onAppChange, }: AppHeaderProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* AppHeader — unified top bar
|
|
4
|
+
*
|
|
5
|
+
* Supabase-style top bar used across the whole console:
|
|
6
|
+
* [Logo] [/ App ▾ / Object ▾ ...] [actions] [user ▾]
|
|
7
|
+
*
|
|
8
|
+
* Variants:
|
|
9
|
+
* - `app` (default when `appName` is present): sidebar trigger + AppSwitcher
|
|
10
|
+
* + breadcrumb path. Used by `ConsoleLayout` inside `/apps/:appName/*`.
|
|
11
|
+
* - `home` : no breadcrumb; displays the "ObjectStack" wordmark next to the
|
|
12
|
+
* logo. Used by `/home`.
|
|
13
|
+
* - `orgs` : no breadcrumb; logo + "Organizations" title. Used by the
|
|
14
|
+
* `/organizations` landing page.
|
|
15
|
+
*
|
|
16
|
+
* The user avatar dropdown includes the organization (workspace) switcher at
|
|
17
|
+
* the top so the same chrome lets users change orgs from any page.
|
|
18
|
+
* @module
|
|
19
|
+
*/
|
|
20
|
+
import { useLocation, useParams, Link, useNavigate } from 'react-router-dom';
|
|
21
|
+
import { SidebarTrigger, Button, DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuGroup, Avatar, AvatarImage, AvatarFallback, } from '@object-ui/components';
|
|
22
|
+
import { Search, HelpCircle, ChevronDown, Settings, LogOut, User as UserIcon, Boxes, } from 'lucide-react';
|
|
23
|
+
import { useState, useEffect, useCallback, useRef } from 'react';
|
|
24
|
+
import { useOffline } from '@object-ui/react';
|
|
25
|
+
import { PresenceAvatars } from '@object-ui/collaboration';
|
|
26
|
+
import { ModeToggle } from './ModeToggle';
|
|
27
|
+
import { LocaleSwitcher } from './LocaleSwitcher';
|
|
28
|
+
import { ConnectionStatus } from './ConnectionStatus';
|
|
29
|
+
import { ActivityFeed } from './ActivityFeed';
|
|
30
|
+
import { AppSwitcher } from './AppSwitcher';
|
|
31
|
+
import { useAdapter } from '../providers/AdapterProvider';
|
|
32
|
+
import { useObjectTranslation, useObjectLabel } from '@object-ui/i18n';
|
|
33
|
+
import { useAuth, getUserInitials } from '@object-ui/auth';
|
|
34
|
+
import { useMetadata } from '../providers/MetadataProvider';
|
|
35
|
+
import { useNavigationContext } from '../context/NavigationContext';
|
|
36
|
+
function humanizeSlug(slug) {
|
|
37
|
+
return slug.replace(/[-_]/g, ' ').replace(/\b\w/g, (c) => c.toUpperCase());
|
|
38
|
+
}
|
|
39
|
+
/** Muted `/` separator between path segments */
|
|
40
|
+
function PathSep() {
|
|
41
|
+
return (_jsx("span", { className: "select-none text-muted-foreground/40 mx-1.5 text-base font-light", "aria-hidden": true, children: "/" }));
|
|
42
|
+
}
|
|
43
|
+
// No fake fallback presence — render nothing when the API has no data so the
|
|
44
|
+
// header doesn't ship phantom collaborators in production.
|
|
45
|
+
const EMPTY_PRESENCE_USERS = [];
|
|
46
|
+
export function AppHeader({ variant, appName, objects, connectionState, presenceUsers, activities, activeAppName, onAppChange, }) {
|
|
47
|
+
const resolvedVariant = variant ?? (appName ? 'app' : 'home');
|
|
48
|
+
const isApp = resolvedVariant === 'app';
|
|
49
|
+
const location = useLocation();
|
|
50
|
+
const params = useParams();
|
|
51
|
+
const navigate = useNavigate();
|
|
52
|
+
const { isOnline } = useOffline();
|
|
53
|
+
const { user, signOut, organizations, activeOrganization, isOrganizationsLoading, } = useAuth();
|
|
54
|
+
const dataSource = useAdapter();
|
|
55
|
+
const { t } = useObjectTranslation();
|
|
56
|
+
const { objectLabel } = useObjectLabel();
|
|
57
|
+
const { apps: metadataApps } = useMetadata();
|
|
58
|
+
const { currentAppName, recordTitle } = useNavigationContext();
|
|
59
|
+
const [apiPresenceUsers, setApiPresenceUsers] = useState(null);
|
|
60
|
+
const [apiActivities, setApiActivities] = useState(null);
|
|
61
|
+
// Once the server returns 404 for these collections we stop retrying for
|
|
62
|
+
// the lifetime of the page — they're optional features and re-requesting
|
|
63
|
+
// on every navigation creates console noise + wasted round trips.
|
|
64
|
+
const presenceUnavailableRef = useRef(false);
|
|
65
|
+
const activityUnavailableRef = useRef(false);
|
|
66
|
+
const fetchPresenceAndActivities = useCallback(async () => {
|
|
67
|
+
if (!dataSource || !isApp)
|
|
68
|
+
return;
|
|
69
|
+
const presenceP = presenceUnavailableRef.current
|
|
70
|
+
? Promise.resolve({ data: [] })
|
|
71
|
+
: dataSource.find('sys_presence').catch((err) => {
|
|
72
|
+
if (err?.status === 404)
|
|
73
|
+
presenceUnavailableRef.current = true;
|
|
74
|
+
return { data: [] };
|
|
75
|
+
});
|
|
76
|
+
const activityP = activityUnavailableRef.current
|
|
77
|
+
? Promise.resolve({ data: [] })
|
|
78
|
+
: dataSource
|
|
79
|
+
.find('sys_activity', { $orderby: { timestamp: 'desc' }, $top: 20 })
|
|
80
|
+
.catch((err) => {
|
|
81
|
+
if (err?.status === 404)
|
|
82
|
+
activityUnavailableRef.current = true;
|
|
83
|
+
return { data: [] };
|
|
84
|
+
});
|
|
85
|
+
try {
|
|
86
|
+
const [presenceResult, activityResult] = await Promise.all([presenceP, activityP]);
|
|
87
|
+
if (presenceResult.data?.length) {
|
|
88
|
+
const users = presenceResult.data.filter((u) => typeof u.userId === 'string');
|
|
89
|
+
if (users.length)
|
|
90
|
+
setApiPresenceUsers(users);
|
|
91
|
+
}
|
|
92
|
+
if (activityResult.data?.length) {
|
|
93
|
+
const items = activityResult.data.filter((a) => typeof a.type === 'string');
|
|
94
|
+
if (items.length)
|
|
95
|
+
setApiActivities(items);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
catch { /* fallback below */ }
|
|
99
|
+
}, [dataSource, isApp]);
|
|
100
|
+
useEffect(() => { fetchPresenceAndActivities(); }, [fetchPresenceAndActivities]);
|
|
101
|
+
const activeUsers = presenceUsers ?? apiPresenceUsers ?? EMPTY_PRESENCE_USERS;
|
|
102
|
+
const activeActivities = activities ?? apiActivities ?? [];
|
|
103
|
+
const orgList = organizations ?? [];
|
|
104
|
+
const hasOrgSection = isOrganizationsLoading || orgList.length > 0 || !!activeOrganization;
|
|
105
|
+
// Build path segments (only used in `app` variant)
|
|
106
|
+
const pathParts = location.pathname.split('/').filter(Boolean);
|
|
107
|
+
const appNameFromRoute = params.appName || pathParts[1];
|
|
108
|
+
const routeType = pathParts[2];
|
|
109
|
+
const baseHref = `/apps/${appNameFromRoute}`;
|
|
110
|
+
const safeObjects = objects ?? [];
|
|
111
|
+
// Filter objects to only those belonging to the current app via its navigation
|
|
112
|
+
const appNameKey = activeAppName || currentAppName || appNameFromRoute;
|
|
113
|
+
const currentApp = (metadataApps || []).find((a) => a.name === appNameKey);
|
|
114
|
+
const appNavObjectNames = new Set();
|
|
115
|
+
const collectNavObjects = (items) => {
|
|
116
|
+
for (const item of items || []) {
|
|
117
|
+
if (item.type === 'object' && item.objectName)
|
|
118
|
+
appNavObjectNames.add(item.objectName);
|
|
119
|
+
if (item.children)
|
|
120
|
+
collectNavObjects(item.children);
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
collectNavObjects(currentApp?.navigation || []);
|
|
124
|
+
for (const area of currentApp?.areas || [])
|
|
125
|
+
collectNavObjects(area.navigation || []);
|
|
126
|
+
const appObjects = appNavObjectNames.size > 0
|
|
127
|
+
? safeObjects.filter((o) => appNavObjectNames.has(o.name))
|
|
128
|
+
: safeObjects.filter((o) => !o.name.startsWith('sys_') && !o.name.startsWith('auth_'));
|
|
129
|
+
const objectSiblings = appObjects.map((o) => ({
|
|
130
|
+
label: objectLabel(o),
|
|
131
|
+
href: `${baseHref}/${o.name}`,
|
|
132
|
+
}));
|
|
133
|
+
const extraSegments = [];
|
|
134
|
+
if (isApp) {
|
|
135
|
+
if (routeType === 'dashboard') {
|
|
136
|
+
extraSegments.push({ label: t('console.breadcrumb.dashboards'), href: baseHref });
|
|
137
|
+
if (pathParts[3])
|
|
138
|
+
extraSegments.push({ label: humanizeSlug(pathParts[3]) });
|
|
139
|
+
}
|
|
140
|
+
else if (routeType === 'page') {
|
|
141
|
+
extraSegments.push({ label: t('console.breadcrumb.pages'), href: baseHref });
|
|
142
|
+
if (pathParts[3])
|
|
143
|
+
extraSegments.push({ label: humanizeSlug(pathParts[3]) });
|
|
144
|
+
}
|
|
145
|
+
else if (routeType === 'report') {
|
|
146
|
+
extraSegments.push({ label: t('console.breadcrumb.reports'), href: baseHref });
|
|
147
|
+
if (pathParts[3])
|
|
148
|
+
extraSegments.push({ label: humanizeSlug(pathParts[3]) });
|
|
149
|
+
}
|
|
150
|
+
else if (routeType === 'system') {
|
|
151
|
+
extraSegments.push({ label: t('console.breadcrumb.system') });
|
|
152
|
+
if (pathParts[3])
|
|
153
|
+
extraSegments.push({ label: humanizeSlug(pathParts[3]) });
|
|
154
|
+
}
|
|
155
|
+
else if (routeType) {
|
|
156
|
+
const currentObject = safeObjects.find((o) => o.name === routeType);
|
|
157
|
+
if (currentObject) {
|
|
158
|
+
extraSegments.push({
|
|
159
|
+
label: objectLabel(currentObject),
|
|
160
|
+
href: `${baseHref}/${routeType}`,
|
|
161
|
+
siblings: objectSiblings,
|
|
162
|
+
});
|
|
163
|
+
if (pathParts[3] === 'record' && pathParts[4]) {
|
|
164
|
+
const shortId = pathParts[4].length > 12 ? `${pathParts[4].slice(0, 8)}…` : pathParts[4];
|
|
165
|
+
const trimmedTitle = recordTitle?.trim();
|
|
166
|
+
const displayTitle = trimmedTitle && trimmedTitle.length > 48
|
|
167
|
+
? `${trimmedTitle.slice(0, 45)}…`
|
|
168
|
+
: trimmedTitle;
|
|
169
|
+
extraSegments.push({ label: displayTitle || `#${shortId}` });
|
|
170
|
+
}
|
|
171
|
+
else if (pathParts[3] === 'view' && pathParts[4]) {
|
|
172
|
+
extraSegments.push({ label: humanizeSlug(pathParts[4]) });
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
const lastSegmentLabel = extraSegments[extraSegments.length - 1]?.label || appName || '';
|
|
178
|
+
return (_jsxs("div", { className: "flex items-center justify-between w-full h-full", children: [_jsxs("div", { className: "flex items-center min-w-0 flex-1", children: [_jsx(Link, { to: "/home", className: "flex items-center justify-center h-7 w-7 shrink-0 rounded-md bg-primary text-primary-foreground hover:bg-primary/90 transition-colors", title: "ObjectStack", children: _jsx(Boxes, { className: "h-4 w-4" }) }), resolvedVariant === 'home' && (_jsx("span", { className: "hidden sm:inline ml-2 text-sm font-semibold tracking-tight", children: "ObjectStack" })), resolvedVariant === 'orgs' && (_jsxs(_Fragment, { children: [_jsx(PathSep, {}), _jsx("span", { className: "text-sm font-medium text-foreground/80 px-1.5", children: t('organizations.title', { defaultValue: 'Organizations' }) })] })), isApp && (_jsxs(_Fragment, { children: [_jsx(SidebarTrigger, { className: "md:hidden shrink-0 ml-1" }), activeAppName && onAppChange ? (_jsxs(_Fragment, { children: [_jsx(PathSep, {}), _jsx(AppSwitcher, { activeAppName: activeAppName, onAppChange: onAppChange })] })) : appName ? (_jsxs(_Fragment, { children: [_jsx(PathSep, {}), _jsx("span", { className: "text-sm font-medium text-foreground/80 px-1.5", children: appName })] })) : null, extraSegments.map((seg, i) => {
|
|
179
|
+
const isLast = i === extraSegments.length - 1;
|
|
180
|
+
return (_jsxs("span", { className: "hidden sm:flex items-center min-w-0", children: [_jsx(PathSep, {}), seg.siblings && seg.siblings.length > 1 ? (_jsxs(DropdownMenu, { children: [_jsxs(DropdownMenuTrigger, { className: `flex items-center gap-1 rounded-md px-1.5 py-1 text-sm font-medium transition-colors outline-none hover:bg-accent hover:text-foreground ${!isLast ? 'text-foreground/60' : 'text-foreground/80'}`, children: [seg.label, _jsx(ChevronDown, { className: "h-3.5 w-3.5 text-muted-foreground" })] }), _jsxs(DropdownMenuContent, { align: "start", sideOffset: 8, className: "w-56 max-h-72 overflow-y-auto", children: [_jsx(DropdownMenuLabel, { className: "text-xs text-muted-foreground font-normal", children: "Switch Object" }), _jsx(DropdownMenuSeparator, {}), seg.siblings.map((sibling) => (_jsx(DropdownMenuItem, { asChild: true, children: _jsx(Link, { to: sibling.href, className: "w-full", children: sibling.label }) }, sibling.href)))] })] })) : seg.href ? (_jsx(Link, { to: seg.href, className: `rounded-md px-1.5 py-1 text-sm font-medium transition-colors hover:bg-accent hover:text-foreground truncate max-w-[160px] ${isLast ? 'text-foreground/80' : 'text-foreground/60'}`, children: seg.label })) : (_jsx("span", { className: `px-1.5 py-1 text-sm font-medium truncate max-w-[160px] ${isLast ? 'text-foreground/80' : 'text-foreground/60'}`, children: seg.label }))] }, i));
|
|
181
|
+
}), _jsx("span", { className: "text-sm font-medium sm:hidden truncate min-w-0 ml-1", children: lastSegmentLabel })] }))] }), _jsxs("div", { className: "flex items-center gap-0.5 sm:gap-1 shrink-0 [&>*+*[data-topbar-group]]:ml-1 [&>[data-topbar-group]+[data-topbar-group]]:border-l [&>[data-topbar-group]+[data-topbar-group]]:border-border/60 [&>[data-topbar-group]+[data-topbar-group]]:pl-1 sm:[&>[data-topbar-group]+[data-topbar-group]]:pl-2 sm:[&>[data-topbar-group]+[data-topbar-group]]:ml-2", children: [!isOnline && (_jsxs("div", { className: "flex items-center gap-1 px-2 py-1 rounded-full bg-yellow-100 dark:bg-yellow-900/30 text-yellow-800 dark:text-yellow-200 text-xs font-medium", children: [_jsx("span", { className: "h-2 w-2 rounded-full bg-yellow-500 animate-pulse" }), "Offline"] })), isApp && connectionState && _jsx(ConnectionStatus, { state: connectionState }), isApp && activeUsers.length > 0 && (_jsx("div", { className: "hidden md:flex items-center shrink-0", title: "Users currently online", children: _jsx(PresenceAvatars, { users: activeUsers, size: "sm", maxVisible: 3, showStatus: true }) })), _jsxs("div", { "data-topbar-group": true, className: "flex items-center gap-0.5 sm:gap-1 shrink-0", children: [_jsxs("button", { onClick: () => document.dispatchEvent(new KeyboardEvent('keydown', { key: 'k', metaKey: true })), className: "hidden lg:flex relative items-center gap-2 w-48 xl:w-64 h-8 px-3 text-sm rounded-md border bg-muted/50 text-muted-foreground hover:bg-muted transition-colors", children: [_jsx(Search, { className: "h-3.5 w-3.5 shrink-0" }), _jsx("span", { className: "flex-1 text-left text-xs", children: t('console.search', { defaultValue: 'Search...' }) }), _jsxs("kbd", { className: "pointer-events-none inline-flex h-5 items-center gap-0.5 rounded border bg-background px-1.5 text-[10px] font-medium text-muted-foreground", children: [_jsx("span", { className: "text-xs", children: "\u2318" }), "K"] })] }), _jsx(Button, { variant: "ghost", size: "icon", className: "lg:hidden h-8 w-8 shrink-0", onClick: () => document.dispatchEvent(new KeyboardEvent('keydown', { key: 'k', metaKey: true })), "aria-label": t('console.search', { defaultValue: 'Search...' }), children: _jsx(Search, { className: "h-4 w-4" }) })] }), _jsxs("div", { "data-topbar-group": true, className: "flex items-center gap-0.5 shrink-0", children: [_jsx("div", { className: "hidden sm:flex shrink-0", children: _jsx(ActivityFeed, { activities: activeActivities }) }), _jsx(Button, { variant: "ghost", size: "icon", className: "h-8 w-8 hidden md:flex shrink-0", asChild: true, "aria-label": t('sidebar.helpTooltip', { defaultValue: 'Help & Documentation' }), children: _jsx("a", { href: "https://docs.objectstack.ai", target: "_blank", rel: "noopener noreferrer", children: _jsx(HelpCircle, { className: "h-4 w-4" }) }) })] }), _jsxs("div", { "data-topbar-group": true, className: "flex items-center gap-0.5 shrink-0", children: [_jsx("div", { className: "hidden sm:flex shrink-0", children: _jsx(ModeToggle, {}) }), _jsx("div", { className: "hidden sm:flex shrink-0", children: _jsx(LocaleSwitcher, {}) }), _jsxs(DropdownMenu, { children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsx(Button, { variant: "ghost", size: "icon", className: "h-8 w-8 shrink-0 rounded-full", children: _jsxs(Avatar, { className: "h-7 w-7 rounded-full", children: [_jsx(AvatarImage, { src: user?.image ?? '/avatars/user.jpg', alt: user?.name ?? 'User' }), _jsx(AvatarFallback, { className: "rounded-full bg-primary text-primary-foreground text-xs", children: getUserInitials(user) })] }) }) }), _jsxs(DropdownMenuContent, { align: "end", className: "min-w-64 rounded-lg", sideOffset: 4, children: [_jsx(DropdownMenuLabel, { className: "p-0 font-normal", children: _jsxs("div", { className: "flex items-center gap-2 px-2 py-2", children: [_jsxs(Avatar, { className: "h-8 w-8 rounded-lg", children: [_jsx(AvatarImage, { src: user?.image ?? '/avatars/user.jpg', alt: user?.name ?? 'User' }), _jsx(AvatarFallback, { className: "rounded-lg bg-primary text-primary-foreground", children: getUserInitials(user) })] }), _jsxs("div", { className: "grid flex-1 text-left text-sm leading-tight", children: [_jsx("span", { className: "truncate font-semibold", children: user?.name ?? 'User' }), _jsx("span", { className: "truncate text-xs text-muted-foreground", children: user?.email ?? '' })] })] }) }), _jsx(DropdownMenuSeparator, {}), _jsxs(DropdownMenuGroup, { children: [hasOrgSection && (_jsxs(DropdownMenuItem, { onClick: () => navigate('/organizations'), className: "cursor-pointer", children: [_jsx(Boxes, { className: "mr-2 h-4 w-4" }), t('organizations.mine', { defaultValue: 'My Organizations' })] })), _jsxs(DropdownMenuItem, { onClick: () => navigate('/apps/setup/system/profile'), children: [_jsx(UserIcon, { className: "mr-2 h-4 w-4" }), t('user.profile', { defaultValue: 'Profile' })] }), _jsxs(DropdownMenuItem, { onClick: () => navigate('/apps/setup'), children: [_jsx(Settings, { className: "mr-2 h-4 w-4" }), t('sidebar.settings', { defaultValue: 'Settings' })] })] }), _jsx(DropdownMenuSeparator, {}), _jsxs(DropdownMenuItem, { className: "text-destructive focus:text-destructive", onClick: () => signOut(), children: [_jsx(LogOut, { className: "mr-2 h-4 w-4" }), t('user.logout', { defaultValue: 'Log out' })] })] })] })] })] })] }));
|
|
182
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AppSidebar
|
|
3
|
+
*
|
|
4
|
+
* Collapsible sidebar navigation for the console. Delegates navigation
|
|
5
|
+
* rendering to `NavigationRenderer` from `@object-ui/layout` (using
|
|
6
|
+
* @dnd-kit drag-to-reorder and pin/unpin), while keeping Console-specific
|
|
7
|
+
* features: app switcher dropdown, user footer, favorites, recent items,
|
|
8
|
+
* and mobile swipe gesture.
|
|
9
|
+
* @module
|
|
10
|
+
*/
|
|
11
|
+
export declare function AppSidebar({ activeAppName, onAppChange }: {
|
|
12
|
+
activeAppName: string;
|
|
13
|
+
onAppChange: (name: string) => void;
|
|
14
|
+
}): import("react/jsx-runtime").JSX.Element;
|