@revenuecat/purchases-ui-js 2.2.1 → 3.0.1

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.
@@ -1,5 +1,6 @@
1
1
  <script module lang="ts">
2
2
  import Package from "./Package.svelte";
3
+ import type { PackageProps } from "../../types/components/package";
3
4
  import { componentDecorator } from "../../stories/component-decorator";
4
5
  import { localizationDecorator } from "../../stories/localization-decorator";
5
6
  import { defineMeta } from "@storybook/addon-svelte-csf";
@@ -9,6 +9,7 @@
9
9
  Video,
10
10
  } from "../..";
11
11
  import ButtonNode from "../button/ButtonNode.svelte";
12
+ import Countdown from "../countdown/Countdown.svelte";
12
13
  import InputMultipleChoice from "../options/InputMultipleChoice.svelte";
13
14
  import InputOption from "../options/InputOption.svelte";
14
15
  import InputSingleChoice from "../options/InputSingleChoice.svelte";
@@ -29,6 +30,7 @@
29
30
  const ComponentTypes = {
30
31
  button: ButtonNode,
31
32
  carousel: Carousel,
33
+ countdown: Countdown,
32
34
  footer: Footer,
33
35
  icon: Icon,
34
36
  image: Image,
@@ -26,13 +26,17 @@
26
26
  import { SHEET_PAYWALL } from "./fixtures/sheet-paywall";
27
27
  import { STACK_PAYWALL } from "./fixtures/stack-paywall";
28
28
  import { VARIABLES } from "./fixtures/variables";
29
+ import { COUNTDOWN_PAYWALL } from "../countdown/fixtures/countdown-paywall";
30
+ import { mockDateDecorator } from "storybook-mock-date-decorator";
29
31
 
30
32
  const { Story } = defineMeta({
31
33
  title: "Example/Paywall",
32
34
  component: Paywall,
33
35
  args: {
34
- onPurchaseClicked: (selectedPackageId: string) =>
35
- alert(`Purchase package ${selectedPackageId}`),
36
+ onPurchaseClicked: (selectedPackageId: string, actionId: string) =>
37
+ alert(
38
+ `Purchase package ${selectedPackageId}${actionId ? ` with action ${actionId}` : ""}`,
39
+ ),
36
40
  onBackClicked: () => alert("Go back"),
37
41
  onVisitCustomerCenterClicked: () => alert("Visit customer center"),
38
42
  onRestorePurchasesClicked: () => alert("Restore purchases"),
@@ -323,16 +327,59 @@
323
327
  }}
324
328
  />
325
329
 
330
+ <Story
331
+ name="With Countdown"
332
+ decorators={[mockDateDecorator]}
333
+ parameters={{
334
+ date: new Date("2024-01-15T12:00:00Z"),
335
+ }}
336
+ args={{
337
+ paywallData: COUNTDOWN_PAYWALL,
338
+ }}
339
+ />
340
+
326
341
  <Story
327
342
  name="Wallet Button"
328
343
  args={{
329
344
  paywallData: pastaPaywallData,
330
- walletButtonRender: (element: HTMLElement, selectedPackageId) => {
345
+ walletButtonRender: (
346
+ element: HTMLElement,
347
+ { selectedPackageId, onReady },
348
+ ) => {
349
+ const newDiv = document.createElement("div");
350
+ newDiv.innerText = "Hi! I was injected from the walletButtonRender";
351
+ newDiv.style.width = "100%";
352
+ newDiv.style.textAlign = "center";
353
+ newDiv.style.border = "1px solid black";
354
+
355
+ setTimeout(() => {
356
+ element.append(newDiv);
357
+ onReady && onReady();
358
+ }, 3000);
359
+ return {};
360
+ },
361
+ }}
362
+ />
363
+
364
+ <Story
365
+ name="Wallet Button Dark"
366
+ args={{
367
+ paywallData: STACK_PAYWALL,
368
+ walletButtonRender: (
369
+ element: HTMLElement,
370
+ { selectedPackageId, onReady },
371
+ ) => {
331
372
  const newDiv = document.createElement("div");
332
373
  newDiv.innerText = "Hi! I was injected from the walletButtonRender";
333
374
  newDiv.style.width = "100%";
334
375
  newDiv.style.textAlign = "center";
335
- element.append(newDiv);
376
+ newDiv.style.color = "white";
377
+ newDiv.style.border = "1px solid white";
378
+
379
+ setTimeout(() => {
380
+ element.append(newDiv);
381
+ onReady && onReady();
382
+ }, 3000);
336
383
  return {};
337
384
  },
338
385
  }}
@@ -33,7 +33,7 @@
33
33
  infoPerPackage?: Record<string, PackageInfo>;
34
34
  uiConfig: UIConfig;
35
35
  preferredColorMode?: ColorMode;
36
- onPurchaseClicked?: (selectedPackageId: string) => void;
36
+ onPurchaseClicked?: (selectedPackageId: string, actionId: string) => void;
37
37
  onBackClicked?: () => void;
38
38
  onVisitCustomerCenterClicked?: () => void;
39
39
  onRestorePurchasesClicked?: () => void;
@@ -42,7 +42,10 @@
42
42
  onError?: (error: unknown) => void;
43
43
  walletButtonRender?: (
44
44
  node: HTMLElement,
45
- selectedPackageId: string,
45
+ params: {
46
+ selectedPackageId: string;
47
+ onReady?: () => void;
48
+ },
46
49
  ) => {
47
50
  destroy?: () => void;
48
51
  update?: (selectedPackageId: string) => void;
@@ -83,10 +86,10 @@
83
86
  findSelectedPackageId(base),
84
87
  );
85
88
 
86
- const onPurchase = () => {
89
+ const onPurchase = (actionId?: string) => {
87
90
  const packageId = $selectedPackageId;
88
91
  if (packageId !== undefined) {
89
- onPurchaseClicked?.(packageId);
92
+ onPurchaseClicked?.(packageId, actionId ?? "");
90
93
  }
91
94
  };
92
95
 
@@ -9,14 +9,17 @@ interface Props {
9
9
  infoPerPackage?: Record<string, PackageInfo>;
10
10
  uiConfig: UIConfig;
11
11
  preferredColorMode?: ColorMode;
12
- onPurchaseClicked?: (selectedPackageId: string) => void;
12
+ onPurchaseClicked?: (selectedPackageId: string, actionId: string) => void;
13
13
  onBackClicked?: () => void;
14
14
  onVisitCustomerCenterClicked?: () => void;
15
15
  onRestorePurchasesClicked?: () => void;
16
16
  onNavigateToUrlClicked?: (url: string) => void;
17
17
  onActionTriggered?: (actionId: string) => void;
18
18
  onError?: (error: unknown) => void;
19
- walletButtonRender?: (node: HTMLElement, selectedPackageId: string) => {
19
+ walletButtonRender?: (node: HTMLElement, params: {
20
+ selectedPackageId: string;
21
+ onReady?: () => void;
22
+ }) => {
20
23
  destroy?: () => void;
21
24
  update?: (selectedPackageId: string) => void;
22
25
  };
@@ -2,19 +2,159 @@
2
2
  import Stack from "../stack/Stack.svelte";
3
3
  import { getPaywallContext } from "../../stores/paywall";
4
4
  import type { PurchaseButtonProps } from "../../types/components/purchase-button";
5
+ import { getSelectedStateContext } from "../../stores/selected";
6
+ import { getActiveStateProps } from "../../utils/style-utils";
7
+ import {
8
+ type CSS,
9
+ css,
10
+ mapBorder,
11
+ mapBorderRadius,
12
+ mapShadow,
13
+ mapSize,
14
+ mapSpacing,
15
+ px,
16
+ } from "../../utils/base-utils";
17
+ import { mapDimension } from "../stack/stack-utils";
18
+ import type { StackProps } from "../../types/components/stack";
19
+ import { DEFAULT_SPACING } from "../../utils/constants";
20
+ import { getColorModeContext } from "../../stores/color-mode";
21
+ import { onMount } from "svelte";
22
+
23
+ const props: PurchaseButtonProps = $props();
24
+ const { stack } = props;
5
25
 
6
- const { stack }: PurchaseButtonProps = $props();
7
26
  const { onPurchase, walletButtonRender, selectedPackageId } =
8
27
  getPaywallContext();
28
+
29
+ const stackProps: StackProps & { style?: CSS } = { ...stack };
30
+ const selectedState = getSelectedStateContext();
31
+ const { size, dimension, spacing, margin, border, shape, shadow } =
32
+ $derived.by(() => {
33
+ return {
34
+ ...stackProps,
35
+ ...getActiveStateProps($selectedState, stackProps.overrides),
36
+ };
37
+ });
38
+
39
+ const minDistanceBetweenPurchaseButtons = 10;
40
+ const maxWaitTimeBeforeClosingSkeleton = 3000;
41
+
42
+ const getColorMode = getColorModeContext();
43
+ const colorMode = $derived(getColorMode());
44
+
45
+ const stackLayoutRules = $derived({
46
+ ...stackProps.style,
47
+ display: "flex",
48
+ position: "relative",
49
+ width: mapSize(size.width),
50
+ height: mapSize(size.height),
51
+ ...mapDimension(dimension),
52
+ gap: px(spacing),
53
+ margin: mapSpacing({
54
+ ...(margin
55
+ ? {
56
+ ...margin,
57
+ bottom: Math.max(margin.bottom, minDistanceBetweenPurchaseButtons),
58
+ }
59
+ : {
60
+ ...DEFAULT_SPACING,
61
+ bottom: minDistanceBetweenPurchaseButtons,
62
+ }),
63
+ }),
64
+ });
65
+
66
+ const stackStyle = $derived(css(stackLayoutRules));
67
+
68
+ const loaderStyle = $derived(
69
+ css({
70
+ ...stackLayoutRules,
71
+ border: mapBorder(colorMode, border),
72
+ "border-radius": mapBorderRadius(shape),
73
+ "box-shadow": mapShadow(colorMode, shadow),
74
+ }),
75
+ );
76
+
77
+ const onclick = () => {
78
+ const actionId = props.triggers?.on_purchase_press;
79
+ onPurchase(actionId);
80
+ };
81
+
82
+ let shouldShowWalletSkeleton = $state(!!walletButtonRender);
83
+ const onWalletButtonReady = () => {
84
+ shouldShowWalletSkeleton = false;
85
+ };
86
+
87
+ onMount(() => {
88
+ setTimeout(() => {
89
+ shouldShowWalletSkeleton = false;
90
+ }, maxWaitTimeBeforeClosingSkeleton);
91
+ });
9
92
  </script>
10
93
 
11
- {#if walletButtonRender}
12
- {#if $selectedPackageId}
94
+ {#if walletButtonRender && $selectedPackageId}
95
+ <div style={stackStyle}>
13
96
  <div
14
- style="width: 100%; margin-bottom:20px"
15
- use:walletButtonRender={$selectedPackageId}
97
+ style="flex-grow: 1; width:100%;"
98
+ use:walletButtonRender={{
99
+ selectedPackageId: $selectedPackageId,
100
+ onReady: onWalletButtonReady,
101
+ }}
16
102
  ></div>
17
- {/if}
103
+ </div>
104
+ {/if}
105
+
106
+ {#if shouldShowWalletSkeleton}
107
+ <div class="wallet-skeleton" style={loaderStyle} aria-hidden="true">
108
+ &nbsp;
109
+ </div>
110
+ {:else}
111
+ <Stack {...stack} {onclick} />
18
112
  {/if}
19
113
 
20
- <Stack {...stack} onclick={onPurchase} />
114
+ <style>
115
+ .wallet-skeleton {
116
+ position: relative;
117
+ overflow: hidden;
118
+ border-radius: 9999px;
119
+ background-color: rgba(128, 128, 128, 0.16);
120
+ backdrop-filter: blur(18px);
121
+ min-height: 48px;
122
+ content: " ";
123
+ flex-grow: 1;
124
+ width: 100%;
125
+ }
126
+
127
+ .wallet-skeleton::before {
128
+ content: "";
129
+ position: absolute;
130
+ inset: 0;
131
+ background: linear-gradient(
132
+ 135deg,
133
+ rgba(255, 255, 255, 0.2),
134
+ rgba(255, 255, 255, 0.01)
135
+ );
136
+ opacity: 0.9;
137
+ mix-blend-mode: screen;
138
+ }
139
+
140
+ .wallet-skeleton::after {
141
+ content: "";
142
+ position: absolute;
143
+ inset: 0;
144
+ transform: translateX(-100%);
145
+ background: linear-gradient(
146
+ 120deg,
147
+ transparent 0%,
148
+ rgba(255, 255, 255, 0.6) 20%,
149
+ transparent 40%
150
+ );
151
+ opacity: 0.8;
152
+ animation: wallet-skeleton-shimmer 1.4s ease-in-out infinite;
153
+ }
154
+
155
+ @keyframes wallet-skeleton-shimmer {
156
+ 100% {
157
+ transform: translateX(100%);
158
+ }
159
+ }
160
+ </style>
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export { default as ButtonDeprecated } from "./components/button/Button.svelte";
2
+ export { default as Countdown } from "./components/countdown/Countdown.svelte";
2
3
  export { default as Footer } from "./components/footer/Footer.svelte";
3
4
  export { default as Image } from "./components/image/Image.svelte";
4
5
  export { default as InputMultipleChoice } from "./components/options/InputMultipleChoice.svelte";
package/dist/index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  // Reexport your entry components here
2
2
  export { default as ButtonDeprecated } from "./components/button/Button.svelte";
3
+ export { default as Countdown } from "./components/countdown/Countdown.svelte";
3
4
  export { default as Footer } from "./components/footer/Footer.svelte";
4
5
  export { default as Image } from "./components/image/Image.svelte";
5
6
  export { default as InputMultipleChoice } from "./components/options/InputMultipleChoice.svelte";
@@ -6,8 +6,11 @@ type PaywallContext = Readonly<{
6
6
  selectedPackageId: Writable<string | undefined>;
7
7
  variablesPerPackage: Readable<Record<string, VariableDictionary> | undefined>;
8
8
  infoPerPackage: Readable<Record<string, PackageInfo> | undefined>;
9
- onPurchase: () => void;
10
- walletButtonRender?: (node: HTMLElement, selectedPackageId: string) => {
9
+ onPurchase: (actionId?: string) => void;
10
+ walletButtonRender?: (node: HTMLElement, params: {
11
+ selectedPackageId: string;
12
+ onReady?: () => void;
13
+ }) => {
11
14
  destroy?: () => void;
12
15
  update?: (selectedPackageId: string) => void;
13
16
  };
@@ -18,6 +18,10 @@ export declare const localizations: {
18
18
  id2: string;
19
19
  id3: string;
20
20
  badge: string;
21
+ countdown: string;
22
+ ended: string;
23
+ days: string;
24
+ time: string;
21
25
  };
22
26
  };
23
27
  export declare const colorModeOverrideTemplate: PaywallData;
@@ -17307,6 +17307,10 @@ export const localizations = {
17307
17307
  id2: "Item 2",
17308
17308
  id3: "Item 3",
17309
17309
  badge: "Badge Text",
17310
+ countdown: "{{ count_days_with_zero }} : {{ count_hours_with_zero }} : {{ count_minutes_with_zero }} : {{ count_seconds_with_zero }}",
17311
+ ended: "Time's up!",
17312
+ days: "{{ count_days_without_zero }} days",
17313
+ time: "{{ count_hours_with_zero }}:{{ count_minutes_with_zero }}:{{ count_seconds_with_zero }}",
17310
17314
  },
17311
17315
  };
17312
17316
  export const colorModeOverrideTemplate = {
@@ -1,5 +1,8 @@
1
1
  import type { DecoratorFunction, Renderer } from "storybook/internal/csf";
2
- export declare function paywallDecorator<TRenderer extends Renderer, TArgs>(walletButtonRender?: (node: HTMLElement, selectedPackageId: string) => {
2
+ export declare function paywallDecorator<TRenderer extends Renderer, TArgs>(walletButtonRender?: (node: HTMLElement, params: {
3
+ selectedPackageId: string;
4
+ onReady?: () => void;
5
+ }) => {
3
6
  destroy?: () => void;
4
7
  update?: (selectedPackageId: string) => void;
5
8
  }): DecoratorFunction<TRenderer, TArgs>;
@@ -1,5 +1,6 @@
1
1
  import type { ButtonProps } from "./components/button";
2
2
  import type { CarouselProps } from "./components/carousel";
3
+ import type { CountdownProps } from "./components/countdown";
3
4
  import type { FooterProps } from "./components/footer";
4
5
  import type { IconProps } from "./components/icon";
5
6
  import type { ImageProps } from "./components/image";
@@ -11,4 +12,4 @@ import type { TabControlButtonProps, TabControlProps, TabControlToggleProps, Tab
11
12
  import type { TextNodeProps } from "./components/text";
12
13
  import type { TimelineProps } from "./components/timeline";
13
14
  import type { VideoProps } from "./components/video";
14
- export type Component = ButtonProps | CarouselProps | FooterProps | IconProps | ImageProps | InputMultipleChoiceProps | InputOptionProps | InputSingleChoiceProps | PackageProps | PurchaseButtonProps | StackProps | TabControlButtonProps | TabControlToggleProps | TabControlProps | TabsProps | TextNodeProps | TimelineProps | VideoProps;
15
+ export type Component = ButtonProps | CarouselProps | CountdownProps | FooterProps | IconProps | ImageProps | InputMultipleChoiceProps | InputOptionProps | InputSingleChoiceProps | PackageProps | PurchaseButtonProps | StackProps | TabControlButtonProps | TabControlToggleProps | TabControlProps | TabsProps | TextNodeProps | TimelineProps | VideoProps;
@@ -0,0 +1,13 @@
1
+ import type { BaseComponent } from "../base";
2
+ import type { StackProps } from "./stack";
3
+ export interface CountdownStyle {
4
+ type: "date";
5
+ date: string;
6
+ }
7
+ export interface CountdownProps extends BaseComponent {
8
+ type: "countdown";
9
+ visible?: boolean | null;
10
+ style: CountdownStyle;
11
+ countdown_stack: StackProps;
12
+ end_stack: StackProps | null;
13
+ }
@@ -0,0 +1 @@
1
+ export {};
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@revenuecat/purchases-ui-js",
3
3
  "description": "Web components for Paywalls. Powered by RevenueCat",
4
4
  "private": false,
5
- "version": "2.2.1",
5
+ "version": "3.0.1",
6
6
  "author": {
7
7
  "name": "RevenueCat, Inc."
8
8
  },
@@ -97,6 +97,7 @@
97
97
  "prettier-plugin-svelte": "3.4.0",
98
98
  "publint": "0.3.15",
99
99
  "storybook": "9.1.10",
100
+ "storybook-mock-date-decorator": "^3.0.0",
100
101
  "svelte": "5.43.0",
101
102
  "svelte-check": "4.3.3",
102
103
  "typescript": "5.9.3",