@r2digisolutions/components 0.19.2 → 0.19.4

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.
@@ -16,11 +16,20 @@
16
16
  priceLabel: string;
17
17
  categoryLabel?: string;
18
18
  installed?: boolean;
19
+ /**
20
+ * Estado legible en el listado (p. ej. «Activo», «En licencia», «No incluido»).
21
+ * Si no se pasa y `installed`, se usa «Activo».
22
+ */
23
+ statusLabel?: string;
19
24
  /** landscape promotional card */
20
25
  featured?: boolean;
26
+ /** Compact row for list view (ignored when featured). */
27
+ layout?: 'grid' | 'list';
21
28
  tags?: StoreProductTag[];
22
29
  icon: Component;
23
30
  accentClass?: string;
31
+ /** Prefer link navigation when set (store detail URLs). */
32
+ href?: string;
24
33
  onclick?: () => void;
25
34
  /** Optional custom badge area */
26
35
  badge?: Snippet;
@@ -32,10 +41,13 @@
32
41
  priceLabel,
33
42
  categoryLabel = '',
34
43
  installed = false,
44
+ statusLabel = '',
35
45
  featured = false,
46
+ layout = 'grid',
36
47
  tags = [],
37
48
  icon: Icon,
38
49
  accentClass = 'from-neutral-800 to-neutral-600',
50
+ href = '',
39
51
  onclick,
40
52
  badge
41
53
  }: Props = $props();
@@ -51,9 +63,25 @@
51
63
  TAG_PRIORITY.filter((t) => visibleTags.includes(t)).slice(0, installed ? 1 : 2)
52
64
  );
53
65
 
66
+ const resolvedStatus = $derived(
67
+ statusLabel.trim() || (installed ? 'Activo' : '')
68
+ );
69
+ const statusToneOk = $derived(
70
+ installed || resolvedStatus === 'Activo' || resolvedStatus === 'Instalado'
71
+ );
72
+ const isList = $derived(!featured && layout === 'list');
73
+ const rootClass = $derived(
74
+ [
75
+ 'group ms-tile',
76
+ featured && 'ms-tile--featured',
77
+ isList && 'ms-tile--list'
78
+ ]
79
+ .filter(Boolean)
80
+ .join(' ')
81
+ );
82
+
54
83
  function tagVariant(tag: StoreProductTag): 'info' | 'warning' | 'default' {
55
84
  if (tag === 'new') return 'info';
56
- // Featured uses custom amber + white via tagClass (avoid warning’s amber text).
57
85
  if (tag === 'featured') return 'default';
58
86
  return 'default';
59
87
  }
@@ -67,18 +95,19 @@
67
95
  }
68
96
  return '';
69
97
  }
98
+
99
+ function onActivate(e: MouseEvent) {
100
+ if (href) return;
101
+ e.preventDefault();
102
+ onclick?.();
103
+ }
70
104
  </script>
71
105
 
