@fayz-ai/ui 0.10.1 → 0.11.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.
Files changed (77) hide show
  1. package/dist/analytics/AnalyticsCard.d.ts.map +1 -1
  2. package/dist/analytics/AnalyticsControls.d.ts.map +1 -1
  3. package/dist/analytics/batch.d.ts +19 -0
  4. package/dist/analytics/batch.d.ts.map +1 -0
  5. package/dist/analytics/context.d.ts.map +1 -1
  6. package/dist/analytics/index.d.ts +2 -0
  7. package/dist/analytics/index.d.ts.map +1 -1
  8. package/dist/{chunk-I6CPKAOD.js → chunk-7BMXN7MP.js} +215 -99
  9. package/dist/chunk-7BMXN7MP.js.map +1 -0
  10. package/dist/{chunk-6V5MGYXH.js → chunk-JGJNRNQW.js} +142 -31
  11. package/dist/chunk-JGJNRNQW.js.map +1 -0
  12. package/dist/{chunk-H63TOSQ7.js → chunk-QSHA2Q27.js} +475 -134
  13. package/dist/chunk-QSHA2Q27.js.map +1 -0
  14. package/dist/{chunk-SEIY2F5F.js → chunk-XRDCYOND.js} +231 -41
  15. package/dist/chunk-XRDCYOND.js.map +1 -0
  16. package/dist/chunk-ZBQ772DU.js +17 -0
  17. package/dist/chunk-ZBQ772DU.js.map +1 -0
  18. package/dist/dashboard/ChartWidget.d.ts.map +1 -1
  19. package/dist/dashboard/KpiCard.d.ts.map +1 -1
  20. package/dist/dashboard/SidebarOnboarding.d.ts +16 -11
  21. package/dist/dashboard/SidebarOnboarding.d.ts.map +1 -1
  22. package/dist/dashboard/Sparkline.d.ts +13 -0
  23. package/dist/dashboard/Sparkline.d.ts.map +1 -0
  24. package/dist/dashboard/index.d.ts +1 -0
  25. package/dist/dashboard/index.d.ts.map +1 -1
  26. package/dist/dashboard/index.js +3 -2
  27. package/dist/index.d.ts +2 -2
  28. package/dist/index.d.ts.map +1 -1
  29. package/dist/index.js +234 -115
  30. package/dist/index.js.map +1 -1
  31. package/dist/layout/AppShell.d.ts +4 -1
  32. package/dist/layout/AppShell.d.ts.map +1 -1
  33. package/dist/layout/AppShellSkeleton.d.ts +23 -0
  34. package/dist/layout/AppShellSkeleton.d.ts.map +1 -0
  35. package/dist/layout/ModulePage.d.ts +5 -1
  36. package/dist/layout/ModulePage.d.ts.map +1 -1
  37. package/dist/layout/Sidebar.d.ts +19 -1
  38. package/dist/layout/Sidebar.d.ts.map +1 -1
  39. package/dist/layout/Topbar.d.ts +4 -1
  40. package/dist/layout/Topbar.d.ts.map +1 -1
  41. package/dist/layout/index.d.ts +2 -0
  42. package/dist/layout/index.d.ts.map +1 -1
  43. package/dist/layout/index.js +2 -1
  44. package/dist/layout/page-trail.d.ts +23 -0
  45. package/dist/layout/page-trail.d.ts.map +1 -0
  46. package/dist/layout/rail-flyout.d.ts +39 -0
  47. package/dist/layout/rail-flyout.d.ts.map +1 -0
  48. package/dist/primitives/bulk-action-bar.d.ts +30 -0
  49. package/dist/primitives/bulk-action-bar.d.ts.map +1 -0
  50. package/dist/primitives/checkbox.d.ts +5 -0
  51. package/dist/primitives/checkbox.d.ts.map +1 -1
  52. package/dist/primitives/data-table.d.ts +11 -1
  53. package/dist/primitives/data-table.d.ts.map +1 -1
  54. package/dist/primitives/dropdown.d.ts.map +1 -1
  55. package/dist/primitives/index.d.ts +4 -0
  56. package/dist/primitives/index.d.ts.map +1 -1
  57. package/dist/primitives/index.js +2 -2
  58. package/dist/primitives/list-chrome.d.ts +20 -0
  59. package/dist/primitives/list-chrome.d.ts.map +1 -0
  60. package/dist/primitives/modal.d.ts.map +1 -1
  61. package/dist/primitives/phone-field.d.ts +19 -0
  62. package/dist/primitives/phone-field.d.ts.map +1 -0
  63. package/dist/primitives/search-select.d.ts +6 -1
  64. package/dist/primitives/search-select.d.ts.map +1 -1
  65. package/dist/primitives/thumbnail.d.ts +19 -0
  66. package/dist/primitives/thumbnail.d.ts.map +1 -0
  67. package/dist/styles.css +111 -1
  68. package/dist/tailwind.cjs +7 -2
  69. package/dist/tailwind.cjs.map +1 -1
  70. package/dist/tailwind.d.ts.map +1 -1
  71. package/dist/tailwind.js +7 -2
  72. package/dist/tailwind.js.map +1 -1
  73. package/package.json +2 -2
  74. package/dist/chunk-6V5MGYXH.js.map +0 -1
  75. package/dist/chunk-H63TOSQ7.js.map +0 -1
  76. package/dist/chunk-I6CPKAOD.js.map +0 -1
  77. package/dist/chunk-SEIY2F5F.js.map +0 -1
