@orsetra/shared-ui 1.1.30 → 1.1.32
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.
|
@@ -14,21 +14,27 @@ import { Menu, ChevronDown, Check } from "lucide-react"
|
|
|
14
14
|
// ─── Dropdown Organisation / Projets ────────────────────────────────────────
|
|
15
15
|
|
|
16
16
|
function OrgProjectDropdown({
|
|
17
|
-
|
|
17
|
+
loadOrg,
|
|
18
18
|
loadProjects,
|
|
19
19
|
currentProject,
|
|
20
20
|
onProjectChange,
|
|
21
21
|
}: {
|
|
22
|
-
|
|
22
|
+
loadOrg: () => Promise<OrgInfo>
|
|
23
23
|
loadProjects: () => Promise<Project[]>
|
|
24
24
|
currentProject?: string | null
|
|
25
25
|
onProjectChange: (id: string) => void
|
|
26
26
|
}) {
|
|
27
|
+
const [org, setOrg] = useState<OrgInfo | null>(null)
|
|
27
28
|
const [open, setOpen] = useState(false)
|
|
28
29
|
const [projects, setProjects] = useState<Project[]>([])
|
|
29
30
|
const [loading, setLoading] = useState(false)
|
|
30
31
|
const ref = useRef<HTMLDivElement>(null)
|
|
31
32
|
|
|
33
|
+
useEffect(() => {
|
|
34
|
+
loadOrg().then(setOrg).catch(() => {})
|
|
35
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
36
|
+
}, [])
|
|
37
|
+
|
|
32
38
|
useEffect(() => {
|
|
33
39
|
if (!open) return
|
|
34
40
|
function handleOutside(e: MouseEvent) {
|
|
@@ -38,6 +44,8 @@ function OrgProjectDropdown({
|
|
|
38
44
|
return () => document.removeEventListener("mousedown", handleOutside)
|
|
39
45
|
}, [open])
|
|
40
46
|
|
|
47
|
+
if (!org) return null
|
|
48
|
+
|
|
41
49
|
const handleToggle = async () => {
|
|
42
50
|
if (!open && projects.length === 0) {
|
|
43
51
|
setLoading(true)
|
|
@@ -114,7 +122,7 @@ interface LayoutContainerProps {
|
|
|
114
122
|
getSidebarMode?: (pathname: string, params: Record<string, string>, searchParams: URLSearchParams) => SidebarMode
|
|
115
123
|
getCurrentMenu?: (pathname: string, searchParams: URLSearchParams) => string
|
|
116
124
|
getCurrentMenuItem?: (pathname: string, searchParams: URLSearchParams) => string
|
|
117
|
-
|
|
125
|
+
loadOrg?: () => Promise<OrgInfo>
|
|
118
126
|
loadProjects?: () => Promise<Project[]>
|
|
119
127
|
currentProject?: string | null
|
|
120
128
|
onProjectChange?: (id: string) => void
|
|
@@ -132,7 +140,7 @@ function LayoutContent({
|
|
|
132
140
|
getSidebarMode,
|
|
133
141
|
getCurrentMenu,
|
|
134
142
|
getCurrentMenuItem,
|
|
135
|
-
|
|
143
|
+
loadOrg,
|
|
136
144
|
loadProjects,
|
|
137
145
|
currentProject,
|
|
138
146
|
onProjectChange,
|
|
@@ -227,7 +235,7 @@ function LayoutContent({
|
|
|
227
235
|
const handleSecondarySidebarOpen = () => setOpen(true)
|
|
228
236
|
|
|
229
237
|
return (
|
|
230
|
-
<div className="flex h-screen w-full bg-white">
|
|
238
|
+
<div className="flex h-screen w-full bg-white overflow-visible">
|
|
231
239
|
{!isMinimized && !isHidden && (
|
|
232
240
|
<Sidebar
|
|
233
241
|
currentMenu={currentMenu}
|
|
@@ -236,7 +244,7 @@ function LayoutContent({
|
|
|
236
244
|
main_base_url={main_base_url}
|
|
237
245
|
sectionLabels={sectionLabels}
|
|
238
246
|
getCurrentMenuItem={getCurrentMenuItem}
|
|
239
|
-
|
|
247
|
+
loadOrg={loadOrg}
|
|
240
248
|
/>
|
|
241
249
|
)}
|
|
242
250
|
|
|
@@ -272,9 +280,9 @@ function LayoutContent({
|
|
|
272
280
|
|
|
273
281
|
{/* Droite : dropdown organisation + user menu */}
|
|
274
282
|
<div className="flex items-center gap-1">
|
|
275
|
-
{
|
|
283
|
+
{loadOrg && loadProjects && onProjectChange && (
|
|
276
284
|
<OrgProjectDropdown
|
|
277
|
-
|
|
285
|
+
loadOrg={loadOrg}
|
|
278
286
|
loadProjects={loadProjects}
|
|
279
287
|
currentProject={currentProject}
|
|
280
288
|
onProjectChange={onProjectChange}
|
|
@@ -172,17 +172,29 @@ interface SidebarProps {
|
|
|
172
172
|
main_base_url?: string
|
|
173
173
|
sectionLabels?: Record<string, string>
|
|
174
174
|
getCurrentMenuItem?: (pathname: string, searchParams: URLSearchParams) => string
|
|
175
|
-
|
|
175
|
+
loadOrg?: () => Promise<OrgInfo>
|
|
176
176
|
}
|
|
177
177
|
|
|
178
178
|
|
|
179
179
|
|
|
180
|
-
function Sidebar({ currentMenu, onMainMenuToggle, sidebarMenus = {}, main_base_url = "", sectionLabels = {}, getCurrentMenuItem,
|
|
180
|
+
function Sidebar({ currentMenu, onMainMenuToggle, sidebarMenus = {}, main_base_url = "", sectionLabels = {}, getCurrentMenuItem, loadOrg }: SidebarProps = {}) {
|
|
181
181
|
const pathname = usePathname()
|
|
182
182
|
const searchParams = useSearchParams()
|
|
183
183
|
const { state } = useSidebar()
|
|
184
184
|
const [settingsOpen, setSettingsOpen] = React.useState(false)
|
|
185
185
|
const settingsRef = React.useRef<HTMLDivElement>(null)
|
|
186
|
+
const [orgData, setOrgData] = React.useState<OrgInfo | null>(null)
|
|
187
|
+
const [orgLoading, setOrgLoading] = React.useState(false)
|
|
188
|
+
|
|
189
|
+
// Load org lazily when settings panel opens
|
|
190
|
+
React.useEffect(() => {
|
|
191
|
+
if (settingsOpen && loadOrg && !orgData && !orgLoading) {
|
|
192
|
+
setOrgLoading(true)
|
|
193
|
+
loadOrg()
|
|
194
|
+
.then((data) => { setOrgData(data); setOrgLoading(false) })
|
|
195
|
+
.catch(() => setOrgLoading(false))
|
|
196
|
+
}
|
|
197
|
+
}, [settingsOpen, loadOrg, orgData, orgLoading])
|
|
186
198
|
|
|
187
199
|
// Close dropdown on click outside
|
|
188
200
|
React.useEffect(() => {
|
|
@@ -204,7 +216,7 @@ function Sidebar({ currentMenu, onMainMenuToggle, sidebarMenus = {}, main_base_u
|
|
|
204
216
|
|| []
|
|
205
217
|
|
|
206
218
|
return (
|
|
207
|
-
<div className="h-screen sticky top-0 flex flex-col bg-gray-50 border-r border-ui-border min-w-[var(--sidebar-width-icon)] transition-[width] duration-200"
|
|
219
|
+
<div className="h-screen sticky top-0 flex flex-col bg-gray-50 border-r border-ui-border min-w-[var(--sidebar-width-icon)] transition-[width] duration-200 overflow-visible"
|
|
208
220
|
style={{ width: state === "expanded" ? "var(--sidebar-width)" : "var(--sidebar-width-icon)" }}>
|
|
209
221
|
{/* Logo avec bouton de menu principal */}
|
|
210
222
|
<div className="h-14 flex items-center justify-between px-4 border-b border-ui-border">
|
|
@@ -269,33 +281,41 @@ function Sidebar({ currentMenu, onMainMenuToggle, sidebarMenus = {}, main_base_u
|
|
|
269
281
|
<div className="relative border-t border-ui-border px-3 py-3" ref={settingsRef}>
|
|
270
282
|
{/* Dropdown vers le haut */}
|
|
271
283
|
{settingsOpen && (
|
|
272
|
-
<div className="absolute bottom-full left-0
|
|
284
|
+
<div className="absolute bottom-full left-0 mb-3 bg-white border border-ui-border shadow-2xl z-50 min-w-[380px]">
|
|
285
|
+
{/* Flèche pointant vers le bas en direction du bouton Paramètres */}
|
|
286
|
+
<div className="absolute -bottom-[5px] left-6 w-[10px] h-[10px] bg-white border-r border-b border-ui-border rotate-45" />
|
|
287
|
+
|
|
273
288
|
{/* Organisation */}
|
|
274
289
|
<Link
|
|
275
290
|
href={`${main_base_url}/organization`}
|
|
276
291
|
onClick={() => setSettingsOpen(false)}
|
|
277
|
-
className="flex items-center gap-
|
|
292
|
+
className="flex items-center gap-4 px-5 py-4 hover:bg-ui-background transition-colors border-b border-ui-border no-underline"
|
|
278
293
|
>
|
|
279
|
-
{
|
|
280
|
-
<
|
|
294
|
+
{orgLoading ? (
|
|
295
|
+
<div className="h-14 w-14 bg-ui-background animate-pulse flex-shrink-0" />
|
|
296
|
+
) : orgData?.logo ? (
|
|
297
|
+
<img src={orgData.logo} alt={orgData.nom} className="h-14 w-14 object-cover flex-shrink-0" />
|
|
281
298
|
) : (
|
|
282
|
-
<div className="h-
|
|
283
|
-
{
|
|
299
|
+
<div className="h-14 w-14 bg-interactive flex items-center justify-center text-white text-2xl font-bold flex-shrink-0">
|
|
300
|
+
{orgData?.nom?.charAt(0)?.toUpperCase() ?? 'O'}
|
|
284
301
|
</div>
|
|
285
302
|
)}
|
|
286
303
|
<div className="flex flex-col min-w-0">
|
|
287
|
-
<span className="text-xs text-text-secondary
|
|
288
|
-
<span className="text-
|
|
304
|
+
<span className="text-xs text-text-secondary uppercase tracking-wide mb-1">Organisation</span>
|
|
305
|
+
<span className="text-base font-semibold text-text-primary truncate">
|
|
306
|
+
{orgLoading ? '…' : (orgData?.nom ?? 'Organisation')}
|
|
307
|
+
</span>
|
|
289
308
|
</div>
|
|
290
309
|
</Link>
|
|
310
|
+
|
|
291
311
|
{/* Paramètres du compte */}
|
|
292
312
|
<Link
|
|
293
313
|
href={`${main_base_url}/profile`}
|
|
294
314
|
onClick={() => setSettingsOpen(false)}
|
|
295
|
-
className="flex items-center gap-3 px-
|
|
315
|
+
className="flex items-center gap-3 px-5 py-3 text-sm text-text-secondary hover:bg-ui-background hover:text-text-primary transition-colors no-underline"
|
|
296
316
|
>
|
|
297
|
-
<User className="h-
|
|
298
|
-
Paramètres du compte
|
|
317
|
+
<User className="h-5 w-5 flex-shrink-0" />
|
|
318
|
+
<span>Paramètres du compte</span>
|
|
299
319
|
</Link>
|
|
300
320
|
</div>
|
|
301
321
|
)}
|