@andreagiugni/tailwind-dashboard-ui 1.0.5 → 1.0.6

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
@@ -199,8 +199,8 @@ Legend: **(+native)** = also extends the element's native HTML attributes.
199
199
  |---|---|
200
200
  | `Input` | `type`, `value` / `defaultValue`, `onChange`, `success`, `error`, `hint`, `disabled`, (+native input) (+override) |
201
201
  | `Label` | `htmlFor`, `children`, (+native label) |
202
- | `Select` | `options` (`{ value, label, icon? }[]`), `placeholder`, `defaultValue`, `onChange(value)`, `disabled` |
203
- | `MultiSelect` | `options`, `selected` / `defaultSelected`, `onChange`, `placeholder`, `disabled` |
202
+ | `Select` | `options` (`{ value, label, icon? }[]`), `placeholder`, `defaultValue`, `onChange(value)`, `disabled`, `portal?` (default `true`, prevents clipping inside tables/cards) |
203
+ | `MultiSelect` | `options`, `selected` / `defaultSelected`, `onChange`, `placeholder`, `disabled`, `portal?` (default `true`, prevents clipping inside tables/cards) |
204
204
  | `Checkbox` | `label`, `checked`, `onChange(checked)`, `disabled`, `checkedColor?` |
205
205
  | `Radio` / `RadioSm` | `id`, `name`, `value`, `checked`, `label`, `onChange(value)`, `disabled` |
206
206
  | `Switch` | `label?`, `checked` / `defaultChecked`, `onChange(checked)`, `color` (blue/gray), `disabled` |