@@ -1,20 +1,20 @@
1
1
  import { useSaveBarVisible, useCrossModuleBack, useBackHandler, HeaderTabs } from './chunk-VAKWUUVC.js';
2
- import { Card, DataTable, Dropdown, DropdownTrigger, DropdownContent, DropdownItem, SegmentedControl } from './chunk-6V5MGYXH.js';
2
+ import { Dropdown, DropdownTrigger, DropdownContent, DropdownItem, Card, DataTable, SegmentedControl } from './chunk-JGJNRNQW.js';
3
3
  import { cn, Button } from './chunk-OKZ6WQB3.js';
4
4
  import * as React13 from 'react';
5
5
  import { useState, useRef, useEffect, useMemo, useCallback } from 'react';
6
6
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
7
7
  import { cva } from 'class-variance-authority';
8
8
  import * as DialogPrimitive from '@radix-ui/react-dialog';
9
- import { X, Check, ChevronDown, ChevronUp, ArrowLeft, Clock, Plus, Search, RefreshCw, Download, ChevronLeft, ChevronRight, Calendar, Loader2, CheckCircle2, AlertTriangle, XCircle, Info } from 'lucide-react';
9
+ import { X, ChevronDown, ChevronUp, Check, ArrowLeft, Search, ChevronLeft, ChevronRight, MoreHorizontal, ImageIcon, Clock, Plus, RefreshCw, Download, Calendar, Loader2, CheckCircle2, AlertTriangle, XCircle, Info } from 'lucide-react';
10
10
  import * as SelectPrimitive from '@radix-ui/react-select';
11
11
  import { Toaster } from 'sonner';
12
12
  export { toast } from 'sonner';
13
13
  import * as TabsPrimitive from '@radix-ui/react-tabs';
14
14
  import * as SeparatorPrimitive from '@radix-ui/react-separator';
15
15
  import * as TooltipPrimitive from '@radix-ui/react-tooltip';
16
+ import { useTranslation, getCountry, unmaskPhone, maskPhone, getCurrentLocale } from '@fayz-ai/core';
16
17
  import { createPortal } from 'react-dom';
17
- import { useTranslation, getCurrentLocale } from '@fayz-ai/core';
18
18
  import * as PopoverPrimitive from '@radix-ui/react-popover';
19
19
 
