negodesign 0.0.32 → 0.0.34

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.
@@ -3,7 +3,7 @@
3
3
  * Card component with a title and content.
4
4
  * @component
5
5
  */
6
- import { InformationCircleIcon, UserIcon } from "@hugeicons/core-free-icons";
6
+ import { UserIcon } from "@hugeicons/core-free-icons";
7
7
  import { t } from "../../../../../i18n";
8
8
  import type { CardProfileProps } from "../../types";
9
9
  import CardStarRating from "../../shared/CardStarRating.svelte";
@@ -12,6 +12,9 @@
12
12
  import CardButton from "../../shared/CardButton.svelte";
13
13
  import CardFavorite from "../../shared/CardFavorite.svelte";
14
14
  import Skeleton from "../../../../ui/skeleton/skeleton.svelte";
15
+ import CardPhoneOrWhatsapp from "../../shared/CardPhoneOrWhatsapp.svelte";
16
+ import CardThumbnailVideo from "../../shared/CardThumbnailVideo.svelte";
17
+ import ImgPlaceholder from "../../../../../assets/placeholder-image.png";
15
18
 
16
19
  let {
17
20
  id,
@@ -24,93 +27,110 @@
24
27
  isTagBorderBottom = true,
25
28
  isFavorite = false,
26
29
  buttonProfileClass,
27
- buttonDetailsClass,
28
30
  isLoading = false,
31
+ imageUrl,
32
+ videoUrl,
33
+ isImageButtonMaximized = false,
34
+ isVideoButtonMaximized = false,
35
+ isDescriptionIcon = false,
36
+ isDescriptionLabel = false,
37
+ onEmailClick,
38
+ onWhatsappClick,
29
39
  onFavoriteClick,
30
40
  onButtonProfile,
31
- onButtonDetails,
32
41
  }: CardProfileProps = $props();
33
-
34
- let isShowButtonProfileAndDetails = $derived(
35
- onButtonProfile != null && onButtonDetails != null,
36
- );
37
42
  </script>
38
43
 
39
44
  <article
40
- class="border border-gray-300 dark:border-gray-700 bg-gray-100 dark:bg-transparent rounded-lg p-5 space-y-1 md:space-y-3"
45
+ class="border border-gray-300 dark:border-gray-700 bg-gray-100 dark:bg-transparent rounded-lg relative"
41
46
  >
