analytica-frontend-lib 1.0.12 → 1.0.14

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.css CHANGED
@@ -54,6 +54,7 @@
54
54
  --default-transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
55
55
  --default-font-family: var(--font-sans);
56
56
  --default-mono-font-family: var(--font-mono);
57
+ --color-primary-50: #999999;
57
58
  --color-primary-400: #525252;
58
59
  --color-primary-500: #333333;
59
60
  --color-primary-600: #292929;
@@ -427,6 +428,9 @@
427
428
  .justify-start {
428
429
  justify-content: flex-start;
429
430
  }
431
+ .gap-0\.5 {
432
+ gap: calc(var(--spacing) * 0.5);
433
+ }
430
434
  .gap-2 {
431
435
  gap: calc(var(--spacing) * 2);
432
436
  }
@@ -521,6 +525,9 @@
521
525
  .bg-error-500 {
522
526
  background-color: var(--color-error-500);
523
527
  }
528
+ .bg-primary-50 {
529
+ background-color: var(--color-primary-50);
530
+ }
524
531
  .bg-primary-950 {
525
532
  background-color: var(--color-primary-950);
526
533
  }
@@ -560,6 +567,12 @@
560
567
  .px-7 {
561
568
  padding-inline: calc(var(--spacing) * 7);
562
569
  }
570
+ .px-12 {
571
+ padding-inline: calc(var(--spacing) * 12);
572
+ }
573
+ .py-1 {
574
+ padding-block: calc(var(--spacing) * 1);
575
+ }
563
576
  .py-2 {
564
577
  padding-block: calc(var(--spacing) * 2);
565
578
  }
@@ -660,6 +673,9 @@
660
673
  --tw-font-weight: var(--font-weight-semibold);
661
674
  font-weight: var(--font-weight-semibold);
662
675
  }
676
+ .whitespace-nowrap {
677
+ white-space: nowrap;
678
+ }
663
679
  .text-error-500 {
664
680
  color: var(--color-error-500);
665
681
  }
@@ -837,6 +853,13 @@
837
853
  }
838
854
  }
839
855
  }
856
+ .hover\:bg-primary-600 {
857
+ &:hover {
858
+ @media (hover: hover) {
859
+ background-color: var(--color-primary-600);
860
+ }
861
+ }
862
+ }
840
863
  .hover\:bg-primary-800 {
841
864
  &:hover {
842
865
  @media (hover: hover) {
@@ -872,6 +895,13 @@
872
895
  }
873
896
  }
874
897
  }
