@r2digisolutions/components 0.18.0 → 0.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/molecules/StoreProductTile/StoreProductTile.stories.d.ts +28 -0
- package/dist/components/molecules/StoreProductTile/StoreProductTile.stories.js +23 -0
- package/dist/components/molecules/StoreProductTile/StoreProductTile.svelte +339 -0
- package/dist/components/molecules/StoreProductTile/StoreProductTile.svelte.d.ts +20 -0
- package/dist/components/molecules/StoreProductTile/StoreProductTileStory.svelte +25 -0
- package/dist/components/molecules/StoreProductTile/StoreProductTileStory.svelte.d.ts +7 -0
- package/dist/components/molecules/StoreRail/StoreRail.stories.js +8 -0
- package/dist/components/molecules/StoreRail/StoreRail.svelte +152 -0
- package/dist/components/molecules/StoreRail/StoreRail.svelte.d.ts +13 -0
- package/dist/components/molecules/StoreRail/StoreRailStory.svelte +31 -0
- package/dist/components/molecules/StoreRail/StoreRailStory.svelte.d.ts +18 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +2 -0
- package/package.json +1 -1
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { StoryObj } from '@storybook/svelte';
|
|
2
|
+
declare const meta: {
|
|
3
|
+
title: string;
|
|
4
|
+
component: import("svelte").Component<{
|
|
5
|
+
featured?: boolean;
|
|
6
|
+
installed?: boolean;
|
|
7
|
+
tags?: ("new" | "featured" | "popular")[];
|
|
8
|
+
}, {}, "">;
|
|
9
|
+
tags: string[];
|
|
10
|
+
argTypes: {
|
|
11
|
+
featured: {
|
|
12
|
+
control: "boolean";
|
|
13
|
+
};
|
|
14
|
+
installed: {
|
|
15
|
+
control: "boolean";
|
|
16
|
+
};
|
|
17
|
+
};
|
|
18
|
+
args: {
|
|
19
|
+
featured: false;
|
|
20
|
+
installed: false;
|
|
21
|
+
tags: "new"[];
|
|
22
|
+
};
|
|
23
|
+
};
|
|
24
|
+
export default meta;
|
|
25
|
+
type Story = StoryObj<typeof meta>;
|
|
26
|
+
export declare const Default: Story;
|
|
27
|
+
export declare const Featured: Story;
|
|
28
|
+
export declare const Installed: Story;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import StoreProductTileStory from './StoreProductTileStory.svelte';
|
|
2
|
+
const meta = {
|
|
3
|
+
title: 'Molecules/StoreProductTile',
|
|
4
|
+
component: StoreProductTileStory,
|
|
5
|
+
tags: ['autodocs'],
|
|
6
|
+
argTypes: {
|
|
7
|
+
featured: { control: 'boolean' },
|
|
8
|
+
installed: { control: 'boolean' }
|
|
9
|
+
},
|
|
10
|
+
args: {
|
|
11
|
+
featured: false,
|
|
12
|
+
installed: false,
|
|
13
|
+
tags: ['new']
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
export default meta;
|
|
17
|
+
export const Default = {};
|
|
18
|
+
export const Featured = {
|
|
19
|
+
args: { featured: true, tags: ['featured', 'new'] }
|
|
20
|
+
};
|
|
21
|
+
export const Installed = {
|
|
22
|
+
args: { installed: true, tags: ['popular'] }
|
|
23
|
+
};
|
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Component, Snippet } from 'svelte';
|
|
3
|
+
import Badge from '../../atoms/Badge/Badge.svelte';
|
|
4
|
+
|
|
5
|
+
export type StoreProductTag = 'new' | 'featured' | 'popular';
|
|
6
|
+
|
|
7
|
+
const TAG_LABELS: Record<StoreProductTag, string> = {
|
|
8
|
+
new: 'Nuevo',
|
|
9
|
+
featured: 'Destacado',
|
|
10
|
+
popular: 'Popular'
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
type Props = {
|
|
14
|
+
name: string;
|
|
15
|
+
description?: string;
|
|
16
|
+
priceLabel: string;
|
|
17
|
+
categoryLabel?: string;
|
|
18
|
+
installed?: boolean;
|
|
19
|
+
/** landscape promotional card */
|
|
20
|
+
featured?: boolean;
|
|
21
|
+
tags?: StoreProductTag[];
|
|
22
|
+
icon: Component;
|
|
23
|
+
accentClass?: string;
|
|
24
|
+
onclick?: () => void;
|
|
25
|
+
/** Optional custom badge area */
|
|
26
|
+
badge?: Snippet;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
let {
|
|
30
|
+
name,
|
|
31
|
+
description = '',
|
|
32
|
+
priceLabel,
|
|
33
|
+
categoryLabel = '',
|
|
34
|
+
installed = false,
|
|
35
|
+
featured = false,
|
|
36
|
+
tags = [],
|
|
37
|
+
icon: Icon,
|
|
38
|
+
accentClass = 'from-neutral-800 to-neutral-600',
|
|
39
|
+
onclick,
|
|
40
|
+
badge
|
|
41
|
+
}: Props = $props();
|
|
42
|
+
|
|
43
|
+
const visibleTags = $derived(
|
|
44
|
+
tags.filter((t): t is StoreProductTag => t === 'new' || t === 'featured' || t === 'popular')
|
|
45
|
+
);
|
|
46
|
+
</script>
|
|
47
|
+
|
|
48
|
+
<button
|
|
49
|
+
type="button"
|
|
50
|
+
class="group ms-tile"
|
|
51
|
+
class:ms-tile--featured={featured}
|
|
52
|
+
{onclick}
|
|
53
|
+
>
|
|
54
|
+
{#if featured}
|
|
55
|
+
<div class="ms-tile__promo bg-gradient-to-br {accentClass}">
|
|
56
|
+
<div class="ms-tile__promo-mesh" aria-hidden="true"></div>
|
|
57
|
+
{#if visibleTags.length || installed || badge}
|
|
58
|
+
<div class="ms-tile__tags">
|
|
59
|
+
{#if badge}
|
|
60
|
+
{@render badge()}
|
|
61
|
+
{:else}
|
|
62
|
+
{#each visibleTags as tag (tag)}
|
|
63
|
+
<Badge
|
|
64
|
+
variant={tag === 'new' ? 'info' : tag === 'featured' ? 'primary' : 'secondary'}
|
|
65
|
+
size="sm"
|
|
66
|
+
>
|
|
67
|
+
{TAG_LABELS[tag]}
|
|
68
|
+
</Badge>
|
|
69
|
+
{/each}
|
|
70
|
+
{#if installed}
|
|
71
|
+
<Badge variant="success" size="sm">Instalado</Badge>
|
|
72
|
+
{/if}
|
|
73
|
+
{/if}
|
|
74
|
+
</div>
|
|
75
|
+
{/if}
|
|
76
|
+
<div class="ms-tile__promo-copy">
|
|
77
|
+
{#if categoryLabel}
|
|
78
|
+
<span class="ms-tile__eyebrow">{categoryLabel}</span>
|
|
79
|
+
{/if}
|
|
80
|
+
<span class="ms-tile__promo-title">{name}</span>
|
|
81
|
+
{#if description}
|
|
82
|
+
<span class="ms-tile__promo-desc">{description}</span>
|
|
83
|
+
{/if}
|
|
84
|
+
<span class="ms-tile__promo-price">{priceLabel}</span>
|
|
85
|
+
</div>
|
|
86
|
+
<div class="ms-tile__promo-icon bg-gradient-to-br {accentClass}">
|
|
87
|
+
<Icon class="size-10 text-white drop-shadow" />
|
|
88
|
+
</div>
|
|
89
|
+
</div>
|
|
90
|
+
{:else}
|
|
91
|
+
<div class="ms-tile__icon-shell">
|
|
92
|
+
<div class="ms-tile__icon bg-gradient-to-br {accentClass}">
|
|
93
|
+
<div class="ms-tile__icon-shine" aria-hidden="true"></div>
|
|
94
|
+
<Icon class="relative size-9 text-white drop-shadow-sm sm:size-10" />
|
|
95
|
+
</div>
|
|
96
|
+
{#if visibleTags[0]}
|
|
97
|
+
<span class="ms-tile__corner">
|
|
98
|
+
<Badge
|
|
99
|
+
variant={visibleTags[0] === 'new'
|
|
100
|
+
? 'info'
|
|
101
|
+
: visibleTags[0] === 'featured'
|
|
102
|
+
? 'primary'
|
|
103
|
+
: 'secondary'}
|
|
104
|
+
size="sm"
|
|
105
|
+
>
|
|
106
|
+
{TAG_LABELS[visibleTags[0]]}
|
|
107
|
+
</Badge>
|
|
108
|
+
</span>
|
|
109
|
+
{:else if installed}
|
|
110
|
+
<span class="ms-tile__dot" title="Instalado" aria-label="Instalado"></span>
|
|
111
|
+
{/if}
|
|
112
|
+
</div>
|
|
113
|
+
<div class="ms-tile__meta">
|
|
114
|
+
<span class="ms-tile__name">{name}</span>
|
|
115
|
+
<span class="ms-tile__publisher">Evoteg</span>
|
|
116
|
+
<span class="ms-tile__price">{priceLabel}</span>
|
|
117
|
+
</div>
|
|
118
|
+
{/if}
|
|
119
|
+
</button>
|
|
120
|
+
|
|
121
|
+
<style>
|
|
122
|
+
.ms-tile {
|
|
123
|
+
display: flex;
|
|
124
|
+
flex-direction: column;
|
|
125
|
+
gap: 0.7rem;
|
|
126
|
+
width: 100%;
|
|
127
|
+
min-width: 0;
|
|
128
|
+
text-align: left;
|
|
129
|
+
border: 0;
|
|
130
|
+
background: transparent;
|
|
131
|
+
padding: 0;
|
|
132
|
+
cursor: pointer;
|
|
133
|
+
border-radius: 1rem;
|
|
134
|
+
transition:
|
|
135
|
+
transform 200ms cubic-bezier(0.22, 1, 0.36, 1),
|
|
136
|
+
filter 200ms ease;
|
|
137
|
+
}
|
|
138
|
+
.ms-tile:hover {
|
|
139
|
+
transform: translateY(-3px);
|
|
140
|
+
}
|
|
141
|
+
.ms-tile:focus-visible {
|
|
142
|
+
outline: 2px solid var(--color-brand-500, #2563eb);
|
|
143
|
+
outline-offset: 4px;
|
|
144
|
+
}
|
|
145
|
+
.ms-tile__icon-shell {
|
|
146
|
+
position: relative;
|
|
147
|
+
width: 100%;
|
|
148
|
+
aspect-ratio: 1;
|
|
149
|
+
display: grid;
|
|
150
|
+
place-items: center;
|
|
151
|
+
border-radius: 1.35rem;
|
|
152
|
+
background: linear-gradient(180deg, rgb(255 255 255 / 0.9), rgb(245 245 245 / 0.95));
|
|
153
|
+
box-shadow:
|
|
154
|
+
0 1px 0 rgb(255 255 255 / 0.8) inset,
|
|
155
|
+
0 1px 2px rgb(0 0 0 / 0.04),
|
|
156
|
+
0 10px 28px rgb(0 0 0 / 0.07);
|
|
157
|
+
}
|
|
158
|
+
:global(.dark) .ms-tile__icon-shell {
|
|
159
|
+
background: linear-gradient(180deg, rgb(38 38 38), rgb(28 28 28));
|
|
160
|
+
box-shadow:
|
|
161
|
+
0 1px 0 rgb(255 255 255 / 0.04) inset,
|
|
162
|
+
0 10px 28px rgb(0 0 0 / 0.35);
|
|
163
|
+
}
|
|
164
|
+
.ms-tile__icon {
|
|
165
|
+
position: relative;
|
|
166
|
+
display: grid;
|
|
167
|
+
place-items: center;
|
|
168
|
+
width: 58%;
|
|
169
|
+
aspect-ratio: 1;
|
|
170
|
+
border-radius: 22%;
|
|
171
|
+
overflow: hidden;
|
|
172
|
+
box-shadow:
|
|
173
|
+
0 8px 20px rgb(0 0 0 / 0.22),
|
|
174
|
+
0 1px 0 rgb(255 255 255 / 0.2) inset;
|
|
175
|
+
transition: transform 260ms cubic-bezier(0.22, 1, 0.36, 1);
|
|
176
|
+
}
|
|
177
|
+
.ms-tile:hover .ms-tile__icon {
|
|
178
|
+
transform: scale(1.04);
|
|
179
|
+
}
|
|
180
|
+
.ms-tile__icon-shine {
|
|
181
|
+
position: absolute;
|
|
182
|
+
inset: 0;
|
|
183
|
+
background:
|
|
184
|
+
linear-gradient(135deg, rgb(255 255 255 / 0.35), transparent 42%),
|
|
185
|
+
radial-gradient(circle at 70% 80%, rgb(0 0 0 / 0.2), transparent 55%);
|
|
186
|
+
}
|
|
187
|
+
.ms-tile__corner {
|
|
188
|
+
position: absolute;
|
|
189
|
+
top: 0.45rem;
|
|
190
|
+
left: 0.45rem;
|
|
191
|
+
z-index: 2;
|
|
192
|
+
}
|
|
193
|
+
.ms-tile__dot {
|
|
194
|
+
position: absolute;
|
|
195
|
+
top: 0.55rem;
|
|
196
|
+
right: 0.55rem;
|
|
197
|
+
width: 0.55rem;
|
|
198
|
+
height: 0.55rem;
|
|
199
|
+
border-radius: 999px;
|
|
200
|
+
background: #16a34a;
|
|
201
|
+
box-shadow: 0 0 0 3px rgb(255 255 255 / 0.9);
|
|
202
|
+
}
|
|
203
|
+
:global(.dark) .ms-tile__dot {
|
|
204
|
+
box-shadow: 0 0 0 3px rgb(28 28 28);
|
|
205
|
+
}
|
|
206
|
+
.ms-tile__meta {
|
|
207
|
+
display: flex;
|
|
208
|
+
flex-direction: column;
|
|
209
|
+
gap: 0.1rem;
|
|
210
|
+
padding-inline: 0.2rem;
|
|
211
|
+
}
|
|
212
|
+
.ms-tile__name {
|
|
213
|
+
color: var(--color-neutral-900, #171717);
|
|
214
|
+
font-size: 0.875rem;
|
|
215
|
+
font-weight: 650;
|
|
216
|
+
letter-spacing: -0.01em;
|
|
217
|
+
overflow: hidden;
|
|
218
|
+
text-overflow: ellipsis;
|
|
219
|
+
white-space: nowrap;
|
|
220
|
+
}
|
|
221
|
+
:global(.dark) .ms-tile__name {
|
|
222
|
+
color: var(--color-neutral-50, #fafafa);
|
|
223
|
+
}
|
|
224
|
+
.ms-tile__publisher,
|
|
225
|
+
.ms-tile__price {
|
|
226
|
+
color: var(--color-neutral-500, #737373);
|
|
227
|
+
font-size: 0.75rem;
|
|
228
|
+
overflow: hidden;
|
|
229
|
+
text-overflow: ellipsis;
|
|
230
|
+
white-space: nowrap;
|
|
231
|
+
}
|
|
232
|
+
.ms-tile__price {
|
|
233
|
+
font-weight: 550;
|
|
234
|
+
color: var(--color-neutral-700, #404040);
|
|
235
|
+
}
|
|
236
|
+
:global(.dark) .ms-tile__price {
|
|
237
|
+
color: var(--color-neutral-300, #d4d4d4);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
.ms-tile--featured {
|
|
241
|
+
height: 100%;
|
|
242
|
+
}
|
|
243
|
+
.ms-tile__promo {
|
|
244
|
+
position: relative;
|
|
245
|
+
display: flex;
|
|
246
|
+
align-items: flex-end;
|
|
247
|
+
justify-content: space-between;
|
|
248
|
+
gap: 1rem;
|
|
249
|
+
min-height: 11.5rem;
|
|
250
|
+
padding: 1.15rem 1.15rem 1.2rem;
|
|
251
|
+
border-radius: 1.35rem;
|
|
252
|
+
overflow: hidden;
|
|
253
|
+
color: white;
|
|
254
|
+
box-shadow:
|
|
255
|
+
0 1px 0 rgb(255 255 255 / 0.12) inset,
|
|
256
|
+
0 16px 40px rgb(0 0 0 / 0.18);
|
|
257
|
+
transition: box-shadow 220ms ease;
|
|
258
|
+
}
|
|
259
|
+
.ms-tile:hover .ms-tile__promo {
|
|
260
|
+
box-shadow:
|
|
261
|
+
0 1px 0 rgb(255 255 255 / 0.16) inset,
|
|
262
|
+
0 22px 48px rgb(0 0 0 / 0.24);
|
|
263
|
+
}
|
|
264
|
+
.ms-tile__promo-mesh {
|
|
265
|
+
position: absolute;
|
|
266
|
+
inset: 0;
|
|
267
|
+
background:
|
|
268
|
+
radial-gradient(circle at 12% 18%, rgb(255 255 255 / 0.28), transparent 40%),
|
|
269
|
+
radial-gradient(circle at 88% 78%, rgb(0 0 0 / 0.28), transparent 48%),
|
|
270
|
+
linear-gradient(120deg, transparent 40%, rgb(255 255 255 / 0.06) 100%);
|
|
271
|
+
}
|
|
272
|
+
.ms-tile__tags {
|
|
273
|
+
position: absolute;
|
|
274
|
+
top: 0.75rem;
|
|
275
|
+
left: 0.75rem;
|
|
276
|
+
z-index: 2;
|
|
277
|
+
display: flex;
|
|
278
|
+
flex-wrap: wrap;
|
|
279
|
+
gap: 0.35rem;
|
|
280
|
+
max-width: calc(100% - 5.5rem);
|
|
281
|
+
}
|
|
282
|
+
.ms-tile__promo-copy {
|
|
283
|
+
position: relative;
|
|
284
|
+
z-index: 1;
|
|
285
|
+
display: flex;
|
|
286
|
+
flex-direction: column;
|
|
287
|
+
gap: 0.35rem;
|
|
288
|
+
min-width: 0;
|
|
289
|
+
max-width: 70%;
|
|
290
|
+
}
|
|
291
|
+
.ms-tile__eyebrow {
|
|
292
|
+
font-size: 0.65rem;
|
|
293
|
+
font-weight: 700;
|
|
294
|
+
letter-spacing: 0.16em;
|
|
295
|
+
text-transform: uppercase;
|
|
296
|
+
color: rgb(255 255 255 / 0.72);
|
|
297
|
+
}
|
|
298
|
+
.ms-tile__promo-title {
|
|
299
|
+
font-size: 1.15rem;
|
|
300
|
+
font-weight: 700;
|
|
301
|
+
letter-spacing: -0.02em;
|
|
302
|
+
line-height: 1.15;
|
|
303
|
+
}
|
|
304
|
+
.ms-tile__promo-desc {
|
|
305
|
+
font-size: 0.78rem;
|
|
306
|
+
line-height: 1.35;
|
|
307
|
+
color: rgb(255 255 255 / 0.82);
|
|
308
|
+
display: -webkit-box;
|
|
309
|
+
-webkit-line-clamp: 2;
|
|
310
|
+
line-clamp: 2;
|
|
311
|
+
-webkit-box-orient: vertical;
|
|
312
|
+
overflow: hidden;
|
|
313
|
+
}
|
|
314
|
+
.ms-tile__promo-price {
|
|
315
|
+
margin-top: 0.25rem;
|
|
316
|
+
font-size: 0.78rem;
|
|
317
|
+
font-weight: 600;
|
|
318
|
+
color: rgb(255 255 255 / 0.92);
|
|
319
|
+
}
|
|
320
|
+
.ms-tile__promo-icon {
|
|
321
|
+
position: relative;
|
|
322
|
+
z-index: 1;
|
|
323
|
+
display: grid;
|
|
324
|
+
place-items: center;
|
|
325
|
+
width: 4.25rem;
|
|
326
|
+
height: 4.25rem;
|
|
327
|
+
border-radius: 22%;
|
|
328
|
+
flex-shrink: 0;
|
|
329
|
+
box-shadow:
|
|
330
|
+
0 12px 28px rgb(0 0 0 / 0.28),
|
|
331
|
+
0 1px 0 rgb(255 255 255 / 0.25) inset;
|
|
332
|
+
border: 1px solid rgb(255 255 255 / 0.18);
|
|
333
|
+
transform: translateY(-0.15rem);
|
|
334
|
+
transition: transform 260ms cubic-bezier(0.22, 1, 0.36, 1);
|
|
335
|
+
}
|
|
336
|
+
.ms-tile:hover .ms-tile__promo-icon {
|
|
337
|
+
transform: translateY(-0.35rem) scale(1.03);
|
|
338
|
+
}
|
|
339
|
+
</style>
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { Component, Snippet } from 'svelte';
|
|
2
|
+
export type StoreProductTag = 'new' | 'featured' | 'popular';
|
|
3
|
+
type Props = {
|
|
4
|
+
name: string;
|
|
5
|
+
description?: string;
|
|
6
|
+
priceLabel: string;
|
|
7
|
+
categoryLabel?: string;
|
|
8
|
+
installed?: boolean;
|
|
9
|
+
/** landscape promotional card */
|
|
10
|
+
featured?: boolean;
|
|
11
|
+
tags?: StoreProductTag[];
|
|
12
|
+
icon: Component;
|
|
13
|
+
accentClass?: string;
|
|
14
|
+
onclick?: () => void;
|
|
15
|
+
/** Optional custom badge area */
|
|
16
|
+
badge?: Snippet;
|
|
17
|
+
};
|
|
18
|
+
declare const StoreProductTile: Component<Props, {}, "">;
|
|
19
|
+
type StoreProductTile = ReturnType<typeof StoreProductTile>;
|
|
20
|
+
export default StoreProductTile;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import StoreProductTile from './StoreProductTile.svelte';
|
|
3
|
+
import Package from '@lucide/svelte/icons/package';
|
|
4
|
+
import Utensils from '@lucide/svelte/icons/utensils';
|
|
5
|
+
|
|
6
|
+
let {
|
|
7
|
+
featured = false,
|
|
8
|
+
installed = false,
|
|
9
|
+
tags = ['new'] as ('new' | 'featured' | 'popular')[]
|
|
10
|
+
} = $props();
|
|
11
|
+
</script>
|
|
12
|
+
|
|
13
|
+
<div class="max-w-sm p-6">
|
|
14
|
+
<StoreProductTile
|
|
15
|
+
name={featured ? 'TPV / POS' : 'Inventario'}
|
|
16
|
+
description="Punto de venta con mesas, KDS y caja."
|
|
17
|
+
priceLabel={featured ? '29 €/mes' : 'Gratis'}
|
|
18
|
+
categoryLabel="Comercial"
|
|
19
|
+
{featured}
|
|
20
|
+
{installed}
|
|
21
|
+
{tags}
|
|
22
|
+
icon={featured ? Utensils : Package}
|
|
23
|
+
accentClass={featured ? 'from-sky-700 via-sky-600 to-cyan-500' : 'from-emerald-800 to-teal-600'}
|
|
24
|
+
/>
|
|
25
|
+
</div>
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
declare const StoreProductTileStory: import("svelte").Component<{
|
|
2
|
+
featured?: boolean;
|
|
3
|
+
installed?: boolean;
|
|
4
|
+
tags?: ("new" | "featured" | "popular")[];
|
|
5
|
+
}, {}, "">;
|
|
6
|
+
type StoreProductTileStory = ReturnType<typeof StoreProductTileStory>;
|
|
7
|
+
export default StoreProductTileStory;
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
import Button from '../../atoms/Button/Button.svelte';
|
|
4
|
+
import ChevronLeft from '@lucide/svelte/icons/chevron-left';
|
|
5
|
+
import ChevronRight from '@lucide/svelte/icons/chevron-right';
|
|
6
|
+
|
|
7
|
+
type Props = {
|
|
8
|
+
title: string;
|
|
9
|
+
subtitle?: string;
|
|
10
|
+
children: Snippet;
|
|
11
|
+
wide?: boolean;
|
|
12
|
+
/** Soft edge fades (narrow by default). */
|
|
13
|
+
fade?: boolean;
|
|
14
|
+
class?: string;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
let {
|
|
18
|
+
title,
|
|
19
|
+
subtitle = '',
|
|
20
|
+
children,
|
|
21
|
+
wide = false,
|
|
22
|
+
fade = true,
|
|
23
|
+
class: className = ''
|
|
24
|
+
}: Props = $props();
|
|
25
|
+
|
|
26
|
+
let track = $state<HTMLDivElement | null>(null);
|
|
27
|
+
|
|
28
|
+
function scrollByDir(dir: -1 | 1) {
|
|
29
|
+
if (!track) return;
|
|
30
|
+
const amount = Math.max(240, track.clientWidth * 0.72) * dir;
|
|
31
|
+
track.scrollBy({ left: amount, behavior: 'smooth' });
|
|
32
|
+
}
|
|
33
|
+
</script>
|
|
34
|
+
|
|
35
|
+
<section class="ms-rail {className}">
|
|
36
|
+
<div class="ms-rail__head">
|
|
37
|
+
<div class="min-w-0">
|
|
38
|
+
<h2 class="ms-rail__title">{title}</h2>
|
|
39
|
+
{#if subtitle}
|
|
40
|
+
<p class="ms-rail__subtitle">{subtitle}</p>
|
|
41
|
+
{/if}
|
|
42
|
+
</div>
|
|
43
|
+
<div class="ms-rail__controls">
|
|
44
|
+
<Button
|
|
45
|
+
variant="secondary"
|
|
46
|
+
size="sm"
|
|
47
|
+
onclick={() => scrollByDir(-1)}
|
|
48
|
+
aria-label="Desplazar a la izquierda"
|
|
49
|
+
>
|
|
50
|
+
<ChevronLeft class="size-4" />
|
|
51
|
+
</Button>
|
|
52
|
+
<Button
|
|
53
|
+
variant="secondary"
|
|
54
|
+
size="sm"
|
|
55
|
+
onclick={() => scrollByDir(1)}
|
|
56
|
+
aria-label="Desplazar a la derecha"
|
|
57
|
+
>
|
|
58
|
+
<ChevronRight class="size-4" />
|
|
59
|
+
</Button>
|
|
60
|
+
</div>
|
|
61
|
+
</div>
|
|
62
|
+
|
|
63
|
+
<div class="ms-rail__viewport">
|
|
64
|
+
<div class="ms-rail__track" class:ms-rail__track--wide={wide} bind:this={track}>
|
|
65
|
+
{@render children()}
|
|
66
|
+
</div>
|
|
67
|
+
{#if fade}
|
|
68
|
+
<div class="ms-rail__fade ms-rail__fade--left" aria-hidden="true"></div>
|
|
69
|
+
<div class="ms-rail__fade ms-rail__fade--right" aria-hidden="true"></div>
|
|
70
|
+
{/if}
|
|
71
|
+
</div>
|
|
72
|
+
</section>
|
|
73
|
+
|
|
74
|
+
<style>
|
|
75
|
+
.ms-rail {
|
|
76
|
+
display: flex;
|
|
77
|
+
flex-direction: column;
|
|
78
|
+
gap: 0.9rem;
|
|
79
|
+
}
|
|
80
|
+
.ms-rail__head {
|
|
81
|
+
display: flex;
|
|
82
|
+
align-items: flex-end;
|
|
83
|
+
justify-content: space-between;
|
|
84
|
+
gap: 1rem;
|
|
85
|
+
padding-inline: 0.1rem;
|
|
86
|
+
}
|
|
87
|
+
.ms-rail__title {
|
|
88
|
+
margin: 0;
|
|
89
|
+
font-size: 1.35rem;
|
|
90
|
+
font-weight: 700;
|
|
91
|
+
letter-spacing: -0.03em;
|
|
92
|
+
color: var(--color-neutral-900, #171717);
|
|
93
|
+
}
|
|
94
|
+
:global(.dark) .ms-rail__title {
|
|
95
|
+
color: var(--color-neutral-50, #fafafa);
|
|
96
|
+
}
|
|
97
|
+
.ms-rail__subtitle {
|
|
98
|
+
margin: 0.15rem 0 0;
|
|
99
|
+
font-size: 0.8125rem;
|
|
100
|
+
color: var(--color-neutral-500, #737373);
|
|
101
|
+
}
|
|
102
|
+
.ms-rail__controls {
|
|
103
|
+
display: flex;
|
|
104
|
+
gap: 0.4rem;
|
|
105
|
+
flex-shrink: 0;
|
|
106
|
+
}
|
|
107
|
+
.ms-rail__viewport {
|
|
108
|
+
position: relative;
|
|
109
|
+
}
|
|
110
|
+
.ms-rail__track {
|
|
111
|
+
display: grid;
|
|
112
|
+
grid-auto-flow: column;
|
|
113
|
+
grid-auto-columns: minmax(8.75rem, 9.75rem);
|
|
114
|
+
gap: 1rem;
|
|
115
|
+
overflow-x: auto;
|
|
116
|
+
padding: 0.35rem 0.15rem 0.85rem;
|
|
117
|
+
scroll-snap-type: x mandatory;
|
|
118
|
+
scrollbar-width: none;
|
|
119
|
+
}
|
|
120
|
+
.ms-rail__track::-webkit-scrollbar {
|
|
121
|
+
display: none;
|
|
122
|
+
}
|
|
123
|
+
.ms-rail__track--wide {
|
|
124
|
+
grid-auto-columns: minmax(17rem, 19rem);
|
|
125
|
+
}
|
|
126
|
+
.ms-rail__track > :global(*) {
|
|
127
|
+
scroll-snap-align: start;
|
|
128
|
+
}
|
|
129
|
+
.ms-rail__fade {
|
|
130
|
+
pointer-events: none;
|
|
131
|
+
position: absolute;
|
|
132
|
+
top: 0;
|
|
133
|
+
bottom: 0.85rem;
|
|
134
|
+
width: 0.85rem;
|
|
135
|
+
z-index: 1;
|
|
136
|
+
opacity: 0.55;
|
|
137
|
+
}
|
|
138
|
+
.ms-rail__fade--left {
|
|
139
|
+
left: 0;
|
|
140
|
+
background: linear-gradient(90deg, var(--store-bg, #fafafa), transparent);
|
|
141
|
+
}
|
|
142
|
+
.ms-rail__fade--right {
|
|
143
|
+
right: 0;
|
|
144
|
+
background: linear-gradient(270deg, var(--store-bg, #fafafa), transparent);
|
|
145
|
+
}
|
|
146
|
+
:global(.dark) .ms-rail__fade--left {
|
|
147
|
+
background: linear-gradient(90deg, var(--store-bg-dark, #0a0a0a), transparent);
|
|
148
|
+
}
|
|
149
|
+
:global(.dark) .ms-rail__fade--right {
|
|
150
|
+
background: linear-gradient(270deg, var(--store-bg-dark, #0a0a0a), transparent);
|
|
151
|
+
}
|
|
152
|
+
</style>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
type Props = {
|
|
3
|
+
title: string;
|
|
4
|
+
subtitle?: string;
|
|
5
|
+
children: Snippet;
|
|
6
|
+
wide?: boolean;
|
|
7
|
+
/** Soft edge fades (narrow by default). */
|
|
8
|
+
fade?: boolean;
|
|
9
|
+
class?: string;
|
|
10
|
+
};
|
|
11
|
+
declare const StoreRail: import("svelte").Component<Props, {}, "">;
|
|
12
|
+
type StoreRail = ReturnType<typeof StoreRail>;
|
|
13
|
+
export default StoreRail;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import StoreRail from './StoreRail.svelte';
|
|
3
|
+
import StoreProductTile from '../StoreProductTile/StoreProductTile.svelte';
|
|
4
|
+
import Package from '@lucide/svelte/icons/package';
|
|
5
|
+
import Utensils from '@lucide/svelte/icons/utensils';
|
|
6
|
+
import Wallet from '@lucide/svelte/icons/wallet';
|
|
7
|
+
import Users from '@lucide/svelte/icons/users';
|
|
8
|
+
import Briefcase from '@lucide/svelte/icons/briefcase';
|
|
9
|
+
|
|
10
|
+
const items = [
|
|
11
|
+
{ name: 'Inventario', price: '15 €/mes', icon: Package, accent: 'from-emerald-800 to-teal-600', tags: ['new'] as const },
|
|
12
|
+
{ name: 'TPV', price: '29 €/mes', icon: Utensils, accent: 'from-sky-700 to-cyan-500', tags: ['featured'] as const },
|
|
13
|
+
{ name: 'VeriFactu', price: '9 €/mes', icon: Wallet, accent: 'from-amber-800 to-orange-500', tags: [] as const },
|
|
14
|
+
{ name: 'Portal', price: 'Gratis', icon: Users, accent: 'from-stone-700 to-neutral-500', tags: ['popular'] as const },
|
|
15
|
+
{ name: 'Proyectos', price: '19 €/mes', icon: Briefcase, accent: 'from-teal-800 to-emerald-500', tags: ['new'] as const }
|
|
16
|
+
];
|
|
17
|
+
</script>
|
|
18
|
+
|
|
19
|
+
<div class="bg-neutral-50 p-6 dark:bg-neutral-950" style="--store-bg: #fafafa; --store-bg-dark: #0a0a0a">
|
|
20
|
+
<StoreRail title="Destacados" subtitle="Ejemplo de rail" wide={false}>
|
|
21
|
+
{#each items as item (item.name)}
|
|
22
|
+
<StoreProductTile
|
|
23
|
+
name={item.name}
|
|
24
|
+
priceLabel={item.price}
|
|
25
|
+
icon={item.icon}
|
|
26
|
+
accentClass={item.accent}
|
|
27
|
+
tags={[...item.tags]}
|
|
28
|
+
/>
|
|
29
|
+
{/each}
|
|
30
|
+
</StoreRail>
|
|
31
|
+
</div>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
|
|
2
|
+
new (options: import('svelte').ComponentConstructorOptions<Props>): import('svelte').SvelteComponent<Props, Events, Slots> & {
|
|
3
|
+
$$bindings?: Bindings;
|
|
4
|
+
} & Exports;
|
|
5
|
+
(internal: unknown, props: {
|
|
6
|
+
$$events?: Events;
|
|
7
|
+
$$slots?: Slots;
|
|
8
|
+
}): Exports & {
|
|
9
|
+
$set?: any;
|
|
10
|
+
$on?: any;
|
|
11
|
+
};
|
|
12
|
+
z_$$bindings?: Bindings;
|
|
13
|
+
}
|
|
14
|
+
declare const StoreRailStory: $$__sveltets_2_IsomorphicComponent<Record<string, never>, {
|
|
15
|
+
[evt: string]: CustomEvent<any>;
|
|
16
|
+
}, {}, {}, string>;
|
|
17
|
+
type StoreRailStory = InstanceType<typeof StoreRailStory>;
|
|
18
|
+
export default StoreRailStory;
|
package/dist/index.d.ts
CHANGED
|
@@ -599,6 +599,9 @@ export { default as DateGroupedList } from './components/molecules/DateGroupedLi
|
|
|
599
599
|
export type { DateGroupedListItem, DateGroupedListGroup, DateGroupedAccent } from './components/molecules/DateGroupedList/DateGroupedList.svelte';
|
|
600
600
|
export { default as BentoGrid } from './components/molecules/BentoGrid/BentoGrid.svelte';
|
|
601
601
|
export type { BentoItem, BentoGridCols, BentoGridGap } from './components/molecules/BentoGrid/BentoGrid.svelte';
|
|
602
|
+
export { default as StoreProductTile } from './components/molecules/StoreProductTile/StoreProductTile.svelte';
|
|
603
|
+
export type { StoreProductTag } from './components/molecules/StoreProductTile/StoreProductTile.svelte';
|
|
604
|
+
export { default as StoreRail } from './components/molecules/StoreRail/StoreRail.svelte';
|
|
602
605
|
export { default as WidgetFrame } from './components/molecules/WidgetFrame/WidgetFrame.svelte';
|
|
603
606
|
export type { WidgetResizeEdge, WidgetRect, WidgetHandleStyle } from './components/molecules/WidgetFrame/WidgetFrame.svelte';
|
|
604
607
|
export { default as DashboardGridToolbar } from './components/molecules/DashboardGridToolbar/DashboardGridToolbar.svelte';
|
package/dist/index.js
CHANGED
|
@@ -412,6 +412,8 @@ export { default as ShippingEstimate } from './components/molecules/ShippingEsti
|
|
|
412
412
|
export { default as SmartSuggestion } from './components/molecules/SmartSuggestion/SmartSuggestion.svelte';
|
|
413
413
|
export { default as DateGroupedList } from './components/molecules/DateGroupedList/DateGroupedList.svelte';
|
|
414
414
|
export { default as BentoGrid } from './components/molecules/BentoGrid/BentoGrid.svelte';
|
|
415
|
+
export { default as StoreProductTile } from './components/molecules/StoreProductTile/StoreProductTile.svelte';
|
|
416
|
+
export { default as StoreRail } from './components/molecules/StoreRail/StoreRail.svelte';
|
|
415
417
|
export { default as WidgetFrame } from './components/molecules/WidgetFrame/WidgetFrame.svelte';
|
|
416
418
|
export { default as DashboardGridToolbar } from './components/molecules/DashboardGridToolbar/DashboardGridToolbar.svelte';
|
|
417
419
|
export { default as WidgetCanvas } from './components/molecules/WidgetCanvas/WidgetCanvas.svelte';
|