negodesign 0.0.56 → 0.0.57

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 (216) hide show
  1. package/dist/components/core/banner/AdCardBanner.svelte +74 -74
  2. package/dist/components/core/banner/CtaCardBanner.svelte +118 -118
  3. package/dist/components/core/breadcrumb/breadcrumb-list.svelte +31 -31
  4. package/dist/components/core/button/ButtonBack.svelte +18 -18
  5. package/dist/components/core/button/ButtonLogin.svelte +15 -15
  6. package/dist/components/core/button/ButtonLoginWith.svelte +57 -57
  7. package/dist/components/core/button/ButtonSend.svelte +15 -15
  8. package/dist/components/core/card/highlight/01/CardHighlight01.svelte +103 -103
  9. package/dist/components/core/card/highlight/02/CardHighlight02.svelte +99 -99
  10. package/dist/components/core/card/highlight/CardHighlight.svelte +22 -22
  11. package/dist/components/core/card/media/01/CardMedia01.svelte +133 -133
  12. package/dist/components/core/card/media/02/CardMedia02.svelte +127 -127
  13. package/dist/components/core/card/media/CardMedia.svelte +18 -18
  14. package/dist/components/core/card/product/01/CardProduct01.svelte +136 -136
  15. package/dist/components/core/card/product/02/CardProduct02.svelte +124 -124
  16. package/dist/components/core/card/product/CardProduct.svelte +22 -22
  17. package/dist/components/core/card/profile/01/CardProfile01.svelte +134 -134
  18. package/dist/components/core/card/profile/02/CardProfile02.svelte +132 -132
  19. package/dist/components/core/card/profile/CardProfile.svelte +18 -18
  20. package/dist/components/core/card/promotion/CardPromotion.svelte +139 -139
  21. package/dist/components/core/card/promotion/CardPromotionPrice.svelte +32 -32
  22. package/dist/components/core/card/shared/CardButton.svelte +42 -42
  23. package/dist/components/core/card/shared/CardCart.svelte +36 -36
  24. package/dist/components/core/card/shared/CardDescription.svelte +41 -41
  25. package/dist/components/core/card/shared/CardFavorite.svelte +33 -33
  26. package/dist/components/core/card/shared/CardLogo.svelte +19 -19
  27. package/dist/components/core/card/shared/CardPhoneOrWhatsapp.svelte +45 -45
  28. package/dist/components/core/card/shared/CardStarRating.svelte +24 -24
  29. package/dist/components/core/card/shared/CardTags.svelte +95 -95
  30. package/dist/components/core/card/shared/CardThumbnailVideo.svelte +232 -232
  31. package/dist/components/core/card/shared/CardTitlePrice.svelte +24 -24
  32. package/dist/components/core/card/shared/TruncatableText.svelte +49 -49
  33. package/dist/components/core/card/types.d.ts +4 -0
  34. package/dist/components/core/carousel/badge/type.d.ts +27 -0
  35. package/dist/components/core/carousel/badge/ui/CarouselBadge.svelte +109 -109
  36. package/dist/components/core/carousel/grid/media/types.d.ts +3 -0
  37. package/dist/components/core/carousel/grid/media/ui/CarouselGridMedia.svelte +2 -1
  38. package/dist/components/core/carousel/grid/product/types.d.ts +3 -0
  39. package/dist/components/core/carousel/grid/product/ui/CarouselGridProduct.svelte +5 -4
  40. package/dist/components/core/carousel/grid/profile/ui/CarouselGridProfile.svelte +1 -1
  41. package/dist/components/core/carousel/grid/promotion/ui/CarouselGridPromotion.svelte +1 -1
  42. package/dist/components/core/carousel/grid/shared/ui/CarouselGridSlot.svelte +8 -3
  43. package/dist/components/core/carousel/grid/shared/ui/CarouselGridSlot.svelte.d.ts +2 -1
  44. package/dist/components/core/carousel/hero/types.d.ts +30 -0
  45. package/dist/components/core/carousel/hero/{CarouselHero.svelte → ui/CarouselHero.svelte} +163 -161
  46. package/dist/components/core/carousel/shared/ui/CarouselHeader.svelte +60 -60
  47. package/dist/components/core/carousel/types.d.ts +2 -56
  48. package/dist/components/core/datatable/ui/DataTableActions.svelte +28 -28
  49. package/dist/components/core/datatable/ui/DataTableCheckbox.svelte +22 -22
  50. package/dist/components/core/datatable/ui/DataTableContent.svelte +59 -59
  51. package/dist/components/core/datatable/ui/DataTableCore.svelte +151 -151
  52. package/dist/components/core/datatable/ui/DataTableDragHandle.svelte +8 -8
  53. package/dist/components/core/datatable/ui/DataTableListFilter.svelte +59 -59
  54. package/dist/components/core/datatable/ui/DataTableListPagination.svelte +35 -35
  55. package/dist/components/core/footer/ui/Footer.svelte +18 -18
  56. package/dist/components/core/form/ui/input-base.svelte +81 -81
  57. package/dist/components/core/form/ui/input-email-or-phone.svelte +23 -23
  58. package/dist/components/core/form/ui/input-email.svelte +23 -23
  59. package/dist/components/core/form/ui/input-password-confirm.svelte +23 -23
  60. package/dist/components/core/form/ui/input-password.svelte +23 -23
  61. package/dist/components/core/form/ui/input-phone.svelte +33 -33
  62. package/dist/components/core/form/ui/input-username.svelte +22 -22
  63. package/dist/components/core/grid/data/types.d.ts +16 -0
  64. package/dist/components/core/grid/ui/01/Grid01.svelte +27 -0
  65. package/dist/components/core/grid/ui/01/Grid01.svelte.d.ts +4 -0
  66. package/dist/components/core/grid/ui/02/Grid02.svelte +27 -0
  67. package/dist/components/core/grid/ui/02/Grid02.svelte.d.ts +4 -0
  68. package/dist/components/core/grid/ui/PanelGrid.svelte +27 -0
  69. package/dist/components/core/grid/ui/PanelGrid.svelte.d.ts +4 -0
  70. package/dist/components/core/grid/ui/SimpleGrid.svelte +35 -0
  71. package/dist/components/core/grid/ui/SimpleGrid.svelte.d.ts +4 -0
  72. package/dist/components/core/grid/ui/shared/DescriptionGrid.svelte +17 -0
  73. package/dist/components/core/grid/ui/shared/DescriptionGrid.svelte.d.ts +7 -0
  74. package/dist/components/core/grid/ui/shared/IconRenderGrid.svelte +16 -0
  75. package/dist/components/core/grid/ui/shared/IconRenderGrid.svelte.d.ts +7 -0
  76. package/dist/components/core/grid/ui/shared/TitleGrid.svelte +11 -0
  77. package/dist/components/core/grid/ui/shared/TitleGrid.svelte.d.ts +7 -0
  78. package/dist/components/core/hero/SimpleHero.svelte +65 -65
  79. package/dist/components/core/label/LabelOr.svelte +25 -25
  80. package/dist/components/core/link/link-forget-password.svelte +12 -12
  81. package/dist/components/core/link/link-privacy-polity.svelte +12 -12
  82. package/dist/components/core/link/link-terms-of-service.svelte +12 -12
  83. package/dist/components/core/nav/data/complex-menu-utils.d.ts +7 -0
  84. package/dist/components/core/nav/data/complex-menu-utils.js +15 -0
  85. package/dist/components/core/nav/data/nav-menu.d.ts +21 -7
  86. package/dist/components/core/nav/{ui/shared → data}/types.d.ts +15 -2
  87. package/dist/components/core/nav/ui/ComplexMenu.svelte +36 -0
  88. package/dist/components/core/nav/ui/ComplexMenu.svelte.d.ts +4 -0
  89. package/dist/components/core/nav/ui/Menu.svelte +70 -66
  90. package/dist/components/core/nav/ui/Menu.svelte.d.ts +1 -1
  91. package/dist/components/core/nav/ui/SimpleMenu.svelte +14 -0
  92. package/dist/components/core/nav/ui/SimpleMenu.svelte.d.ts +4 -0
  93. package/dist/components/core/nav/ui/mobile/ComplexMenuMobile.svelte +98 -0
  94. package/dist/components/core/nav/ui/mobile/ComplexMenuMobile.svelte.d.ts +4 -0
  95. package/dist/components/core/nav/ui/mobile/MenuMobile.svelte +54 -0
  96. package/dist/components/core/nav/ui/mobile/MenuMobile.svelte.d.ts +11 -0
  97. package/dist/components/core/nav/ui/mobile/SimpleMenuMobile.svelte +42 -0
  98. package/dist/components/core/nav/ui/mobile/{nav-menu-drawer.svelte.d.ts → SimpleMenuMobile.svelte.d.ts} +3 -3
  99. package/dist/components/core/nav/ui/shared/CollapsibleNavMenu.svelte +24 -0
  100. package/dist/components/core/nav/ui/shared/CollapsibleNavMenu.svelte.d.ts +8 -0
  101. package/dist/components/core/nav/ui/shared/MenuButton.svelte +51 -0
  102. package/dist/components/core/nav/ui/shared/MenuButton.svelte.d.ts +4 -0
  103. package/dist/components/core/nav/ui/shared/MenuLinks.svelte +40 -40
  104. package/dist/components/core/nav/ui/shared/MenuListItem.svelte +48 -48
  105. package/dist/components/core/nav/ui/shared/MenuListItem.svelte.d.ts +1 -1
  106. package/dist/components/core/nav/ui/shared/MenuLogo.svelte +13 -0
  107. package/dist/components/core/nav/ui/shared/MenuLogo.svelte.d.ts +4 -0
  108. package/dist/components/core/nav/ui/shared/NavMenuItem.svelte +28 -0
  109. package/dist/components/core/nav/ui/shared/NavMenuItem.svelte.d.ts +4 -0
  110. package/dist/components/core/nav/ui/shared/dropdown/DropdownMenuListItem.svelte +40 -0
  111. package/dist/components/core/nav/ui/shared/dropdown/DropdownMenuListItem.svelte.d.ts +4 -0
  112. package/dist/components/core/nav/ui/shared/dropdown/DropdownNavigationMenuItem.svelte +12 -0
  113. package/dist/components/core/nav/ui/shared/dropdown/DropdownNavigationMenuItem.svelte.d.ts +4 -0
  114. package/dist/components/core/nav/ui/shared/dropdown/DropdownNavigationMenuItemCard.svelte +49 -0
  115. package/dist/components/core/nav/ui/shared/dropdown/DropdownNavigationMenuItemCard.svelte.d.ts +4 -0
  116. package/dist/components/core/nav/ui/shared/dropdown/DropdownNavigationMenuItemGrid.svelte +30 -0
  117. package/dist/components/core/nav/ui/shared/dropdown/DropdownNavigationMenuItemGrid.svelte.d.ts +4 -0
  118. package/dist/components/core/nav/ui/shared/dropdown/DropdownNavigationMenuItemList.svelte +24 -0
  119. package/dist/components/core/nav/ui/shared/dropdown/DropdownNavigationMenuItemList.svelte.d.ts +4 -0
  120. package/dist/components/core/nav/ui/shared/dropdown/DropdownNavigationMenuItems.svelte +24 -0
  121. package/dist/components/core/nav/ui/shared/dropdown/DropdownNavigationMenuItems.svelte.d.ts +4 -0
  122. package/dist/components/core/nav/ui/shared/{NavigationMenuItem.svelte → navigation/NavigationMenuItem.svelte} +15 -15
  123. package/dist/components/core/nav/ui/shared/{NavigationMenuItem.svelte.d.ts → navigation/NavigationMenuItem.svelte.d.ts} +1 -1
  124. package/dist/components/core/nav/ui/shared/{NavigationMenuItemCard.svelte → navigation/NavigationMenuItemCard.svelte} +62 -62
  125. package/dist/components/core/nav/ui/shared/{NavigationMenuItemCard.svelte.d.ts → navigation/NavigationMenuItemCard.svelte.d.ts} +1 -1
  126. package/dist/components/core/nav/ui/shared/{NavigationMenuItemGrid.svelte → navigation/NavigationMenuItemGrid.svelte} +32 -32
  127. package/dist/components/core/nav/ui/shared/{NavigationMenuItemGrid.svelte.d.ts → navigation/NavigationMenuItemGrid.svelte.d.ts} +1 -1
  128. package/dist/components/core/nav/ui/shared/{NavigationMenuItemList.svelte → navigation/NavigationMenuItemList.svelte} +24 -24
  129. package/dist/components/core/nav/ui/shared/{NavigationMenuItemList.svelte.d.ts → navigation/NavigationMenuItemList.svelte.d.ts} +1 -1
  130. package/dist/components/core/nav/ui/shared/{NavigationMenuItems.svelte → navigation/NavigationMenuItems.svelte} +21 -21
  131. package/dist/components/core/nav/ui/shared/{NavigationMenuItems.svelte.d.ts → navigation/NavigationMenuItems.svelte.d.ts} +1 -1
  132. package/dist/components/core/panel/CarouselSlot.svelte +57 -57
  133. package/dist/components/core/panel/LeftHero.svelte +117 -117
  134. package/dist/components/core/search/SearchInput.svelte +32 -32
  135. package/dist/components/core/search/shared/SearchInputCore.svelte +66 -66
  136. package/dist/components/core/sidebar/MenuBarSidebar.svelte +70 -70
  137. package/dist/components/core/sidebar/MenuBarSidebar.svelte.d.ts +2 -2
  138. package/dist/components/core/sidebar/data/nav-menu-sidebar.js +1 -0
  139. package/dist/components/core/sidebar/data/nav-top-bar-sidebar.js +1 -0
  140. package/dist/components/core/sidebar/data/nav-user-sidebar.js +1 -0
  141. package/dist/components/core/sidebar/types.d.ts +2 -2
  142. package/dist/components/core/sidebar/ui/navbar/NavMenuBarSidebar.svelte +97 -97
  143. package/dist/components/core/sidebar/ui/navbar/NavMenuBarSidebar.svelte.d.ts +1 -1
  144. package/dist/components/core/sidebar/ui/navbar/NavMenuBottomSidebar.svelte +33 -33
  145. package/dist/components/core/sidebar/ui/navbar/NavMenuBottomSidebar.svelte.d.ts +1 -1
  146. package/dist/components/core/sidebar/ui/navbar/NavMenuTopBarSidebar.svelte +103 -103
  147. package/dist/components/core/sidebar/ui/navbar/NavMenuTopBarSidebar.svelte.d.ts +1 -1
  148. package/dist/components/core/tabs/ui/tab-underline.svelte +43 -43
  149. package/dist/components/pages/admin/01/ui/AdminContent.svelte +14 -14
  150. package/dist/components/pages/admin/01/ui/AdminPanel.svelte +46 -46
  151. package/dist/components/pages/privacy-policy-or-terms-of-use/types.d.ts +2 -0
  152. package/dist/components/pages/security/login/01/ui/FormRegister.svelte +28 -28
  153. package/dist/components/pages/security/login/01/ui/LoginCard.svelte +107 -107
  154. package/dist/components/pages/security/login/PageLogin.svelte +22 -22
  155. package/dist/components/ui/badge/badge.svelte +49 -49
  156. package/dist/components/ui/button/button.svelte +82 -82
  157. package/dist/components/ui/chart/chart-container.svelte +80 -80
  158. package/dist/components/ui/chart/chart-style.svelte +37 -37
  159. package/dist/components/ui/chart/chart-tooltip.svelte +184 -184
  160. package/dist/components/ui/checkbox/checkbox.svelte +40 -40
  161. package/dist/components/ui/data-table/flex-render.svelte +40 -40
  162. package/dist/components/ui/drawer/drawer-close.svelte +7 -7
  163. package/dist/components/ui/drawer/drawer-content.svelte +33 -33
  164. package/dist/components/ui/drawer/drawer-description.svelte +17 -17
  165. package/dist/components/ui/drawer/drawer-footer.svelte +20 -20
  166. package/dist/components/ui/drawer/drawer-header.svelte +20 -20
  167. package/dist/components/ui/drawer/drawer-nested.svelte +12 -12
  168. package/dist/components/ui/drawer/drawer-overlay.svelte +17 -17
  169. package/dist/components/ui/drawer/drawer-portal.svelte +7 -7
  170. package/dist/components/ui/drawer/drawer-title.svelte +17 -17
  171. package/dist/components/ui/drawer/drawer-trigger.svelte +7 -7
  172. package/dist/components/ui/drawer/drawer.svelte +12 -12
  173. package/dist/components/ui/select/select-content.svelte +45 -45
  174. package/dist/components/ui/select/select-group-heading.svelte +21 -21
  175. package/dist/components/ui/select/select-group.svelte +17 -17
  176. package/dist/components/ui/select/select-item.svelte +41 -41
  177. package/dist/components/ui/select/select-label.svelte +20 -20
  178. package/dist/components/ui/select/select-portal.svelte +7 -7
  179. package/dist/components/ui/select/select-scroll-down-button.svelte +21 -21
  180. package/dist/components/ui/select/select-scroll-up-button.svelte +21 -21
  181. package/dist/components/ui/select/select-separator.svelte +18 -18
  182. package/dist/components/ui/select/select-trigger.svelte +30 -30
  183. package/dist/components/ui/select/select.svelte +11 -11
  184. package/dist/components/ui/sidebar/sidebar-menu-sub-button.svelte +39 -39
  185. package/dist/components/ui/star-rating/star-rating-star.svelte +41 -41
  186. package/dist/components/ui/star-rating/star-rating.svelte +20 -20
  187. package/dist/components/ui/table/table-body.svelte +15 -15
  188. package/dist/components/ui/table/table-caption.svelte +20 -20
  189. package/dist/components/ui/table/table-cell.svelte +15 -15
  190. package/dist/components/ui/table/table-footer.svelte +20 -20
  191. package/dist/components/ui/table/table-head.svelte +15 -15
  192. package/dist/components/ui/table/table-header.svelte +20 -20
  193. package/dist/components/ui/table/table-row.svelte +15 -15
  194. package/dist/components/ui/table/table.svelte +17 -17
  195. package/dist/components/ui/tabs/tabs-content.svelte +17 -17
  196. package/dist/components/ui/tabs/tabs-list.svelte +40 -40
  197. package/dist/components/ui/tabs/tabs-trigger.svelte +23 -23
  198. package/dist/components/ui/tabs/tabs.svelte +19 -19
  199. package/dist/globals.css +56 -2
  200. package/dist/index.d.ts +1 -8
  201. package/dist/index.js +1 -8
  202. package/dist/styles/globals.css +149 -149
  203. package/dist/styles/hgi-stroke-rounded.css +1 -1
  204. package/dist/types/index.d.ts +9 -7
  205. package/package.json +113 -113
  206. package/dist/components/core/nav/ui/NavMenu.svelte +0 -122
  207. package/dist/components/core/nav/ui/NavMenu.svelte.d.ts +0 -4
  208. package/dist/components/core/nav/ui/mobile/nav-menu-drawer.svelte +0 -85
  209. package/dist/components/core/{nav/data/nav-menu-sidebar.js → carousel/badge/type.js} +0 -0
  210. package/dist/components/core/{nav/ui/shared → carousel/hero}/types.js +0 -0
  211. package/dist/components/core/carousel/hero/{CarouselHero.svelte.d.ts → ui/CarouselHero.svelte.d.ts} +1 -1
  212. /package/dist/components/core/{nav/data/nav-top-bar-sidebar.js → grid/data/types.js} +0 -0
  213. /package/dist/components/core/nav/data/{nav-user-sidebar.js → types.js} +0 -0
  214. /package/dist/components/core/{nav → sidebar}/data/nav-menu-sidebar.d.ts +0 -0
  215. /package/dist/components/core/{nav → sidebar}/data/nav-top-bar-sidebar.d.ts +0 -0
  216. /package/dist/components/core/{nav → sidebar}/data/nav-user-sidebar.d.ts +0 -0
