@mission-studio/puck 1.0.1 → 1.0.2

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.
@@ -524,6 +524,8 @@ function ImageCarousel({
524
524
  }) {
525
525
  const [currentIndex, setCurrentIndex] = useState2(0);
526
526
  const { resolveColor: resolveColor2 } = useTheme();
527
+ const sendEvent = useGtmEvent();
528
+ const utm = useUtmParams();
527
529
  const resolvedArrowColor = (() => {
528
530
  if (!arrowColor) return { color: "#FFFFFF", opacity: 100 };
529
531
  if (typeof arrowColor === "string")
@@ -544,13 +546,33 @@ function ImageCarousel({
544
546
  return resolveColor2("primary");
545
547
  })();
546
548
  const goToPrevious = () => {
547
- setCurrentIndex((prev) => prev === 0 ? images.length - 1 : prev - 1);
549
+ const newIndex = currentIndex === 0 ? images.length - 1 : currentIndex - 1;
550
+ setCurrentIndex(newIndex);
551
+ sendEvent("carousel_navigate", {
552
+ direction: "previous",
553
+ slideIndex: newIndex,
554
+ totalSlides: images.length,
555
+ ...utm
556
+ });
548
557
  };
549
558
  const goToNext = () => {
550
- setCurrentIndex((prev) => prev === images.length - 1 ? 0 : prev + 1);
559
+ const newIndex = currentIndex === images.length - 1 ? 0 : currentIndex + 1;
560
+ setCurrentIndex(newIndex);
561
+ sendEvent("carousel_navigate", {
562
+ direction: "next",
563
+ slideIndex: newIndex,
564
+ totalSlides: images.length,
565
+ ...utm
566
+ });
551
567
  };
552
568
  const goToSlide = (index) => {
553
569
  setCurrentIndex(index);
570
+ sendEvent("carousel_navigate", {
571
+ direction: "direct",
572
+ slideIndex: index,
573
+ totalSlides: images.length,
574
+ ...utm
575
+ });
554
576
  };
555
577
  if (images.length === 0) {
556
578
  return /* @__PURE__ */ jsx5(
@@ -1655,11 +1677,27 @@ function Footer({
1655
1677
  puck
1656
1678
  }) {
1657
1679
  const DropZone = puck?.renderDropZone;
1680
+ const sendEvent = useGtmEvent();
1681
+ const utm = useUtmParams();
1682
+ const getSocialPlatform = (url) => {
1683
+ if (url.includes("facebook")) return "facebook";
1684
+ if (url.includes("instagram")) return "instagram";
1685
+ if (url.includes("twitter")) return "twitter";
1686
+ return "social";
1687
+ };
1688
+ const handleSocialClick = (url) => {
1689
+ const platform = getSocialPlatform(url);
1690
+ sendEvent("social_click", {
1691
+ platform,
1692
+ url,
1693
+ ...utm
1694
+ });
1695
+ };
1658
1696
  const socialLinks = [
1659
1697
  { url: facebookUrl, Icon: Facebook },
1660
1698
  { url: instagramUrl, Icon: Instagram },
1661
1699
  { url: twitterUrl, Icon: Twitter }
1662
- ].filter((link) => link.url);
1700
+ ].filter((link) => !!link.url);
1663
1701
  return /* @__PURE__ */ jsx18(
1664
1702
  "footer",
1665
1703
  {
@@ -1675,6 +1713,7 @@ function Footer({
1675
1713
  target: "_blank",
1676
1714
  rel: "noopener noreferrer",
1677
1715
  className: "transition-opacity hover:opacity-80",
1716
+ onClick: () => handleSocialClick(url),
1678
1717
  children: /* @__PURE__ */ jsx18(Icon3, { size: 24, style: { color: textColor } })
1679
1718
  },
1680
1719
  index
@@ -1701,6 +1740,24 @@ function Topbar({
1701
1740
  }) {
1702
1741
  const DropZone = puck?.renderDropZone;
1703
1742
  const [mobileMenuOpen, setMobileMenuOpen] = useState3(false);
1743
+ const sendEvent = useGtmEvent();
1744
+ const utm = useUtmParams();
1745
+ const handleNavClick = (item) => {
1746
+ sendEvent("nav_click", {
1747
+ name: item.name,
1748
+ url: item.url,
1749
+ linkType: item.linkType || "internal",
1750
+ ...utm
1751
+ });
1752
+ };
1753
+ const handleMobileMenuToggle = () => {
1754
+ const newState = !mobileMenuOpen;
1755
+ setMobileMenuOpen(newState);
1756
+ sendEvent("mobile_menu_toggle", {
1757
+ open: newState,
1758
+ ...utm
1759
+ });
1760
+ };
1704
1761
  const renderLink = (item, index) => {
1705
1762
  const className = "hover:opacity-80 transition-opacity";
1706
1763
  if (item.linkType === "external") {
@@ -1711,15 +1768,34 @@ function Topbar({
1711
1768
  target: "_blank",
1712
1769
  rel: "noopener noreferrer",
1713
1770
  className,
1771
+ onClick: () => handleNavClick(item),
1714
1772
  children: item.name
1715
1773
  },
1716
1774
  index
1717
1775
  );
1718
1776
  }
1719
1777
  if (item.linkType === "scrollTo") {
1720
- return /* @__PURE__ */ jsx19("a", { href: item.url, className, children: item.name }, index);
1778
+ return /* @__PURE__ */ jsx19(
1779
+ "a",
1780
+ {
1781
+ href: item.url,
1782
+ className,
1783
+ onClick: () => handleNavClick(item),
1784
+ children: item.name
1785
+ },
1786
+ index
1787
+ );
1721
1788
  }
1722
- return /* @__PURE__ */ jsx19(Link, { href: item.url, className, children: item.name }, index);
1789
+ return /* @__PURE__ */ jsx19(
1790
+ Link,
1791
+ {
1792
+ href: item.url,
1793
+ className,
1794
+ onClick: () => handleNavClick(item),
1795
+ children: item.name
1796
+ },
1797
+ index
1798
+ );
1723
1799
  };
1724
1800
  return /* @__PURE__ */ jsxs8(
1725
1801
  "nav",
@@ -1733,19 +1809,25 @@ function Topbar({
1733
1809
  className: "mx-auto flex items-center justify-between",
1734
1810
  style: { maxWidth },
1735
1811
  children: [
1736
- /* @__PURE__ */ jsx19(Link, { href: logoUrl, className: "flex-shrink-0", children: logo ? /* @__PURE__ */ jsx19("img", { src: logo, alt: "Logo", className: "h-8" }) : /* @__PURE__ */ jsx19("span", { className: "text-xl font-bold", children: "Logo" }) }),
1812
+ /* @__PURE__ */ jsx19(
1813
+ Link,
1814
+ {
1815
+ href: logoUrl,
1816
+ className: "flex-shrink-0",
1817
+ onClick: () => sendEvent("nav_click", {
1818
+ name: "logo",
1819
+ url: logoUrl,
1820
+ linkType: "internal",
1821
+ ...utm
1822
+ }),
1823
+ children: logo ? /* @__PURE__ */ jsx19("img", { src: logo, alt: "Logo", className: "h-8" }) : /* @__PURE__ */ jsx19("span", { className: "text-xl font-bold", children: "Logo" })
1824
+ }
1825
+ ),
1737
1826
  /* @__PURE__ */ jsxs8("div", { className: "hidden items-center gap-8 md:flex", children: [
1738
1827
  navItems.map(renderLink),
1739
1828
  DropZone && /* @__PURE__ */ jsx19(DropZone, { zone: "cta" })
1740
1829
  ] }),
1741
- /* @__PURE__ */ jsx19(
1742
- "button",
1743
- {
1744
- className: "md:hidden",
1745
- onClick: () => setMobileMenuOpen(!mobileMenuOpen),
1746
- children: mobileMenuOpen ? /* @__PURE__ */ jsx19(X, { size: 24 }) : /* @__PURE__ */ jsx19(Menu, { size: 24 })
1747
- }
1748
- )
1830
+ /* @__PURE__ */ jsx19("button", { className: "md:hidden", onClick: handleMobileMenuToggle, children: mobileMenuOpen ? /* @__PURE__ */ jsx19(X, { size: 24 }) : /* @__PURE__ */ jsx19(Menu, { size: 24 }) })
1749
1831
  ]
1750
1832
  }
1751
1833
  ),
@@ -1797,10 +1879,24 @@ function Popup({
1797
1879
  puck
1798
1880
  }) {
1799
1881
  const [isOpen, setIsOpen] = useState4(false);
1882
+ const sendEvent = useGtmEvent();
1883
+ const utm = useUtmParams();
1884
+ const handleOpen = () => {
1885
+ setIsOpen(true);
1886
+ sendEvent("popup_open", {
1887
+ ctaText,
1888
+ type: textLink ? "link" : "button",
1889
+ ...utm
1890
+ });
1891
+ };
1892
+ const handleClose = () => {
1893
+ setIsOpen(false);
1894
+ sendEvent("popup_close", { ctaText, ...utm });
1895
+ };
1800
1896
  const trigger = textLink ? /* @__PURE__ */ jsx20(
1801
1897
  "button",
1802
1898
  {
1803
- onClick: () => setIsOpen(true),
1899
+ onClick: handleOpen,
1804
1900
  className: "underline hover:opacity-80",
1805
1901
  style: { color: buttonColor },
1806
1902
  children: ctaText
@@ -1808,7 +1904,7 @@ function Popup({
1808
1904
  ) : /* @__PURE__ */ jsxs9(
1809
1905
  "button",
1810
1906
  {
1811
- onClick: () => setIsOpen(true),
1907
+ onClick: handleOpen,
1812
1908
  className: cn(
1813
1909
  "flex items-center gap-2 rounded-full font-medium",
1814
1910
  sizeMap8[size]
@@ -1827,7 +1923,7 @@ function Popup({
1827
1923
  "div",
1828
1924
  {
1829
1925
  className: "fixed inset-0 z-50 flex items-center justify-center bg-black/50 p-4",
1830
- onClick: () => setIsOpen(false),
1926
+ onClick: handleClose,
1831
1927
  children: /* @__PURE__ */ jsxs9(
1832
1928
  "div",
1833
1929
  {
@@ -1840,7 +1936,7 @@ function Popup({
1840
1936
  /* @__PURE__ */ jsx20(
1841
1937
  "button",
1842
1938
  {
1843
- onClick: () => setIsOpen(false),
1939
+ onClick: handleClose,
1844
1940
  className: "absolute top-4 right-4 text-gray-500 hover:text-gray-700",
1845
1941
  children: /* @__PURE__ */ jsx20(X2, { size: 24 })
1846
1942
  }
@@ -1857,8 +1953,6 @@ function Popup({
1857
1953
  export {
1858
1954
  Heading,
1859
1955
  Paragraph,
1860
- useGtmEvent,
1861
- useUtmParams,
1862
1956
  Button,
1863
1957
  Image,
1864
1958
  ImageCarousel,
@@ -0,0 +1,262 @@
1
+ // entries/context.tsx
2
+ import { createContext, useContext } from "react";
3
+ import { jsx } from "react/jsx-runtime";
4
+ var EntriesContext = createContext(null);
5
+ function EntriesProvider({
6
+ entries,
7
+ children
8
+ }) {
9
+ const entriesMap = new Map(entries.map((e) => [e.name, e]));
10
+ const contextValue = {
11
+ entries,
12
+ entryNames: entries.map((e) => e.name),
13
+ getEntry: (name) => entriesMap.get(name),
14
+ getEntryValue: (entryName, fieldKey) => {
15
+ const entry = entriesMap.get(entryName);
16
+ if (!entry) return void 0;
17
+ return entry.content[fieldKey];
18
+ }
19
+ };
20
+ return /* @__PURE__ */ jsx(EntriesContext.Provider, { value: contextValue, children });
21
+ }
22
+ function useEntries() {
23
+ const context = useContext(EntriesContext);
24
+ if (!context) {
25
+ return {
26
+ entries: [],
27
+ entryNames: [],
28
+ getEntry: () => void 0,
29
+ getEntryValue: () => void 0
30
+ };
31
+ }
32
+ return context;
33
+ }
34
+
35
+ // theme/defaults.ts
36
+ var DEFAULT_THEME = {
37
+ id: "default",
38
+ name: "Default Theme",
39
+ colors: {
40
+ primary: { color: "#3B82F6", opacity: 100 },
41
+ secondary: { color: "#8B5CF6", opacity: 100 },
42
+ accent: { color: "#10B981", opacity: 100 },
43
+ background: { color: "#FFFFFF", opacity: 100 },
44
+ foreground: { color: "#111827", opacity: 100 },
45
+ muted: { color: "#F3F4F6", opacity: 100 }
46
+ },
47
+ typography: {
48
+ fontFamily: {
49
+ heading: "system-ui, sans-serif",
50
+ body: "system-ui, sans-serif"
51
+ },
52
+ fontSize: {
53
+ base: "base",
54
+ heading: "4xl"
55
+ },
56
+ fontWeight: {
57
+ normal: 400,
58
+ heading: 700
59
+ }
60
+ },
61
+ spacing: {
62
+ xs: 8,
63
+ sm: 12,
64
+ md: 16,
65
+ lg: 24,
66
+ xl: 32
67
+ },
68
+ borders: {
69
+ radiusSmall: 4,
70
+ radiusMedium: 8,
71
+ radiusLarge: 16
72
+ },
73
+ shadows: {
74
+ small: "sm",
75
+ medium: "md",
76
+ large: "lg"
77
+ }
78
+ };
79
+
80
+ // theme/context.tsx
81
+ import { createContext as createContext2, useContext as useContext2 } from "react";
82
+ import { jsx as jsx2 } from "react/jsx-runtime";
83
+ var ThemeContext = createContext2(null);
84
+ function ThemeProvider({
85
+ theme,
86
+ children
87
+ }) {
88
+ const activeTheme = theme ?? DEFAULT_THEME;
89
+ const contextValue = {
90
+ theme: activeTheme,
91
+ resolveColor: (key) => activeTheme.colors[key],
92
+ resolveSpacing: (key) => activeTheme.spacing[key],
93
+ resolveBorderRadius: (key) => activeTheme.borders[key],
94
+ resolveShadow: (key) => activeTheme.shadows[key]
95
+ };
96
+ return /* @__PURE__ */ jsx2(ThemeContext.Provider, { value: contextValue, children });
97
+ }
98
+ function useTheme() {
99
+ const context = useContext2(ThemeContext);
100
+ if (!context) {
101
+ return {
102
+ theme: DEFAULT_THEME,
103
+ resolveColor: (key) => DEFAULT_THEME.colors[key],
104
+ resolveSpacing: (key) => DEFAULT_THEME.spacing[key],
105
+ resolveBorderRadius: (key) => DEFAULT_THEME.borders[key],
106
+ resolveShadow: (key) => DEFAULT_THEME.shadows[key]
107
+ };
108
+ }
109
+ return context;
110
+ }
111
+
112
+ // design-system/shadows.ts
113
+ var shadowPresets = [
114
+ { label: "None", value: "none", css: "none" },
115
+ { label: "XS", value: "xs", css: "0 1px 2px 0 rgb(0 0 0 / 0.05)" },
116
+ {
117
+ label: "SM",
118
+ value: "sm",
119
+ css: "0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1)"
120
+ },
121
+ {
122
+ label: "MD",
123
+ value: "md",
124
+ css: "0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1)"
125
+ },
126
+ {
127
+ label: "LG",
128
+ value: "lg",
129
+ css: "0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1)"
130
+ },
131
+ {
132
+ label: "XL",
133
+ value: "xl",
134
+ css: "0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1)"
135
+ },
136
+ { label: "2XL", value: "2xl", css: "0 25px 50px -12px rgb(0 0 0 / 0.25)" },
137
+ {
138
+ label: "Inner",
139
+ value: "inner",
140
+ css: "inset 0 2px 4px 0 rgb(0 0 0 / 0.05)"
141
+ }
142
+ ];
143
+ var getShadowCSS = (value) => {
144
+ const preset = shadowPresets.find((p) => p.value === value);
145
+ return preset?.css ?? "none";
146
+ };
147
+
148
+ // design-system/borders.ts
149
+ var borderRadiusScale = [
150
+ { label: "None", value: 0 },
151
+ { label: "XS", value: 2 },
152
+ { label: "SM", value: 4 },
153
+ { label: "MD", value: 6 },
154
+ { label: "LG", value: 8 },
155
+ { label: "XL", value: 12 },
156
+ { label: "2XL", value: 16 },
157
+ { label: "3XL", value: 24 }
158
+ ];
159
+ var getClosestBorderRadiusValue = (value) => {
160
+ return borderRadiusScale.reduce(
161
+ (prev, curr) => Math.abs(curr.value - value) < Math.abs(prev.value - value) ? curr : prev
162
+ ).value;
163
+ };
164
+ var getBorderRadiusCSS = (value) => {
165
+ return `${value}px`;
166
+ };
167
+
168
+ // design-system/spacing.ts
169
+ var spacingScale = [
170
+ { label: "None", value: 0 },
171
+ { label: "2XS", value: 4 },
172
+ { label: "XS", value: 8 },
173
+ { label: "SM", value: 12 },
174
+ { label: "MD", value: 16 },
175
+ { label: "LG", value: 24 },
176
+ { label: "XL", value: 32 },
177
+ { label: "2XL", value: 48 },
178
+ { label: "3XL", value: 64 },
179
+ { label: "4XL", value: 96 }
180
+ ];
181
+ var getClosestSpacingValue = (value) => {
182
+ return spacingScale.reduce(
183
+ (prev, curr) => Math.abs(curr.value - value) < Math.abs(prev.value - value) ? curr : prev
184
+ ).value;
185
+ };
186
+
187
+ // design-system/typography.ts
188
+ var fontFamilies = [
189
+ { label: "System", value: "system-ui, sans-serif" },
190
+ { label: "Sans", value: "ui-sans-serif, system-ui, sans-serif" },
191
+ { label: "Serif", value: "ui-serif, Georgia, serif" },
192
+ { label: "Mono", value: "ui-monospace, monospace" }
193
+ ];
194
+ var fontSizes = [
195
+ { label: "XS", value: "xs", css: "0.75rem" },
196
+ { label: "SM", value: "sm", css: "0.875rem" },
197
+ { label: "Base", value: "base", css: "1rem" },
198
+ { label: "LG", value: "lg", css: "1.125rem" },
199
+ { label: "XL", value: "xl", css: "1.25rem" },
200
+ { label: "2XL", value: "2xl", css: "1.5rem" },
201
+ { label: "3XL", value: "3xl", css: "1.875rem" },
202
+ { label: "4XL", value: "4xl", css: "2.25rem" },
203
+ { label: "5XL", value: "5xl", css: "3rem" }
204
+ ];
205
+ var fontWeights = [
206
+ { label: "Light", value: 300 },
207
+ { label: "Normal", value: 400 },
208
+ { label: "Medium", value: 500 },
209
+ { label: "Semibold", value: 600 },
210
+ { label: "Bold", value: 700 }
211
+ ];
212
+ var getFontSizeCSS = (value) => {
213
+ const preset = fontSizes.find((p) => p.value === value);
214
+ return preset?.css ?? "1rem";
215
+ };
216
+
217
+ // utils/index.ts
218
+ import { twMerge } from "tailwind-merge";
219
+ import { clsx } from "clsx";
220
+ function isValidHex(color) {
221
+ return /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/.test(color);
222
+ }
223
+ function hexToRgba(hex, opacity) {
224
+ const sanitized = hex.replace("#", "");
225
+ const r = parseInt(sanitized.slice(0, 2), 16);
226
+ const g = parseInt(sanitized.slice(2, 4), 16);
227
+ const b = parseInt(sanitized.slice(4, 6), 16);
228
+ return `rgba(${r}, ${g}, ${b}, ${opacity / 100})`;
229
+ }
230
+ function normalizeHex(hex) {
231
+ const sanitized = hex.replace("#", "").toUpperCase();
232
+ if (sanitized.length === 3) {
233
+ return `#${sanitized[0]}${sanitized[0]}${sanitized[1]}${sanitized[1]}${sanitized[2]}${sanitized[2]}`;
234
+ }
235
+ return `#${sanitized}`;
236
+ }
237
+ function cn(...inputs) {
238
+ return twMerge(clsx(inputs));
239
+ }
240
+
241
+ export {
242
+ EntriesProvider,
243
+ useEntries,
244
+ DEFAULT_THEME,
245
+ ThemeProvider,
246
+ useTheme,
247
+ isValidHex,
248
+ hexToRgba,
249
+ normalizeHex,
250
+ cn,
251
+ shadowPresets,
252
+ getShadowCSS,
253
+ borderRadiusScale,
254
+ getClosestBorderRadiusValue,
255
+ getBorderRadiusCSS,
256
+ spacingScale,
257
+ getClosestSpacingValue,
258
+ fontFamilies,
259
+ fontSizes,
260
+ fontWeights,
261
+ getFontSizeCSS
262
+ };
@@ -0,0 +1,16 @@
1
+ // design-system/colors.ts
2
+ var neutralColors = [
3
+ { label: "White", value: "#FFFFFF" },
4
+ { label: "Gray 100", value: "#F3F4F6" },
5
+ { label: "Gray 300", value: "#D1D5DB" },
6
+ { label: "Gray 500", value: "#6B7280" },
7
+ { label: "Gray 700", value: "#374151" },
8
+ { label: "Gray 900", value: "#111827" },
9
+ { label: "Black", value: "#000000" }
10
+ ];
11
+ var allColorPresets = [...neutralColors];
12
+
13
+ export {
14
+ neutralColors,
15
+ allColorPresets
16
+ };
@@ -0,0 +1,5 @@
1
+ import { Config } from '@measured/puck';
2
+
3
+ declare const config: Config;
4
+
5
+ export { config };
@@ -0,0 +1,5 @@
1
+ import { Config } from '@measured/puck';
2
+
3
+ declare const config: Config;
4
+
5
+ export { config };