898
+ .hover\:text-text {
899
+ &:hover {
900
+ @media (hover: hover) {
901
+ color: var(--color-text);
902
+ }
903
+ }
904
+ }
875
905
  .hover\:shadow-hard-shadow-1 {
876
906
  &:hover {
877
907
  @media (hover: hover) {
@@ -1049,6 +1079,11 @@
1049
1079
  --tw-ring-offset-shadow: var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
1050
1080
  }
1051
1081
  }
1082
+ .disabled\:pointer-events-none {
1083
+ &:disabled {
1084
+ pointer-events: none;
1085
+ }
1086
+ }
1052
1087
  .disabled\:cursor-not-allowed {
1053
1088
  &:disabled {
1054
1089
  cursor: not-allowed;
package/dist/index.d.mts CHANGED
@@ -197,4 +197,82 @@ declare const TableHead: react.ForwardRefExoticComponent<TdHTMLAttributes<HTMLTa
197
197
  declare const TableCell: react.ForwardRefExoticComponent<TdHTMLAttributes<HTMLTableCellElement> & react.RefAttributes<HTMLTableCellElement>>;
198
198
  declare const TableCaption: react.ForwardRefExoticComponent<HTMLAttributes<HTMLTableCaptionElement> & react.RefAttributes<HTMLTableCaptionElement>>;
199
199
 
200
- export { Button, IconRoundedButton, SelectionButton, Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableRow, Text };
200
+ interface DropdownMenuProps {
201
+ children: ReactNode;
202
+ open?: boolean;
203
+ onOpenChange?: (open: boolean) => void;
204
+ }
205
+ declare const DropdownMenu: ({ children, open, onOpenChange }: DropdownMenuProps) => react_jsx_runtime.JSX.Element;
206
+ declare const DropdownMenuTrigger: react.ForwardRefExoticComponent<ButtonHTMLAttributes<HTMLButtonElement> & react.RefAttributes<HTMLButtonElement>>;
207
+ declare const MenuLabel: react.ForwardRefExoticComponent<HTMLAttributes<HTMLFieldSetElement> & {
208
+ inset?: boolean;
209
+ } & react.RefAttributes<HTMLFieldSetElement>>;
210
+ declare const MenuContent: react.ForwardRefExoticComponent<HTMLAttributes<HTMLDivElement> & {
211
+ align?: "start" | "center" | "end";
212
+ side?: "top" | "right" | "bottom" | "left";
213
+ sideOffset?: number;
214
+ } & react.RefAttributes<HTMLDivElement>>;
215
+ declare const MenuItem: react.ForwardRefExoticComponent<HTMLAttributes<HTMLDivElement> & {
216
+ inset?: boolean;
217
+ size?: "small" | "medium";
218
+ iconLeft?: ReactNode;
219
+ iconRight?: ReactNode;
220
+ disabled?: boolean;
221
+ } & react.RefAttributes<HTMLDivElement>>;
222
+ declare const MenuSeparator: react.ForwardRefExoticComponent<HTMLAttributes<HTMLDivElement> & react.RefAttributes<HTMLDivElement>>;
223
+
224
+ /**
225
+ * NavButton component for Analytica Ensino platforms
226
+ *
227
+ * Um botão de navegação com ícone e texto para navegação principal.
228
+ * Ideal para menus de navegação, sidebar, tabs de navegação, etc.
229
+ * Compatível com Next.js 15 e React 19.
230
+ * Suporta forwardRef para acesso programático ao elemento DOM.
231
+ *
232
+ * @param icon - O ícone a ser exibido no botão
233
+ * @param label - O texto/label a ser exibido
234
+ * @param selected - Estado de seleção do botão
235
+ * @param className - Classes CSS adicionais
236
+ * @param props - Todos os outros atributos HTML padrão de button
237
+ * @returns Um elemento button estilizado para navegação
238
+ *
239
+ * @example
240
+ * ```tsx
241
+ * <NavButton
242
+ * icon={<HomeIcon />}
243
+ * label="Início"
244
+ * selected={false}
245
+ * onClick={() => navigate('/')}
246
+ * />
247
+ * ```
248
+ *
249
+ * @example
250
+ * ```tsx
251
+ * // Usando ref para foco programático
252
+ * const buttonRef = useRef<HTMLButtonElement>(null);
253
+ *
254
+ * const handleFocus = () => {
255
+ * buttonRef.current?.focus();
256
+ * };
257
+ *
258
+ * <NavButton
259
+ * ref={buttonRef}
260
+ * icon={<HomeIcon />}
261
+ * label="Dashboard"
262
+ * selected={isActive}
263
+ * onClick={() => setActiveTab('dashboard')}
264
+ * />
265
+ * ```
266
+ */
267
+ declare const NavButton: react.ForwardRefExoticComponent<{
268
+ /** Ícone a ser exibido no botão */
269
+ icon: ReactNode;
270
+ /** Texto/label a ser exibido ao lado do ícone */
271
+ label: string;
272
+ /** Estado de seleção do botão */
273
+ selected?: boolean;
274
+ /** Additional CSS classes to apply */
275
+ className?: string;
276
+ } & ButtonHTMLAttributes<HTMLButtonElement> & react.RefAttributes<HTMLButtonElement>>;
277
+
278
+ export { Button, DropdownMenu, DropdownMenuTrigger, IconRoundedButton, MenuContent, MenuItem, MenuLabel, MenuSeparator, NavButton, SelectionButton, Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableRow, Text };
package/dist/index.d.ts CHANGED
@@ -197,4 +197,82 @@ declare const TableHead: react.ForwardRefExoticComponent<TdHTMLAttributes<HTMLTa
197
197
  declare const TableCell: react.ForwardRefExoticComponent<TdHTMLAttributes<HTMLTableCellElement> & react.RefAttributes<HTMLTableCellElement>>;
198
198
  declare const TableCaption: react.ForwardRefExoticComponent<HTMLAttributes<HTMLTableCaptionElement> & react.RefAttributes<HTMLTableCaptionElement>>;
199
199
 
200
- export { Button, IconRoundedButton, SelectionButton, Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableRow, Text };
200
+ interface DropdownMenuProps {
201
+ children: ReactNode;
202
+ open?: boolean;
203
+ onOpenChange?: (open: boolean) => void;
204
+ }
205
+ declare const DropdownMenu: ({ children, open, onOpenChange }: DropdownMenuProps) => react_jsx_runtime.JSX.Element;
206
+ declare const DropdownMenuTrigger: react.ForwardRefExoticComponent<ButtonHTMLAttributes<HTMLButtonElement> & react.RefAttributes<HTMLButtonElement>>;
207
+ declare const MenuLabel: react.ForwardRefExoticComponent<HTMLAttributes<HTMLFieldSetElement> & {
208
+ inset?: boolean;
209
+ } & react.RefAttributes<HTMLFieldSetElement>>;
210
+ declare const MenuContent: react.ForwardRefExoticComponent<HTMLAttributes<HTMLDivElement> & {
211
+ align?: "start" | "center" | "end";
212
+ side?: "top" | "right" | "bottom" | "left";
213
+ sideOffset?: number;
214
+ } & react.RefAttributes<HTMLDivElement>>;
215
+ declare const MenuItem: react.ForwardRefExoticComponent<HTMLAttributes<HTMLDivElement> & {
216
+ inset?: boolean;
217
+ size?: "small" | "medium";
218
+ iconLeft?: ReactNode;
219
+ iconRight?: ReactNode;
220
+ disabled?: boolean;
221
+ } & react.RefAttributes<HTMLDivElement>>;
222
+ declare const MenuSeparator: react.ForwardRefExoticComponent<HTMLAttributes<HTMLDivElement> & react.RefAttributes<HTMLDivElement>>;
223
+
224
+ /**
225
+ * NavButton component for Analytica Ensino platforms
226
+ *
227
+ * Um botão de navegação com ícone e texto para navegação principal.
228
+ * Ideal para menus de navegação, sidebar, tabs de navegação, etc.
229
+ * Compatível com Next.js 15 e React 19.
230
+ * Suporta forwardRef para acesso programático ao elemento DOM.
231
+ *
232
+ * @param icon - O ícone a ser exibido no botão
233
+ * @param label - O texto/label a ser exibido
234
+ * @param selected - Estado de seleção do botão
235
+ * @param className - Classes CSS adicionais
236
+ * @param props - Todos os outros atributos HTML padrão de button
237
+ * @returns Um elemento button estilizado para navegação
238
+ *
239
+ * @example
240
+ * ```tsx
241
+ * <NavButton
242
+ * icon={<HomeIcon />}
243
+ * label="Início"
244
+ * selected={false}
245
+ * onClick={() => navigate('/')}
246
+ * />
247
+ * ```
248
+ *
249
+ * @example
250
+ * ```tsx
251
+ * // Usando ref para foco programático
252
+ * const buttonRef = useRef<HTMLButtonElement>(null);
253
+ *
254
+ * const handleFocus = () => {
255
+ * buttonRef.current?.focus();
256
+ * };
257
+ *
258
+ * <NavButton
259
+ * ref={buttonRef}
260
+ * icon={<HomeIcon />}
261
+ * label="Dashboard"
262
+ * selected={isActive}
263
+ * onClick={() => setActiveTab('dashboard')}
264
+ * />
265
+ * ```
266
+ */
267
+ declare const NavButton: react.ForwardRefExoticComponent<{
268
+ /** Ícone a ser exibido no botão */
269
+ icon: ReactNode;
270
+ /** Texto/label a ser exibido ao lado do ícone */
271
+ label: string;
272
+ /** Estado de seleção do botão */
273
+ selected?: boolean;
274
+ /** Additional CSS classes to apply */
275
+ className?: string;
276
+ } & ButtonHTMLAttributes<HTMLButtonElement> & react.RefAttributes<HTMLButtonElement>>;
277
+
278
+ export { Button, DropdownMenu, DropdownMenuTrigger, IconRoundedButton, MenuContent, MenuItem, MenuLabel, MenuSeparator, NavButton, SelectionButton, Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableRow, Text };
package/dist/index.js CHANGED
@@ -21,7 +21,14 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
23
  Button: () => Button,
24
+ DropdownMenu: () => DropdownMenu,
25
+ DropdownMenuTrigger: () => DropdownMenuTrigger,
24
26
  IconRoundedButton: () => IconRoundedButton,
27
+ MenuContent: () => MenuContent,
28
+ MenuItem: () => MenuItem,
29
+ MenuLabel: () => MenuLabel,
30
+ MenuSeparator: () => MenuSeparator,
31
+ NavButton: () => NavButton,
25
32
  SelectionButton: () => SelectionButton,
26
33
  Table: () => Table,
27
34
  TableBody: () => TableBody,
@@ -340,10 +347,266 @@ var TableCaption = (0, import_react2.forwardRef)(({ className, ...props }, ref)
340
347
  }
341
348
  ));
342
349
  TableCaption.displayName = "TableCaption";
350
+
351
+ // src/components/DropdownMenu/DropdownMenu.tsx
352
+ var import_react3 = require("react");
353
+ var import_jsx_runtime6 = require("react/jsx-runtime");
354
+ var DropdownMenuContext = (0, import_react3.createContext)(
355
+ void 0
356
+ );
357
+ var DropdownMenu = ({ children, open, onOpenChange }) => {
358
+ const [internalOpen, setInternalOpen] = (0, import_react3.useState)(false);
359
+ const isControlled = open !== void 0;
360
+ const currentOpen = isControlled ? open : internalOpen;
361
+ const setOpen = (0, import_react3.useCallback)(
362
+ (newOpen) => {
363
+ if (onOpenChange) onOpenChange(newOpen);
364
+ if (!isControlled) setInternalOpen(newOpen);
365
+ },
366
+ [isControlled, onOpenChange]
367
+ );
368
+ const menuRef = (0, import_react3.useRef)(null);
369
+ const handleEscape = (event) => {
370
+ if (event.key === "Escape") {
371
+ setOpen(false);
372
+ }
373
+ };
374
+ const handleClickOutside = (event) => {
375
+ if (menuRef.current && !menuRef.current.contains(event.target)) {
376
+ setOpen(false);
377
+ }
378
+ };
379
+ (0, import_react3.useEffect)(() => {
380
+ if (currentOpen) {
381
+ document.addEventListener("mousedown", handleClickOutside);
382
+ document.addEventListener("keydown", handleEscape);
383
+ }
384
+ return () => {
385
+ document.removeEventListener("mousedown", handleClickOutside);
386
+ document.removeEventListener("keydown", handleEscape);
387
+ };
388
+ }, [currentOpen]);
389
+ const value = (0, import_react3.useMemo)(
390
+ () => ({ open: currentOpen, setOpen }),
391
+ [currentOpen, setOpen]
392
+ );
393
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(DropdownMenuContext.Provider, { value, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "relative", ref: menuRef, children }) });
394
+ };
395
+ var DropdownMenuTrigger = (0, import_react3.forwardRef)(({ className, children, onClick, ...props }, ref) => {
396
+ const context = (0, import_react3.useContext)(DropdownMenuContext);
397
+ if (!context)
398
+ throw new Error("DropdownMenuTrigger must be used within a DropdownMenu");
399
+ const { open, setOpen } = context;
400
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
401
+ "button",
402
+ {
403
+ ref,
404
+ className: `border border-border-200 cursor-pointer bg-background-muted hover:bg-background-200 transition-colors px-4 py-2 rounded-sm ${className}`,
405
+ onClick: (e) => {
406
+ e.stopPropagation();
407
+ setOpen(!open);
408
+ if (onClick) onClick(e);
409
+ },
410
+ "aria-expanded": open,
411
+ ...props,
412
+ children
413
+ }
414
+ );
415
+ });
416
+ DropdownMenuTrigger.displayName = "DropdownMenuTrigger";
417
+ var ITEM_SIZE_CLASSES = {
418
+ small: "text-sm",
419
+ medium: "text-md"
420
+ };
421
+ var SIDE_CLASSES = {
422
+ top: "bottom-full",
423
+ right: "top-full",
424
+ bottom: "top-full",
425
+ left: "top-full"
426
+ };
427
+ var ALIGN_CLASSES = {
428
+ start: "left-0",
429
+ center: "left-1/2 -translate-x-1/2",
430
+ end: "right-0"
431
+ };
432
+ var MenuLabel = (0, import_react3.forwardRef)(({ className, inset, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
433
+ "fieldset",
434
+ {
435
+ ref,
436
+ role: "group",
437
+ className: `text-sm w-full ${inset ? "pl-8" : ""} ${className ?? ""}`,
438
+ ...props
439
+ }
440
+ ));
441
+ MenuLabel.displayName = "MenuLabel";
442
+ var MenuContent = (0, import_react3.forwardRef)(
443
+ ({
444
+ className,
445
+ align = "start",
446
+ side = "bottom",
447
+ sideOffset = 4,
448
+ children,
449
+ ...props
450
+ }, ref) => {
451
+ const { open } = (0, import_react3.useContext)(DropdownMenuContext);
452
+ const [isVisible, setIsVisible] = (0, import_react3.useState)(open);
453
+ (0, import_react3.useEffect)(() => {
454
+ if (open) {
455
+ setIsVisible(true);
456
+ } else {
457
+ const timer = setTimeout(() => setIsVisible(false), 200);
458
+ return () => clearTimeout(timer);
459
+ }
460
+ }, [open]);
461
+ if (!isVisible) return null;
462
+ const getPositionClasses = () => {
463
+ const vertical = SIDE_CLASSES[side];
464
+ const horizontal = ALIGN_CLASSES[align];
465
+ return `absolute ${vertical} ${horizontal}`;
466
+ };
467
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
468
+ "div",
469
+ {
470
+ ref,
471
+ role: "menu",
472
+ className: `
473
+ bg-background z-50 min-w-[210px] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md border-border-100
474
+ ${open ? "animate-in fade-in-0 zoom-in-95" : "animate-out fade-out-0 zoom-out-95"}
475
+ ${getPositionClasses()}
476
+ ${className}
477
+ `,
478
+ style: {
479
+ marginTop: side === "bottom" ? sideOffset : void 0,
480
+ marginBottom: side === "top" ? sideOffset : void 0,
481
+ marginLeft: side === "right" ? sideOffset : void 0,
482
+ marginRight: side === "left" ? sideOffset : void 0
483
+ },
484
+ ...props,
485
+ children
486
+ }
487
+ );
488
+ }
489
+ );
490
+ MenuContent.displayName = "MenuContent";
491
+ var MenuItem = (0, import_react3.forwardRef)(
492
+ ({
493
+ className,
494
+ inset,
495
+ size = "small",
496
+ children,
497
+ iconRight,
498
+ iconLeft,
499
+ disabled = false,
500
+ onClick,
501
+ ...props
502
+ }, ref) => {
503
+ const sizeClasses = ITEM_SIZE_CLASSES[size];
504
+ const handleClick = (e) => {
505
+ if (disabled) {
506
+ e.preventDefault();
507
+ e.stopPropagation();
508
+ return;
509
+ }
510
+ onClick?.(e);
511
+ };
512
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
513
+ "div",
514
+ {
515
+ ref,
516
+ role: "menuitem",
517
+ "aria-disabled": disabled,
518
+ className: `
519
+ relative flex select-none items-center gap-2 rounded-sm p-3 text-sm outline-none transition-colors [&>svg]:size-4 [&>svg]:shrink-0
520
+ ${inset && "pl-8"}
521
+ ${sizeClasses}
522
+ ${className}
523
+ ${disabled ? "cursor-not-allowed text-text-400" : "cursor-pointer hover:bg-background-50 text-text-700 focus:bg-accent focus:text-accent-foreground hover:bg-accent hover:text-accent-foreground"}
524
+ `,
525
+ onClick: handleClick,
526
+ onKeyDown: (e) => {
527
+ if (e.key === "Enter" || e.key === " ") handleClick(e);
528
+ },
529
+ tabIndex: disabled ? -1 : 0,
530
+ ...props,
531
+ children: [
532
+ iconLeft,
533
+ children,
534
+ iconRight
535
+ ]
536
+ }
537
+ );
538
+ }
539
+ );
540
+ MenuItem.displayName = "MenuItem";
541
+ var MenuSeparator = (0, import_react3.forwardRef)(({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
542
+ "div",
543
+ {
544
+ ref,
545
+ className: `my-1 h-px bg-border-200 ${className}`,
546
+ ...props
547
+ }
548
+ ));
549
+ MenuSeparator.displayName = "MenuSeparator";
550
+
551
+ // src/components/NavButton/NavButton.tsx
552
+ var import_react4 = require("react");
553
+ var import_jsx_runtime7 = require("react/jsx-runtime");
554
+ var NavButton = (0, import_react4.forwardRef)(
555
+ ({ icon, label, selected = false, className = "", disabled, ...props }, ref) => {
556
+ const baseClasses = [
557
+ "flex",
558
+ "flex-col",
559
+ "items-center",
560
+ "justify-center",
561
+ "gap-0.5",
562
+ "px-12",
563
+ "py-1",
564
+ "rounded-sm",
565
+ "cursor-pointer",
566
+ "text-text-950",
567
+ "text-xs",
568
+ "font-medium",
569
+ "hover:text-text",
570
+ "hover:bg-primary-600",
571
+ "focus-visible:outline-none",
572
+ "focus-visible:ring-2",
573
+ "focus-visible:ring-offset-0",
574
+ "focus-visible:ring-indicator-info",
575
+ "disabled:opacity-50",
576
+ "disabled:cursor-not-allowed",
577
+ "disabled:pointer-events-none"
578
+ ];
579
+ const stateClasses = selected ? ["bg-primary-50", "text-primary-950"] : [];
580
+ const allClasses = [...baseClasses, ...stateClasses].join(" ");
581
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
582
+ "button",
583
+ {
584
+ ref,
585
+ type: "button",
586
+ className: `${allClasses} ${className}`,
587
+ disabled,
588
+ "aria-pressed": selected,
589
+ ...props,
590
+ children: [
591
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "flex items-center justify-center w-5 h-5", children: icon }),
592
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "whitespace-nowrap", children: label })
593
+ ]
594
+ }
595
+ );
596
+ }
597
+ );
598
+ NavButton.displayName = "NavButton";
343
599
  // Annotate the CommonJS export names for ESM import in node:
