@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
- org,
17
+ loadOrg,
18
18
  loadProjects,
19
19
  currentProject,
20
20
  onProjectChange,
21
21
  }: {
22
- org: OrgInfo
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
- org?: OrgInfo
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
- org,
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
- org={org}
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
- {org && loadProjects && onProjectChange && (
283
+ {loadOrg && loadProjects && onProjectChange && (
276
284
  <OrgProjectDropdown
277
- org={org}
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
- org?: OrgInfo
175
+ loadOrg?: () => Promise<OrgInfo>
176
176
  }
177
177
 
178
178
 
179
179
 
180
- function Sidebar({ currentMenu, onMainMenuToggle, sidebarMenus = {}, main_base_url = "", sectionLabels = {}, getCurrentMenuItem, org }: SidebarProps = {}) {
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 right-0 mb-0 bg-white border border-ui-border shadow-lg z-50">
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-3 px-3 py-3 hover:bg-ui-background transition-colors border-b border-ui-border no-underline"
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
- {org?.logo ? (
280
- <img src={org.logo} alt={org.nom} className="h-8 w-8 object-cover flex-shrink-0" />
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-8 w-8 bg-interactive flex items-center justify-center text-white text-sm font-bold flex-shrink-0">
283
- {org?.nom?.charAt(0)?.toUpperCase() ?? 'O'}
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 leading-none mb-0.5">Organisation</span>
288
- <span className="text-sm font-medium text-text-primary truncate">{org?.nom ?? 'Organisation'}</span>
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-3 py-2 text-sm text-text-secondary hover:bg-ui-background hover:text-text-primary transition-colors no-underline"
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-4 w-4 flex-shrink-0" />
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
  )}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@orsetra/shared-ui",
3
- "version": "1.1.30",
3
+ "version": "1.1.32",
4
4
  "description": "Shared UI components for Orsetra platform",
5
5
  "main": "./index.ts",
6
6
  "types": "./index.ts",