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