package/dist/index.cjs CHANGED
@@ -2319,15 +2319,20 @@ var Select = ({
2319
2319
  onChange,
2320
2320
  className,
2321
2321
  defaultValue = "",
2322
- disabled = false
2322
+ disabled = false,
2323
+ portal = true
2323
2324
  }) => {
2324
2325
  const [selectedValue, setSelectedValue] = React5.useState(defaultValue);
2325
2326
  const [isOpen, setIsOpen] = React5.useState(false);
2326
2327
  const rootRef = React5.useRef(null);
2328
+ const triggerRef = React5.useRef(null);
2329
+ const menuRef = React5.useRef(null);
2330
+ const [menuPosition, setMenuPosition] = React5.useState();
2327
2331
  React5.useEffect(() => {
2328
2332
  if (!isOpen) return;
2329
2333
  const onPointerDown = (e) => {
2330
- if (rootRef.current && !rootRef.current.contains(e.target)) {
2334
+ const target = e.target;
2335
+ if (rootRef.current && !rootRef.current.contains(target) && !menuRef.current?.contains(target)) {
2331
2336
  setIsOpen(false);
2332
2337
  }
2333
2338
  };
@@ -2341,6 +2346,33 @@ var Select = ({
2341
2346
  document.removeEventListener("keydown", onKeyDown);
2342
2347
  };
2343
2348
  }, [isOpen]);
2349
+ React5.useLayoutEffect(() => {
2350
+ if (!isOpen || !portal) return;
2351
+ const updatePosition = () => {
2352
+ const trigger = triggerRef.current;
2353
+ const menu = menuRef.current;
2354
+ if (!trigger || !menu) return;
2355
+ const rect = trigger.getBoundingClientRect();
2356
+ const menuHeight = menu.offsetHeight;
2357
+ const viewportPadding = 8;
2358
+ const gap = 6;
2359
+ const width = rect.width;
2360
+ const fitsBelow = window.innerHeight - rect.bottom - gap >= menuHeight + viewportPadding;
2361
+ const top = fitsBelow ? rect.bottom + gap : Math.max(viewportPadding, rect.top - gap - menuHeight);
2362
+ const left = Math.min(
2363
+ Math.max(viewportPadding, rect.left),
2364
+ window.innerWidth - width - viewportPadding
2365
+ );
2366
+ setMenuPosition({ top, left, width });
2367
+ };
2368
+ updatePosition();
2369
+ window.addEventListener("resize", updatePosition);
2370
+ window.addEventListener("scroll", updatePosition, true);
2371
+ return () => {
2372
+ window.removeEventListener("resize", updatePosition);
2373
+ window.removeEventListener("scroll", updatePosition, true);
2374
+ };
2375
+ }, [isOpen, portal]);
2344
2376
  const toggleOpen = () => {
2345
2377
  if (!disabled) setIsOpen((prev) => !prev);
2346
2378
  };
@@ -2354,6 +2386,7 @@ var Select = ({
2354
2386
  /* @__PURE__ */ jsxRuntime.jsxs(
2355
2387
  "button",
2356
2388
  {
2389
+ ref: triggerRef,
2357
2390
  type: "button",
2358
2391
  onClick: toggleOpen,
2359
2392
  disabled,
@@ -2389,52 +2422,61 @@ var Select = ({
2389
2422
  ]
2390
2423
  }
2391
2424
  ),
2392
- isOpen && /* @__PURE__ */ jsxRuntime.jsx(
2393
- "ul",
2394
- {
2395
- role: "listbox",
2396
- className: chunkYERNSNT4_cjs.cn(
2397
- "absolute left-0 top-full mt-1.5 max-h-60 w-full overflow-y-auto rounded-lg border border-gray-200 bg-white py-1 shadow-theme-lg dark:border-gray-800 dark:bg-gray-900",
2398
- chunkTF3G3E72_cjs.layers.menu
2399
- ),
2400
- children: options.map((option) => {
2401
- const isSelected = option.value === selectedValue;
2402
- return /* @__PURE__ */ jsxRuntime.jsxs(
2403
- "li",
2404
- {
2405
- role: "option",
2406
- "aria-selected": isSelected,
2407
- onClick: () => handleSelect(option.value),
2408
- className: chunkYERNSNT4_cjs.cn(
2409
- "flex cursor-pointer items-center justify-between px-4 py-2 text-sm transition hover:bg-gray-50 dark:hover:bg-white/5",
2410
- isSelected ? "bg-brand-50 font-medium text-brand-600 dark:bg-brand-500/15 dark:text-brand-400" : "text-gray-700 dark:text-gray-300"
2411
- ),
2412
- children: [
2413
- /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex min-w-0 items-center gap-2", children: [
2414
- option.icon && /* @__PURE__ */ jsxRuntime.jsx(
2415
- "span",
2425
+ isOpen && (() => {
2426
+ const menu = /* @__PURE__ */ jsxRuntime.jsx(
2427
+ "ul",
2428
+ {
2429
+ ref: menuRef,
2430
+ role: "listbox",
2431
+ className: chunkYERNSNT4_cjs.cn(
2432
+ "max-h-60 overflow-y-auto rounded-lg border border-gray-200 bg-white py-1 shadow-theme-lg dark:border-gray-800 dark:bg-gray-900",
2433
+ portal ? chunkYERNSNT4_cjs.cn("fixed", chunkTF3G3E72_cjs.layers.portal) : chunkYERNSNT4_cjs.cn("absolute left-0 top-full mt-1.5 w-full", chunkTF3G3E72_cjs.layers.menu)
2434
+ ),
2435
+ style: {
2436
+ ...portal && !menuPosition ? { visibility: "hidden" } : void 0,
2437
+ ...portal ? menuPosition : void 0
2438
+ },
2439
+ children: options.map((option) => {
2440
+ const isSelected = option.value === selectedValue;
2441
+ return /* @__PURE__ */ jsxRuntime.jsxs(
2442
+ "li",
2443
+ {
2444
+ role: "option",
2445
+ "aria-selected": isSelected,
2446
+ onClick: () => handleSelect(option.value),
2447
+ className: chunkYERNSNT4_cjs.cn(
2448
+ "flex cursor-pointer items-center justify-between px-4 py-2 text-sm transition hover:bg-gray-50 dark:hover:bg-white/5",
2449
+ isSelected ? "bg-brand-50 font-medium text-brand-600 dark:bg-brand-500/15 dark:text-brand-400" : "text-gray-700 dark:text-gray-300"
2450
+ ),
2451
+ children: [
2452
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex min-w-0 items-center gap-2", children: [
2453
+ option.icon && /* @__PURE__ */ jsxRuntime.jsx(
2454
+ "span",
2455
+ {
2456
+ "aria-hidden": "true",
2457
+ className: "flex h-5 w-5 shrink-0 items-center justify-center [&>svg]:size-4",
2458
+ children: option.icon
2459
+ }
2460
+ ),
2461
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: option.label })
2462
+ ] }),
2463
+ isSelected && /* @__PURE__ */ jsxRuntime.jsx(
2464
+ lucideReact.Check,
2416
2465
  {
2417
- "aria-hidden": "true",
2418
- className: "flex h-5 w-5 shrink-0 items-center justify-center [&>svg]:size-4",
2419
- children: option.icon
2466
+ className: "ml-2 size-4 shrink-0 stroke-current",
2467
+ "aria-hidden": "true"
2420
2468
  }
2421
- ),
2422
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: option.label })
2423
- ] }),
2424
- isSelected && /* @__PURE__ */ jsxRuntime.jsx(
2425
- lucideReact.Check,
2426
- {
2427
- className: "ml-2 size-4 shrink-0 stroke-current",
2428
- "aria-hidden": "true"
2429
- }
2430
- )
2431
- ]
2432
- },
2433
- option.value
2434
- );
2435
- })
2436
- }
2437
- )
2469
+ )
2470
+ ]
2471
+ },
2472
+ option.value
2473
+ );
2474
+ })
2475
+ }
2476
+ );
2477
+ if (!portal || typeof document === "undefined") return menu;
2478
+ return reactDom.createPortal(menu, document.body);
2479
+ })()
2438
2480
  ] });