72
- <button
73
- type="button"
74
- class="group ms-tile"
75
- class:ms-tile--featured={featured}
76
- {onclick}
77
- >
106
+ {#snippet body()}
78
107
  {#if featured}
79
108
  <div class="ms-tile__promo bg-gradient-to-br {accentClass}">
80
109
  <div class="ms-tile__promo-mesh" aria-hidden="true"></div>
81
- {#if displayTags.length || installed || badge}
110
+ {#if displayTags.length || resolvedStatus || badge}
82
111
  <div class="ms-tile__tags">
83
112
  {#if badge}
84
113
  {@render badge()}
@@ -88,8 +117,10 @@
88
117
  {TAG_LABELS[tag]}
89
118
  </Badge>
90
119
  {/each}
91
- {#if installed}
92
- <Badge variant="success" size="sm">Instalado</Badge>
120
+ {#if resolvedStatus}
121
+ <Badge variant={statusToneOk ? 'success' : 'secondary'} size="sm">
122
+ {resolvedStatus}
123
+ </Badge>
93
124
  {/if}
94
125
  {/if}
95
126
  </div>
@@ -102,19 +133,64 @@
102
133
  {#if description}
103
134
  <span class="ms-tile__promo-desc">{description}</span>
104
135
  {/if}
105
- <span class="ms-tile__promo-price">{priceLabel}</span>
136
+ <span class="ms-tile__promo-price">
137
+ {#if resolvedStatus && statusToneOk}
138
+ {resolvedStatus}
139
+ <span class="ms-tile__promo-sep" aria-hidden="true">·</span>
140
+ {/if}
141
+ {priceLabel}
142
+ </span>
106
143
  </div>
107
144
  <div class="ms-tile__promo-icon bg-gradient-to-br {accentClass}">
108
145
  <Icon class="size-10 text-white drop-shadow" />
109
146
  </div>
110
147
  </div>
148
+ {:else if isList}
149
+ <div class="ms-tile__list-icon bg-gradient-to-br {accentClass}">
150
+ <div class="ms-tile__icon-shine" aria-hidden="true"></div>
151
+ <Icon class="relative size-7 text-white drop-shadow-sm" />
152
+ </div>
153
+ <div class="ms-tile__list-body">
154
+ <div class="ms-tile__list-top">
155
+ <span class="ms-tile__name">{name}</span>
156
+ {#if resolvedStatus}
157
+ <Badge variant={statusToneOk ? 'success' : 'secondary'} size="sm">
158
+ {resolvedStatus}
159
+ </Badge>
160
+ {:else if displayTags[0]}
161
+ <Badge
162
+ variant={tagVariant(displayTags[0])}
163
+ size="sm"
164
+ class={tagClass(displayTags[0])}
165
+ >
166
+ {TAG_LABELS[displayTags[0]]}
167
+ </Badge>
168
+ {/if}
169
+ </div>
170
+ {#if description}
171
+ <span class="ms-tile__list-desc">{description}</span>
172
+ {/if}
173
+ <div class="ms-tile__list-meta">
174
+ {#if categoryLabel}
175
+ <span>{categoryLabel}</span>
176
+ <span aria-hidden="true">·</span>
177
+ {/if}
178
+ <span class="ms-tile__price">{priceLabel}</span>
179
+ <span aria-hidden="true">·</span>
180
+ <span class="ms-tile__publisher">Evoteg</span>
181
+ </div>
182
+ </div>
111
183
  {:else}
112
184
  <div class="ms-tile__icon-shell">
113
185
  <div class="ms-tile__icon bg-gradient-to-br {accentClass}">
114
186
  <div class="ms-tile__icon-shine" aria-hidden="true"></div>
115
187
  <Icon class="relative size-9 text-white drop-shadow-sm sm:size-10" />
116
188
  </div>
117
- {#if displayTags[0]}
189
+ {#if statusToneOk}
190
+ <span class="ms-tile__corner">
191
+ <Badge variant="success" size="sm">{resolvedStatus || 'Activo'}</Badge>
192
+ </span>
193
+ {:else if displayTags[0]}
118
194
  <span class="ms-tile__corner">
119
195
  <Badge
120
196
  variant={tagVariant(displayTags[0])}
@@ -124,17 +200,37 @@
124
200
  {TAG_LABELS[displayTags[0]]}
125
201
  </Badge>
126
202
  </span>
127
- {:else if installed}
128
- <span class="ms-tile__dot" title="Instalado" aria-label="Instalado"></span>
203
+ {:else if resolvedStatus}
204
+ <span class="ms-tile__corner">
205
+ <Badge variant="secondary" size="sm">{resolvedStatus}</Badge>
206
+ </span>
129
207
  {/if}
130
208
  </div>
131
209
  <div class="ms-tile__meta">
132
210
  <span class="ms-tile__name">{name}</span>
133
211
  <span class="ms-tile__publisher">Evoteg</span>
212
+ {#if resolvedStatus}
213
+ <span
214
+ class="ms-tile__status"
215
+ class:ms-tile__status--ok={statusToneOk}
216
+ >
217
+ {resolvedStatus}
218
+ </span>
219
+ {/if}
134
220
  <span class="ms-tile__price">{priceLabel}</span>
135
221
  </div>
136
222
  {/if}
137
- </button>
223
+ {/snippet}
224
+
225
+ {#if href}
226
+ <a class={rootClass} {href} data-sveltekit-preload-data="hover">
227
+ {@render body()}
228
+ </a>
229
+ {:else}
230
+ <button type="button" class={rootClass} onclick={onActivate}>
231
+ {@render body()}
232
+ </button>
233
+ {/if}
138
234
 
139
235
  <style>
140
236
  .ms-tile {
@@ -149,6 +245,8 @@
149
245
  padding: 0;
150
246
  cursor: pointer;
151
247
  border-radius: 1rem;
248
+ text-decoration: none;
249
+ color: inherit;
152
250
  transition:
153
251
  transform 200ms cubic-bezier(0.22, 1, 0.36, 1),
154
252
  filter 200ms ease;
@@ -160,6 +258,71 @@
160
258
  outline: 2px solid var(--color-brand-500, #2563eb);
161
259
  outline-offset: 4px;
162
260
  }
261
+ .ms-tile--list {
262
+ flex-direction: row;
263
+ align-items: center;
264
+ gap: 0.9rem;
265
+ padding: 0.75rem 0.85rem;
266
+ border-radius: 1rem;
267
+ background: rgb(255 255 255 / 0.78);
268
+ border: 1px solid rgb(0 0 0 / 0.06);
269
+ box-shadow: 0 6px 18px rgb(0 0 0 / 0.04);
270
+ }
271
+ .ms-tile--list:hover {
272
+ transform: translateY(-1px);
273
+ border-color: rgb(0 0 0 / 0.1);
274
+ }
275
+ :global(.dark) .ms-tile--list {
276
+ background: rgb(23 23 23 / 0.88);
277
+ border-color: rgb(255 255 255 / 0.06);
278
+ }
279
+ .ms-tile__list-icon {
280
+ position: relative;
281
+ display: grid;
282
+ place-items: center;
283
+ width: 3.25rem;
284
+ height: 3.25rem;
285
+ border-radius: 22%;
286
+ flex-shrink: 0;
287
+ overflow: hidden;
288
+ box-shadow:
289
+ 0 8px 18px rgb(0 0 0 / 0.2),
290
+ 0 1px 0 rgb(255 255 255 / 0.2) inset;
291
+ }
292
+ .ms-tile__list-body {
293
+ min-width: 0;
294
+ flex: 1;
295
+ display: flex;
296
+ flex-direction: column;
297
+ gap: 0.2rem;
298
+ }
299
+ .ms-tile__list-top {
300
+ display: flex;
301
+ align-items: center;
302
+ justify-content: space-between;
303
+ gap: 0.75rem;
304
+ }
305
+ .ms-tile__list-desc {
306
+ font-size: 0.8rem;
307
+ line-height: 1.35;
308
+ color: var(--color-neutral-600, #525252);
309
+ display: -webkit-box;
310
+ -webkit-line-clamp: 1;
311
+ line-clamp: 1;
312
+ -webkit-box-orient: vertical;
313
+ overflow: hidden;
314
+ }
315
+ :global(.dark) .ms-tile__list-desc {
316
+ color: var(--color-neutral-400, #a3a3a3);
317
+ }
318
+ .ms-tile__list-meta {
319
+ display: flex;
320
+ flex-wrap: wrap;
321
+ align-items: center;
322
+ gap: 0.35rem;
323
+ font-size: 0.75rem;
324
+ color: var(--color-neutral-500, #737373);
325
+ }
163
326
  .ms-tile__icon-shell {
164
327
  position: relative;
165
328
  width: 100%;
@@ -208,19 +371,6 @@
208
371
  left: 0.45rem;
209
372
  z-index: 2;
210
373
  }
211
- .ms-tile__dot {
212
- position: absolute;
213
- top: 0.55rem;
214
- right: 0.55rem;
215
- width: 0.55rem;
216
- height: 0.55rem;
217
- border-radius: 999px;
218
- background: #16a34a;
219
- box-shadow: 0 0 0 3px rgb(255 255 255 / 0.9);
220
- }
221
- :global(.dark) .ms-tile__dot {
222
- box-shadow: 0 0 0 3px rgb(28 28 28);
223
- }
224
374
  .ms-tile__meta {
225
375
  display: flex;
226
376
  flex-direction: column;
@@ -240,13 +390,27 @@
240
390
  color: var(--color-neutral-50, #fafafa);
241
391
  }
242
392
  .ms-tile__publisher,
243
- .ms-tile__price {
393
+ .ms-tile__price,
394
+ .ms-tile__status {
244
395
  color: var(--color-neutral-500, #737373);
245
396
  font-size: 0.75rem;
246
397
  overflow: hidden;
247
398
  text-overflow: ellipsis;
248
399
  white-space: nowrap;
249
400
  }
401
+ .ms-tile__status {
402
+ font-weight: 600;
403
+ color: var(--color-neutral-600, #525252);
404
+ }
405
+ .ms-tile__status--ok {
406
+ color: #15803d;
407
+ }
408
+ :global(.dark) .ms-tile__status {
409
+ color: var(--color-neutral-400, #a3a3a3);
410
+ }
411
+ :global(.dark) .ms-tile__status--ok {
412
+ color: #4ade80;
413
+ }
250
414
  .ms-tile__price {
251
415
  font-weight: 550;
252
416
  color: var(--color-neutral-700, #404040);
@@ -254,6 +418,10 @@
254
418
  :global(.dark) .ms-tile__price {
255
419
  color: var(--color-neutral-300, #d4d4d4);
256
420
  }
421
+ .ms-tile__promo-sep {
422
+ margin-inline: 0.25rem;
423
+ opacity: 0.65;
424
+ }
257
425
 
258
426
  .ms-tile--featured {
259
427
  height: 100%;
@@ -6,11 +6,20 @@ type Props = {
6
6
  priceLabel: string;
7
7
  categoryLabel?: string;
8
8
  installed?: boolean;
9
+ /**
10
+ * Estado legible en el listado (p. ej. «Activo», «En licencia», «No incluido»).
11
+ * Si no se pasa y `installed`, se usa «Activo».
12
+ */
13
+ statusLabel?: string;
9
14
  /** landscape promotional card */
10
15
  featured?: boolean;
16
+ /** Compact row for list view (ignored when featured). */
17
+ layout?: 'grid' | 'list';
11
18
  tags?: StoreProductTag[];
12
19
  icon: Component;
13
20
  accentClass?: string;
21
+ /** Prefer link navigation when set (store detail URLs). */
22
+ href?: string;
14
23
  onclick?: () => void;
15
24
  /** Optional custom badge area */
16
25
  badge?: Snippet;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@r2digisolutions/components",
3
- "version": "0.19.2",
3
+ "version": "0.19.4",
4
4
  "private": false,
5
5
  "description": "R2DigiSolutions Svelte 5 component library — Atomic Design, Tailwind 4, Light/Dark mode",
6
6
  "license": "MIT",