@startsimpli/ui 0.4.28 → 0.4.30
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@startsimpli/ui",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.30",
|
|
4
4
|
"description": "Shared UI components package for StartSimpli applications",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"types": "./src/index.ts",
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"next": "^14.0.0 || ^15.0.0 || ^16.0.0",
|
|
33
33
|
"react": "^18.0.0 || ^19.0.0",
|
|
34
34
|
"react-dom": "^18.0.0 || ^19.0.0",
|
|
35
|
-
"@startsimpli/auth": "0.4.
|
|
35
|
+
"@startsimpli/auth": "0.4.25"
|
|
36
36
|
},
|
|
37
37
|
"peerDependenciesMeta": {
|
|
38
38
|
"@startsimpli/auth": {
|
|
@@ -91,14 +91,14 @@ export function ChatMessage({ message, renderContent, className }: ChatMessagePr
|
|
|
91
91
|
data-status={status}
|
|
92
92
|
aria-busy={isStreaming || undefined}
|
|
93
93
|
className={cn(
|
|
94
|
-
'mx-auto flex max-w-[90%] items-
|
|
94
|
+
'mx-auto flex max-w-[90%] items-start gap-2 rounded-md border border-border bg-muted/40 px-3 py-1.5 text-xs text-muted-foreground',
|
|
95
95
|
isError && 'border-red-300 bg-red-50 text-red-700',
|
|
96
96
|
className
|
|
97
97
|
)}
|
|
98
98
|
>
|
|
99
|
-
{kind === 'tool' && <Wrench className="h-3.5 w-3.5 shrink-0" aria-hidden="true" />}
|
|
100
|
-
{toolName && <span className="font-medium text-foreground">{toolName}</span>}
|
|
101
|
-
<span className="
|
|
99
|
+
{kind === 'tool' && <Wrench className="mt-0.5 h-3.5 w-3.5 shrink-0" aria-hidden="true" />}
|
|
100
|
+
{toolName && <span className="shrink-0 font-medium text-foreground">{toolName}</span>}
|
|
101
|
+
<span className="min-w-0 whitespace-pre-wrap break-words">{content}</span>
|
|
102
102
|
{isStreaming && <StreamingDots />}
|
|
103
103
|
</div>
|
|
104
104
|
)
|
package/src/components/index.ts
CHANGED
|
@@ -52,6 +52,7 @@ export * from './unified-table/utils'
|
|
|
52
52
|
|
|
53
53
|
// Export Navigation
|
|
54
54
|
export * from './navigation/sidebar'
|
|
55
|
+
export * from './navigation/app-shell'
|
|
55
56
|
|
|
56
57
|
// Workspace layout primitives (SplitPane, DualPaneWorkspace) — generic two-pane shell
|
|
57
58
|
export * from './workspace'
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useEffect, type ReactNode } from 'react';
|
|
4
|
+
import { useAuth, buildCentralAuthUrl } from '@startsimpli/auth';
|
|
5
|
+
import { LogOut, User } from 'lucide-react';
|
|
6
|
+
import { Sidebar, SidebarLayout, type SidebarSection } from './sidebar';
|
|
7
|
+
import { Button } from '../ui/button';
|
|
8
|
+
import {
|
|
9
|
+
DropdownMenu,
|
|
10
|
+
DropdownMenuContent,
|
|
11
|
+
DropdownMenuItem,
|
|
12
|
+
DropdownMenuLabel,
|
|
13
|
+
DropdownMenuSeparator,
|
|
14
|
+
DropdownMenuTrigger,
|
|
15
|
+
} from '../ui/dropdown-menu';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* The one shared authenticated app shell — Sidebar SaaS chrome for EVERY app.
|
|
19
|
+
*
|
|
20
|
+
* Previously each app hand-rolled the same assembly in its own layout: the
|
|
21
|
+
* central-auth gate (bounce to sign-in when there's no session), the loading
|
|
22
|
+
* state, the account menu (email + Log out), and composing
|
|
23
|
+
* <Sidebar> + <SidebarLayout>. That's the duplication — it now lives here once.
|
|
24
|
+
* An app supplies only its slug + brand + nav sections:
|
|
25
|
+
*
|
|
26
|
+
* <AppShell app="market" appName="Market Simpli" sections={NAV}>{children}</AppShell>
|
|
27
|
+
*
|
|
28
|
+
* `useAuth` / `buildCentralAuthUrl` come from the @startsimpli/auth peer dep, so
|
|
29
|
+
* no app wires auth itself. The visual is the shared <Sidebar> (dark, collapsible),
|
|
30
|
+
* identical across apps.
|
|
31
|
+
*/
|
|
32
|
+
export interface AppShellProps {
|
|
33
|
+
/** App slug for the central-auth return flow, e.g. 'market' | 'foundry'. */
|
|
34
|
+
app: string;
|
|
35
|
+
/** Brand / product name shown at the top of the sidebar. */
|
|
36
|
+
appName: string;
|
|
37
|
+
/** Sidebar navigation sections (label + href + optional icon). */
|
|
38
|
+
sections: SidebarSection[];
|
|
39
|
+
/** Extra items rendered above "Log out" in the account menu (optional). */
|
|
40
|
+
accountMenuExtra?: ReactNode;
|
|
41
|
+
/** Loading label while the session bootstraps (default "Loading…"). */
|
|
42
|
+
loadingLabel?: string;
|
|
43
|
+
children: ReactNode;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function AppShell({
|
|
47
|
+
app,
|
|
48
|
+
appName,
|
|
49
|
+
sections,
|
|
50
|
+
accountMenuExtra,
|
|
51
|
+
loadingLabel = 'Loading…',
|
|
52
|
+
children,
|
|
53
|
+
}: AppShellProps) {
|
|
54
|
+
const { user, isLoading, logout } = useAuth();
|
|
55
|
+
|
|
56
|
+
const signin = (returnTo: string) => buildCentralAuthUrl('signin', { app, returnTo });
|
|
57
|
+
|
|
58
|
+
// Gate: no session once bootstrap settles -> bounce to central auth,
|
|
59
|
+
// preserving the current URL so the user returns here post-signin.
|
|
60
|
+
useEffect(() => {
|
|
61
|
+
if (isLoading || user) return;
|
|
62
|
+
if (typeof window === 'undefined') return;
|
|
63
|
+
window.location.href = signin(window.location.href);
|
|
64
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
65
|
+
}, [user, isLoading, app]);
|
|
66
|
+
|
|
67
|
+
const handleLogout = async () => {
|
|
68
|
+
try {
|
|
69
|
+
await logout();
|
|
70
|
+
} finally {
|
|
71
|
+
if (typeof window !== 'undefined') {
|
|
72
|
+
window.location.href = signin(`${window.location.origin}/`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
if (isLoading || !user) {
|
|
78
|
+
return (
|
|
79
|
+
<div className="flex min-h-screen items-center justify-center bg-background text-sm text-muted-foreground">
|
|
80
|
+
{loadingLabel}
|
|
81
|
+
</div>
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const account = user.email || user.name || 'Account';
|
|
86
|
+
|
|
87
|
+
const userDropdown = (
|
|
88
|
+
<DropdownMenu>
|
|
89
|
+
<DropdownMenuTrigger asChild>
|
|
90
|
+
<Button
|
|
91
|
+
variant="ghost"
|
|
92
|
+
size="sm"
|
|
93
|
+
className="w-full justify-start gap-2 text-gray-300 hover:bg-gray-800 hover:text-white"
|
|
94
|
+
>
|
|
95
|
+
<User className="h-4 w-4 flex-shrink-0" />
|
|
96
|
+
<span className="truncate text-sm">{account}</span>
|
|
97
|
+
</Button>
|
|
98
|
+
</DropdownMenuTrigger>
|
|
99
|
+
<DropdownMenuContent align="end" className="w-56">
|
|
100
|
+
<DropdownMenuLabel className="truncate">{account}</DropdownMenuLabel>
|
|
101
|
+
<DropdownMenuSeparator />
|
|
102
|
+
{accountMenuExtra}
|
|
103
|
+
<DropdownMenuItem
|
|
104
|
+
className="cursor-pointer text-red-600 focus:text-red-600"
|
|
105
|
+
onClick={handleLogout}
|
|
106
|
+
>
|
|
107
|
+
<LogOut className="mr-2 h-4 w-4" /> Log out
|
|
108
|
+
</DropdownMenuItem>
|
|
109
|
+
</DropdownMenuContent>
|
|
110
|
+
</DropdownMenu>
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
return (
|
|
114
|
+
<>
|
|
115
|
+
<Sidebar appName={appName} sections={sections} userDropdown={userDropdown} collapsible />
|
|
116
|
+
<SidebarLayout>{children}</SidebarLayout>
|
|
117
|
+
</>
|
|
118
|
+
);
|
|
119
|
+
}
|