analytica-frontend-lib 1.0.13 → 1.0.15

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/README.md CHANGED
@@ -14,6 +14,27 @@ Instale a biblioteca em seu projeto com o comando:
14
14
  yarn add analytica-frontend-lib
15
15
  ```
16
16
 
17
+ ### Importe os componentes
18
+
19
+ Para usar os componentes, basta importá-los no seu projeto:
20
+
21
+ ```tsx
22
+ import { Text } from 'analytica-frontend-lib'
23
+
24
+ const MyComponent = () => {
25
+ return <Text>Olá mundo!</Text>
26
+ }
27
+ ```
28
+
29
+ ### Importando Estilos CSS
30
+
31
+ Para usar os estilos e variáveis CSS da lib, importe o arquivo de estilos:
32
+
33
+ ```tsx
34
+ // No seu arquivo layout.tsx (Next.js) ou main.tsx (Vite)
35
+ import 'analytica-frontend-lib/styles.css';
36
+ ```
37
+
17
38
  ---
18
39
 
19
40
  ## Etapas para criar um novo componente
@@ -33,6 +54,10 @@ yarn add analytica-frontend-lib
33
54
  - Os componentes devem ser totalmente compatíveis com Next.js versão 15+.
34
55
  - Testes unitários são obrigatórios, com coverage mínimo de 80%.
35
56
  - Siga o princípio da responsabilidade única (`single responsibility`): construa componentes compostos por componentes menores.
57
+ - No arquivo `package.json` da biblioteca temos:
58
+ - `peerDependencies`: Framework core (React, Next.js)
59
+ - `dependencies`: Bibliotecas específicas usadas pelos componentes
60
+ - `devDependencies`: Ferramentas de build, testes, linting
36
61
 
37
62
  ## Ladle
38
63
 
package/dist/index.css CHANGED
@@ -1551,4 +1551,4 @@
1551
1551
  }
1552
1552
  }
1553
1553
  }
1554
- /*! tailwindcss v4.1.8 | MIT License | https://tailwindcss.com */
1554
+ /*! tailwindcss v4.1.10 | MIT License | https://tailwindcss.com */
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.13",
3
+ "version": "1.0.15",
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",
@@ -12,7 +12,8 @@
12
12
  ".": {
13
13
  "import": "./dist/index.js",
14
14
  "require": "./dist/index.cjs"
15
- }
15
+ },
16
+ "./styles.css": "./dist/index.css"
16
17
  },
17
18
  "scripts": {
18
19
  "lint": "npx eslint \"{src,app}/**/*.{js,jsx,ts,tsx}\" --fix",
@@ -47,11 +48,6 @@
47
48
  "publishConfig": {
48
49
  "access": "public"
49
50
  },
50
- "peerDependencies": {
51
- "next": ">=15.0.0",
52
- "react": ">=19.0.0",
53
- "react-dom": ">=19.0.0"
54
- },
55
51
  "author": "Analytica Ensino LTDA",
56
52
  "license": "MIT",
57
53
  "repository": {
@@ -62,10 +58,13 @@
62
58
  "bugs": {
63
59
  "url": "https://github.com/analytica-ensino/analytica-frontend-lib/issues"
64
60
  },
61
+ "peerDependencies": {
62
+ "next": ">=15.0.0",
63
+ "react": ">=19.0.0",
64
+ "react-dom": ">=19.0.0"
65
+ },
65
66
  "dependencies": {
66
- "phosphor-react": "^1.4.1",
67
- "react": "^19.1.0",
68
- "react-dom": "^19.1.0"
67
+ "phosphor-react": "^1.4.1"
69
68
  },
70
69
  "devDependencies": {
71
70
  "@eslint/eslintrc": "^3.3.1",
@@ -95,6 +94,8 @@
95
94
  "polished": "^4.3.1",
96
95
  "postcss": "^8.5.4",
97
96
  "prettier": "^3.5.3",
97
+ "react": "^19.1.0",
98
+ "react-dom": "^19.1.0",
98
99
  "sonarqube-scanner": "^4.3.0",
99
100
  "tailwindcss": "^4.1.8",
100
101
  "ts-jest": "^29.3.4",