344
600
  0 && (module.exports = {
345
601
  Button,
602
+ DropdownMenu,
603
+ DropdownMenuTrigger,
346
604
  IconRoundedButton,
605
+ MenuContent,
606
+ MenuItem,
607
+ MenuLabel,
608
+ MenuSeparator,
609
+ NavButton,
347
610
  SelectionButton,
348
611
  Table,
349
612
  TableBody,
package/dist/index.mjs CHANGED
@@ -303,9 +303,274 @@ var TableCaption = forwardRef2(({ className, ...props }, ref) => /* @__PURE__ */
303
303
  }
304
304
  ));
305
305
  TableCaption.displayName = "TableCaption";
306
+
307
+ // src/components/DropdownMenu/DropdownMenu.tsx
308
+ import {
309
+ createContext,
310
+ useState,
311
+ useCallback,
312
+ useContext,
313
+ forwardRef as forwardRef3,
314
+ useEffect,
315
+ useRef,
316
+ useMemo
317
+ } from "react";
318
+ import { jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
319
+ var DropdownMenuContext = createContext(
320
+ void 0
321
+ );
322
+ var DropdownMenu = ({ children, open, onOpenChange }) => {
323
+ const [internalOpen, setInternalOpen] = useState(false);
324
+ const isControlled = open !== void 0;
325
+ const currentOpen = isControlled ? open : internalOpen;
326
+ const setOpen = useCallback(
327
+ (newOpen) => {
328
+ if (onOpenChange) onOpenChange(newOpen);
329
+ if (!isControlled) setInternalOpen(newOpen);
330
+ },
331
+ [isControlled, onOpenChange]
332
+ );
333
+ const menuRef = useRef(null);
334
+ const handleEscape = (event) => {
335
+ if (event.key === "Escape") {
336
+ setOpen(false);
337
+ }
338
+ };
339
+ const handleClickOutside = (event) => {
340
+ if (menuRef.current && !menuRef.current.contains(event.target)) {
341
+ setOpen(false);
342
+ }
343
+ };
344
+ useEffect(() => {
345
+ if (currentOpen) {
346
+ document.addEventListener("mousedown", handleClickOutside);
347
+ document.addEventListener("keydown", handleEscape);
348
+ }
349
+ return () => {
350
+ document.removeEventListener("mousedown", handleClickOutside);
351
+ document.removeEventListener("keydown", handleEscape);
352
+ };
353
+ }, [currentOpen]);
354
+ const value = useMemo(
355
+ () => ({ open: currentOpen, setOpen }),
356
+ [currentOpen, setOpen]
357
+ );
358
+ return /* @__PURE__ */ jsx6(DropdownMenuContext.Provider, { value, children: /* @__PURE__ */ jsx6("div", { className: "relative", ref: menuRef, children }) });
359
+ };
360
+ var DropdownMenuTrigger = forwardRef3(({ className, children, onClick, ...props }, ref) => {
361
+ const context = useContext(DropdownMenuContext);
362
+ if (!context)
363
+ throw new Error("DropdownMenuTrigger must be used within a DropdownMenu");
364
+ const { open, setOpen } = context;
365
+ return /* @__PURE__ */ jsx6(
366
+ "button",
367
+ {
368
+ ref,
369
+ className: `border border-border-200 cursor-pointer bg-background-muted hover:bg-background-200 transition-colors px-4 py-2 rounded-sm ${className}`,
370
+ onClick: (e) => {
371
+ e.stopPropagation();
372
+ setOpen(!open);
373
+ if (onClick) onClick(e);
374
+ },
375
+ "aria-expanded": open,
376
+ ...props,
377
+ children
378
+ }
379
+ );
380
+ });
381
+ DropdownMenuTrigger.displayName = "DropdownMenuTrigger";
382
+ var ITEM_SIZE_CLASSES = {
383
+ small: "text-sm",
384
+ medium: "text-md"
385
+ };
386
+ var SIDE_CLASSES = {
387
+ top: "bottom-full",
388
+ right: "top-full",
389
+ bottom: "top-full",
390
+ left: "top-full"
391
+ };
392
+ var ALIGN_CLASSES = {
393
+ start: "left-0",
394
+ center: "left-1/2 -translate-x-1/2",
395
+ end: "right-0"
396
+ };
397
+ var MenuLabel = forwardRef3(({ className, inset, ...props }, ref) => /* @__PURE__ */ jsx6(
398
+ "fieldset",
399
+ {
400
+ ref,
401
+ role: "group",
402
+ className: `text-sm w-full ${inset ? "pl-8" : ""} ${className ?? ""}`,
403
+ ...props
404
+ }
405
+ ));
406
+ MenuLabel.displayName = "MenuLabel";
407
+ var MenuContent = forwardRef3(
408
+ ({
409
+ className,
410
+ align = "start",
411
+ side = "bottom",
412
+ sideOffset = 4,
413
+ children,
414
+ ...props
415
+ }, ref) => {
416
+ const { open } = useContext(DropdownMenuContext);
417
+ const [isVisible, setIsVisible] = useState(open);
418
+ useEffect(() => {
419
+ if (open) {
420
+ setIsVisible(true);
421
+ } else {
422
+ const timer = setTimeout(() => setIsVisible(false), 200);
423
+ return () => clearTimeout(timer);
424
+ }
425
+ }, [open]);
426
+ if (!isVisible) return null;
427
+ const getPositionClasses = () => {
428
+ const vertical = SIDE_CLASSES[side];
429
+ const horizontal = ALIGN_CLASSES[align];
430
+ return `absolute ${vertical} ${horizontal}`;
431
+ };
432
+ return /* @__PURE__ */ jsx6(
433
+ "div",
434
+ {
435
+ ref,
436
+ role: "menu",
437
+ className: `
438
+ bg-background z-50 min-w-[210px] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md border-border-100
439
+ ${open ? "animate-in fade-in-0 zoom-in-95" : "animate-out fade-out-0 zoom-out-95"}
440
+ ${getPositionClasses()}
441
+ ${className}
442
+ `,
443
+ style: {
444
+ marginTop: side === "bottom" ? sideOffset : void 0,
445
+ marginBottom: side === "top" ? sideOffset : void 0,
446
+ marginLeft: side === "right" ? sideOffset : void 0,
447
+ marginRight: side === "left" ? sideOffset : void 0
448
+ },
449
+ ...props,
450
+ children
451
+ }
452
+ );
453
+ }
454
+ );
455
+ MenuContent.displayName = "MenuContent";
456
+ var MenuItem = forwardRef3(
457
+ ({
458
+ className,
459
+ inset,
460
+ size = "small",
461
+ children,
462
+ iconRight,
463
+ iconLeft,
464
+ disabled = false,
465
+ onClick,
466
+ ...props
467
+ }, ref) => {
468
+ const sizeClasses = ITEM_SIZE_CLASSES[size];
469
+ const handleClick = (e) => {
470
+ if (disabled) {
471
+ e.preventDefault();
472
+ e.stopPropagation();
473
+ return;
474
+ }
475
+ onClick?.(e);
476
+ };
477
+ return /* @__PURE__ */ jsxs4(
478
+ "div",
479
+ {
480
+ ref,
481
+ role: "menuitem",
482
+ "aria-disabled": disabled,
483
+ className: `
484
+ relative flex select-none items-center gap-2 rounded-sm p-3 text-sm outline-none transition-colors [&>svg]:size-4 [&>svg]:shrink-0
485
+ ${inset && "pl-8"}
486
+ ${sizeClasses}
487
+ ${className}
488
+ ${disabled ? "cursor-not-allowed text-text-400" : "cursor-pointer hover:bg-background-50 text-text-700 focus:bg-accent focus:text-accent-foreground hover:bg-accent hover:text-accent-foreground"}
489
+ `,
490
+ onClick: handleClick,
491
+ onKeyDown: (e) => {
492
+ if (e.key === "Enter" || e.key === " ") handleClick(e);
493
+ },
494
+ tabIndex: disabled ? -1 : 0,
495
+ ...props,
496
+ children: [
497
+ iconLeft,
498
+ children,
499
+ iconRight
500
+ ]
501
+ }
502
+ );
503
+ }
504
+ );
505
+ MenuItem.displayName = "MenuItem";
506
+ var MenuSeparator = forwardRef3(({ className, ...props }, ref) => /* @__PURE__ */ jsx6(
507
+ "div",
508
+ {
509
+ ref,
510
+ className: `my-1 h-px bg-border-200 ${className}`,
511
+ ...props
512
+ }
513
+ ));
514
+ MenuSeparator.displayName = "MenuSeparator";
515
+
516
+ // src/components/NavButton/NavButton.tsx
517
+ import { forwardRef as forwardRef4 } from "react";
518
+ import { jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
519
+ var NavButton = forwardRef4(
520
+ ({ icon, label, selected = false, className = "", disabled, ...props }, ref) => {
521
+ const baseClasses = [
522
+ "flex",
523
+ "flex-col",
524
+ "items-center",
525
+ "justify-center",
526
+ "gap-0.5",
527
+ "px-12",
528
+ "py-1",
529
+ "rounded-sm",
530
+ "cursor-pointer",
531
+ "text-text-950",
532
+ "text-xs",
533
+ "font-medium",
534
+ "hover:text-text",
535
+ "hover:bg-primary-600",
536
+ "focus-visible:outline-none",
537
+ "focus-visible:ring-2",
538
+ "focus-visible:ring-offset-0",
539
+ "focus-visible:ring-indicator-info",
540
+ "disabled:opacity-50",
541
+ "disabled:cursor-not-allowed",
542
+ "disabled:pointer-events-none"
543
+ ];
544
+ const stateClasses = selected ? ["bg-primary-50", "text-primary-950"] : [];
545
+ const allClasses = [...baseClasses, ...stateClasses].join(" ");
546
+ return /* @__PURE__ */ jsxs5(
547
+ "button",
548
+ {
549
+ ref,
550
+ type: "button",
551
+ className: `${allClasses} ${className}`,
552
+ disabled,
553
+ "aria-pressed": selected,
554
+ ...props,
555
+ children: [
556
+ /* @__PURE__ */ jsx7("span", { className: "flex items-center justify-center w-5 h-5", children: icon }),
557
+ /* @__PURE__ */ jsx7("span", { className: "whitespace-nowrap", children: label })
558
+ ]
559
+ }
560
+ );
561
+ }
562
+ );
563
+ NavButton.displayName = "NavButton";
306
564
  export {
307
565
  Button,
566
+ DropdownMenu,
567
+ DropdownMenuTrigger,
308
568
  IconRoundedButton,
569
+ MenuContent,
570
+ MenuItem,
571
+ MenuLabel,
572
+ MenuSeparator,
573
+ NavButton,
309
574
  SelectionButton,
310
575
  Table,
311
576
  TableBody,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "analytica-frontend-lib",
3
- "version": "1.0.12",
3
+ "version": "1.0.14",
4
4
  "description": "Repositório público dos componentes utilizados nas plataformas da Analytica Ensino",
5
5
  "main": "dist/index.cjs",
6
6
  "module": "./dist/index.js",