2439
2481
  };
2440
2482
  var MultiSelect = ({
@@ -2442,10 +2484,60 @@ var MultiSelect = ({
2442
2484
  options,
2443
2485
  defaultSelected = [],
2444
2486
  onChange,
2445
- disabled = false
2487
+ disabled = false,
2488
+ portal = true
2446
2489
  }) => {
2447
2490
  const [selectedOptions, setSelectedOptions] = React5.useState(defaultSelected);
2448
2491
  const [isOpen, setIsOpen] = React5.useState(false);
2492
+ const rootRef = React5.useRef(null);
2493
+ const triggerRef = React5.useRef(null);
2494
+ const menuRef = React5.useRef(null);
2495
+ const [menuPosition, setMenuPosition] = React5.useState();
2496
+ React5.useEffect(() => {
2497
+ if (!isOpen) return;
2498
+ const onPointerDown = (event) => {
2499
+ const target = event.target;
2500
+ if (rootRef.current && !rootRef.current.contains(target) && !menuRef.current?.contains(target)) {
2501
+ setIsOpen(false);
2502
+ }
2503
+ };
2504
+ const onKeyDown = (event) => {
2505
+ if (event.key === "Escape") setIsOpen(false);
2506
+ };
2507
+ document.addEventListener("mousedown", onPointerDown);
2508
+ document.addEventListener("keydown", onKeyDown);
2509
+ return () => {
2510
+ document.removeEventListener("mousedown", onPointerDown);
2511
+ document.removeEventListener("keydown", onKeyDown);
2512
+ };
2513
+ }, [isOpen]);
2514
+ React5.useLayoutEffect(() => {
2515
+ if (!isOpen || !portal) return;
2516
+ const updatePosition = () => {
2517
+ const trigger = triggerRef.current;
2518
+ const menu = menuRef.current;
2519
+ if (!trigger || !menu) return;
2520
+ const rect = trigger.getBoundingClientRect();
2521
+ const menuHeight = menu.offsetHeight;
2522
+ const viewportPadding = 8;
2523
+ const gap = 6;
2524
+ const width = rect.width;
2525
+ const fitsBelow = window.innerHeight - rect.bottom - gap >= menuHeight + viewportPadding;
2526
+ const top = fitsBelow ? rect.bottom + gap : Math.max(viewportPadding, rect.top - gap - menuHeight);
2527
+ const left = Math.min(
2528
+ Math.max(viewportPadding, rect.left),
2529
+ window.innerWidth - width - viewportPadding
2530
+ );
2531
+ setMenuPosition({ top, left, width });
2532
+ };
2533
+ updatePosition();
2534
+ window.addEventListener("resize", updatePosition);
2535
+ window.addEventListener("scroll", updatePosition, true);
2536
+ return () => {
2537
+ window.removeEventListener("resize", updatePosition);
2538
+ window.removeEventListener("scroll", updatePosition, true);
2539
+ };
2540
+ }, [isOpen, portal]);
2449
2541
  const toggleDropdown = () => {
2450
2542
  if (disabled) return;
2451
2543
  setIsOpen((prev) => !prev);
@@ -2463,94 +2555,110 @@ var MultiSelect = ({
2463
2555
  const selectedValuesText = selectedOptions.map(
2464
2556
  (value) => options.find((option) => option.value === value)?.text || ""
2465
2557
  );
2466
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "w-full", children: [
2558
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { ref: rootRef, className: "w-full", children: [
2467
2559
  /* @__PURE__ */ jsxRuntime.jsx("label", { className: "mb-1.5 block text-sm font-medium text-gray-700 dark:text-gray-400", children: label }),
2468
2560
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "relative inline-block w-full", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative flex flex-col items-center", children: [
2469
- /* @__PURE__ */ jsxRuntime.jsx("div", { onClick: toggleDropdown, className: "w-full", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mb-2 flex h-11 rounded-lg border border-gray-300 bg-white py-1.5 px-4 shadow-theme-xs outline-hidden transition focus:border-brand-300 focus:shadow-focus-ring dark:border-gray-700 dark:bg-gray-900 dark:focus:border-brand-300", children: [
2470
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-wrap flex-auto gap-2", children: selectedValuesText.length > 0 ? selectedValuesText.map((text, index) => /* @__PURE__ */ jsxRuntime.jsxs(
2471
- "div",
2472
- {
2473
- className: "group flex items-center justify-center rounded-full border-[0.7px] border-transparent bg-gray-100 py-1 pl-2.5 pr-2 text-sm text-gray-800 hover:border-gray-200 dark:bg-gray-800 dark:text-white/90 dark:hover:border-gray-800",
2474
- children: [
2475
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-initial max-w-full", children: text }),
2476
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-row-reverse flex-auto", children: /* @__PURE__ */ jsxRuntime.jsx(
2477
- "div",
2478
- {
2479
- onClick: () => removeOption(index, selectedOptions[index]),
2480
- className: "pl-2 text-gray-500 cursor-pointer group-hover:text-gray-400 dark:text-gray-400",
2481
- children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: "size-4", role: "button", "aria-hidden": "true" })
2482
- }
2483
- ) })
2484
- ]
2485
- },
2486
- index
2487
- )) : /* @__PURE__ */ jsxRuntime.jsx(
2488
- "input",
2489
- {
2490
- placeholder: "Select option",
2491
- className: "w-full h-full p-1 pr-2 text-sm bg-transparent border-0 outline-hidden appearance-none placeholder:text-gray-800 focus:border-0 focus:outline-hidden focus:ring-0 dark:placeholder:text-white/90",
2492
- readOnly: true,
2493
- value: "Select option"
2494
- }
2495
- ) }),
2496
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center py-1 pl-1 pr-1 w-7", children: /* @__PURE__ */ jsxRuntime.jsx(
2497
- "button",
2498
- {
2499
- type: "button",
2500
- onClick: toggleDropdown,
2501
- className: "w-5 h-5 text-gray-700 outline-hidden cursor-pointer focus:outline-hidden dark:text-gray-400",
2502
- children: /* @__PURE__ */ jsxRuntime.jsx(
2503
- lucideReact.ChevronDown,
2504
- {
2505
- className: `size-4 stroke-current ${isOpen ? "rotate-180" : ""}`,
2506
- "aria-hidden": "true"
2507
- }
2508
- )
2509
- }
2510
- ) })
2511
- ] }) }),
2512
- isOpen && /* @__PURE__ */ jsxRuntime.jsx(
2561
+ /* @__PURE__ */ jsxRuntime.jsx("div", { onClick: toggleDropdown, className: "w-full", children: /* @__PURE__ */ jsxRuntime.jsxs(
2513
2562
  "div",
2514
2563
  {
2515
- className: chunkYERNSNT4_cjs.cn(
2516
- "absolute left-0 top-full max-h-select w-full overflow-y-auto rounded-lg bg-white shadow-sm dark:bg-gray-900",
2517
- chunkTF3G3E72_cjs.layers.menu
2518
- ),
2519
- onClick: (e) => e.stopPropagation(),
2520
- children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-col", children: options.map((option, index) => {
2521
- const isSelected = selectedOptions.includes(option.value);
2522
- return /* @__PURE__ */ jsxRuntime.jsx("div", { children: /* @__PURE__ */ jsxRuntime.jsx(
2564
+ ref: triggerRef,
2565
+ className: "mb-2 flex h-11 rounded-lg border border-gray-300 bg-white py-1.5 px-4 shadow-theme-xs outline-hidden transition focus:border-brand-300 focus:shadow-focus-ring dark:border-gray-700 dark:bg-gray-900 dark:focus:border-brand-300",
2566
+ children: [
2567
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-wrap flex-auto gap-2", children: selectedValuesText.length > 0 ? selectedValuesText.map((text, index) => /* @__PURE__ */ jsxRuntime.jsxs(
2523
2568
  "div",
2524
2569
  {
2525
- className: `w-full cursor-pointer rounded-t border-b border-gray-200 hover:bg-brand-500/5 dark:border-gray-800`,
2526
- onClick: () => handleSelect(option.value),
2527
- children: /* @__PURE__ */ jsxRuntime.jsxs(
2528
- "div",
2570
+ className: "group flex items-center justify-center rounded-full border-[0.7px] border-transparent bg-gray-100 py-1 pl-2.5 pr-2 text-sm text-gray-800 hover:border-gray-200 dark:bg-gray-800 dark:text-white/90 dark:hover:border-gray-800",
2571
+ children: [
2572
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-initial max-w-full", children: text }),
2573
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-row-reverse flex-auto", children: /* @__PURE__ */ jsxRuntime.jsx(
2574
+ "div",
2575
+ {
2576
+ onClick: () => removeOption(index, selectedOptions[index]),
2577
+ className: "pl-2 text-gray-500 cursor-pointer group-hover:text-gray-400 dark:text-gray-400",
2578
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: "size-4", role: "button", "aria-hidden": "true" })
2579
+ }
2580
+ ) })
2581
+ ]
2582
+ },
2583
+ index
2584
+ )) : /* @__PURE__ */ jsxRuntime.jsx(
2585
+ "input",
2586
+ {
2587
+ placeholder: "Select option",
2588
+ className: "w-full h-full p-1 pr-2 text-sm bg-transparent border-0 outline-hidden appearance-none placeholder:text-gray-800 focus:border-0 focus:outline-hidden focus:ring-0 dark:placeholder:text-white/90",
2589
+ readOnly: true,
2590
+ value: "Select option"
2591
+ }
2592
+ ) }),
2593
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center py-1 pl-1 pr-1 w-7", children: /* @__PURE__ */ jsxRuntime.jsx(
2594
+ "button",
2595
+ {
2596
+ type: "button",
2597
+ onClick: toggleDropdown,
2598
+ className: "w-5 h-5 text-gray-700 outline-hidden cursor-pointer focus:outline-hidden dark:text-gray-400",
2599
+ children: /* @__PURE__ */ jsxRuntime.jsx(
2600
+ lucideReact.ChevronDown,
2529
2601
  {
2530
- className: `relative flex w-full items-center justify-between p-2 pl-2 ${isSelected ? "bg-brand-500/10" : ""}`,
2531
- children: [
2532
- /* @__PURE__ */ jsxRuntime.jsx(
2533
- "div",
2534
- {
2535
- className: `mx-2 leading-6 ${isSelected ? "font-medium text-brand-600 dark:text-brand-400" : "text-gray-800 dark:text-white/90"}`,
2536
- children: option.text
2537
- }
2538
- ),
2539
- isSelected && /* @__PURE__ */ jsxRuntime.jsx(
2540
- lucideReact.Check,
2541
- {
2542
- className: "mr-2 size-4 shrink-0 stroke-current text-brand-500",
2543
- "aria-hidden": "true"
2544
- }
2545
- )
2546
- ]
2602
+ className: `size-4 stroke-current ${isOpen ? "rotate-180" : ""}`,
2603
+ "aria-hidden": "true"
2547
2604
  }
2548
2605
  )
2549
2606
  }
2550
- ) }, index);
2551
- }) })
2607
+ ) })
2608
+ ]
2552
2609
  }