20
20
  var Input = React13.forwardRef(
@@ -143,7 +143,10 @@ var ModalContent = React13.forwardRef(({ className, children, size = "lg", hideC
143
143
  onInteractOutside?.(e);
144
144
  },
145
145
  className: cn(
146
- "fayz-glass-surface fayz-modal-content saas-mdl-ct relative z-50 flex flex-col w-full outline-none",
146
+ // fz-container: a form inside a dialog measures the DIALOG. Without
147
+ // it a two-column field grid in an `sm` modal read the window's width
148
+ // and kept both columns in 380px.
149
+ "fayz-glass-surface fayz-modal-content saas-mdl-ct fz-container relative z-50 flex flex-col w-full outline-none",
147
150
  // Mobile fullscreen
148
151
  "h-full rounded-none border-0",
149
152
  // Desktop centered card
@@ -176,38 +179,6 @@ var ModalTitle = React13.forwardRef(({ className, ...props }, ref) => /* @__PURE
176
179
  ModalTitle.displayName = "ModalTitle";
177
180
  var ModalDescription = React13.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(DialogPrimitive.Description, { ref, className: cn("text-sm text-muted-foreground", className), ...props }));
178
181
  ModalDescription.displayName = "ModalDescription";
179
- var COLOR_CLASSES = {
180
- primary: "border-primary bg-primary text-primary-foreground",
181
- success: "border-success bg-success text-white dark:border-success dark:bg-success",
182
- warning: "border-warning bg-warning text-white",
183
- destructive: "border-destructive bg-destructive text-destructive-foreground"
184
- };
185
- var Checkbox = React13.forwardRef(
186
- ({ checked = false, onChange, disabled, className, color = "primary", id, ...props }, ref) => {
187
- return /* @__PURE__ */ jsx(
188
- "button",
189
- {
190
- ref,
191
- id,
192
- type: "button",
193
- role: "checkbox",
194
- "aria-checked": checked,
195
- disabled,
196
- onClick: () => onChange?.(!checked),
197
- className: cn(
198
- "peer inline-flex h-[18px] w-[18px] shrink-0 items-center justify-center rounded-[5px] border-2 transition-all duration-150",
199
- "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background",
200
- "disabled:cursor-not-allowed disabled:opacity-50",
201
- checked ? `${COLOR_CLASSES[color]} shadow-sm` : "border-muted-foreground/40 bg-background hover:border-muted-foreground/60",
202
- className
203
- ),
204
- ...props,
205
- children: checked && /* @__PURE__ */ jsx(Check, { className: "h-3.5 w-3.5", strokeWidth: 3 })
206
- }
207
- );
208
- }
209
- );
210
- Checkbox.displayName = "Checkbox";
211
182
  var Select = SelectPrimitive.Root;
212
183
  var SelectGroup = SelectPrimitive.Group;
213
184
  var SelectValue = SelectPrimitive.Value;
@@ -493,6 +464,222 @@ var TooltipContent = React13.forwardRef(({ className, sideOffset = 4, ...props }
493
464
  }
494
465
  ));
495
466
  TooltipContent.displayName = TooltipPrimitive.Content.displayName;
467
+ function SearchField({ value, onChange, placeholder, ...rest }) {
468
+ return /* @__PURE__ */ jsxs("div", { className: "relative", children: [
469
+ /* @__PURE__ */ jsx(Search, { className: "pointer-events-none absolute left-2.5 top-1/2 h-3.5 w-3.5 -translate-y-1/2 text-muted-foreground" }),
470
+ /* @__PURE__ */ jsx(
471
+ "input",
472
+ {
473
+ type: "text",
474
+ value,
475
+ onChange: (event) => onChange(event.target.value),
476
+ placeholder,
477
+ "data-testid": rest["data-testid"],
478
+ className: "w-full bg-transparent py-1.5 pl-8 pr-3 text-sm placeholder:text-muted-foreground focus:outline-none"
479
+ }
480
+ )
481
+ ] });
482
+ }
483
+ function ChevronPager({ page, pageSize, total, onPageChange, noun }) {
484
+ const pages = Math.max(1, Math.ceil(total / pageSize));
485
+ const from = total === 0 ? 0 : (page - 1) * pageSize + 1;
486
+ const to = Math.min(page * pageSize, total);
487
+ const button = "flex h-7 w-7 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-muted hover:text-foreground disabled:pointer-events-none disabled:opacity-40";
488
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center justify-between gap-2 border-t border-border px-3 py-2", children: [
489
+ /* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground", children: [
490
+ from.toLocaleString(),
491
+ "\u2013",
492
+ to.toLocaleString(),
493
+ " de ",
494
+ total.toLocaleString(),
495
+ noun ? ` ${noun}` : ""
496
+ ] }),
497
+ pages > 1 && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
498
+ /* @__PURE__ */ jsx(
499
+ "button",
500
+ {
501
+ type: "button",
502
+ "aria-label": "P\xE1gina anterior",
503
+ className: button,
504
+ disabled: page <= 1,
505
+ onClick: () => onPageChange(page - 1),
506
+ children: /* @__PURE__ */ jsx(ChevronLeft, { className: "h-4 w-4" })
507
+ }
508
+ ),
509
+ /* @__PURE__ */ jsxs("span", { className: "px-1 text-xs tabular-nums text-muted-foreground", children: [
510
+ page.toLocaleString(),
511
+ " / ",
512
+ pages.toLocaleString()
513
+ ] }),
514
+ /* @__PURE__ */ jsx(
515
+ "button",
516
+ {
517
+ type: "button",
518
+ "aria-label": "Pr\xF3xima p\xE1gina",
519
+ className: button,
520
+ disabled: page >= pages,
521
+ onClick: () => onPageChange(page + 1),
522
+ children: /* @__PURE__ */ jsx(ChevronRight, { className: "h-4 w-4" })
523
+ }
524
+ )
525
+ ] })
526
+ ] });
527
+ }
528
+ function BulkActionBar({
529
+ count,
530
+ total,
531
+ onSelectAll,
532
+ onClear,
533
+ actions = [],
534
+ className
535
+ }) {
536
+ const t = useTranslation();
537
+ if (count === 0) return null;
538
+ const inline = actions.filter((a) => !a.overflow);
539
+ const overflow = actions.filter((a) => a.overflow);
540
+ return /* @__PURE__ */ jsxs(
541
+ "div",
542
+ {
543
+ className: cn(
544
+ "flex flex-wrap items-center justify-between gap-2 rounded-lg border border-border bg-muted/30 px-3 py-2",
545
+ className
546
+ ),
547
+ role: "region",
548
+ "aria-label": t("crud.list.bulkActions"),
549
+ children: [
550
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-3 text-sm", children: [
551
+ /* @__PURE__ */ jsx("span", { className: "font-medium text-foreground", children: count === 1 ? t("crud.list.selectedOne") : t("crud.list.selectedMany", { count: count.toLocaleString() }) }),
552
+ onSelectAll && count < total && /* @__PURE__ */ jsx(
553
+ "button",
554
+ {
555
+ type: "button",
556
+ onClick: onSelectAll,
557
+ className: "text-muted-foreground underline-offset-2 transition-colors hover:text-foreground hover:underline",
558
+ children: t("crud.list.selectAllCount", { total: total.toLocaleString() })
559
+ }
560
+ ),
561
+ /* @__PURE__ */ jsx(
562
+ "button",
563
+ {
564
+ type: "button",
565
+ onClick: onClear,
566
+ className: "text-muted-foreground underline-offset-2 transition-colors hover:text-foreground hover:underline",
567
+ children: t("crud.list.deselectAll")
568
+ }
569
+ )
570
+ ] }),
571
+ (inline.length > 0 || overflow.length > 0) && /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
572
+ inline.map((action) => /* @__PURE__ */ jsxs(
573
+ Button,
574
+ {
575
+ variant: "outline",
576
+ size: "sm",
577
+ disabled: action.disabled,
578
+ onClick: () => void action.onSelect(),
579
+ className: action.destructive ? "text-destructive hover:text-destructive" : void 0,
580
+ children: [
581
+ action.icon && /* @__PURE__ */ jsx(action.icon, { className: "mr-1.5 h-3.5 w-3.5" }),
582
+ action.label
583
+ ]
584
+ },
585
+ action.id
586
+ )),
587
+ overflow.length > 0 && /* @__PURE__ */ jsxs(Dropdown, { children: [
588
+ /* @__PURE__ */ jsx(DropdownTrigger, { asChild: true, children: /* @__PURE__ */ jsx(Button, { variant: "outline", size: "sm", "aria-label": t("crud.list.moreActions"), children: /* @__PURE__ */ jsx(MoreHorizontal, { className: "h-3.5 w-3.5" }) }) }),
589
+ /* @__PURE__ */ jsx(DropdownContent, { align: "end", children: overflow.map((action) => /* @__PURE__ */ jsxs(
590
+ DropdownItem,
591
+ {
592
+ disabled: action.disabled,
593
+ onClick: () => void action.onSelect(),
594
+ className: action.destructive ? "text-destructive focus:text-destructive" : void 0,
595
+ children: [
596
+ action.icon && /* @__PURE__ */ jsx(action.icon, { className: "mr-2 h-3.5 w-3.5 shrink-0" }),
597
+ action.label
598
+ ]
599
+ },
600
+ action.id
601
+ )) })
602
+ ] })
603
+ ] })
604
+ ]
605
+ }
606
+ );
607
+ }
608
+ BulkActionBar.displayName = "BulkActionBar";
609
+ var SIZE_CLASSES = {
610
+ sm: "h-8 w-8 rounded",
611
+ md: "h-9 w-9 rounded",
612
+ lg: "h-12 w-12 rounded-md"
613
+ };
614
+ var ICON_CLASSES = {
615
+ sm: "h-3.5 w-3.5",
616
+ md: "h-4 w-4",
617
+ lg: "h-5 w-5"
618
+ };
619
+ function Thumbnail({ src, alt = "", size = "md", icon: Icon2 = ImageIcon, className }) {
620
+ const [failed, setFailed] = React13.useState(false);
621
+ React13.useEffect(() => {
622
+ setFailed(false);
623
+ }, [src]);
624
+ const base = cn("shrink-0 overflow-hidden", SIZE_CLASSES[size], className);
625
+ if (!src || failed) {
626
+ return /* @__PURE__ */ jsx(
627
+ "div",
628
+ {
629
+ className: cn(base, "flex items-center justify-center border border-border/60 bg-muted/50 text-muted-foreground/60"),
630
+ "aria-hidden": true,
631
+ children: /* @__PURE__ */ jsx(Icon2, { className: ICON_CLASSES[size], strokeWidth: 1.75 })
632
+ }
633
+ );
634
+ }
635
+ return /* @__PURE__ */ jsx(
636
+ "img",
637
+ {
638
+ src,
639
+ alt,
640
+ loading: "lazy",
641
+ onError: () => setFailed(true),
642
+ className: cn(base, "border border-border/60 object-cover")
643
+ }
644
+ );
645
+ }
646
+ Thumbnail.displayName = "Thumbnail";
647
+ function PhoneField({
648
+ value,
649
+ onChange,
650
+ country,
651
+ placeholder,
652
+ disabled,
653
+ autoFocus,
654
+ className,
655
+ ...rest
656
+ }) {
657
+ const mask = getCountry(country).mask;
658
+ return /* @__PURE__ */ jsx(
659
+ "input",
660
+ {
661
+ type: "tel",
662
+ inputMode: "tel",
663
+ autoComplete: "tel",
664
+ value: maskPhone(value, mask),
665
+ onChange: (event) => onChange(unmaskPhone(event.target.value)),
666
+ placeholder: placeholder ?? mask.replace(/#/gu, "0"),
667
+ disabled,
668
+ autoFocus,
669
+ "aria-label": rest["aria-label"],
670
+ "data-testid": rest["data-testid"],
671
+ className: cn(
672
+ "w-full rounded-md border bg-background px-2.5 py-1.5 text-sm outline-none focus:border-primary",
673
+ className
674
+ )
675
+ }
676
+ );
677
+ }
678
+ function formatPhone2(value, country) {
679
+ const digits = unmaskPhone(String(value ?? ""));
680
+ if (!digits) return "";
681
+ return maskPhone(digits, getCountry(country).mask) || digits;
682
+ }
496
683
  function StatTiles({ tiles, loading, className }) {
497
684
  if (tiles.length === 0) return null;
498
685
  return /* @__PURE__ */ jsx("div", { className: cn("grid gap-4 sm:grid-cols-2 lg:grid-cols-4", className), children: tiles.map((tile) => {
@@ -1034,6 +1221,8 @@ function SearchSelect({
1034
1221
  required,
1035
1222
  debounce = 250,
1036
1223
  minChars = 1,
1224
+ noResultsLabel,
1225
+ searchingLabel = "Searching...",
1037
1226
  allowCreate,
1038
1227
  createLabel = "Add",
1039
1228
  onCreate,
@@ -1180,9 +1369,10 @@ function SearchSelect({
1180
1369
  ),
1181
1370
  showDropdown && /* @__PURE__ */ jsx("div", { className: "fayz-glass-surface absolute top-full left-0 right-0 z-50 mt-1 rounded-lg border bg-popover shadow-lg overflow-hidden", children: loading ? /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-center gap-2 px-4 py-6 text-xs text-muted-foreground", children: [
1182
1371
  /* @__PURE__ */ jsx(Loader2, { className: "h-3.5 w-3.5 animate-spin" }),
1183
- " Searching..."
1372
+ " ",
1373
+ searchingLabel
1184
1374
  ] }) : results.length === 0 ? /* @__PURE__ */ jsxs("div", { className: "px-4 py-4 text-center", children: [
1185
- /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: query ? `No results for "${query}"` : "No results" }),
1375
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: noResultsLabel ?? (query ? `No results for "${query}"` : "No results") }),
1186
1376
  allowCreate && onCreate && /* @__PURE__ */ jsxs(
1187
1377
  "button",
1188
1378
  {
@@ -1816,6 +2006,6 @@ function SearchCombobox({
1816
2006
  ] });
1817
2007
  }
1818
2008
 
1819
- export { Badge, Breadcrumb, Checkbox, ConfirmDialog, CurrencyInput, DatePicker, FilterChip, Hint, Input, ListView, Markdown, MarkdownEditor, Modal, ModalBody, ModalClose, ModalContent, ModalDescription, ModalFooter, ModalHeader, ModalOverlay, ModalPortal, ModalTitle, ModalTrigger, PhoneLink, Popover, PopoverContent, PopoverTrigger, SearchCombobox, SearchSelect, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, Separator2 as Separator, StatTiles, Switch, Tabs, TabsContent, TabsList, TabsTrigger, TimePicker, ToastProvider, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, badgeVariants, normalizePhone, renderMarkdown };
1820
- //# sourceMappingURL=chunk-SEIY2F5F.js.map
1821
- //# sourceMappingURL=chunk-SEIY2F5F.js.map
2009
+ export { Badge, Breadcrumb, BulkActionBar, ChevronPager, ConfirmDialog, CurrencyInput, DatePicker, FilterChip, Hint, Input, ListView, Markdown, MarkdownEditor, Modal, ModalBody, ModalClose, ModalContent, ModalDescription, ModalFooter, ModalHeader, ModalOverlay, ModalPortal, ModalTitle, ModalTrigger, PhoneField, PhoneLink, Popover, PopoverContent, PopoverTrigger, SearchCombobox, SearchField, SearchSelect, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, Separator2 as Separator, StatTiles, Switch, Tabs, TabsContent, TabsList, TabsTrigger, Thumbnail, TimePicker, ToastProvider, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, badgeVariants, formatPhone2 as formatPhone, normalizePhone, renderMarkdown };
2010
+ //# sourceMappingURL=chunk-XRDCYOND.js.map
2011
+ //# sourceMappingURL=chunk-XRDCYOND.js.map