@streamscloud/blocks 0.1.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.
Files changed (91) hide show
  1. package/dist/content/analytics/index.d.ts +1 -0
  2. package/dist/content/analytics/index.js +1 -0
  3. package/dist/content/analytics/types.d.ts +9 -0
  4. package/dist/content/analytics/types.js +1 -0
  5. package/dist/content/content-viewer/attachments-horizontal.svelte +210 -0
  6. package/dist/content/content-viewer/attachments-horizontal.svelte.d.ts +15 -0
  7. package/dist/content/content-viewer/cmp.content-viewer.svelte +143 -0
  8. package/dist/content/content-viewer/cmp.content-viewer.svelte.d.ts +23 -0
  9. package/dist/content/content-viewer/content-texts.svelte +96 -0
  10. package/dist/content/content-viewer/content-texts.svelte.d.ts +14 -0
  11. package/dist/content/content-viewer/content-viewer-localization.d.ts +4 -0
  12. package/dist/content/content-viewer/content-viewer-localization.js +16 -0
  13. package/dist/content/content-viewer/heading.svelte +66 -0
  14. package/dist/content/content-viewer/heading.svelte.d.ts +11 -0
  15. package/dist/content/content-viewer/index.d.ts +1 -0
  16. package/dist/content/content-viewer/index.js +1 -0
  17. package/dist/content/content-viewer/media/content-media.svelte +53 -0
  18. package/dist/content/content-viewer/media/content-media.svelte.d.ts +12 -0
  19. package/dist/content/content-viewer/ui-manager.svelte.d.ts +10 -0
  20. package/dist/content/content-viewer/ui-manager.svelte.js +18 -0
  21. package/dist/content/controls/content-actions-generator.svelte.d.ts +18 -0
  22. package/dist/content/controls/content-actions-generator.svelte.js +27 -0
  23. package/dist/content/controls/content-actions-handler.svelte.d.ts +23 -0
  24. package/dist/content/controls/content-actions-handler.svelte.js +55 -0
  25. package/dist/content/controls/index.d.ts +1 -0
  26. package/dist/content/controls/index.js +1 -0
  27. package/dist/content/model/content-media-model.svelte.d.ts +20 -0
  28. package/dist/content/model/content-media-model.svelte.js +16 -0
  29. package/dist/content/model/content-model.d.ts +24 -0
  30. package/dist/content/model/content-model.js +32 -0
  31. package/dist/content/model/index.d.ts +3 -0
  32. package/dist/content/model/index.js +2 -0
  33. package/dist/content/model/types.d.ts +61 -0
  34. package/dist/content/model/types.js +1 -0
  35. package/dist/content/model/utils.d.ts +4 -0
  36. package/dist/content/model/utils.js +7 -0
  37. package/dist/content/sharing/index.d.ts +1 -0
  38. package/dist/content/sharing/index.js +1 -0
  39. package/dist/content/sharing/types.d.ts +5 -0
  40. package/dist/content/sharing/types.js +1 -0
  41. package/dist/content/social-interactions/index.d.ts +1 -0
  42. package/dist/content/social-interactions/index.js +1 -0
  43. package/dist/content/social-interactions/types.d.ts +8 -0
  44. package/dist/content/social-interactions/types.js +1 -0
  45. package/dist/core/enums.d.ts +4 -0
  46. package/dist/core/enums.js +1 -0
  47. package/dist/cta/cta-card/cmp.cta-card.svelte +259 -0
  48. package/dist/cta/cta-card/cmp.cta-card.svelte.d.ts +24 -0
  49. package/dist/cta/cta-card/index.d.ts +2 -0
  50. package/dist/cta/cta-card/index.js +1 -0
  51. package/dist/cta/cta-card/types.d.ts +18 -0
  52. package/dist/cta/cta-card/types.js +1 -0
  53. package/dist/feed-player/cmp.close-button.svelte +43 -0
  54. package/dist/feed-player/cmp.close-button.svelte.d.ts +19 -0
  55. package/dist/feed-player/cmp.feed-player.svelte +510 -0
  56. package/dist/feed-player/cmp.feed-player.svelte.d.ts +13 -0
  57. package/dist/feed-player/feed-player-localization.d.ts +16 -0
  58. package/dist/feed-player/feed-player-localization.js +70 -0
  59. package/dist/feed-player/index.d.ts +7 -0
  60. package/dist/feed-player/index.js +2 -0
  61. package/dist/feed-player/sidebar/article-tab.svelte +90 -0
  62. package/dist/feed-player/sidebar/article-tab.svelte.d.ts +10 -0
  63. package/dist/feed-player/sidebar/information-tab.svelte +85 -0
  64. package/dist/feed-player/sidebar/information-tab.svelte.d.ts +15 -0
  65. package/dist/feed-player/sidebar/panel-surface.svelte +13 -0
  66. package/dist/feed-player/sidebar/panel-surface.svelte.d.ts +7 -0
  67. package/dist/feed-player/sidebar/playlist-tab.svelte +90 -0
  68. package/dist/feed-player/sidebar/playlist-tab.svelte.d.ts +11 -0
  69. package/dist/feed-player/sidebar/post-card.svelte +92 -0
  70. package/dist/feed-player/sidebar/post-card.svelte.d.ts +14 -0
  71. package/dist/feed-player/sidebar/recommended-tab.svelte +161 -0
  72. package/dist/feed-player/sidebar/recommended-tab.svelte.d.ts +8 -0
  73. package/dist/feed-player/sidebar/sidebar-panel.svelte +69 -0
  74. package/dist/feed-player/sidebar/sidebar-panel.svelte.d.ts +26 -0
  75. package/dist/feed-player/sidebar/sidebar-tab-bar.svelte +44 -0
  76. package/dist/feed-player/sidebar/sidebar-tab-bar.svelte.d.ts +12 -0
  77. package/dist/feed-player/sidebar/types.d.ts +4 -0
  78. package/dist/feed-player/sidebar/types.js +1 -0
  79. package/dist/feed-player/types.d.ts +65 -0
  80. package/dist/feed-player/types.js +1 -0
  81. package/dist/index.d.ts +1 -0
  82. package/dist/index.js +1 -0
  83. package/dist/products/product-card/cmp.product-card.svelte +282 -0
  84. package/dist/products/product-card/cmp.product-card.svelte.d.ts +30 -0
  85. package/dist/products/product-card/index.d.ts +2 -0
  86. package/dist/products/product-card/index.js +1 -0
  87. package/dist/products/product-card/product-card-localization.d.ts +4 -0
  88. package/dist/products/product-card/product-card-localization.js +19 -0
  89. package/dist/products/product-card/types.d.ts +12 -0
  90. package/dist/products/product-card/types.js +1 -0
  91. package/package.json +92 -0