2553
- )
2610
+ ) }),
2611
+ isOpen && (() => {
2612
+ const menu = /* @__PURE__ */ jsxRuntime.jsx(
2613
+ "div",
2614
+ {
2615
+ ref: menuRef,
2616
+ className: chunkYERNSNT4_cjs.cn(
2617
+ "max-h-select overflow-y-auto rounded-lg bg-white shadow-sm dark:bg-gray-900",
2618
+ portal ? chunkYERNSNT4_cjs.cn("fixed", chunkTF3G3E72_cjs.layers.portal) : chunkYERNSNT4_cjs.cn("absolute left-0 top-full w-full", chunkTF3G3E72_cjs.layers.menu)
2619
+ ),
2620
+ style: {
2621
+ ...portal && !menuPosition ? { visibility: "hidden" } : void 0,
2622
+ ...portal ? menuPosition : void 0
2623
+ },
2624
+ onClick: (e) => e.stopPropagation(),
2625
+ children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-col", children: options.map((option, index) => {
2626
+ const isSelected = selectedOptions.includes(option.value);
2627
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { children: /* @__PURE__ */ jsxRuntime.jsx(
2628
+ "div",
2629
+ {
2630
+ className: `w-full cursor-pointer rounded-t border-b border-gray-200 hover:bg-brand-500/5 dark:border-gray-800`,
2631
+ onClick: () => handleSelect(option.value),
2632
+ children: /* @__PURE__ */ jsxRuntime.jsxs(
2633
+ "div",
2634
+ {
2635
+ className: `relative flex w-full items-center justify-between p-2 pl-2 ${isSelected ? "bg-brand-500/10" : ""}`,
2636
+ children: [
2637
+ /* @__PURE__ */ jsxRuntime.jsx(
2638
+ "div",
2639
+ {
2640
+ className: `mx-2 leading-6 ${isSelected ? "font-medium text-brand-600 dark:text-brand-400" : "text-gray-800 dark:text-white/90"}`,
2641
+ children: option.text
2642
+ }
2643
+ ),
2644
+ isSelected && /* @__PURE__ */ jsxRuntime.jsx(
2645
+ lucideReact.Check,
2646
+ {
2647
+ className: "mr-2 size-4 shrink-0 stroke-current text-brand-500",
2648
+ "aria-hidden": "true"
2649
+ }
2650
+ )
2651
+ ]
2652
+ }
2653
+ )
2654
+ }
2655
+ ) }, index);
2656
+ }) })
2657
+ }
2658
+ );
2659
+ if (!portal || typeof document === "undefined") return menu;
2660
+ return reactDom.createPortal(menu, document.body);
2661
+ })()
2554
2662
  ] }) })
2555
2663
  ] });
2556
2664
  };