42
- <div class="flex justify-between items-center w-full mb-4">
43
- <div class="flex justify-start gap-2">
44
- <div class="flex justify-center items-center">
45
- {#if isLoading}
46
- <Skeleton class="w-12 h-12 rounded-lg bg-gray-300" />
47
- {:else if logo}
48
- <img src={logo} alt={logo} class="w-12 h-12 rounded-lg" />
49
- {/if}
47
+ <div class="p-5">
48
+ <div class="flex justify-between items-center w-full">
49
+ <div class="w-full flex flex-col justify-center items-center relative">
50
+ <div>
51
+ {#if isLoading}
52
+ <Skeleton
53
+ class="w-12 h-12 md:w-14 md:h-14 lg:w-20 lg:h-20 rounded-full bg-gray-300"
54
+ />
55
+ {:else if imageUrl}
56
+ <img
57
+ src={imageUrl}
58
+ alt={imageUrl}
59
+ class="w-12 h-12 md:w-14 md:h-14 lg:w-20 lg:h-20 rounded-full"
60
+ onerror={(e) =>
61
+ ((e.target as HTMLImageElement).src = ImgPlaceholder)}
62
+ />
63
+ {/if}
64
+ </div>
65
+ <div>
66
+ {#if title}
67
+ <div class="text-center font-bold">{title}</div>
68
+ {:else if isLoading}
69
+ <Skeleton class="w-40 h-5 rounded-lg bg-gray-400 mb-0.5" />
70
+ {/if}
71
+ </div>
72
+ <div>
73
+ {#if startNumber}
74
+ <CardStarRating {startNumber} {startMax} {isLoading} />
75
+ {:else if isLoading}
76
+ <Skeleton class="w-20 h-4 rounded-lg bg-gray-300 mt-0.5" />
77
+ {/if}
78
+ </div>
50
79
  </div>
51
- <div class="flex-col">
52
- {#if title}
53
- <div class="text-center font-bold text-lg">{title}</div>
54
- {:else if isLoading}
55
- <Skeleton class="w-40 h-5 rounded-lg bg-gray-400 mb-0.5" />
56
- {/if}
57
80
 
58
- {#if startNumber}
59
- <CardStarRating {startNumber} {startMax} {isLoading} />
60
- {:else if isLoading}
61
- <Skeleton class="w-20 h-4 rounded-lg bg-gray-300 mt-0.5" />
62
- {/if}
81
+ <div class="flex justify-end items-center absolute top-1 right-1">
82
+ <CardFavorite
83
+ {id}
84
+ {isFavorite}
85
+ onFavoriteClick={() => onFavoriteClick!(id)}
86
+ {isLoading}
87
+ />
63
88
  </div>
64
89
  </div>
65
- <div class="flex justify-end items-center">
66
- <CardFavorite
67
- {id}
68
- {isFavorite}
69
- onFavoriteClick={() => onFavoriteClick!(id)}
90
+
91
+ <div class="my-2">
92
+ <CardDescription
93
+ {content}
70
94
  {isLoading}
95
+ {isDescriptionIcon}
96
+ {isDescriptionLabel}
97
+ lines={tags?.length == 0 ? 3 : 2}
71
98
  />
72
99
  </div>
73
- </div>
74
100
 
75
- <div class="h-15 my-2">
76
- <CardDescription {content} {isLoading} />
77
- </div>
101
+ <CardTags
102
+ className="my-4"
103
+ tags={tags ?? []}
104
+ {isTagBorderBottom}
105
+ {isLoading}
106
+ />
78
107
 
79
- <CardTags
80
- className="my-6"
81
- tags={tags ?? []}
82
- {isTagBorderBottom}
83
- {isLoading}
84
- />
108
+ <CardThumbnailVideo
109
+ {imageUrl}
110
+ {videoUrl}
111
+ {isImageButtonMaximized}
112
+ {isVideoButtonMaximized}
113
+ {isLoading}
114
+ class="md:h-32.5 lg:h-44"
115
+ />
85
116
 
86
- {#if onButtonProfile || onButtonDetails}
87
- <div class="flex justify-center items-center gap-2 mt-4 w-full">
88
- {#if onButtonProfile}
89
- <CardButton
90
- {id}
91
- icon={UserIcon}
92
- className={buttonProfileClass}
93
- text={$t("label.view.profile")}
94
- onClick={onButtonProfile}
95
- isFlex={isShowButtonProfileAndDetails}
96
- {isLoading}
97
- />
98
- {:else if isLoading}
99
- <Skeleton class="w-20 h-4 rounded-lg bg-gray-400 mt-0.5" />
100
- {/if}
101
- {#if onButtonDetails}
102
- <CardButton
103
- {id}
104
- icon={InformationCircleIcon}
105
- className={buttonDetailsClass}
106
- text={$t("label.more.details")}
107
- onClick={onButtonDetails}
108
- isFlex={isShowButtonProfileAndDetails}
109
- {isLoading}
110
- />
111
- {:else if isLoading}
112
- <Skeleton class="w-20 h-4 rounded-lg bg-gray-400 mt-0.5" />
113
- {/if}
114
- </div>
115
- {/if}
117
+ {#if onButtonProfile}
118
+ <div class="flex justify-center items-center gap-2 w-full mt-3 -mb-1">
119
+ {#if onButtonProfile}
120
+ <CardButton
121
+ {id}
122
+ icon={UserIcon}
123
+ className={buttonProfileClass}
124
+ text={$t("label.view.profile")}
125
+ onClick={onButtonProfile}
126
+ {isLoading}
127
+ isFlex
128
+ />
129
+ {:else if isLoading}
130
+ <Skeleton class="w-20 h-4 rounded-lg bg-gray-400 mt-0.5" />
131
+ {/if}
132
+ </div>
133
+ {/if}
134
+ </div>
135
+ <CardPhoneOrWhatsapp {id} {onEmailClick} {onWhatsappClick} />
116
136
  </article>
@@ -12,6 +12,10 @@
12
12
  import CardButton from "../../shared/CardButton.svelte";
13
13
  import CardFavorite from "../../shared/CardFavorite.svelte";
14
14
  import Skeleton from "../../../../ui/skeleton/skeleton.svelte";
15
+ import CardThumbnailVideo from "../../shared/CardThumbnailVideo.svelte";
16
+ import CardPhoneOrWhatsapp from "../../shared/CardPhoneOrWhatsapp.svelte";
17
+ import ImgPlaceholder from "../../../../../assets/placeholder-image.png";
18
+
15
19
  let {
16
20
  id,
17
21
  tags,
@@ -24,45 +28,56 @@
24
28
  isTagBorderBottom = true,
25
29
  isFavorite = false,
26
30
  buttonProfileClass,
27
- buttonDetailsClass,
31
+ onEmailClick,
32
+ onWhatsappClick,
28
33
  isLoading = false,
29
34
  onFavoriteClick,
30
35
  onButtonProfile,
31
- onButtonDetails,
36
+ imageUrl,
37
+ videoUrl,
38
+ isImageButtonMaximized = false,
39
+ isVideoButtonMaximized = false,
40
+ isDescriptionLabel = false,
41
+ isDescriptionIcon = false,
32
42
  }: CardProfileProps = $props();
33
-
34
- let isShowButtonProfileAndDetails = $derived(
35
- onButtonProfile != null && onButtonDetails != null,
36
- );
37
43
  </script>
38
44
 
39
45
  <article
40
- class="border border-gray-300 dark:border-gray-700 bg-gray-100 dark:bg-transparent rounded-lg p-5 space-y-1 md:space-y-3 {className}"
46
+ class="border border-gray-300 dark:border-gray-700 bg-gray-100 dark:bg-transparent rounded-lg space-y-1 md:space-y-3 relative {className}"
41
47
  >
42
- <div class="flex flex-col justify-center items-center w-full relative">
43
- <div class="flex flex-col justify-start gap-2">
44
- <div class="flex justify-center items-center">
45
- {#if isLoading}
46
- <Skeleton class="w-12 h-12 rounded-lg bg-gray-400 mt-0.5" />
47
- {:else if logo}
48
- <img src={logo} alt={logo} class="w-12 h-12 rounded-lg" />
49
- {/if}
50
- </div>
51
- <div class="flex flex-col justify-center items-center">
52
- {#if title}
53
- <div class="text-center font-bold text-lg">{title}</div>
54
- {:else if isLoading}
55
- <Skeleton class="w-20 h-4 rounded-lg bg-gray-400 mt-0.5" />
56
- {/if}
48
+ <div class="p-5">
49
+ <div class="flex justify-start items-center w-full relative">
50
+ <div class="flex justify-start gap-2">
51
+ <div class="flex justify-center items-center">
52
+ {#if isLoading}
53
+ <Skeleton class="w-12 h-12 rounded-lg bg-gray-400 mt-0.5" />
54
+ {:else if imageUrl}
55
+ <img
56
+ src={imageUrl}
57
+ alt={imageUrl}
58
+ class="w-12 h-12 rounded-lg"
59
+ onerror={(e) =>
60
+ ((e.target as HTMLImageElement).src = ImgPlaceholder)}
61
+ />
62
+ {/if}
63
+ </div>
64
+ <div class="flex flex-col justify-start items-start">
65
+ {#if title}
66
+ <div class="text-left font-bold">{title}</div>
67
+ {:else if isLoading}
68
+ <Skeleton class="w-20 h-4 rounded-lg bg-gray-400 mt-0.5" />
69
+ {/if}
57
70
 
58
- {#if startNumber}
59
- <CardStarRating {startNumber} {startMax} {isLoading} />
60
- {:else if isLoading}
61
- <Skeleton class="w-20 h-4 rounded-lg bg-gray-300 mt-0.5" />
62
- {/if}
71
+ {#if startNumber}
72
+ <CardStarRating {startNumber} {startMax} {isLoading} />
73
+ {:else if isLoading}
74
+ <Skeleton class="w-20 h-4 rounded-lg bg-gray-300 mt-0.5" />
75
+ {/if}
76
+ </div>
63
77
  </div>
64
78
  </div>
65
- <div class="flex justify-end items-center absolute top-0 right-0">
79
+
80
+ <div class="flex justify-end items-center absolute top-1 right-1">
66
81
  <CardFavorite
67
82
  {id}
68
83
  {isFavorite}
@@ -70,45 +85,57 @@
70
85
  {isLoading}
71
86
  />
72
87
  </div>
73
- </div>
74
88
 
75
- <div class="h-15 mb-8">
76
- <CardDescription {content} {isLoading} />
77
- </div>
89
+ <div class="mb-4">
90
+ <CardDescription
91
+ {content}
92
+ {isLoading}
93
+ {isDescriptionLabel}
94
+ {isDescriptionIcon}
95
+ />
96
+ </div>
78
97
 
79
- <CardTags
80
- className="my-4"
81
- tags={tags ?? []}
82
- {isTagBorderBottom}
83
- {isLoading}
84
- />
98
+ {#if tags && tags.length > 0}
99
+ <CardTags
100
+ className="my-4"
101
+ tags={tags ?? []}
102
+ {isTagBorderBottom}
103
+ {isLoading}
104
+ />
105
+ {:else}
106
+ <CardTags
107
+ className="my-4"
108
+ tags={[{ text: "- - -" }]}
109
+ {isTagBorderBottom}
110
+ {isLoading}
111
+ />
112
+ {/if}
85
113
 
86
- {#if onButtonProfile || onButtonDetails}
87
- <div class="flex justify-center items-center gap-2 mt-4 w-full">
88
- {#if onButtonProfile}
89
- <CardButton
90
- {id}
91
- icon={UserIcon}
92
- className={buttonProfileClass}
93
- text={$t("label.view.profile")}
94
- onClick={onButtonProfile}
95
- isFlex={isShowButtonProfileAndDetails}
96
- />
97
- {:else if isLoading}
98
- <Skeleton class="w-20 h-4 rounded-lg bg-gray-400 mt-0.5" />
99
- {/if}
100
- {#if onButtonDetails}
101
- <CardButton
102
- {id}
103
- icon={InformationCircleIcon}
104
- className={buttonDetailsClass}
105
- text={$t("label.more.details")}
106
- onClick={onButtonDetails}
107
- isFlex={isShowButtonProfileAndDetails}
108
- />
109
- {:else if isLoading}
110
- <Skeleton class="w-20 h-4 rounded-lg bg-gray-400 mt-0.5" />
111
- {/if}
112
- </div>
113
- {/if}
114
+ <CardThumbnailVideo
115
+ {imageUrl}
116
+ {videoUrl}
117
+ {isImageButtonMaximized}
118
+ {isVideoButtonMaximized}
119
+ {isLoading}
120
+ class="md:h-32.5 lg:h-44"
121
+ />
122
+
123
+ {#if onButtonProfile}
124
+ <div class="flex justify-center items-center gap-2 mt-2 -mb-2 w-full">
125
+ {#if onButtonProfile}
126
+ <CardButton
127
+ {id}
128
+ icon={UserIcon}
129
+ className={buttonProfileClass}
130
+ text={$t("label.view.profile")}
131
+ onClick={onButtonProfile}
132
+ isFlex
133
+ />
134
+ {:else if isLoading}
135
+ <Skeleton class="w-20 h-4 rounded-lg bg-gray-400 mt-0.5" />
136
+ {/if}
137
+ </div>
138
+ {/if}
139
+ </div>
140
+ <CardPhoneOrWhatsapp {id} {onEmailClick} {onWhatsappClick} />
114
141
  </article>
@@ -2,7 +2,7 @@
2
2
  import type { CardProfileProps } from "../types";
3
3
  import CardProfile01 from "./01/CardProfile01.svelte";
4
4
  import CardProfile02 from "./02/CardProfile02.svelte";
5
-
5
+
6
6
  type Props = {
7
7
  variant?: 1 | 2;
8
8
  isLoading?: boolean;
@@ -8,6 +8,8 @@ const items = [
8
8
  startNumber: 5,
9
9
  startMax: 5,
10
10
  isFavorite: true,
11
+ imageUrl: 'https://api.dicebear.com/9.x/initials/svg?seed=Hotel%20Baía%20Azul&backgroundColor=1a2420&textColor=e3b563',
12
+ videoUrl: 'https://api.dicebear.com/9.x/initials/svg?seed=Hotel%20Baía%20Azul&backgroundColor=1a2420&textColor=e3b563',
11
13
  tags: [
12
14
  {
13
15
  icon: GlobalIcon,
@@ -30,6 +32,8 @@ const items = [
30
32
  content: 'Suítes modernas no coração da cidade, com spa completo e serviço de quarto 24 horas.',
31
33
  startNumber: 4,
32
34
  startMax: 5,
35
+ imageUrl: 'https://api.dicebear.com/9.x/initials/svg?seed=Hotel%20Baía%20Azul&backgroundColor=1a2420&textColor=e3b563',
36
+ videoUrl: 'https://api.dicebear.com/9.x/initials/svg?seed=Hotel%20Baía%20Azul&backgroundColor=1a2420&textColor=e3b563',
33
37
  tags: [
34
38
  {
35
39
  icon: GlobalIcon,
@@ -57,6 +61,8 @@ const items = [
57
61
  startNumber: 5,
58
62
  startMax: 5,
59
63
  isFavorite: true,
64
+ imageUrl: 'https://api.dicebear.com/9.x/initials/svg?seed=Hotel%20Baía%20Azul&backgroundColor=1a2420&textColor=e3b563',
65
+ videoUrl: 'https://api.dicebear.com/9.x/initials/svg?seed=Hotel%20Baía%20Azul&backgroundColor=1a2420&textColor=e3b563',
60
66
  tags: [
61
67
  {
62
68
  icon: GlobalIcon,
@@ -75,6 +81,8 @@ const items = [
75
81
  content: 'Localização privilegiada no centro urbano, ideal para viagens de negócios, com centro de conferências.',
76
82
  startNumber: 3,
77
83
  startMax: 5,
84
+ imageUrl: 'https://api.dicebear.com/9.x/initials/svg?seed=Hotel%20Baía%20Azul&backgroundColor=1a2420&textColor=e3b563',
85
+ videoUrl: 'https://api.dicebear.com/9.x/initials/svg?seed=Hotel%20Baía%20Azul&backgroundColor=1a2420&textColor=e3b563',
78
86
  tags: [
79
87
  {
80
88
  icon: NecklaceIcon,
@@ -102,6 +110,8 @@ const items = [
102
110
  startNumber: 4,
103
111
  startMax: 5,
104
112
  isFavorite: true,
113
+ imageUrl: 'https://api.dicebear.com/9.x/initials/svg?seed=Hotel%20Baía%20Azul&backgroundColor=1a2420&textColor=e3b563',
114
+ videoUrl: 'https://api.dicebear.com/9.x/initials/svg?seed=Hotel%20Baía%20Azul&backgroundColor=1a2420&textColor=e3b563',
105
115
  },
106
116
  ];
107
117
  export default items;
@@ -6,6 +6,7 @@
6
6
  import Skeleton from "../../../ui/skeleton/skeleton.svelte";
7
7
 
8
8
  type CardDescriptionProps = {
9
+ lines?: number;
9
10
  content?: string;
10
11
  isLoading?: boolean;
11
12
  isDescriptionIcon?: boolean;
@@ -13,9 +14,10 @@
13
14
  };
14
15
 
15
16
  let {
17
+ lines = 2,
16
18
  content,
17
19
  isLoading = false,
18
- isDescriptionIcon = true,
20
+ isDescriptionIcon = false,
19
21
  isDescriptionLabel = true,
20
22
  }: CardDescriptionProps = $props();
21
23
  </script>
@@ -27,12 +29,13 @@
27
29
  <div class="mb-1 text-[14px] md:text-[15px] flex items-center gap-1">
28
30
  {#if isDescriptionIcon}
29
31
  <HugeiconsIcon icon={Message01Icon} class="h-4 w-4" />
30
- {/if}11
32
+ {/if}
31
33
  <div>{$t("label.description")}</div>
32
34
  </div>
33
35
  {/if}
34
36
  <TruncatableText
35
37
  text={content}
36
- class="text-justify text-[13px] md:text-[14px] text-gray-800 dark:text-gray-50 mt-3 my-4"
38
+ {lines}
39
+ class="text-justify text-[13px] md:text-[14px] text-gray-800 dark:text-gray-50 mt-3 my-2"
37
40
  />
38
41
  {/if}
@@ -1,4 +1,5 @@
1
1
  type CardDescriptionProps = {
2
+ lines?: number;
2
3
  content?: string;
3
4
  isLoading?: boolean;
4
5
  isDescriptionIcon?: boolean;
@@ -0,0 +1,38 @@
1
+ <script lang="ts">
2
+ import { Mail01Icon, WhatsappIcon } from "@hugeicons/core-free-icons";
3
+ import { HugeiconsIcon } from "@hugeicons/svelte";
4
+ import type { CardPhoneOrWhatsappProps } from "../types";
5
+
6
+ let { id, onEmailClick, onWhatsappClick }: CardPhoneOrWhatsappProps =
7
+ $props();
8
+
9
+ let isFlex = $derived(onEmailClick && onWhatsappClick);
10
+ </script>
11
+
12
+ <div class="flex border-t border-gray-300 dark:border-gray-700">
13
+ {#if onEmailClick}
14
+ <button
15
+ class="flex-1 flex justify-center items-center gap-2 p-3 hover:bg-red-800 hover:text-white cursor-pointer rounded-bl-lg {isFlex
16
+ ? 'rounded-br-lg'
17
+ : ''}"
18
+ onclick={() => onEmailClick?.(id)}
19
+ >
20
+ <HugeiconsIcon icon={Mail01Icon} size={20} />
21
+ <span>Email</span>
22
+ </button>
23
+ {/if}
24
+ {#if isFlex}
25
+ <div class="border-l border-gray-300 dark:border-gray-700"></div>
26
+ {/if}
27
+ {#if onWhatsappClick}
28
+ <button
29
+ class="flex-1 flex justify-center items-center gap-2 p-3 hover:bg-green-600 hover:text-white cursor-pointer rounded-br-lg {isFlex
30
+ ? 'rounded-bl-lg'
31
+ : ''}"
32
+ onclick={() => onWhatsappClick?.(id)}
33
+ >
34
+ <HugeiconsIcon icon={WhatsappIcon} size={20} />
35
+ <span>Whatsapp</span>
36
+ </button>
37
+ {/if}
38
+ </div>
@@ -0,0 +1,4 @@
1
+ import type { CardPhoneOrWhatsappProps } from "../types";
2
+ declare const CardPhoneOrWhatsapp: import("svelte").Component<CardPhoneOrWhatsappProps, {}, "">;
3
+ type CardPhoneOrWhatsapp = ReturnType<typeof CardPhoneOrWhatsapp>;
4
+ export default CardPhoneOrWhatsapp;
@@ -19,7 +19,7 @@
19
19
  />
20
20
  {/if}
21
21
  {#if newPrice && oldPrice}
22
- <div class="z-20 flex justify-center items-center">
22
+ <div class="z-20 flex justify-start items-center">
23
23
  <div class="font-extrabold text-lg">{newPrice}</div>
24
24
  <div class="mx-2">/</div>
25
25
  <div class="font-semibold line-through text-red-600 text-[10px]">
@@ -12,7 +12,7 @@
12
12
  };
13
13
 
14
14
  const {
15
- tags,
15
+ tags = [],
16
16
  className,
17
17
  isTagBorderBottom,
18
18
  isWrap = false,
@@ -55,7 +55,7 @@
55
55
  </div>
56
56
  {:else}
57
57
  <div
58
- class="flex overflow-x-auto no-scrollbar w-full
58
+ class="flex overflow-x-auto no-scrollbar w-full min-w-0
59
59
  {isJustify ? 'justify-between gap-3' : 'gap-4 md:gap-5 lg:gap-10'}
60
60
  {className}"
61
61
  >
@@ -1,56 +1,48 @@
1
1
  <!-- shared/TruncatableText.svelte -->
2
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";
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
11
 
12
- type Props = { text: string; class?: string; lines?: 2 | 3 | 4 };
13
- let { text, class: className = "", lines = 2 }: Props = $props();
12
+ type Props = { text: string; class?: string; lines?: number };
13
+ let { text, class: className = "", lines = 2 }: Props = $props();
14
14
 
15
- // Tailwind precisa de classes literais no código-fonte para as gerar —
16
- // "line-clamp-{lines}" interpolado nunca é detectado pelo scanner.
17
- const clampClasses = {
18
- 2: "line-clamp-2",
19
- 3: "line-clamp-3",
20
- 4: "line-clamp-4",
21
- } as const;
15
+ let textEl: HTMLParagraphElement | undefined = $state();
16
+ let isTruncated = $state(false);
22
17
 
23
- let textEl: HTMLParagraphElement | undefined = $state();
24
- let isTruncated = $state(false);
25
-
26
- $effect(() => {
27
- if (!textEl) return;
28
- const check = () =>
29
- (isTruncated = textEl!.scrollHeight > textEl!.clientHeight + 1);
30
- // requestAnimationFrame garante que o layout com o clamp já foi aplicado
31
- // antes de medirmos — medir no mesmo tick pode dar valores desatualizados.
32
- requestAnimationFrame(check);
33
- const ro = new ResizeObserver(check);
34
- ro.observe(textEl);
35
- return () => ro.disconnect();
36
- });
18
+ $effect(() => {
19
+ if (!textEl) return;
20
+ const check = () =>
21
+ (isTruncated = textEl!.scrollHeight > textEl!.clientHeight + 1);
22
+ // requestAnimationFrame garante que o layout com o clamp já foi aplicado
23
+ // antes de medirmos — medir no mesmo tick pode dar valores desatualizados.
24
+ requestAnimationFrame(check);
25
+ const ro = new ResizeObserver(check);
26
+ ro.observe(textEl);
27
+ return () => ro.disconnect();
28
+ });
37
29
  </script>
38
30
 
39
31
  <div class="relative">
40
- <p bind:this={textEl} class="{clampClasses[lines]} {className}">
41
- {text}
42
- </p>
32
+ <p bind:this={textEl} class="line-clamp-{lines} {className}">
33
+ {text}
34
+ </p>
43
35
 
44
- {#if isTruncated}
45
- <Popover.Root>
46
- <Popover.Trigger
47
- class="absolute bottom-0 right-0 bg-background pl-1 text-[11px] font-medium text-primary underline underline-offset-2 cursor-pointer"
48
- >
49
- {$t("label.view.full")}
50
- </Popover.Trigger>
51
- <Popover.Content>
52
- <p class="max-w-xs text-justify text-[12px]">{text}</p>
53
- </Popover.Content>
54
- </Popover.Root>
55
- {/if}
56
- </div>
36
+ {#if isTruncated}
37
+ <Popover.Root>
38
+ <Popover.Trigger
39
+ class="absolute bottom-0 right-0 bg-background pl-1 text-[11px] font-medium text-primary underline underline-offset-2 cursor-pointer"
40
+ >
41
+ {$t("label.view.full")}
42
+ </Popover.Trigger>
43
+ <Popover.Content>
44
+ <p class="max-w-xs text-justify text-[12px]">{text}</p>
45
+ </Popover.Content>
46
+ </Popover.Root>
47
+ {/if}
48
+ </div>
@@ -1,7 +1,7 @@
1
1
  type Props = {
2
2
  text: string;
3
3
  class?: string;
4
- lines?: 2 | 3 | 4;
4
+ lines?: number;
5
5
  };
6
6
  declare const TruncatableText: import("svelte").Component<Props, {}, "">;
7
7
  type TruncatableText = ReturnType<typeof TruncatableText>;
@@ -86,14 +86,24 @@ export interface CardProfileProps {
86
86
  isTagBorderBottom?: boolean;
87
87
  /** Classe CSS personalizada para botão de perfil */
88
88
  buttonProfileClass?: string;
89
- /** Classe CSS personalizada para botão de detalhes */
90
- buttonDetailsClass?: string;
91
89
  /** Callback ao clicar no botão favorito */
92
90
  onFavoriteClick?: (id: string | number) => void;
93
91
  /** Callback ao clicar no botão perfil */
94
92
  onButtonProfile?: (id: string | number) => void;
95
- /** Callback ao clicar no botão detalhes */
96
- onButtonDetails?: (id: string | number) => void;
93
+ /** Callback ao clicar no botão email */
94
+ onEmailClick?: (id: string | number) => void;
95
+ /** Callback ao clicar no botão whatsapp */
96
+ onWhatsappClick?: (id: string | number) => void;
97
+ /** URL da foto/imagem do perfil */
98
+ imageUrl?: string;
99
+ /** URL do vídeo do perfil */
100
+ videoUrl?: string;
101
+ /** Indica se o botão de maximizar imagem está ativo */
102
+ isImageButtonMaximized?: boolean;
103
+ /** Indica se o botão de maximizar vídeo está ativo */
104
+ isVideoButtonMaximized?: boolean;
105
+ isDescriptionIcon?: boolean;
106
+ isDescriptionLabel?: boolean;
97
107
  }
98
108
  /**
99
109
  * Props do CardPromotion — card de promoção com preço antigo/novo,
@@ -177,3 +187,8 @@ export interface CardHighlightProps {
177
187
  /** Callback ao clicar no botão principal */
178
188
  onClickBtn?: (id: string | number) => void;
179
189
  }
190
+ export interface CardPhoneOrWhatsappProps {
191
+ id: string | number;
192
+ onEmailClick?: (id: string | number) => void;
193
+ onWhatsappClick?: (id: string | number) => void;
194
+ }
@@ -21,7 +21,7 @@
21
21
  <CarouselHeader {...headerProps}>
22
22
  <CarouselGridSlot {slotProps} {gridClass} totalItems={items.length}>
23
23
  {#if isLoading}
24
- {#each Array.from( { length: responsive.isMobile ? 5 : 9 }, ) as _, i (`loading-${i}`)}
24
+ {#each Array.from( { length: responsive.isMobile ? 3 : 6 }, ) as _, i (`loading-${i}`)}
25
25
  {#if responsive.isMobile}
26
26
  <Carousel.Item class="pl-2 basis-auto">
27
27
  <CardMedia id={i} {variant} isLoading />
@@ -18,4 +18,13 @@ export interface CarouselGridProfileProps {
18
18
  items: CardProfileProps[];
19
19
  /** Estado de carregamento (skeleton) */
20
20
  isLoading?: boolean;
21
+ isDescriptionIcon?: boolean;
22
+ isDescriptionLabel?: boolean;
23
+ onFavoriteClick?: (id: string | number) => void;
24
+ /** Callback ao clicar no botão perfil */
25
+ onButtonProfile?: (id: string | number) => void;
26
+ /** Callback ao clicar no botão email */
27
+ onEmailClick?: (id: string | number) => void;
28
+ /** Callback ao clicar no botão whatsapp */
29
+ onWhatsappClick?: (id: string | number) => void;
21
30
  }
@@ -9,10 +9,16 @@
9
9
  const {
10
10
  headerProps,
11
11
  slotProps,
12
- gridClass,
12
+ gridClass = "lg:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-5",
13
13
  items,
14
14
  variant,
15
15
  isLoading = false,
16
+ isDescriptionIcon = false,
17
+ isDescriptionLabel = false,
18
+ onEmailClick,
19
+ onWhatsappClick,
20
+ onButtonProfile,
21
+ onFavoriteClick,
16
22
  }: CarouselGridProfileProps = $props();
17
23
 
18
24
  const responsive = useDevice();
@@ -21,7 +27,7 @@
21
27
  <CarouselHeader {...headerProps}>
22
28
  <CarouselGridSlot {slotProps} {gridClass} totalItems={items.length}>
23
29
  {#if isLoading}
24
- {#each Array.from( { length: responsive.isMobile ? 5 : 9 }, ) as _, i (`loading-${i}`)}
30
+ {#each Array.from( { length: responsive.isMobile ? 3 : 6 }, ) as _, i (`loading-${i}`)}
25
31
  {#if responsive.isMobile}
26
32
  <Carousel.Item class="pl-2 basis-auto">
27
33
  <CardProfile id={i} {variant} isLoading />
@@ -36,11 +42,29 @@
36
42
  {#each items as item, i (`profile-${item.id ?? i}`)}
37
43
  {#if responsive.isMobile}
38
44
  <Carousel.Item class="pl-2 basis-auto">
39
- <CardProfile {...item} {variant} />
45
+ <CardProfile
46
+ {...item}
47
+ {variant}
48
+ {isDescriptionIcon}
49
+ {isDescriptionLabel}
50
+ {onEmailClick}
51
+ {onWhatsappClick}
52
+ {onButtonProfile}
53
+ {onFavoriteClick}
54
+ />
40
55
  </Carousel.Item>
41
56
  {:else}
42
57
  <div>
43
- <CardProfile {...item} {variant} />
58
+ <CardProfile
59
+ {...item}
60
+ {variant}
61
+ {isDescriptionIcon}
62
+ {isDescriptionLabel}
63
+ {onEmailClick}
64
+ {onWhatsappClick}
65
+ {onButtonProfile}
66
+ {onFavoriteClick}
67
+ />
44
68
  </div>
45
69
  {/if}
46
70
  {/each}
@@ -27,7 +27,7 @@
27
27
  >
28
28
  {#if isLoading}
29
29
  {#each Array.from( { length: responsive.isMobile ? 5 : 10 }, ) as _, i (`loading-${i}`)}
30
- <Carousel.Item class="pl-2 w-75 basis-auto">
30
+ <Carousel.Item class="pl-5 w-75 basis-auto">
31
31
  <CardPromotion
32
32
  id={i}
33
33
  {isLoading}
package/dist/globals.css CHANGED
@@ -7,9 +7,11 @@
7
7
  "Courier New", monospace;
8
8
  --color-red-500: oklch(63.7% 0.237 25.331);
9
9
  --color-red-600: oklch(57.7% 0.245 27.325);
10
+ --color-red-800: oklch(44.4% 0.177 26.899);
10
11
  --color-red-900: oklch(39.6% 0.141 25.723);
11
12
  --color-yellow-500: oklch(79.5% 0.184 86.047);
12
13
  --color-green-500: oklch(72.3% 0.219 149.579);
14
+ --color-green-600: oklch(62.7% 0.194 149.214);
13
15
  --color-blue-50: oklch(97% 0.014 254.604);
14
16
  --color-blue-400: oklch(70.7% 0.165 254.624);
15
17
  --color-blue-500: oklch(62.3% 0.214 259.815);
@@ -539,6 +541,12 @@
539
541
  .mr-2 {
540
542
  margin-right: calc(var(--spacing) * 2);
541
543
  }
544
+ .-mb-1 {
545
+ margin-bottom: calc(var(--spacing) * -1);
546
+ }
547
+ .-mb-2 {
548
+ margin-bottom: calc(var(--spacing) * -2);
549
+ }
542
550
  .-mb-8 {
543
551
  margin-bottom: calc(var(--spacing) * -8);
544
552
  }
@@ -585,24 +593,6 @@
585
593
  -webkit-box-orient: vertical;
586
594
  -webkit-line-clamp: 1;
587
595
  }
588
- .line-clamp-2 {
589
- overflow: hidden;
590
- display: -webkit-box;
591
- -webkit-box-orient: vertical;
592
- -webkit-line-clamp: 2;
593
- }
594
- .line-clamp-3 {
595
- overflow: hidden;
596
- display: -webkit-box;
597
- -webkit-box-orient: vertical;
598
- -webkit-line-clamp: 3;
599
- }
600
- .line-clamp-4 {
601
- overflow: hidden;
602
- display: -webkit-box;
603
- -webkit-box-orient: vertical;
604
- -webkit-line-clamp: 4;
605
- }
606
596
  .block {
607
597
  display: block;
608
598
  }
@@ -1437,6 +1427,9 @@
1437
1427
  .rounded-tr-lg {
1438
1428
  border-top-right-radius: var(--radius);
1439
1429
  }
1430
+ .rounded-br-lg {
1431
+ border-bottom-right-radius: var(--radius);
1432
+ }
1440
1433
  .rounded-bl-lg {
1441
1434
  border-bottom-left-radius: var(--radius);
1442
1435
  }
@@ -2999,6 +2992,9 @@
2999
2992
  background-color: color-mix(in oklab, var(--destructive) 20%, transparent);
3000
2993
  }
3001
2994
  }
2995
+ .hover\:bg-green-600:hover {
2996
+ background-color: var(--color-green-600);
2997
+ }
3002
2998
  .hover\:bg-input\/50:hover {
3003
2999
  background-color: var(--input);
3004
3000
  }
@@ -3037,6 +3033,9 @@
3037
3033
  background-color: color-mix(in oklab, var(--primary) 80%, transparent);
3038
3034
  }
3039
3035
  }
3036
+ .hover\:bg-red-800:hover {
3037
+ background-color: var(--color-red-800);
3038
+ }
3040
3039
  .hover\:bg-secondary\/80:hover {
3041
3040
  background-color: var(--secondary);
3042
3041
  }
@@ -3835,9 +3834,15 @@
3835
3834
  .md\:flex {
3836
3835
  display: flex;
3837
3836
  }
3837
+ .md\:h-14 {
3838
+ height: calc(var(--spacing) * 14);
3839
+ }
3838
3840
  .md\:h-20 {
3839
3841
  height: calc(var(--spacing) * 20);
3840
3842
  }
3843
+ .md\:h-32\.5 {
3844
+ height: calc(var(--spacing) * 32.5);
3845
+ }
3841
3846
  .md\:h-50 {
3842
3847
  height: calc(var(--spacing) * 50);
3843
3848
  }
@@ -3865,6 +3870,9 @@
3865
3870
  .md\:w-11\/12 {
3866
3871
  width: calc(11 / 12 * 100%);
3867
3872
  }
3873
+ .md\:w-14 {
3874
+ width: calc(var(--spacing) * 14);
3875
+ }
3868
3876
  .md\:w-20 {
3869
3877
  width: calc(var(--spacing) * 20);
3870
3878
  }
@@ -4009,15 +4017,24 @@
4009
4017
  .lg\:block {
4010
4018
  display: block;
4011
4019
  }
4020
+ .lg\:h-20 {
4021
+ height: calc(var(--spacing) * 20);
4022
+ }
4012
4023
  .lg\:h-24 {
4013
4024
  height: calc(var(--spacing) * 24);
4014
4025
  }
4026
+ .lg\:h-44 {
4027
+ height: calc(var(--spacing) * 44);
4028
+ }
4015
4029
  .lg\:h-105 {
4016
4030
  height: calc(var(--spacing) * 105);
4017
4031
  }
4018
4032
  .lg\:h-225 {
4019
4033
  height: calc(var(--spacing) * 225);
4020
4034
  }
4035
+ .lg\:w-20 {
4036
+ width: calc(var(--spacing) * 20);
4037
+ }
4021
4038
  .lg\:w-32 {
4022
4039
  width: calc(var(--spacing) * 32);
4023
4040
  }
@@ -4057,6 +4074,11 @@
4057
4074
  grid-template-columns: repeat(4, minmax(0, 1fr));
4058
4075
  }
4059
4076
  }
4077
+ @media (width >= 96rem) {
4078
+ .\32 xl\:grid-cols-5 {
4079
+ grid-template-columns: repeat(5, minmax(0, 1fr));
4080
+ }
4081
+ }
4060
4082
  .ltr\:fill-current:where(:dir(ltr), [dir="ltr"], [dir="ltr"] *) {
4061
4083
  fill: currentcolor;
4062
4084
  }
@@ -17191,7 +17213,6 @@
17191
17213
  *
17192
17214
  * Copyright (c) 2020 Animate.css
17193
17215
  */
17194
- @charset "UTF-8";
17195
17216
  :root {
17196
17217
  --animate-duration: 1s;
17197
17218
  --animate-delay: 1s;
@@ -1,5 +1,3 @@
1
- @charset "UTF-8";
2
-
3
1
  /*!
4
2
  * animate.css - https://animate.style/
5
3
  * Version - 4.1.1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "negodesign",
3
- "version": "0.0.32",
3
+ "version": "0.0.34",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "sideEffects": [