@@ -0,0 +1,8 @@
1
+ import type { IFeedPlayerRecommendedHandler } from '../types';
2
+ type Props = {
3
+ handler: IFeedPlayerRecommendedHandler;
4
+ currentContentId: string;
5
+ };
6
+ declare const RecommendedTab: import("svelte").Component<Props, {}, "">;
7
+ type RecommendedTab = ReturnType<typeof RecommendedTab>;
8
+ export default RecommendedTab;
@@ -0,0 +1,69 @@
1
+ <script lang="ts">import { ProductCard } from '../../products/product-card';
2
+ import { default as ArticleTab } from './article-tab.svelte';
3
+ import { default as InformationTab } from './information-tab.svelte';
4
+ import { default as PlaylistTab } from './playlist-tab.svelte';
5
+ import { default as RecommendedTab } from './recommended-tab.svelte';
6
+ import { default as SidebarTabBar } from './sidebar-tab-bar.svelte';
7
+ const selectedProduct = $derived.by(() => {
8
+ if (!selectedProductId || !model.attachments) {
9
+ return null;
10
+ }
11
+ return model.attachments.products.find((p) => p.id === selectedProductId) ?? null;
12
+ });
13
+ let { tabs, activeTabId, model, selectedProductId, interactionsDisabled, recommendedHandler, playlistHandler, on } = $props();
14
+ </script>
15
+
16
+ <div class="sidebar-panel">
17
+ <SidebarTabBar tabs={tabs} activeTabId={activeTabId} disabled={interactionsDisabled} on={{ tabChange: on.tabChange }} />
18
+
19
+ <div class="sidebar-panel__content">
20
+ {#if activeTabId === 'information'}
21
+ <InformationTab
22
+ model={model}
23
+ selectedProductId={selectedProductId}
24
+ on={{
25
+ productImpression: on.productImpression,
26
+ productBuy: on.productBuy,
27
+ productSelect: on.productSelect,
28
+ ctaClick: on.ctaClick,
29
+ ctaImpression: on.ctaImpression
30
+ }} />
31
+ {:else if activeTabId === 'product' && selectedProduct}
32
+ <div class="sidebar-panel__product">
33
+ <ProductCard
34
+ product={selectedProduct}
35
+ on={{
36
+ click: on.productClick,
37
+ buy: on.productBuy
38
+ }} />
39
+ </div>
40
+ {:else if activeTabId === 'article'}
41
+ <ArticleTab model={model} on={{ readMore: on.articleReadMore }} />
42
+ {:else if activeTabId === 'recommended' && recommendedHandler}
43
+ <RecommendedTab handler={recommendedHandler} currentContentId={model.id} />
44
+ {:else if activeTabId === 'playlist' && playlistHandler}
45
+ <PlaylistTab handler={playlistHandler} activeContentId={model.id} on={{ contentActivate: on.contentActivate }} />
46
+ {/if}
47
+ </div>
48
+ </div>
49
+
50
+ <style>.sidebar-panel {
51
+ display: flex;
52
+ flex-direction: column;
53
+ gap: 1rem;
54
+ height: 100%;
55
+ overflow: hidden;
56
+ }
57
+ .sidebar-panel__content {
58
+ flex: 1;
59
+ min-height: 0;
60
+ overflow-y: auto;
61
+ overflow-x: hidden;
62
+ overscroll-behavior: contain;
63
+ scrollbar-width: none;
64
+ border-radius: 0.5rem;
65
+ }
66
+ .sidebar-panel__product {
67
+ padding: 0.75rem;
68
+ --sc-blocks--product-card--aspect-ratio: auto;
69
+ }</style>
@@ -0,0 +1,26 @@
1
+ import type { ContentModel } from '../../content/model';
2
+ import type { IFeedPlayerPlaylistHandler, IFeedPlayerRecommendedHandler } from '../types';
3
+ import type { SidebarTab } from './types';
4
+ type Props = {
5
+ tabs: SidebarTab[];
6
+ activeTabId: string;
7
+ model: ContentModel;
8
+ selectedProductId: string | null;
9
+ interactionsDisabled?: boolean;
10
+ recommendedHandler?: IFeedPlayerRecommendedHandler;
11
+ playlistHandler?: IFeedPlayerPlaylistHandler;
12
+ on: {
13
+ tabChange: (id: string) => void;
14
+ productClick?: (productId: string) => void;
15
+ productImpression?: (productId: string) => void;
16
+ productBuy?: (productId: string) => void;
17
+ productSelect?: (productId: string) => void;
18
+ ctaClick?: (ctaId: string) => void;
19
+ ctaImpression?: (ctaId: string) => void;
20
+ articleReadMore?: (articleId: string) => void;
21
+ contentActivate?: (contentId: string) => void;
22
+ };
23
+ };
24
+ declare const SidebarPanel: import("svelte").Component<Props, {}, "">;
25
+ type SidebarPanel = ReturnType<typeof SidebarPanel>;
26
+ export default SidebarPanel;
@@ -0,0 +1,44 @@
1
+ <script lang="ts">let { tabs, activeTabId, disabled, on } = $props();
2
+ export {};
3
+ </script>
4
+
5
+ <div class="sidebar-tab-bar" class:sidebar-tab-bar--disabled={disabled}>
6
+ {#each tabs as tab (tab.id)}
7
+ <button type="button" class="sidebar-tab-bar__tab" class:sidebar-tab-bar__tab--active={tab.id === activeTabId} onclick={() => on.tabChange(tab.id)}>
8
+ {tab.label}
9
+ </button>
10
+ {/each}
11
+ </div>
12
+
13
+ <style>.sidebar-tab-bar {
14
+ --_sidebar-tab-bar--color: var(--sc-blocks--sidebar-tab-bar--color, var(--sc-kit--color--text--secondary));
15
+ --_sidebar-tab-bar--color-active: var(--sc-blocks--sidebar-tab-bar--color-active, var(--sc-kit--color--text--primary));
16
+ --_sidebar-tab-bar--background: var(--sc-blocks--sidebar-tab-bar--background, rgb(from var(--sc-kit--color--bg--panel) r g b / 0.58));
17
+ display: flex;
18
+ gap: 2.25rem;
19
+ flex-shrink: 0;
20
+ padding: var(--sc-kit--space--2) var(--sc-kit--space--4);
21
+ border-radius: var(--sc-kit--radius--md);
22
+ background: var(--_sidebar-tab-bar--background);
23
+ overflow-x: auto;
24
+ scrollbar-width: none;
25
+ }
26
+ .sidebar-tab-bar__tab {
27
+ font-size: var(--sc-kit--font-size--sm);
28
+ font-weight: var(--sc-kit--font-weight--regular);
29
+ line-height: var(--sc-kit--line-height--xs);
30
+ color: var(--_sidebar-tab-bar--color);
31
+ background: none;
32
+ border: none;
33
+ padding: 0;
34
+ cursor: pointer;
35
+ white-space: nowrap;
36
+ }
37
+ .sidebar-tab-bar__tab--active {
38
+ font-weight: var(--sc-kit--font-weight--medium);
39
+ color: var(--_sidebar-tab-bar--color-active);
40
+ }
41
+ .sidebar-tab-bar--disabled .sidebar-tab-bar__tab:not(.sidebar-tab-bar__tab--active) {
42
+ pointer-events: none;
43
+ opacity: 0.5;
44
+ }</style>
@@ -0,0 +1,12 @@
1
+ import type { SidebarTab } from './types';
2
+ type Props = {
3
+ tabs: SidebarTab[];
4
+ activeTabId: string;
5
+ disabled?: boolean;
6
+ on: {
7
+ tabChange: (id: string) => void;
8
+ };
9
+ };
10
+ declare const SidebarTabBar: import("svelte").Component<Props, {}, "">;
11
+ type SidebarTabBar = ReturnType<typeof SidebarTabBar>;
12
+ export default SidebarTabBar;
@@ -0,0 +1,4 @@
1
+ export type SidebarTab = {
2
+ id: string;
3
+ label: string;
4
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,65 @@
1
+ import type { IContentAnalyticsHandler } from '../content/analytics';
2
+ import type { IContentModel } from '../content/model';
3
+ import type { IContentSharingHandler } from '../content/sharing';
4
+ import type { IContentSocialInteractionsHandler } from '../content/social-interactions';
5
+ import type { ProductCardModel } from '../products/product-card';
6
+ import type { AppLocaleValue } from '@streamscloud/kit/core/locale';
7
+ import type { IPlayerDataProvider } from '@streamscloud/kit/ui/player/providers';
8
+ import type { Snippet } from 'svelte';
9
+ export type FeedPlayerContentPreview = {
10
+ id: string;
11
+ thumbnailUrl: string | null;
12
+ title: string | null;
13
+ description: string | null;
14
+ displayDate: string;
15
+ viewsCount: number;
16
+ durationSeconds: number | null;
17
+ };
18
+ export type FeedPlayerSuggestedPlaylist = {
19
+ id: string;
20
+ name: string;
21
+ postsCount: number;
22
+ thumbnailUrl: string | null;
23
+ };
24
+ export type FeedPlayerRecommendedData = {
25
+ relatedContent: FeedPlayerContentPreview[];
26
+ suggestedPlaylists: FeedPlayerSuggestedPlaylist[];
27
+ suggestedProducts: ProductCardModel[];
28
+ };
29
+ export interface IFeedPlayerRecommendedHandler {
30
+ getRecommendations: (contentId: string) => Promise<FeedPlayerRecommendedData>;
31
+ onContentSelect?: (contentId: string) => void;
32
+ onPlaylistSelect?: (playlistId: string) => void;
33
+ onPlaylistShow?: (playlistId: string) => void;
34
+ onProductSelect?: (productId: string) => void;
35
+ onProductBuy?: (productId: string) => void;
36
+ }
37
+ export type FeedPlayerPlaylistData = {
38
+ name: string;
39
+ updatedAt: string;
40
+ content: FeedPlayerContentPreview[];
41
+ };
42
+ export interface IFeedPlayerPlaylistHandler {
43
+ getPlaylist: () => Promise<FeedPlayerPlaylistData>;
44
+ }
45
+ export type FeedPlayerProps = {
46
+ dataProvider: IPlayerDataProvider<IContentModel>;
47
+ socialInteractionsHandler?: IContentSocialInteractionsHandler;
48
+ sharingHandler?: IContentSharingHandler;
49
+ analyticsHandler?: IContentAnalyticsHandler;
50
+ recommendedHandler?: IFeedPlayerRecommendedHandler;
51
+ playlistHandler?: IFeedPlayerPlaylistHandler;
52
+ settings?: FeedPlayerSettings;
53
+ header?: Snippet;
54
+ on?: {
55
+ contentActivated?: (id: string) => void;
56
+ productBuy?: (productId: string) => void;
57
+ articleReadMore?: (articleId: string) => void;
58
+ closed?: () => void;
59
+ };
60
+ };
61
+ export type FeedPlayerSettings = {
62
+ locale?: AppLocaleValue;
63
+ background?: 'solid' | 'glass' | 'transparent';
64
+ mode?: 'default' | 'preview';
65
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export type { ContentType, CtaType, Currency, MediaType } from './core/enums';
package/dist/index.js ADDED
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,282 @@
1
+ <script lang="ts">import { ProductCardLocalization } from './product-card-localization';
2
+ import IconCart from '@fluentui/svg-icons/icons/cart_20_regular.svg?raw';
3
+ import { toPriceRepresentation } from '@streamscloud/kit/core/utils';
4
+ import { Button } from '@streamscloud/kit/ui/button';
5
+ import { IconText } from '@streamscloud/kit/ui/icon-text';
6
+ import { Image } from '@streamscloud/kit/ui/image';
7
+ import { LineClamp } from '@streamscloud/kit/ui/line-clamp';
8
+ import { ProportionalContainer } from '@streamscloud/kit/ui/proportional-container';
9
+ let { product, includeBeforeNowPrefix, inert = false, on } = $props();
10
+ const localization = new ProductCardLocalization();
11
+ const shortDescriptionPresented = $derived(product.shortDescription && product.shortDescription.length > 0);
12
+ const trackImpression = (node) => {
13
+ if (on?.impression) {
14
+ const observer = new IntersectionObserver((entries) => {
15
+ entries.forEach((entry) => {
16
+ if (entry.isIntersecting && entry.intersectionRatio >= 0.5) {
17
+ on.impression?.(product.id);
18
+ observer.unobserve(entry.target);
19
+ }
20
+ });
21
+ }, { threshold: 0.5 });
22
+ observer.observe(node);
23
+ return {
24
+ destroy() {
25
+ observer.disconnect();
26
+ }
27
+ };
28
+ }
29
+ };
30
+ const onProductClicked = (event) => {
31
+ event.preventDefault();
32
+ event.stopPropagation();
33
+ if (on?.click) {
34
+ on.click(product.id);
35
+ }
36
+ if (product.link) {
37
+ window.open(product.link, '_blank', 'noopener noreferrer');
38
+ }
39
+ };
40
+ const onBuyClicked = (event) => {
41
+ event.preventDefault();
42
+ event.stopPropagation();
43
+ on?.buy?.(product.id);
44
+ };
45
+ </script>
46
+
47
+ <div class="product-card" class:product-card--with-buy={!!on?.buy} use:trackImpression>
48
+ <ProportionalContainer ratio={1}>
49
+ <Image src={product.image} />
50
+ </ProportionalContainer>
51
+
52
+ <div class="product-card__info">
53
+ <LineClamp maxLines={1}>
54
+ <div class="product-card__brand">{product.brandName}</div>
55
+ </LineClamp>
56
+ <LineClamp maxLines={shortDescriptionPresented ? 1 : 2}>
57
+ <div class="product-card__title" class:two-lines={!shortDescriptionPresented}>{product.title}</div>
58
+ </LineClamp>
59
+ <LineClamp maxLines={2}>
60
+ <div class="product-card__description" class:two-lines={shortDescriptionPresented}>{product.shortDescription}</div>
61
+ </LineClamp>
62
+ <div class="product-price">
63
+ <div class="product-price__before-price">
64
+ {#if product.salePrice}
65
+ {#if includeBeforeNowPrefix}
66
+ {localization.beforeNowPrefix}
67
+ {/if}
68
+ {toPriceRepresentation({ amount: product.price, currency: product.currency })}
69
+ {/if}
70
+ </div>
71
+ <div class="product-price__price" class:product-price__price--sale={product.salePrice}>
72
+ {toPriceRepresentation({ amount: product.salePrice ?? product.price, currency: product.currency })}
73
+ </div>
74
+ </div>
75
+
76
+ {#if on?.buy}
77
+ <div class="product-card__buy" onclick={onBuyClicked} onkeydown={() => {}} role="none">
78
+ <Button type="button" size="md" variant="secondary" on={{ click: onBuyClicked }} --sc-kit--button--min-width="100%">
79
+ <IconText icon={IconCart}>{localization.buy}</IconText>
80
+ </Button>
81
+ </div>
82
+ {/if}
83
+ </div>
84
+
85
+ {#if !inert}
86
+ {#if product.link}
87
+ <a href={product.link} onclick={onProductClicked} target="_blank" rel="noopener noreferrer" class="product-card__link" aria-label="none">&nbsp;</a>
88
+ {:else if on?.click}
89
+ <button type="button" onclick={onProductClicked} class="product-card__link" aria-label="none">&nbsp;</button>
90
+ {/if}
91
+ {/if}
92
+ </div>
93
+
94
+ <!--
95
+ @component
96
+ Product card with image, brand, title, description, price, and buy button.
97
+
98
+ ### CSS Custom Properties
99
+ | Property | Description | Default |
100
+ |---|---|---|
101
+ | `--sc-blocks--product-card--aspect-ratio` | Card aspect ratio | `10/16` |
102
+ | `--sc-blocks--product-card--border-radius` | Card border radius | `8px` |
103
+ | `--sc-blocks--product-card--background-color` | Card background | `rgb(from light-dark(white, black) r g b / 90%)` |
104
+ | `--sc-blocks--product-card--border-color` | Card border color | `--sc-kit--color--border` |
105
+ | `--sc-blocks--product-card--button--background` | Buy button background | `--sc-kit--color--bg--element` |
106
+ | `--sc-blocks--product-card--button--color` | Buy button text color | `--sc-kit--color--text--muted` |
107
+ | `--sc-blocks--product-card--price-color` | Price color | `inherit` |
108
+ | `--sc-blocks--product-card--sale-price-color` | Sale price color | `inherit` |
109
+ | `--sc-blocks--product-card--text-secondary` | Secondary text color | `--sc-kit--color--text--secondary` |
110
+ -->
111
+
112
+ <style>.product-card {
113
+ --_product-card--aspect-ratio: var(--sc-blocks--product-card--aspect-ratio, 10/16);
114
+ --_product-card--border-radius: var(--sc-blocks--product-card--border-radius, var(--sc-kit--radius--lg));
115
+ --_product-card--background-color: var(
116
+ --sc-blocks--product-card--background-color,
117
+ rgb(from light-dark(#ffffff, #000000) r g b / 90%)
118
+ );
119
+ --_product-card--border-color: var(--sc-blocks--product-card--border-color, var(--sc-kit--color--border));
120
+ --_product-card--button--background: var(--sc-blocks--product-card--button--background, var(--sc-kit--color--bg--element));
121
+ --_product-card--button--color: var(--sc-blocks--product-card--button--color, var(--sc-kit--color--text--muted));
122
+ --_product-card--price-color: var(--sc-blocks--product-card--price-color, inherit);
123
+ --_product-card--sale-price-color: var(--sc-blocks--product-card--sale-price-color, inherit);
124
+ --_product-card--text-secondary: var(--sc-blocks--product-card--text-secondary, var(--sc-kit--color--text--secondary));
125
+ --sc-kit--image--border-radius: var(--sc-kit--radius--sm);
126
+ --sc-kit--image--object-fit: cover;
127
+ width: 100%;
128
+ height: max-content;
129
+ display: flex;
130
+ flex-direction: column;
131
+ position: relative;
132
+ container-type: inline-size;
133
+ aspect-ratio: var(--_product-card--aspect-ratio);
134
+ background-color: var(--_product-card--background-color);
135
+ border: 1px solid var(--_product-card--border-color);
136
+ border-radius: var(--_product-card--border-radius);
137
+ padding: var(--sc-kit--space--3) var(--sc-kit--space--3) 1.125rem;
138
+ justify-content: space-between;
139
+ /* Set 'container-type: inline-size;' to reference container*/
140
+ }
141
+ @container (width < 230px) {
142
+ .product-card {
143
+ padding: var(--sc-kit--space--2) var(--sc-kit--space--2) var(--sc-kit--space--3);
144
+ }
145
+ }
146
+ .product-card--with-buy {
147
+ aspect-ratio: auto;
148
+ }
149
+ .product-card__info {
150
+ display: flex;
151
+ flex-direction: column;
152
+ gap: 0.375rem;
153
+ margin-top: 0.1875rem;
154
+ /* Set 'container-type: inline-size;' to reference container*/
155
+ }
156
+ @container (width < 230px) {
157
+ .product-card__info {
158
+ gap: var(--sc-kit--space--1);
159
+ margin-top: 0.125rem;
160
+ }
161
+ }
162
+ .product-card__brand {
163
+ font-weight: var(--sc-kit--font-weight--bold);
164
+ font-size: var(--sc-kit--font-size--sm);
165
+ line-height: 1.375rem;
166
+ min-height: 1.375rem;
167
+ /* Set 'container-type: inline-size;' to reference container*/
168
+ }
169
+ @container (width < 230px) {
170
+ .product-card__brand {
171
+ font-size: 0.5rem;
172
+ line-height: 0.875rem;
173
+ min-height: 0.875rem;
174
+ }
175
+ }
176
+ .product-card__title {
177
+ font-weight: var(--sc-kit--font-weight--bold);
178
+ font-size: 1.125rem;
179
+ line-height: 1.375rem;
180
+ min-height: 1.375rem;
181
+ /* Set 'container-type: inline-size;' to reference container*/
182
+ }
183
+ @container (width < 230px) {
184
+ .product-card__title {
185
+ font-size: var(--sc-kit--font-size--sm);
186
+ line-height: 0.875rem;
187
+ min-height: 0.875rem;
188
+ }
189
+ }
190
+ .product-card__description {
191
+ font-weight: var(--sc-kit--font-weight--regular);
192
+ color: var(--_product-card--text-secondary);
193
+ font-size: 0.9375rem;
194
+ line-height: 1.375rem;
195
+ min-height: 1.375rem;
196
+ /* Set 'container-type: inline-size;' to reference container*/
197
+ }
198
+ @container (width < 230px) {
199
+ .product-card__description {
200
+ font-size: 0.625rem;
201
+ line-height: 0.875rem;
202
+ min-height: 0.875rem;
203
+ }
204
+ }
205
+ .product-card .two-lines {
206
+ height: 2.75rem;
207
+ /* Set 'container-type: inline-size;' to reference container*/
208
+ }
209
+ @container (width < 230px) {
210
+ .product-card .two-lines {
211
+ height: 1.75rem;
212
+ }
213
+ }
214
+ .product-card__buy {
215
+ position: relative;
216
+ z-index: 1;
217
+ margin-top: 0.625rem;
218
+ --sc-kit--button--background: var(--_product-card--button--background);
219
+ --sc-kit--button--color: var(--_product-card--button--color);
220
+ --sc-kit--button--border: none;
221
+ --sc-kit--button--font-size: 0.75rem;
222
+ /* Set 'container-type: inline-size;' to reference container*/
223
+ }
224
+ @container (width < 230px) {
225
+ .product-card__buy {
226
+ margin-top: 0.375rem;
227
+ }
228
+ }
229
+ .product-card__link {
230
+ position: absolute;
231
+ inset: 0;
232
+ }
233
+
234
+ .product-price {
235
+ --_product-price--before--color: var(--_product-card--text-secondary);
236
+ --_product-price--before--font-size: 0.75em;
237
+ --_product-price--font-size: 1.875em;
238
+ --_product-price--gap: 0.375em;
239
+ --_product-price--align: right;
240
+ width: 100%;
241
+ display: flex;
242
+ flex-direction: column;
243
+ justify-items: end;
244
+ gap: var(--_product-price--gap);
245
+ /* Set 'container-type: inline-size;' to reference container*/
246
+ }
247
+ @container (width < 230px) {
248
+ .product-price {
249
+ --_product-price--before--font-size: 0.5rem;
250
+ --_product-price--font-size: 1.25rem;
251
+ --_product-price--gap: 0.25rem;
252
+ }
253
+ }
254
+ .product-price__before-price {
255
+ width: 100%;
256
+ color: var(--_product-price--before--color);
257
+ text-decoration: line-through;
258
+ font-size: var(--_product-price--before--font-size);
259
+ font-weight: var(--sc-kit--font-weight--medium);
260
+ text-align: var(--_product-price--align);
261
+ min-height: var(--_product-price--before--font-size);
262
+ text-overflow: ellipsis;
263
+ max-width: 100%;
264
+ white-space: nowrap;
265
+ overflow: hidden;
266
+ }
267
+ .product-price__price {
268
+ width: 100%;
269
+ color: var(--_product-card--price-color);
270
+ font-size: var(--_product-price--font-size);
271
+ font-weight: 800;
272
+ margin-top: auto;
273
+ text-align: var(--_product-price--align);
274
+ min-height: var(--_product-price--font-size);
275
+ text-overflow: ellipsis;
276
+ max-width: 100%;
277
+ white-space: nowrap;
278
+ overflow: hidden;
279
+ }
280
+ .product-price__price--sale {
281
+ color: var(--_product-card--sale-price-color);
282
+ }</style>
@@ -0,0 +1,30 @@
1
+ import type { ProductCardModel } from './types';
2
+ type Props = {
3
+ product: ProductCardModel;
4
+ includeBeforeNowPrefix?: boolean;
5
+ inert?: boolean;
6
+ on?: {
7
+ click?: (id: string) => void;
8
+ impression?: (id: string) => void;
9
+ buy?: (id: string) => void;
10
+ };
11
+ };
12
+ /**
13
+ * Product card with image, brand, title, description, price, and buy button.
14
+ *
15
+ * ### CSS Custom Properties
16
+ * | Property | Description | Default |
17
+ * |---|---|---|
18
+ * | `--sc-blocks--product-card--aspect-ratio` | Card aspect ratio | `10/16` |
19
+ * | `--sc-blocks--product-card--border-radius` | Card border radius | `8px` |
20
+ * | `--sc-blocks--product-card--background-color` | Card background | `rgb(from light-dark(white, black) r g b / 90%)` |
21
+ * | `--sc-blocks--product-card--border-color` | Card border color | `--sc-kit--color--border` |
22
+ * | `--sc-blocks--product-card--button--background` | Buy button background | `--sc-kit--color--bg--element` |
23
+ * | `--sc-blocks--product-card--button--color` | Buy button text color | `--sc-kit--color--text--muted` |
24
+ * | `--sc-blocks--product-card--price-color` | Price color | `inherit` |
25
+ * | `--sc-blocks--product-card--sale-price-color` | Sale price color | `inherit` |
26
+ * | `--sc-blocks--product-card--text-secondary` | Secondary text color | `--sc-kit--color--text--secondary` |
27
+ */
28
+ declare const Cmp: import("svelte").Component<Props, {}, "">;
29
+ type Cmp = ReturnType<typeof Cmp>;
30
+ export default Cmp;
@@ -0,0 +1,2 @@
1
+ export { default as ProductCard } from './cmp.product-card.svelte';
2
+ export type { ProductCardModel } from './types';
@@ -0,0 +1 @@
1
+ export { default as ProductCard } from './cmp.product-card.svelte';
@@ -0,0 +1,4 @@
1
+ export declare class ProductCardLocalization {
2
+ get beforeNowPrefix(): string | null;
3
+ get buy(): string;
4
+ }
@@ -0,0 +1,19 @@
1
+ import { AppLocale } from '@streamscloud/kit/core/locale';
2
+ export class ProductCardLocalization {
3
+ get beforeNowPrefix() {
4
+ return loc.beforeNowPrefix[AppLocale.current];
5
+ }
6
+ get buy() {
7
+ return loc.buy[AppLocale.current];
8
+ }
9
+ }
10
+ const loc = {
11
+ beforeNowPrefix: {
12
+ en: 'Before',
13
+ no: 'Før'
14
+ },
15
+ buy: {
16
+ en: 'Buy',
17
+ no: 'Kjøp'
18
+ }
19
+ };
@@ -0,0 +1,12 @@
1
+ import type { Currency } from '../../core/enums';
2
+ export type ProductCardModel = {
3
+ id: string;
4
+ title: string;
5
+ shortDescription: string | null;
6
+ link: string | null;
7
+ image: string | null;
8
+ brandName: string | null;
9
+ price: number;
10
+ currency: Currency;
11
+ salePrice: number | null;
12
+ };
@@ -0,0 +1 @@
1
+ export {};