@@ -1,232 +1,232 @@
1
- <!-- CardMedia.svelte -->
2
- <script lang="ts">
3
- /**
4
- * Image thumbnail with hover-to-play video, lazy loading,
5
- * duration badge, video indicator, and optional maximize overlay.
6
- * @component
7
- */
8
- import { HugeiconsIcon } from "@hugeicons/svelte";
9
- import {
10
- Maximize01Icon,
11
- Cancel01Icon,
12
- VideoReplayIcon,
13
- } from "@hugeicons/core-free-icons";
14
- import Skeleton from "../../../ui/skeleton/skeleton.svelte";
15
- import ImgPlaceholder from "../../../../assets/placeholder-image.png";
16
- import VideoPlaceholder from "../../../../assets/lonely-404.mp4";
17
-
18
- type Props = {
19
- imageUrl?: string;
20
- videoUrl?: string;
21
- class?: string;
22
- isImageButtonMaximized?: boolean;
23
- isVideoButtonMaximized?: boolean;
24
- isLoading?: boolean;
25
- };
26
-
27
- let {
28
- imageUrl,
29
- videoUrl,
30
- class: className = "h-32 w-full md:h-64",
31
- isImageButtonMaximized = false,
32
- isVideoButtonMaximized = false,
33
- isLoading = false,
34
- }: Props = $props();
35
-
36
- let containerEl: HTMLDivElement | undefined = $state();
37
- let videoEl: HTMLVideoElement | undefined = $state();
38
-
39
- let isHovering = $state(false);
40
- let isVisible = $state(false);
41
- let isMaximized = $state(false);
42
- let duration = $state("");
43
-
44
- let showVideo = $derived(!imageUrl || isHovering);
45
- let showMaximize = $derived(
46
- (imageUrl && isImageButtonMaximized) ||
47
- (videoUrl && isVideoButtonMaximized),
48
- );
49
- let showVideoBadge = $derived(!!imageUrl && !!videoUrl && !isHovering);
50
-
51
- function onLoadedMetadata() {
52
- if (!videoEl) return;
53
- const m = Math.floor(videoEl.duration / 60);
54
- const s = Math.floor(videoEl.duration % 60);
55
- duration = `${m}:${s.toString().padStart(2, "0")}`;
56
- }
57
-
58
- function toggleHover(state: boolean) {
59
- if (!videoUrl) return;
60
- isHovering = state;
61
- if (state) videoEl?.play();
62
- else {
63
- videoEl?.pause();
64
- if (imageUrl && videoEl) videoEl.currentTime = 0;
65
- }
66
- }
67
-
68
- $effect(() => {
69
- if (!containerEl) return;
70
- const observer = new IntersectionObserver(
71
- ([entry]) => {
72
- if (entry.isIntersecting) {
73
- isVisible = true;
74
- observer.disconnect();
75
- }
76
- },
77
- { rootMargin: "100px" },
78
- );
79
- observer.observe(containerEl);
80
- return () => observer.disconnect();
81
- });
82
- </script>
83
-
84
- <svelte:window
85
- onkeydown={isMaximized
86
- ? (e) => e.key === "Escape" && (isMaximized = false)
87
- : undefined}
88
- />
89
-
90
- {#snippet maximizeButton()}
91
- <button
92
- type="button"
93
- class="absolute top-2 right-2 z-10 rounded-full bg-black/50 p-1.5 text-white transition-colors hover:bg-black/70"
94
- onclick={(e) => {
95
- e.stopPropagation();
96
- isMaximized = true;
97
- videoEl?.pause();
98
- }}
99
- aria-label="Maximizar"
100
- >
101
- <HugeiconsIcon
102
- icon={Maximize01Icon}
103
- size={16}
104
- color="currentColor"
105
- strokeWidth={1.5}
106
- />
107
- </button>
108
- {/snippet}
109
-
110
- {#if isLoading}
111
- <Skeleton class="w-full h-12.5 md:h-50 bg-gray-500/90" />
112
- {:else}
113
- {#if imageUrl || videoUrl}
114
- <div
115
- bind:this={containerEl}
116
- class="relative w-full {className}"
117
- role="button"
118
- tabindex="0"
119
- onmouseenter={() => toggleHover(true)}
120
- onmouseleave={() => toggleHover(false)}
121
- onfocus={() => toggleHover(true)}
122
- onblur={() => toggleHover(false)}
123
- onkeydown={(e) =>
124
- (e.key === "Enter" || e.key === " ") &&
125
- (e.preventDefault(), toggleHover(!isHovering))}
126
- >
127
- {#if imageUrl}
128
- <img
129
- src={isVisible ? imageUrl : undefined}
130
- loading="lazy"
131
- decoding="async"
132
- alt={imageUrl}
133
- class="absolute inset-0 h-full w-full rounded-md object-cover transition-opacity duration-500"
134
- class:opacity-0={videoUrl && showVideo}
135
- onerror={(e) => ((e.target as HTMLImageElement).src = ImgPlaceholder)}
136
- />
137
- {/if}
138
-
139
- {#if videoUrl && isVisible}
140
- <video
141
- bind:this={videoEl}
142
- src={videoUrl}
143
- preload="metadata"
144
- onloadedmetadata={onLoadedMetadata}
145
- class="absolute inset-0 h-full w-full rounded-md object-cover transition-opacity duration-500"
146
- class:opacity-0={imageUrl && !showVideo}
147
- muted
148
- loop
149
- playsinline
150
- onerror={(e) =>
151
- ((e.target as HTMLImageElement).src = VideoPlaceholder)}
152
- ></video>
153
- {/if}
154
-
155
- {#if showVideoBadge}
156
- <div
157
- class="pointer-events-none absolute inset-0 flex items-center justify-center transition-opacity duration-300"
158
- >
159
- <div
160
- class="flex h-11 w-11 items-center justify-center rounded-full bg-black/50 backdrop-blur-sm"
161
- >
162
- <HugeiconsIcon
163
- icon={VideoReplayIcon}
164
- size={22}
165
- color="white"
166
- strokeWidth={1.5}
167
- />
168
- </div>
169
- </div>
170
- {#if duration}
171
- <span
172
- class="absolute bottom-2 right-2 z-10 rounded bg-black/60 px-1.5 py-0.5 text-xs font-medium text-white"
173
- >
174
- {duration}
175
- </span>
176
- {/if}
177
- {/if}
178
-
179
- {#if showMaximize}
180
- {@render maximizeButton()}
181
- {/if}
182
- </div>
183
- {/if}
184
-
185
- {#if isMaximized}
186
- <div class="fixed inset-0 z-50">
187
- <button
188
- type="button"
189
- class="absolute inset-0 h-full w-full cursor-default bg-black/80"
190
- onclick={() => (isMaximized = false)}
191
- aria-label="Fechar modal"
192
- ></button>
193
-
194
- <div
195
- class="pointer-events-none fixed inset-0 z-50 flex items-center justify-center p-4"
196
- >
197
- <div class="pointer-events-auto relative max-h-[90vh] max-w-4xl">
198
- <button
199
- type="button"
200
- class="absolute top-3 right-3 rounded-full bg-red-900/50 p-2 text-white hover:bg-black/70"
201
- onclick={() => (isMaximized = false)}
202
- aria-label="Fechar"
203
- >
204
- <HugeiconsIcon
205
- icon={Cancel01Icon}
206
- size={20}
207
- color="currentColor"
208
- strokeWidth={1.5}
209
- />
210
- </button>
211
-
212
- {#if videoUrl && showVideo}
213
- <!-- svelte-ignore a11y_media_has_caption -->
214
- <video
215
- src={videoUrl}
216
- class="max-h-[90vh] max-w-full rounded-md"
217
- controls
218
- autoplay
219
- playsinline
220
- ></video>
221
- {:else if imageUrl}
222
- <img
223
- src={imageUrl}
224
- alt={imageUrl}
225
- class="max-h-[90vh] max-w-full rounded-md object-contain"
226
- />
227
- {/if}
228
- </div>
229
- </div>
230
- </div>
231
- {/if}
232
- {/if}
1
+ <!-- CardMedia.svelte -->
2
+ <script lang="ts">
3
+ /**
4
+ * Image thumbnail with hover-to-play video, lazy loading,
5
+ * duration badge, video indicator, and optional maximize overlay.
6
+ * @component
7
+ */
8
+ import { HugeiconsIcon } from "@hugeicons/svelte";
9
+ import {
10
+ Maximize01Icon,
11
+ Cancel01Icon,
12
+ VideoReplayIcon,
13
+ } from "@hugeicons/core-free-icons";
14
+ import Skeleton from "../../../ui/skeleton/skeleton.svelte";
15
+ import ImgPlaceholder from "../../../../assets/placeholder-image.png";
16
+ import VideoPlaceholder from "../../../../assets/lonely-404.mp4";
17
+
18
+ type Props = {
19
+ imageUrl?: string;
20
+ videoUrl?: string;
21
+ class?: string;
22
+ isImageButtonMaximized?: boolean;
23
+ isVideoButtonMaximized?: boolean;
24
+ isLoading?: boolean;
25
+ };
26
+
27
+ let {
28
+ imageUrl,
29
+ videoUrl,
30
+ class: className = "h-32 w-full md:h-64",
31
+ isImageButtonMaximized = false,
32
+ isVideoButtonMaximized = false,
33
+ isLoading = false,
34
+ }: Props = $props();
35
+
36
+ let containerEl: HTMLDivElement | undefined = $state();
37
+ let videoEl: HTMLVideoElement | undefined = $state();
38
+
39
+ let isHovering = $state(false);
40
+ let isVisible = $state(false);
41
+ let isMaximized = $state(false);
42
+ let duration = $state("");
43
+
44
+ let showVideo = $derived(!imageUrl || isHovering);
45
+ let showMaximize = $derived(
46
+ (imageUrl && isImageButtonMaximized) ||
47
+ (videoUrl && isVideoButtonMaximized),
48
+ );
49
+ let showVideoBadge = $derived(!!imageUrl && !!videoUrl && !isHovering);
50
+
51
+ function onLoadedMetadata() {
52
+ if (!videoEl) return;
53
+ const m = Math.floor(videoEl.duration / 60);
54
+ const s = Math.floor(videoEl.duration % 60);
55
+ duration = `${m}:${s.toString().padStart(2, "0")}`;
56
+ }
57
+
58
+ function toggleHover(state: boolean) {
59
+ if (!videoUrl) return;
60
+ isHovering = state;
61
+ if (state) videoEl?.play();
62
+ else {
63
+ videoEl?.pause();
64
+ if (imageUrl && videoEl) videoEl.currentTime = 0;
65
+ }
66
+ }
67
+
68
+ $effect(() => {
69
+ if (!containerEl) return;
70
+ const observer = new IntersectionObserver(
71
+ ([entry]) => {
72
+ if (entry.isIntersecting) {
73
+ isVisible = true;
74
+ observer.disconnect();
75
+ }
76
+ },
77
+ { rootMargin: "100px" },
78
+ );
79
+ observer.observe(containerEl);
80
+ return () => observer.disconnect();
81
+ });
82
+ </script>
83
+
84
+ <svelte:window
85
+ onkeydown={isMaximized
86
+ ? (e) => e.key === "Escape" && (isMaximized = false)
87
+ : undefined}
88
+ />
89
+
90
+ {#snippet maximizeButton()}
91
+ <button
92
+ type="button"
93
+ class="absolute top-2 right-2 z-10 rounded-full bg-black/50 p-1.5 text-white transition-colors hover:bg-black/70"
94
+ onclick={(e) => {
95
+ e.stopPropagation();
96
+ isMaximized = true;
97
+ videoEl?.pause();
98
+ }}
99
+ aria-label="Maximizar"
100
+ >
101
+ <HugeiconsIcon
102
+ icon={Maximize01Icon}
103
+ size={16}
104
+ color="currentColor"
105
+ strokeWidth={1.5}
106
+ />
107
+ </button>
108
+ {/snippet}
109
+
110
+ {#if isLoading}
111
+ <Skeleton class="w-full h-12.5 md:h-50 bg-gray-500/90" />
112
+ {:else}
113
+ {#if imageUrl || videoUrl}
114
+ <div
115
+ bind:this={containerEl}
116
+ class="relative w-full {className}"
117
+ role="button"
118
+ tabindex="0"
119
+ onmouseenter={() => toggleHover(true)}
120
+ onmouseleave={() => toggleHover(false)}
121
+ onfocus={() => toggleHover(true)}
122
+ onblur={() => toggleHover(false)}
123
+ onkeydown={(e) =>
124
+ (e.key === "Enter" || e.key === " ") &&
125
+ (e.preventDefault(), toggleHover(!isHovering))}
126
+ >
127
+ {#if imageUrl}
128
+ <img
129
+ src={isVisible ? imageUrl : undefined}
130
+ loading="lazy"
131
+ decoding="async"
132
+ alt={imageUrl}
133
+ class="absolute inset-0 h-full w-full rounded-md object-cover transition-opacity duration-500"
134
+ class:opacity-0={videoUrl && showVideo}
135
+ onerror={(e) => ((e.target as HTMLImageElement).src = ImgPlaceholder)}
136
+ />
137
+ {/if}
138
+
139
+ {#if videoUrl && isVisible}
140
+ <video
141
+ bind:this={videoEl}
142
+ src={videoUrl}
143
+ preload="metadata"
144
+ onloadedmetadata={onLoadedMetadata}
145
+ class="absolute inset-0 h-full w-full rounded-md object-cover transition-opacity duration-500"
146
+ class:opacity-0={imageUrl && !showVideo}
147
+ muted
148
+ loop
149
+ playsinline
150
+ onerror={(e) =>
151
+ ((e.target as HTMLImageElement).src = VideoPlaceholder)}
152
+ ></video>
153
+ {/if}
154
+
155
+ {#if showVideoBadge}
156
+ <div
157
+ class="pointer-events-none absolute inset-0 flex items-center justify-center transition-opacity duration-300"
158
+ >
159
+ <div
160
+ class="flex h-11 w-11 items-center justify-center rounded-full bg-black/50 backdrop-blur-sm"
161
+ >
162
+ <HugeiconsIcon
163
+ icon={VideoReplayIcon}
164
+ size={22}
165
+ color="white"
166
+ strokeWidth={1.5}
167
+ />
168
+ </div>
169
+ </div>
170
+ {#if duration}
171
+ <span
172
+ class="absolute bottom-2 right-2 z-10 rounded bg-black/60 px-1.5 py-0.5 text-xs font-medium text-white"
173
+ >
174
+ {duration}
175
+ </span>
176
+ {/if}
177
+ {/if}
178
+
179
+ {#if showMaximize}
180
+ {@render maximizeButton()}
181
+ {/if}
182
+ </div>
183
+ {/if}
184
+
185
+ {#if isMaximized}
186
+ <div class="fixed inset-0 z-50">
187
+ <button
188
+ type="button"
189
+ class="absolute inset-0 h-full w-full cursor-default bg-black/80"
190
+ onclick={() => (isMaximized = false)}
191
+ aria-label="Fechar modal"
192
+ ></button>
193
+
194
+ <div
195
+ class="pointer-events-none fixed inset-0 z-50 flex items-center justify-center p-4"
196
+ >
197
+ <div class="pointer-events-auto relative max-h-[90vh] max-w-4xl">
198
+ <button
199
+ type="button"
200
+ class="absolute top-3 right-3 rounded-full bg-red-900/50 p-2 text-white hover:bg-black/70"
201
+ onclick={() => (isMaximized = false)}
202
+ aria-label="Fechar"
203
+ >
204
+ <HugeiconsIcon
205
+ icon={Cancel01Icon}
206
+ size={20}
207
+ color="currentColor"
208
+ strokeWidth={1.5}
209
+ />
210
+ </button>
211
+
212
+ {#if videoUrl && showVideo}
213
+ <!-- svelte-ignore a11y_media_has_caption -->
214
+ <video
215
+ src={videoUrl}
216
+ class="max-h-[90vh] max-w-full rounded-md"
217
+ controls
218
+ autoplay
219
+ playsinline
220
+ ></video>
221
+ {:else if imageUrl}
222
+ <img
223
+ src={imageUrl}
224
+ alt={imageUrl}
225
+ class="max-h-[90vh] max-w-full rounded-md object-contain"
226
+ />
227
+ {/if}
228
+ </div>
229
+ </div>
230
+ </div>
231
+ {/if}
232
+ {/if}
@@ -1,24 +1,24 @@
1
- <script>
2
- import Skeleton from "../../../ui/skeleton/skeleton.svelte";
3
-
4
- let { title, isLoading, price } = $props();
5
- </script>
6
-
7
- <div class="flex justify-between w-full items-center">
8
- {#if title}
9
- {#if isLoading}
10
- <Skeleton class="h-4 w-30 rounded-lg" />
11
- {:else}
12
- <div class="font-semibold line-clamp-1 flex-nowrap">
13
- {title}
14
- </div>
15
- {/if}
16
- {/if}
17
- <div class="flex justify-start gap-2">
18
- {#if isLoading}
19
- <Skeleton class="h-5 bg-black/10 w-25 my-2 mr-2" />
20
- {:else if price}
21
- <div class="text-lg font-semibold text-primary">{price}</div>
22
- {/if}
23
- </div>
24
- </div>
1
+ <script>
2
+ import Skeleton from "../../../ui/skeleton/skeleton.svelte";
3
+
4
+ let { title, isLoading, price } = $props();
5
+ </script>
6
+
7
+ <div class="flex justify-between w-full items-center">
8
+ {#if title}
9
+ {#if isLoading}
10
+ <Skeleton class="h-4 w-30 rounded-lg" />
11
+ {:else}
12
+ <div class="font-semibold line-clamp-1 flex-nowrap">
13
+ {title}
14
+ </div>
15
+ {/if}
16
+ {/if}
17
+ <div class="flex justify-start gap-2">
18
+ {#if isLoading}
19
+ <Skeleton class="h-5 bg-black/10 w-25 my-2 mr-2" />
20
+ {:else if price}
21
+ <div class="text-lg font-semibold text-primary">{price}</div>
22
+ {/if}
23
+ </div>
24
+ </div>
@@ -1,49 +1,49 @@
1
- <!-- shared/TruncatableText.svelte -->
2
- <script lang="ts">
3
- /**
4
- * Clamps text to N lines; if it overflows, shows an indicator
5
- * ("Ler texto completo") positioned absolutely at the end of the
6
- * text, that opens the full text in a Popover.
7
- * @component
8
- */
9
- import * as Popover from "../../../ui/popover";
10
- import { t } from "../../../../i18n";
11
-
12
- type Props = { text: string; class?: string; lines?: number };
13
- let { text, class: className = "", lines = 2 }: Props = $props();
14
-
15
- let textEl: HTMLParagraphElement | undefined = $state();
16
- let isTruncated = $state(false);
17
-
18
- $effect(() => {
19
- if (!textEl) return;
20
- const check = () =>
21
- (isTruncated =
22
- (textEl?.scrollHeight ?? 0) > (textEl?.clientHeight ?? 0) + 1);
23
- // requestAnimationFrame garante que o layout com o clamp já foi aplicado
24
- // antes de medirmos — medir no mesmo tick pode dar valores desatualizados.
25
- requestAnimationFrame(check);
26
- const ro = new ResizeObserver(check);
27
- ro.observe(textEl);
28
- return () => ro.disconnect();
29
- });
30
- </script>
31
-
32
- <div class="relative">
33
- <p bind:this={textEl} class="line-clamp-3 {className}">
34
- {text}
35
- </p>
36
-
37
- {#if isTruncated}
38
- <Popover.Root>
39
- <Popover.Trigger
40
- class="absolute bottom-0 right-0 bg-background pl-1 text-[11px] font-medium text-primary underline underline-offset-2 cursor-pointer"
41
- >
42
- {$t("label.view.full")}
43
- </Popover.Trigger>
44
- <Popover.Content>
45
- <p class="max-w-xs text-justify text-[12px]">{text}</p>
46
- </Popover.Content>
47
- </Popover.Root>
48
- {/if}
49
- </div>
1
+ <!-- shared/TruncatableText.svelte -->
2
+ <script lang="ts">
3
+ /**
4
+ * Clamps text to N lines; if it overflows, shows an indicator
5
+ * ("Ler texto completo") positioned absolutely at the end of the
6
+ * text, that opens the full text in a Popover.
7
+ * @component
8
+ */
9
+ import * as Popover from "../../../ui/popover";
10
+ import { t } from "../../../../i18n";
11
+
12
+ type Props = { text: string; class?: string; lines?: number };
13
+ let { text, class: className = "", lines = 2 }: Props = $props();
14
+
15
+ let textEl: HTMLParagraphElement | undefined = $state();
16
+ let isTruncated = $state(false);
17
+
18
+ $effect(() => {
19
+ if (!textEl) return;
20
+ const check = () =>
21
+ (isTruncated =
22
+ (textEl?.scrollHeight ?? 0) > (textEl?.clientHeight ?? 0) + 1);
23
+ // requestAnimationFrame garante que o layout com o clamp já foi aplicado
24
+ // antes de medirmos — medir no mesmo tick pode dar valores desatualizados.
25
+ requestAnimationFrame(check);
26
+ const ro = new ResizeObserver(check);
27
+ ro.observe(textEl);
28
+ return () => ro.disconnect();
29
+ });
30
+ </script>
31
+
32
+ <div class="relative">
33
+ <p bind:this={textEl} class="line-clamp-3 {className}">
34
+ {text}
35
+ </p>
36
+
37
+ {#if isTruncated}
38
+ <Popover.Root>
39
+ <Popover.Trigger
40
+ class="absolute bottom-0 right-0 bg-background pl-1 text-[11px] font-medium text-primary underline underline-offset-2 cursor-pointer"
41
+ >
42
+ {$t("label.view.full")}
43
+ </Popover.Trigger>
44
+ <Popover.Content>
45
+ <p class="max-w-xs text-justify text-[12px]">{text}</p>
46
+ </Popover.Content>
47
+ </Popover.Root>
48
+ {/if}
49
+ </div>
@@ -90,10 +90,14 @@ export interface CardProfileProps {
90
90
  isTagBorderBottom?: boolean;
91
91
  /** Classe CSS personalizada para botão de perfil */
92
92
  buttonProfileClass?: string;
93
+ /** Classe CSS personalizada para o botão de detalhes */
94
+ buttonDetailsClass?: string;
93
95
  /** Callback ao clicar no botão favorito */
94
96
  onFavoriteClick?: (id: string | number) => void;
95
97
  /** Callback ao clicar no botão perfil */
96
98
  onButtonProfile?: (id: string | number) => void;
99
+ /** Callback acionado ao clicar no botão de detalhes */
100
+ onButtonDetails?: (id: string | number) => void;
97
101
  /** Callback ao clicar no botão email */
98
102
  onEmailClick?: (id: string | number) => void;
99
103
  /** Callback ao clicar no botão whatsapp */
@@ -0,0 +1,27 @@
1
+ import type { ItemCarousel } from "../types";
2
+ /**
3
+ * Props do CarouselBadge — carousel de badges/categorias
4
+ * com orientação horizontal ou vertical.
5
+ */
6
+ export interface CarouselBadgeProps {
7
+ /** Lista de itens do carousel de badges */
8
+ items: ItemCarousel[];
9
+ /** Orientação do layout: horizontal (padrão) ou vertical */
10
+ orientation?: "horizontal" | "vertical";
11
+ /** Classe CSS personalizada para a imagem */
12
+ imageClass?: string;
13
+ /** Classe CSS personalizada para o ícone */
14
+ iconClass?: string;
15
+ /** Classe CSS personalizada para o label */
16
+ labelClass?: string;
17
+ /** Classe CSS personalizada para o estado ativo */
18
+ activeClass?: string;
19
+ /** Estado de carregamento (skeleton) */
20
+ isLoading?: boolean;
21
+ itemClass?: string;
22
+ btnNavClass?: string;
23
+ isBorderInline?: boolean;
24
+ showButton?: boolean;
25
+ itemStyle?: 'BORDER' | 'INLINE' | 'DEFAULT';
26
+ onClick?: (value: string | number) => void;
27
+ }