@revenuecat/purchases-ui-js 4.12.2 → 4.12.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.
@@ -360,6 +360,16 @@
360
360
 
361
361
  const lastPage = pages.length - 1;
362
362
 
363
+ const pagesWithShadow: CarouselProps["pages"] = pages.map((page) => ({
364
+ ...page,
365
+ shadow: {
366
+ x: 0,
367
+ y: 4,
368
+ radius: 14,
369
+ color: { light: { type: "hex", value: "#00000040" } },
370
+ },
371
+ }));
372
+
363
373
  const { Story } = defineMeta({
364
374
  title: "Components/Carousel",
365
375
  component: Carousel,
@@ -1085,3 +1095,45 @@
1085
1095
  },
1086
1096
  }}
1087
1097
  />
1098
+
1099
+ <Story
1100
+ name="Page shadows"
1101
+ args={{
1102
+ padding: { top: 16, bottom: 16, leading: 0, trailing: 0 },
1103
+ page_alignment: "top",
1104
+ page_control: null,
1105
+ auto_advance: null,
1106
+ pages: pagesWithShadow,
1107
+ }}
1108
+ />
1109
+
1110
+ <Story
1111
+ name="Rounded gradient border with page shadows"
1112
+ args={{
1113
+ padding: { top: 16, bottom: 16, leading: 16, trailing: 16 },
1114
+ shape: {
1115
+ type: "rectangle",
1116
+ corners: {
1117
+ top_leading: 24,
1118
+ top_trailing: 24,
1119
+ bottom_leading: 24,
1120
+ bottom_trailing: 24,
1121
+ },
1122
+ },
1123
+ border: {
1124
+ width: 4,
1125
+ color: {
1126
+ light: {
1127
+ type: "linear",
1128
+ degrees: 90,
1129
+ points: [
1130
+ { color: "#000000", percent: 0 },
1131
+ { color: "#FF0000", percent: 100 },
1132
+ ],
1133
+ },
1134
+ },
1135
+ },
1136
+ auto_advance: null,
1137
+ pages: pagesWithShadow,
1138
+ }}
1139
+ />
@@ -72,7 +72,6 @@
72
72
  const carouselStyle = $derived(
73
73
  css({
74
74
  margin: mapSpacing(margin),
75
- padding: mapSpacing(padding),
76
75
  ...videoBackgroundHostStyles({
77
76
  hasVideoBg,
78
77
  positionRelative: true,
@@ -85,6 +84,9 @@
85
84
  }),
86
85
  );
87
86
 
87
+ // Padding lives inside the clip so page shadows can paint into it.
88
+ const clipStyle = $derived(css({ padding: mapSpacing(padding) }));
89
+
88
90
  const sliderStyle = $derived(
89
91
  css({
90
92
  gap: px(page_spacing),
@@ -321,7 +323,7 @@
321
323
  />
322
324
  </div>
323
325
  {/if}
324
- <div class="carousel-clip">
326
+ <div class="carousel-clip" style={clipStyle}>
325
327
  <div
326
328
  bind:this={slider}
327
329
  bind:clientWidth={carouselWidth}
@@ -57,7 +57,10 @@
57
57
  ...(margin
58
58
  ? {
59
59
  ...margin,
60
- bottom: Math.max(margin.bottom, minDistanceBetweenPurchaseButtons),
60
+ bottom: Math.max(
61
+ margin.bottom ?? 0,
62
+ minDistanceBetweenPurchaseButtons,
63
+ ),
61
64
  }
62
65
  : {
63
66
  ...DEFAULT_SPACING,
@@ -5,6 +5,7 @@
5
5
  import { localizations } from "../../stories/fixtures";
6
6
  import { localizationDecorator } from "../../stories/localization-decorator";
7
7
  import type { StackProps } from "../../types/components/stack";
8
+ import type { Spacing } from "../../types";
8
9
  import type { Component } from "../../types/component";
9
10
  import { STORY_BACKGROUND_VIDEO_FILL } from "../../stories/video-background-story-fixture";
10
11
  import { DEFAULT_TEXT_COLOR } from "../../utils/constants";
@@ -107,6 +108,62 @@
107
108
  },
108
109
  ];
109
110
 
111
+ function marginBox(
112
+ id: string,
113
+ labelLid: string,
114
+ margin: Spacing,
115
+ ): StackProps {
116
+ return {
117
+ type: "stack",
118
+ id,
119
+ name: id,
120
+ components: [
121
+ {
122
+ type: "text",
123
+ id: `${id}-label`,
124
+ name: "Label",
125
+ text_lid: labelLid,
126
+ color: { light: { type: "hex", value: "#ffffff" } },
127
+ font_name: null,
128
+ font_size: "body_m",
129
+ font_weight: "bold",
130
+ horizontal_alignment: "leading",
131
+ size: { width: { type: "fit" }, height: { type: "fit" } },
132
+ },
133
+ {
134
+ type: "text",
135
+ id: `${id}-expected`,
136
+ name: "Expected",
137
+ text_lid: "margin_expected",
138
+ color: { light: { type: "hex", value: "#ffffff" } },
139
+ font_name: null,
140
+ font_size: "body_s",
141
+ font_weight: "regular",
142
+ horizontal_alignment: "leading",
143
+ size: { width: { type: "fit" }, height: { type: "fit" } },
144
+ },
145
+ ] as unknown as TextNodeProps[],
146
+ size: { width: { type: "fill" }, height: { type: "fit" } },
147
+ dimension: {
148
+ type: "vertical",
149
+ alignment: "leading",
150
+ distribution: "start",
151
+ },
152
+ spacing: 4,
153
+ margin,
154
+ padding: { top: 12, trailing: 12, bottom: 12, leading: 12 },
155
+ background_color: {
156
+ light: { type: "hex", value: "#47aab1" },
157
+ dark: { type: "hex", value: "#47aab1" },
158
+ },
159
+ background: null,
160
+ border: null,
161
+ shape: null,
162
+ shadow: null,
163
+ badge: null,
164
+ };
165
+ }
166
+
110
167
  const { Story } = defineMeta({
111
168
  title: "Components/Stack",
112
169
  component: Stack,
@@ -483,6 +540,44 @@
483
540
  }}
484
541
  />
485
542
 
543
+ <Story
544
+ name="Nested stack — partial margin"
545
+ parameters={{
546
+ docs: {
547
+ description: {
548
+ story:
549
+ "The first inner stack only sets `margin.top`; the second sets all four sides explicitly. Missing sides render as 0, matching iOS and Android, so both boxes should look identical: 48px below whatever is above them and flush with both horizontal edges.",
550
+ },
551
+ },
552
+ }}
553
+ args={{
554
+ size: {
555
+ width: { type: "fixed", value: 300 },
556
+ height: { type: "fit" },
557
+ },
558
+ dimension: {
559
+ type: "vertical",
560
+ alignment: "leading",
561
+ distribution: "start",
562
+ },
563
+ spacing: 0,
564
+ padding: { top: 0, trailing: 0, bottom: 0, leading: 0 },
565
+ background_color: {
566
+ light: { type: "hex", value: "#F0F0F0" },
567
+ dark: { type: "hex", value: "#F0F0F0" },
568
+ },
569
+ components: [
570
+ marginBox("partial", "partial_margin", { top: 48 }),
571
+ marginBox("explicit", "explicit_margin", {
572
+ top: 48,
573
+ trailing: 0,
574
+ bottom: 0,
575
+ leading: 0,
576
+ }),
577
+ ],
578
+ }}
579
+ />
580
+
486
581
  <Story
487
582
  name="Complex - pill"
488
583
  args={{
@@ -3,7 +3,7 @@
3
3
  import { getColorModeContext } from "../../stores/color-mode";
4
4
  import { useConditionContext } from "../../stores/condition-context.svelte";
5
5
  import type { TimelineItemProps } from "../../types/components/timeline";
6
- import { css, mapColor } from "../../utils/base-utils";
6
+ import { css, mapColor, mapSpacing } from "../../utils/base-utils";
7
7
  import { resolveOverrideProperties } from "../../utils/style-utils";
8
8
  import Icon from "../icon/Icon.svelte";
9
9
  import TextNode from "../text/TextNode.svelte";
@@ -56,8 +56,7 @@
56
56
  "z-index": "1",
57
57
  width: `${connector.width}px`,
58
58
  height: `100%`, // Extend beyond icon
59
- "padding-top": `${connector.margin.top}px`,
60
- "padding-bottom": `${connector.margin.bottom}px`,
59
+ padding: mapSpacing(connector.margin),
61
60
  });
62
61
  });
63
62
 
@@ -18,6 +18,9 @@ export declare const localizations: {
18
18
  id2: string;
19
19
  id3: string;
20
20
  badge: string;
21
+ partial_margin: string;
22
+ explicit_margin: string;
23
+ margin_expected: string;
21
24
  countdown: string;
22
25
  ended: string;
23
26
  days: string;
@@ -17317,6 +17317,9 @@ export const localizations = {
17317
17317
  id2: "Item 2",
17318
17318
  id3: "Item 3",
17319
17319
  badge: "Badge Text",
17320
+ partial_margin: "margin: { top: 48 }",
17321
+ explicit_margin: "margin: { top: 48, trailing: 0, bottom: 0, leading: 0 }",
17322
+ margin_expected: "48px gap above, edge to edge horizontally",
17320
17323
  countdown: "{{ count_days_with_zero }} : {{ count_hours_with_zero }} : {{ count_minutes_with_zero }} : {{ count_seconds_with_zero }}",
17321
17324
  ended: "Time's up!",
17322
17325
  days: "{{ count_days_without_zero }} days",
@@ -20,8 +20,8 @@ export interface TimelineProps extends BaseComponent {
20
20
  export interface TimelineConnectorProps {
21
21
  width: number;
22
22
  margin: {
23
- top: number;
24
- bottom: number;
23
+ top?: number;
24
+ bottom?: number;
25
25
  };
26
26
  color: ColorGradientScheme | null;
27
27
  }
package/dist/types.d.ts CHANGED
@@ -87,10 +87,10 @@ export type BorderType = {
87
87
  width: number;
88
88
  };
89
89
  export type Spacing = {
90
- top: number;
91
- bottom: number;
92
- leading: number;
93
- trailing: number;
90
+ top?: number;
91
+ bottom?: number;
92
+ leading?: number;
93
+ trailing?: number;
94
94
  };
95
95
  export type CornerRadiusType = {
96
96
  bottom_leading: number;
@@ -34,7 +34,7 @@ export function mapSize(size) {
34
34
  }
35
35
  }
36
36
  export function mapSpacing(spacing) {
37
- const { top, bottom, leading, trailing } = spacing ?? DEFAULT_SPACING;
37
+ const { top = 0, bottom = 0, leading = 0, trailing = 0, } = spacing ?? DEFAULT_SPACING;
38
38
  return mapTuple(top, trailing, bottom, leading);
39
39
  }
40
40
  export function mapColorInfo(info) {
@@ -162,8 +162,9 @@ const conditionMatches = (condition, context) => {
162
162
  if (condition.type === "error")
163
163
  return !!context.error;
164
164
  if (condition.type === "multiple_intro_offers") {
165
- // No web data source for this; never matches.
166
- return false;
165
+ // A Web Billing multiphase offer has a free trial followed by an
166
+ // introductory price.
167
+ return (!!context.packageInfo?.hasTrial && !!context.packageInfo?.hasIntroOffer);
167
168
  }
168
169
  if (condition.type === "intro_offer" ||
169
170
  condition.type === "intro_offer_condition") {
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": "4.12.2",
5
+ "version": "4.12.4",
6
6
  "author": {
7
7
  "name": "RevenueCat, Inc."
8
8
  },