rootzz-layout 0.1.0

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.mjs ADDED
@@ -0,0 +1,836 @@
1
+ "use client";
2
+ import { createContext, forwardRef, useEffect, useState, useCallback, useMemo, Children, isValidElement, useContext, useRef, useId } from 'react';
3
+ import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
4
+
5
+ // src/components/RootzzLayout.tsx
6
+ function useControllableState(options) {
7
+ const { value, defaultValue, onChange } = options;
8
+ const isControlled = value !== void 0;
9
+ const [internal, setInternal] = useState(defaultValue);
10
+ const onChangeRef = useRef(onChange);
11
+ onChangeRef.current = onChange;
12
+ const current = isControlled ? value : internal;
13
+ const currentRef = useRef(current);
14
+ currentRef.current = current;
15
+ const setValue = useCallback(
16
+ (next) => {
17
+ const resolved = typeof next === "function" ? next(currentRef.current) : next;
18
+ if (!isControlled) {
19
+ setInternal(resolved);
20
+ }
21
+ if (resolved !== currentRef.current) {
22
+ onChangeRef.current?.(resolved);
23
+ }
24
+ },
25
+ [isControlled]
26
+ );
27
+ return [current, setValue];
28
+ }
29
+ var MOBILE_QUERY = "(max-width: 767.98px)";
30
+ var LayoutContext = createContext(null);
31
+ function useLayout() {
32
+ const ctx = useContext(LayoutContext);
33
+ if (!ctx) {
34
+ throw new Error("useLayout deve ser usado dentro de <RootzzLayout> ou <LayoutShell>.");
35
+ }
36
+ return ctx;
37
+ }
38
+ function LayoutProvider({
39
+ children,
40
+ theme: themeProp,
41
+ defaultTheme = "light",
42
+ onThemeChange,
43
+ linkComponent = "a"
44
+ }) {
45
+ const [theme, setTheme] = useControllableState({
46
+ value: themeProp,
47
+ defaultValue: defaultTheme,
48
+ onChange: onThemeChange
49
+ });
50
+ const [mobileOpen, setMobileOpen] = useState(false);
51
+ const [isMobile, setIsMobile] = useState(false);
52
+ useEffect(() => {
53
+ if (typeof window === "undefined" || !window.matchMedia) return;
54
+ const mql = window.matchMedia(MOBILE_QUERY);
55
+ const update = () => setIsMobile(mql.matches);
56
+ update();
57
+ mql.addEventListener("change", update);
58
+ return () => mql.removeEventListener("change", update);
59
+ }, []);
60
+ useEffect(() => {
61
+ if (!isMobile && mobileOpen) setMobileOpen(false);
62
+ }, [isMobile, mobileOpen]);
63
+ useEffect(() => {
64
+ if (typeof document === "undefined") return;
65
+ if (!mobileOpen || !isMobile) return;
66
+ const previous = document.body.style.overflow;
67
+ document.body.style.overflow = "hidden";
68
+ return () => {
69
+ document.body.style.overflow = previous;
70
+ };
71
+ }, [mobileOpen, isMobile]);
72
+ const toggleTheme = useCallback(() => {
73
+ setTheme(theme === "dark" ? "light" : "dark");
74
+ }, [theme, setTheme]);
75
+ const value = useMemo(
76
+ () => ({
77
+ theme,
78
+ setTheme,
79
+ toggleTheme,
80
+ mobileOpen,
81
+ setMobileOpen,
82
+ isMobile,
83
+ linkComponent
84
+ }),
85
+ [theme, setTheme, toggleTheme, mobileOpen, isMobile, linkComponent]
86
+ );
87
+ return /* @__PURE__ */ jsx(LayoutContext.Provider, { value, children });
88
+ }
89
+
90
+ // src/lib/cx.ts
91
+ function cx(...inputs) {
92
+ const out = [];
93
+ for (const input of inputs) {
94
+ if (!input) continue;
95
+ if (typeof input === "string" || typeof input === "number") {
96
+ out.push(String(input));
97
+ } else if (Array.isArray(input)) {
98
+ const inner = cx(...input);
99
+ if (inner) out.push(inner);
100
+ } else if (typeof input === "object") {
101
+ for (const key in input) {
102
+ if (input[key]) out.push(key);
103
+ }
104
+ }
105
+ }
106
+ return out.join(" ");
107
+ }
108
+ function Content({ children, maxWidth, className, noPadding = false }) {
109
+ const useToken = maxWidth === void 0;
110
+ const style = maxWidth === void 0 || maxWidth === false ? void 0 : { maxWidth };
111
+ return /* @__PURE__ */ jsx("main", { className: "rootzz-content", children: /* @__PURE__ */ jsx(
112
+ "div",
113
+ {
114
+ style,
115
+ className: cx(
116
+ "rootzz-content__inner",
117
+ useToken && "rootzz-content__inner--max",
118
+ !noPadding && "rootzz-content__inner--padded",
119
+ className
120
+ ),
121
+ children
122
+ }
123
+ ) });
124
+ }
125
+ var SidebarContext = createContext({});
126
+ var SidebarProvider = SidebarContext.Provider;
127
+ function useSidebar() {
128
+ return useContext(SidebarContext);
129
+ }
130
+ function base({ size = 20, ...props }) {
131
+ return {
132
+ width: size,
133
+ height: size,
134
+ viewBox: "0 0 24 24",
135
+ fill: "none",
136
+ xmlns: "http://www.w3.org/2000/svg",
137
+ "aria-hidden": true,
138
+ focusable: false,
139
+ ...props
140
+ };
141
+ }
142
+ var stroke = {
143
+ stroke: "currentColor",
144
+ strokeWidth: 1.5,
145
+ strokeLinecap: "round",
146
+ strokeLinejoin: "round"
147
+ };
148
+ function AppsGridIcon(props) {
149
+ const xs = [5, 12, 19];
150
+ const ys = [5, 12, 19];
151
+ return /* @__PURE__ */ jsx("svg", { ...base(props), children: ys.flatMap(
152
+ (cy) => xs.map((cx2) => /* @__PURE__ */ jsx("circle", { cx: cx2, cy, r: 1, ...stroke }, `${cx2}-${cy}`))
153
+ ) });
154
+ }
155
+ function ChevronDownIcon(props) {
156
+ return /* @__PURE__ */ jsx("svg", { ...base(props), children: /* @__PURE__ */ jsx("path", { d: "m6 9 6 6 6-6", ...stroke }) });
157
+ }
158
+ function MenuIcon(props) {
159
+ return /* @__PURE__ */ jsx("svg", { ...base(props), children: /* @__PURE__ */ jsx("path", { d: "M3 6h18M3 12h18M3 18h18", ...stroke }) });
160
+ }
161
+ function CloseIcon(props) {
162
+ return /* @__PURE__ */ jsx("svg", { ...base(props), children: /* @__PURE__ */ jsx("path", { d: "M18 6 6 18M6 6l12 12", ...stroke }) });
163
+ }
164
+ function SearchIcon(props) {
165
+ return /* @__PURE__ */ jsxs("svg", { ...base(props), children: [
166
+ /* @__PURE__ */ jsx("circle", { cx: 11, cy: 11, r: 7, ...stroke }),
167
+ /* @__PURE__ */ jsx("path", { d: "m21 21-4.3-4.3", ...stroke })
168
+ ] });
169
+ }
170
+ function SunIcon(props) {
171
+ return /* @__PURE__ */ jsxs("svg", { ...base(props), children: [
172
+ /* @__PURE__ */ jsx("circle", { cx: 12, cy: 12, r: 4, ...stroke }),
173
+ /* @__PURE__ */ jsx(
174
+ "path",
175
+ {
176
+ d: "M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M6.34 17.66l-1.41 1.41M19.07 4.93l-1.41 1.41",
177
+ ...stroke
178
+ }
179
+ )
180
+ ] });
181
+ }
182
+ function MoonIcon(props) {
183
+ return /* @__PURE__ */ jsx("svg", { ...base(props), children: /* @__PURE__ */ jsx("path", { d: "M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79Z", ...stroke }) });
184
+ }
185
+ var IconButton = forwardRef(function IconButton2({ children, badge, size = 40, className, type = "button", ...rest }, ref) {
186
+ const hasBadge = badge !== void 0 && badge !== false && badge !== null;
187
+ const isDot = badge === true;
188
+ return /* @__PURE__ */ jsxs(
189
+ "button",
190
+ {
191
+ ref,
192
+ type,
193
+ style: { width: size, height: size },
194
+ className: cx("rootzz-icon-button", className),
195
+ ...rest,
196
+ children: [
197
+ children,
198
+ hasBadge && /* @__PURE__ */ jsx(
199
+ "span",
200
+ {
201
+ className: cx(
202
+ "rootzz-icon-button__badge",
203
+ isDot ? "rootzz-icon-button__badge--dot" : "rootzz-icon-button__badge--count"
204
+ ),
205
+ children: !isDot && badge
206
+ }
207
+ )
208
+ ]
209
+ }
210
+ );
211
+ });
212
+ function usePresence(open, duration = 220) {
213
+ const [mounted, setMounted] = useState(open);
214
+ const [active, setActive] = useState(open);
215
+ useEffect(() => {
216
+ if (open) {
217
+ setMounted(true);
218
+ const raf = requestAnimationFrame(() => setActive(true));
219
+ return () => cancelAnimationFrame(raf);
220
+ }
221
+ setActive(false);
222
+ const timer = setTimeout(() => setMounted(false), duration);
223
+ return () => clearTimeout(timer);
224
+ }, [open, duration]);
225
+ return { mounted, active };
226
+ }
227
+ function Overlay({ open, onClick, className }) {
228
+ const { mounted, active } = usePresence(open);
229
+ if (!mounted) return null;
230
+ return /* @__PURE__ */ jsx(
231
+ "div",
232
+ {
233
+ "data-rzl": "overlay",
234
+ "aria-hidden": true,
235
+ onClick,
236
+ className: cx(
237
+ "rootzz-overlay",
238
+ active ? "rootzz-overlay--active" : "rootzz-overlay--inactive",
239
+ className
240
+ )
241
+ }
242
+ );
243
+ }
244
+ function SidebarItem({ item, nested = false }) {
245
+ const { linkComponent: Link, isMobile, setMobileOpen } = useLayout();
246
+ const { activeKey } = useSidebar();
247
+ const isActive = item.active ?? (activeKey != null && item.key === activeKey);
248
+ const handleClick = (event) => {
249
+ item.onClick?.(event);
250
+ if (isMobile) setMobileOpen(false);
251
+ };
252
+ const className = cx(
253
+ "rootzz-sidebar-item",
254
+ nested && "rootzz-sidebar-item--nested",
255
+ item.disabled && "rootzz-sidebar-item--disabled",
256
+ isActive && "rootzz-sidebar-item--active"
257
+ );
258
+ const content = /* @__PURE__ */ jsxs(Fragment, { children: [
259
+ item.icon && /* @__PURE__ */ jsx("span", { className: "rootzz-sidebar-item__icon", children: item.icon }),
260
+ /* @__PURE__ */ jsx("span", { className: "rootzz-sidebar-item__label", children: item.label }),
261
+ item.badge != null && /* @__PURE__ */ jsx("span", { className: "rootzz-sidebar-item__badge", children: item.badge })
262
+ ] });
263
+ if (item.href && !item.disabled) {
264
+ return /* @__PURE__ */ jsx(
265
+ Link,
266
+ {
267
+ href: item.href,
268
+ target: item.target,
269
+ onClick: handleClick,
270
+ "aria-current": isActive ? "page" : void 0,
271
+ className,
272
+ children: content
273
+ }
274
+ );
275
+ }
276
+ return /* @__PURE__ */ jsx(
277
+ "button",
278
+ {
279
+ type: "button",
280
+ onClick: handleClick,
281
+ disabled: item.disabled,
282
+ "aria-current": isActive ? "page" : void 0,
283
+ className,
284
+ children: content
285
+ }
286
+ );
287
+ }
288
+ function SidebarGroup({ group }) {
289
+ const { activeKey } = useSidebar();
290
+ const collapsible = group.collapsible ?? true;
291
+ const [collapsed, setCollapsed] = useState(collapsible ? group.defaultCollapsed ?? false : false);
292
+ const open = collapsible ? !collapsed : true;
293
+ const regionId = useId();
294
+ const hasActiveChild = group.items.some(
295
+ (it) => it.active || activeKey != null && it.key === activeKey
296
+ );
297
+ const header = /* @__PURE__ */ jsxs("div", { className: "rootzz-sidebar-group__header", children: [
298
+ group.icon && /* @__PURE__ */ jsx("span", { className: "rootzz-sidebar-group__icon", children: group.icon }),
299
+ /* @__PURE__ */ jsx(
300
+ "span",
301
+ {
302
+ className: cx(
303
+ "rootzz-sidebar-group__label",
304
+ hasActiveChild && "rootzz-sidebar-group__label--active"
305
+ ),
306
+ children: group.label
307
+ }
308
+ ),
309
+ collapsible && /* @__PURE__ */ jsx(
310
+ ChevronDownIcon,
311
+ {
312
+ size: 16,
313
+ className: cx(
314
+ "rootzz-sidebar-group__chevron",
315
+ open && "rootzz-sidebar-group__chevron--open"
316
+ )
317
+ }
318
+ )
319
+ ] });
320
+ return /* @__PURE__ */ jsxs("div", { className: "rootzz-sidebar-group", children: [
321
+ collapsible ? /* @__PURE__ */ jsx(
322
+ "button",
323
+ {
324
+ type: "button",
325
+ onClick: () => setCollapsed((v) => !v),
326
+ "aria-expanded": open,
327
+ "aria-controls": regionId,
328
+ className: "rootzz-sidebar-group__trigger",
329
+ children: header
330
+ }
331
+ ) : header,
332
+ /* @__PURE__ */ jsx(
333
+ "div",
334
+ {
335
+ id: regionId,
336
+ "aria-hidden": !open,
337
+ className: cx(
338
+ "rootzz-sidebar-group__region",
339
+ open && "rootzz-sidebar-group__region--open"
340
+ ),
341
+ children: /* @__PURE__ */ jsx("div", { className: "rootzz-sidebar-group__clip", children: /* @__PURE__ */ jsx("div", { className: "rootzz-sidebar-group__items", children: group.items.map((item) => /* @__PURE__ */ jsx(SidebarItem, { item, nested: true }, item.key)) }) })
342
+ }
343
+ )
344
+ ] });
345
+ }
346
+ function renderEntry(entry, index) {
347
+ if (entry.type === "divider") {
348
+ return /* @__PURE__ */ jsx("div", { className: "rootzz-nav__divider" }, entry.key ?? `divider-${index}`);
349
+ }
350
+ if (entry.type === "group") {
351
+ const group = entry;
352
+ return /* @__PURE__ */ jsx(SidebarGroup, { group }, group.key);
353
+ }
354
+ const item = entry;
355
+ return /* @__PURE__ */ jsx(SidebarItem, { item }, item.key);
356
+ }
357
+ function Nav({
358
+ menu,
359
+ header,
360
+ footer,
361
+ ariaLabel,
362
+ children
363
+ }) {
364
+ return /* @__PURE__ */ jsxs("nav", { "aria-label": ariaLabel ?? "Navega\xE7\xE3o principal", className: "rootzz-nav", children: [
365
+ header && /* @__PURE__ */ jsx("div", { className: "rootzz-nav__header", children: header }),
366
+ /* @__PURE__ */ jsx("div", { className: "rootzz-nav__scroll rootzz-scrollbar-none", children: children ?? /* @__PURE__ */ jsx("div", { className: "rootzz-nav__list", children: menu?.map(renderEntry) }) }),
367
+ footer && /* @__PURE__ */ jsx("div", { className: "rootzz-nav__footer", children: footer })
368
+ ] });
369
+ }
370
+ function Sidebar({
371
+ menu,
372
+ activeKey,
373
+ header,
374
+ footer,
375
+ ariaLabel,
376
+ children,
377
+ className,
378
+ mobileTitle
379
+ }) {
380
+ const { mobileOpen, setMobileOpen } = useLayout();
381
+ useEffect(() => {
382
+ if (!mobileOpen) return;
383
+ if (typeof document === "undefined") return;
384
+ const onKey = (e) => {
385
+ if (e.key === "Escape") setMobileOpen(false);
386
+ };
387
+ document.addEventListener("keydown", onKey);
388
+ return () => document.removeEventListener("keydown", onKey);
389
+ }, [mobileOpen, setMobileOpen]);
390
+ const navProps = { menu, header, footer, ariaLabel, children };
391
+ return /* @__PURE__ */ jsxs(SidebarProvider, { value: { activeKey }, children: [
392
+ /* @__PURE__ */ jsx("aside", { "data-rzl": "sidebar", className: cx("rootzz-sidebar", className), children: /* @__PURE__ */ jsx(Nav, { ...navProps }) }),
393
+ /* @__PURE__ */ jsx(Overlay, { open: mobileOpen, onClick: () => setMobileOpen(false) }),
394
+ /* @__PURE__ */ jsxs(
395
+ "aside",
396
+ {
397
+ "data-rzl": "drawer",
398
+ "data-state": mobileOpen ? "open" : "closed",
399
+ "aria-hidden": !mobileOpen,
400
+ className: cx("rootzz-drawer", mobileOpen && "rootzz-drawer--open", className),
401
+ children: [
402
+ /* @__PURE__ */ jsxs("div", { className: "rootzz-drawer__header", children: [
403
+ /* @__PURE__ */ jsx("span", { className: "rootzz-drawer__title", children: mobileTitle }),
404
+ /* @__PURE__ */ jsx(IconButton, { "aria-label": "Fechar menu", onClick: () => setMobileOpen(false), children: /* @__PURE__ */ jsx(CloseIcon, { size: 22 }) })
405
+ ] }),
406
+ /* @__PURE__ */ jsx("div", { className: "rootzz-drawer__body", children: /* @__PURE__ */ jsx(Nav, { ...navProps }) })
407
+ ]
408
+ }
409
+ )
410
+ ] });
411
+ }
412
+ var DEFAULT_APPS_URL = "https://cdn.eduzzcdn.com/topbar/applications.json";
413
+ var cache = /* @__PURE__ */ new Map();
414
+ function fetchApps(url) {
415
+ let promise = cache.get(url);
416
+ if (!promise) {
417
+ promise = fetch(url).then((res) => {
418
+ if (!res.ok) throw new Error(`HTTP ${res.status} ao buscar apps`);
419
+ return res.json();
420
+ }).then((data) => Array.isArray(data) ? data : []).catch((err) => {
421
+ cache.delete(url);
422
+ throw err;
423
+ });
424
+ cache.set(url, promise);
425
+ }
426
+ return promise;
427
+ }
428
+ function resolveSource(source) {
429
+ if (!source) return { staticItems: null, url: null };
430
+ if (source === true) return { staticItems: null, url: DEFAULT_APPS_URL };
431
+ if (typeof source === "string") return { staticItems: null, url: source };
432
+ if (Array.isArray(source)) return { staticItems: source, url: null };
433
+ if (source.items) return { staticItems: source.items, url: null };
434
+ return { staticItems: null, url: source.url ?? DEFAULT_APPS_URL };
435
+ }
436
+ function useApps(source) {
437
+ const { staticItems, url } = resolveSource(source);
438
+ const [items, setItems] = useState(staticItems ?? []);
439
+ const [loading, setLoading] = useState(!!url && !staticItems);
440
+ const [error, setError] = useState(null);
441
+ useEffect(() => {
442
+ if (staticItems) {
443
+ setItems(staticItems);
444
+ setLoading(false);
445
+ setError(null);
446
+ return;
447
+ }
448
+ if (!url) {
449
+ setItems([]);
450
+ setLoading(false);
451
+ return;
452
+ }
453
+ let active = true;
454
+ setLoading(true);
455
+ setError(null);
456
+ fetchApps(url).then((data) => {
457
+ if (!active) return;
458
+ setItems(data);
459
+ setLoading(false);
460
+ }).catch((err) => {
461
+ if (!active) return;
462
+ setError(err);
463
+ setLoading(false);
464
+ });
465
+ return () => {
466
+ active = false;
467
+ };
468
+ }, [url, staticItems ? staticItems.length : null]);
469
+ return { items, loading, error };
470
+ }
471
+ function useApplication(application, url = DEFAULT_APPS_URL) {
472
+ const [app, setApp] = useState(null);
473
+ const [loading, setLoading] = useState(!!application);
474
+ const [error, setError] = useState(null);
475
+ useEffect(() => {
476
+ if (!application) {
477
+ setApp(null);
478
+ setLoading(false);
479
+ setError(null);
480
+ return;
481
+ }
482
+ let active = true;
483
+ setLoading(true);
484
+ setError(null);
485
+ fetchApps(url).then((data) => {
486
+ if (!active) return;
487
+ setApp(data.find((item) => item.application === application) ?? null);
488
+ setLoading(false);
489
+ }).catch((err) => {
490
+ if (!active) return;
491
+ setError(err);
492
+ setLoading(false);
493
+ });
494
+ return () => {
495
+ active = false;
496
+ };
497
+ }, [application, url]);
498
+ return { app, loading, error };
499
+ }
500
+ function useDismiss(open, refs, onDismiss) {
501
+ useEffect(() => {
502
+ if (!open) return;
503
+ if (typeof document === "undefined") return;
504
+ const handlePointer = (event) => {
505
+ const target = event.target;
506
+ if (!target) return;
507
+ const inside = refs.some((ref) => ref.current && ref.current.contains(target));
508
+ if (!inside) onDismiss();
509
+ };
510
+ const handleKey = (event) => {
511
+ if (event.key === "Escape") onDismiss();
512
+ };
513
+ document.addEventListener("pointerdown", handlePointer, true);
514
+ document.addEventListener("keydown", handleKey);
515
+ return () => {
516
+ document.removeEventListener("pointerdown", handlePointer, true);
517
+ document.removeEventListener("keydown", handleKey);
518
+ };
519
+ }, [open, onDismiss, refs]);
520
+ }
521
+ function Dropdown({
522
+ renderTrigger,
523
+ children,
524
+ align = "end",
525
+ panelClassName,
526
+ panelWidth,
527
+ closeOnContentClick = true,
528
+ ariaLabel,
529
+ open: openProp,
530
+ onOpenChange
531
+ }) {
532
+ const [open, setOpen] = useControllableState({
533
+ value: openProp,
534
+ defaultValue: false,
535
+ onChange: onOpenChange
536
+ });
537
+ const containerRef = useRef(null);
538
+ const panelId = useId();
539
+ const { mounted, active } = usePresence(open);
540
+ useDismiss(open, [containerRef], () => setOpen(false));
541
+ const toggle = () => setOpen((v) => !v);
542
+ return /* @__PURE__ */ jsxs("div", { ref: containerRef, className: "rootzz-dropdown", children: [
543
+ renderTrigger({
544
+ open,
545
+ toggle,
546
+ triggerProps: {
547
+ "aria-haspopup": "menu",
548
+ "aria-expanded": open,
549
+ "aria-controls": panelId
550
+ }
551
+ }),
552
+ mounted && /* @__PURE__ */ jsx(
553
+ "div",
554
+ {
555
+ id: panelId,
556
+ role: "menu",
557
+ "aria-label": ariaLabel,
558
+ onClick: () => closeOnContentClick && setOpen(false),
559
+ style: panelWidth !== void 0 ? { width: panelWidth } : void 0,
560
+ className: cx(
561
+ "rootzz-dropdown__panel",
562
+ align === "end" ? "rootzz-dropdown__panel--end" : "rootzz-dropdown__panel--start",
563
+ active ? "rootzz-dropdown__panel--active" : "rootzz-dropdown__panel--inactive",
564
+ panelClassName
565
+ ),
566
+ children
567
+ }
568
+ )
569
+ ] });
570
+ }
571
+ function AppsMenu({ source = true, label = "Aplicativos", title = "Aplicativos" }) {
572
+ const { items, loading, error } = useApps(source);
573
+ if (source === false) return null;
574
+ return /* @__PURE__ */ jsxs(
575
+ Dropdown,
576
+ {
577
+ align: "start",
578
+ ariaLabel: title,
579
+ panelWidth: 332,
580
+ panelClassName: "rootzz-apps",
581
+ renderTrigger: ({ toggle, triggerProps }) => /* @__PURE__ */ jsx(IconButton, { onClick: toggle, "aria-label": label, title: label, ...triggerProps, children: /* @__PURE__ */ jsx(AppsGridIcon, { size: 22 }) }),
582
+ children: [
583
+ /* @__PURE__ */ jsx("div", { className: "rootzz-apps__title", children: title }),
584
+ loading && /* @__PURE__ */ jsx("div", { className: "rootzz-apps__status", children: "Carregando\u2026" }),
585
+ error && !loading && /* @__PURE__ */ jsx("div", { className: "rootzz-apps__status", children: "N\xE3o foi poss\xEDvel carregar os aplicativos." }),
586
+ !loading && !error && items.length > 0 && /* @__PURE__ */ jsx("div", { className: "rootzz-apps__grid rootzz-scrollbar-none", children: items.map((app, index) => /* @__PURE__ */ jsxs(
587
+ "a",
588
+ {
589
+ href: app.url,
590
+ title: app.description ?? app.label,
591
+ role: "menuitem",
592
+ className: "rootzz-apps__item",
593
+ children: [
594
+ /* @__PURE__ */ jsx("img", { src: app.icon, alt: "", className: "rootzz-apps__icon", loading: "lazy" }),
595
+ /* @__PURE__ */ jsx("span", { className: "rootzz-apps__label", children: app.label })
596
+ ]
597
+ },
598
+ app.application ?? `${app.label}-${index}`
599
+ )) })
600
+ ]
601
+ }
602
+ );
603
+ }
604
+ var MYEDUZZ_LOGO = {
605
+ src: "https://cdn.eduzzcdn.com/topbar/orbita-new.svg",
606
+ alt: "MyEduzz",
607
+ height: 28
608
+ };
609
+ function isMyEduzzShortcut(value) {
610
+ return typeof value === "string" && value.trim().toLowerCase() === "myeduzz";
611
+ }
612
+ function isLogoConfig(value) {
613
+ return typeof value === "object" && value !== null && !isValidElement(value) && "src" in value;
614
+ }
615
+ function Logo({ logo }) {
616
+ const { linkComponent: Link } = useLayout();
617
+ if (isLogoConfig(logo)) {
618
+ const img = /* @__PURE__ */ jsx("img", { src: logo.src, alt: logo.alt ?? "", className: "rootzz-brand__logo-img" });
619
+ if (logo.href) {
620
+ return /* @__PURE__ */ jsx(Link, { href: logo.href, onClick: logo.onClick, className: "rootzz-brand__logo-link", children: img });
621
+ }
622
+ if (logo.onClick) {
623
+ return /* @__PURE__ */ jsx("button", { type: "button", onClick: logo.onClick, className: "rootzz-brand__logo-link", children: img });
624
+ }
625
+ return /* @__PURE__ */ jsx("span", { className: "rootzz-brand__logo-link", children: img });
626
+ }
627
+ return /* @__PURE__ */ jsx(Fragment, { children: logo });
628
+ }
629
+ function ApplicationBrand({ application }) {
630
+ const { linkComponent: Link } = useLayout();
631
+ const { app } = useApplication(application);
632
+ if (!app) return null;
633
+ return /* @__PURE__ */ jsxs(Link, { href: app.url, className: "rootzz-brand__app", title: app.label, children: [
634
+ /* @__PURE__ */ jsx("img", { src: app.icon, alt: app.label, className: "rootzz-brand__app-icon" }),
635
+ /* @__PURE__ */ jsx("span", { className: "rootzz-brand__app-name", children: app.label })
636
+ ] });
637
+ }
638
+ function Brand({ logo, application, apps, appsLabel }) {
639
+ const resolvedLogo = isMyEduzzShortcut(logo) ? MYEDUZZ_LOGO : logo;
640
+ return /* @__PURE__ */ jsxs("div", { className: "rootzz-brand", children: [
641
+ /* @__PURE__ */ jsx(AppsMenu, { source: apps, label: appsLabel }),
642
+ application ? /* @__PURE__ */ jsx("span", { className: "rootzz-brand__logo", children: /* @__PURE__ */ jsx(ApplicationBrand, { application }) }) : resolvedLogo != null && /* @__PURE__ */ jsx("span", { className: "rootzz-brand__logo", children: /* @__PURE__ */ jsx(Logo, { logo: resolvedLogo }) })
643
+ ] });
644
+ }
645
+ function ThemeToggle({ label = "Alternar tema" }) {
646
+ const { theme, toggleTheme } = useLayout();
647
+ const isDark = theme === "dark";
648
+ return /* @__PURE__ */ jsx(IconButton, { onClick: toggleTheme, "aria-label": label, title: label, children: /* @__PURE__ */ jsxs("span", { className: "rootzz-theme-toggle", children: [
649
+ /* @__PURE__ */ jsx(SunIcon, { size: 20, className: cxFade(isDark) }),
650
+ /* @__PURE__ */ jsx(MoonIcon, { size: 20, className: cxFade(!isDark) })
651
+ ] }) });
652
+ }
653
+ function cxFade(visible) {
654
+ return [
655
+ "rootzz-theme-toggle__icon",
656
+ visible ? "rootzz-theme-toggle__icon--visible" : "rootzz-theme-toggle__icon--hidden"
657
+ ].join(" ");
658
+ }
659
+ function computeInitials(name) {
660
+ const parts = name.trim().split(/\s+/).filter(Boolean);
661
+ if (parts.length === 0) return "?";
662
+ if (parts.length === 1) return parts[0].slice(0, 2).toUpperCase();
663
+ return (parts[0][0] + parts[parts.length - 1][0]).toUpperCase();
664
+ }
665
+ function Avatar({ user, size = 32 }) {
666
+ const initials = user.initials ?? computeInitials(user.name);
667
+ if (user.avatarUrl) {
668
+ return /* @__PURE__ */ jsx(
669
+ "img",
670
+ {
671
+ src: user.avatarUrl,
672
+ alt: user.name,
673
+ style: { width: size, height: size },
674
+ className: "rootzz-avatar"
675
+ }
676
+ );
677
+ }
678
+ return /* @__PURE__ */ jsx("span", { "aria-hidden": true, style: { width: size, height: size }, className: "rootzz-avatar rootzz-avatar--initials", children: initials });
679
+ }
680
+ function isDivider(entry) {
681
+ return entry.type === "divider";
682
+ }
683
+ function UserMenu({ user }) {
684
+ const { linkComponent: Link } = useLayout();
685
+ const hasMenu = !!user.menu && user.menu.length > 0;
686
+ const trigger = (extra) => /* @__PURE__ */ jsxs(
687
+ "button",
688
+ {
689
+ type: "button",
690
+ onClick: extra ? extra.toggle : user.onClick,
691
+ ...extra?.triggerProps ?? {},
692
+ className: "rootzz-user",
693
+ children: [
694
+ /* @__PURE__ */ jsx(Avatar, { user }),
695
+ /* @__PURE__ */ jsx("span", { className: "rootzz-user__name", children: user.name }),
696
+ hasMenu && /* @__PURE__ */ jsx(ChevronDownIcon, { size: 16, className: "rootzz-user__chevron" })
697
+ ]
698
+ }
699
+ );
700
+ if (!hasMenu) {
701
+ return trigger();
702
+ }
703
+ return /* @__PURE__ */ jsxs(
704
+ Dropdown,
705
+ {
706
+ align: "end",
707
+ ariaLabel: `Menu de ${user.name}`,
708
+ panelWidth: 240,
709
+ renderTrigger: ({ toggle, triggerProps }) => trigger({ toggle, triggerProps }),
710
+ children: [
711
+ /* @__PURE__ */ jsxs("div", { className: "rootzz-user-panel__header", children: [
712
+ /* @__PURE__ */ jsx(Avatar, { user, size: 36 }),
713
+ /* @__PURE__ */ jsxs("div", { className: "rootzz-user-panel__info", children: [
714
+ /* @__PURE__ */ jsx("div", { className: "rootzz-user-panel__name", children: user.name }),
715
+ user.email && /* @__PURE__ */ jsx("div", { className: "rootzz-user-panel__email", children: user.email })
716
+ ] })
717
+ ] }),
718
+ /* @__PURE__ */ jsx("div", { className: "rootzz-divider" }),
719
+ user.menu.map((entry, index) => {
720
+ if (isDivider(entry)) {
721
+ return /* @__PURE__ */ jsx("div", { className: "rootzz-divider" }, entry.key ?? `divider-${index}`);
722
+ }
723
+ const itemClass = cx(
724
+ "rootzz-menu-item",
725
+ entry.disabled && "rootzz-menu-item--disabled",
726
+ entry.danger && "rootzz-menu-item--danger"
727
+ );
728
+ const content = /* @__PURE__ */ jsxs(Fragment, { children: [
729
+ entry.icon && /* @__PURE__ */ jsx("span", { className: "rootzz-menu-item__icon", children: entry.icon }),
730
+ /* @__PURE__ */ jsx("span", { className: "rootzz-menu-item__label", children: entry.label })
731
+ ] });
732
+ if (entry.href) {
733
+ return /* @__PURE__ */ jsx(Link, { href: entry.href, role: "menuitem", className: itemClass, children: content }, entry.key ?? index);
734
+ }
735
+ return /* @__PURE__ */ jsx("button", { type: "button", role: "menuitem", onClick: entry.onClick, className: itemClass, children: content }, entry.key ?? index);
736
+ })
737
+ ]
738
+ }
739
+ );
740
+ }
741
+ function Topbar({
742
+ logo,
743
+ application,
744
+ apps,
745
+ search,
746
+ actions,
747
+ user,
748
+ showThemeToggle = false,
749
+ showMenuButton = true,
750
+ appsLabel,
751
+ menuButtonLabel = "Abrir menu",
752
+ className
753
+ }) {
754
+ const { mobileOpen, setMobileOpen } = useLayout();
755
+ return /* @__PURE__ */ jsxs("header", { className: cx("rootzz-topbar", className), children: [
756
+ /* @__PURE__ */ jsxs("div", { className: "rootzz-topbar__left", children: [
757
+ showMenuButton && /* @__PURE__ */ jsx(
758
+ IconButton,
759
+ {
760
+ className: "rootzz-topbar__menu-button",
761
+ "aria-label": menuButtonLabel,
762
+ "aria-expanded": mobileOpen,
763
+ onClick: () => setMobileOpen(!mobileOpen),
764
+ children: /* @__PURE__ */ jsx(MenuIcon, { size: 22 })
765
+ }
766
+ ),
767
+ /* @__PURE__ */ jsx(Brand, { logo, application, apps, appsLabel })
768
+ ] }),
769
+ /* @__PURE__ */ jsx("div", { className: "rootzz-topbar__center", children: search && /* @__PURE__ */ jsx("div", { className: "rootzz-topbar__search", children: search }) }),
770
+ /* @__PURE__ */ jsxs("div", { className: "rootzz-topbar__right", children: [
771
+ actions,
772
+ showThemeToggle && /* @__PURE__ */ jsx(ThemeToggle, {}),
773
+ user && /* @__PURE__ */ jsx(UserMenu, { user })
774
+ ] })
775
+ ] });
776
+ }
777
+ function Shell({ children, className, style, id }) {
778
+ const { theme } = useLayout();
779
+ const items = Children.toArray(children).filter(isValidElement);
780
+ const topbar = items.find((el) => el.type === Topbar);
781
+ const sidebars = items.filter((el) => el.type === Sidebar);
782
+ const body = items.filter((el) => el.type !== Topbar && el.type !== Sidebar);
783
+ return /* @__PURE__ */ jsxs("div", { id, "data-theme": theme, style, className: cx("rootzz-layout", className), children: [
784
+ topbar,
785
+ /* @__PURE__ */ jsxs("div", { className: "rootzz-layout__body", children: [
786
+ sidebars,
787
+ body
788
+ ] })
789
+ ] });
790
+ }
791
+ function RootzzLayoutBase({
792
+ children,
793
+ theme,
794
+ defaultTheme,
795
+ onThemeChange,
796
+ linkComponent,
797
+ className,
798
+ style,
799
+ id
800
+ }) {
801
+ return /* @__PURE__ */ jsx(
802
+ LayoutProvider,
803
+ {
804
+ theme,
805
+ defaultTheme,
806
+ onThemeChange,
807
+ linkComponent,
808
+ children: /* @__PURE__ */ jsx(Shell, { className, style, id, children })
809
+ }
810
+ );
811
+ }
812
+ var RootzzLayout = Object.assign(RootzzLayoutBase, {
813
+ Topbar,
814
+ Sidebar,
815
+ Content
816
+ });
817
+ var SearchBar = forwardRef(function SearchBar2({ placeholder = "Buscar\u2026", shortcut, wrapperClassName, className, ...rest }, ref) {
818
+ return /* @__PURE__ */ jsxs("div", { className: cx("rootzz-search", wrapperClassName), children: [
819
+ /* @__PURE__ */ jsx(SearchIcon, { size: 18, className: "rootzz-search__icon" }),
820
+ /* @__PURE__ */ jsx(
821
+ "input",
822
+ {
823
+ ref,
824
+ type: "search",
825
+ placeholder,
826
+ className: cx("rootzz-search__input", className),
827
+ ...rest
828
+ }
829
+ ),
830
+ shortcut != null && /* @__PURE__ */ jsx("kbd", { className: "rootzz-search__shortcut", children: shortcut })
831
+ ] });
832
+ });
833
+
834
+ export { AppsMenu, Brand, Content, DEFAULT_APPS_URL, Dropdown, IconButton, LayoutProvider, Overlay, RootzzLayout, SearchBar, Sidebar, SidebarGroup, SidebarItem, ThemeToggle, Topbar, UserMenu, cx, useApplication, useApps, useLayout };
835
+ //# sourceMappingURL=index.mjs.map
836
+ //# sourceMappingURL=index.mjs.map