@mission-studio/puck 1.0.0 → 1.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.
@@ -158,6 +158,75 @@ function Paragraph({
158
158
  return /* @__PURE__ */ jsx2("p", { id, style, children: resolvedText });
159
159
  }
160
160
 
161
+ // hooks/useGtmEvent.ts
162
+ function useGtmEvent() {
163
+ return (eventName, data) => {
164
+ if (typeof window === "undefined") return;
165
+ if (typeof window.gtag !== "function") {
166
+ console.warn("GTM not initialized. Make sure @next/third-parties/google GoogleTagManager is added to your layout.");
167
+ return;
168
+ }
169
+ const eventData = {
170
+ event: eventName,
171
+ ...data && { value: data }
172
+ };
173
+ window.gtag("event", eventName, data || {});
174
+ };
175
+ }
176
+
177
+ // hooks/useUtmParams.ts
178
+ import { useEffect, useState } from "react";
179
+ function useUtmParams() {
180
+ const [utmParams, setUtmParams] = useState({});
181
+ useEffect(() => {
182
+ if (typeof window === "undefined") return;
183
+ const urlParams = new URLSearchParams(window.location.search);
184
+ const source = urlParams.get("utm_source");
185
+ const medium = urlParams.get("utm_medium");
186
+ const campaign = urlParams.get("utm_campaign");
187
+ const content = urlParams.get("utm_content");
188
+ const term = urlParams.get("utm_term");
189
+ const params = {};
190
+ if (source) {
191
+ params.source = source;
192
+ sessionStorage.setItem("utm_source", source);
193
+ } else {
194
+ const stored = sessionStorage.getItem("utm_source");
195
+ if (stored) params.source = stored;
196
+ }
197
+ if (medium) {
198
+ params.medium = medium;
199
+ sessionStorage.setItem("utm_medium", medium);
200
+ } else {
201
+ const stored = sessionStorage.getItem("utm_medium");
202
+ if (stored) params.medium = stored;
203
+ }
204
+ if (campaign) {
205
+ params.campaign = campaign;
206
+ sessionStorage.setItem("utm_campaign", campaign);
207
+ } else {
208
+ const stored = sessionStorage.getItem("utm_campaign");
209
+ if (stored) params.campaign = stored;
210
+ }
211
+ if (content) {
212
+ params.content = content;
213
+ sessionStorage.setItem("utm_content", content);
214
+ } else {
215
+ const stored = sessionStorage.getItem("utm_content");
216
+ if (stored) params.content = stored;
217
+ }
218
+ if (term) {
219
+ params.term = term;
220
+ sessionStorage.setItem("utm_term", term);
221
+ } else {
222
+ const stored = sessionStorage.getItem("utm_term");
223
+ if (stored) params.term = stored;
224
+ }
225
+ setUtmParams(params);
226
+ }, []);
227
+ return utmParams;
228
+ }
229
+
161
230
  // components/page/Button.tsx
162
231
  import { jsx as jsx3 } from "react/jsx-runtime";
163
232
  var sizeStyles = {
@@ -194,6 +263,8 @@ function Button({
194
263
  }) {
195
264
  const { resolveColor: resolveColor2 } = useTheme();
196
265
  const { getEntryValue } = useEntries();
266
+ const sendEvent = useGtmEvent();
267
+ const utm = useUtmParams();
197
268
  const resolvedText = (() => {
198
269
  if (!text) return "Button";
199
270
  if (typeof text === "string") return text;
@@ -205,6 +276,14 @@ function Button({
205
276
  }
206
277
  return "Button";
207
278
  })();
279
+ const handleClick = () => {
280
+ sendEvent("button_click", {
281
+ text: resolvedText,
282
+ href: href || void 0,
283
+ variant,
284
+ ...utm
285
+ });
286
+ };
208
287
  const resolvedColor = (() => {
209
288
  if (!color) return resolveColor2("primary");
210
289
  if (typeof color === "string") return { color, opacity: 100 };
@@ -278,11 +357,12 @@ function Button({
278
357
  target,
279
358
  style,
280
359
  rel: target === "_blank" ? "noopener noreferrer" : void 0,
360
+ onClick: handleClick,
281
361
  children: resolvedText
282
362
  }
283
363
  ) });
284
364
  }
285
- return /* @__PURE__ */ jsx3("div", { style: wrapperStyle, children: /* @__PURE__ */ jsx3("button", { id, type: "button", style, children: resolvedText }) });
365
+ return /* @__PURE__ */ jsx3("div", { style: wrapperStyle, children: /* @__PURE__ */ jsx3("button", { id, type: "button", style, onClick: handleClick, children: resolvedText }) });
286
366
  }
287
367
 
288
368
  // components/page/Image.tsx
@@ -415,7 +495,7 @@ function Image({
415
495
  }
416
496
 
417
497
  // components/page/ImageCarousel.tsx
418
- import { useState } from "react";
498
+ import { useState as useState2 } from "react";
419
499
  import { Fragment, jsx as jsx5, jsxs as jsxs2 } from "react/jsx-runtime";
420
500
  var aspectRatioMap2 = {
421
501
  "16:9": "16 / 9",
@@ -442,7 +522,7 @@ function ImageCarousel({
442
522
  dotColor,
443
523
  id
444
524
  }) {
445
- const [currentIndex, setCurrentIndex] = useState(0);
525
+ const [currentIndex, setCurrentIndex] = useState2(0);
446
526
  const { resolveColor: resolveColor2 } = useTheme();
447
527
  const resolvedArrowColor = (() => {
448
528
  if (!arrowColor) return { color: "#FFFFFF", opacity: 100 };
@@ -1606,7 +1686,7 @@ function Footer({
1606
1686
  }
1607
1687
 
1608
1688
  // components/page/Topbar.tsx
1609
- import { useState as useState2 } from "react";
1689
+ import { useState as useState3 } from "react";
1610
1690
  import Link from "next/link";
1611
1691
  import { Menu, X } from "lucide-react";
1612
1692
  import { jsx as jsx19, jsxs as jsxs8 } from "react/jsx-runtime";
@@ -1620,7 +1700,7 @@ function Topbar({
1620
1700
  puck
1621
1701
  }) {
1622
1702
  const DropZone = puck?.renderDropZone;
1623
- const [mobileMenuOpen, setMobileMenuOpen] = useState2(false);
1703
+ const [mobileMenuOpen, setMobileMenuOpen] = useState3(false);
1624
1704
  const renderLink = (item, index) => {
1625
1705
  const className = "hover:opacity-80 transition-opacity";
1626
1706
  if (item.linkType === "external") {
@@ -1686,7 +1766,7 @@ function Topbar({
1686
1766
  }
1687
1767
 
1688
1768
  // components/page/Popup.tsx
1689
- import { useState as useState3 } from "react";
1769
+ import { useState as useState4 } from "react";
1690
1770
  import { icons as icons4, X as X2 } from "lucide-react";
1691
1771
  import { Fragment as Fragment2, jsx as jsx20, jsxs as jsxs9 } from "react/jsx-runtime";
1692
1772
  function Icon2({ name, ...props }) {
@@ -1716,7 +1796,7 @@ function Popup({
1716
1796
  textLink = false,
1717
1797
  puck
1718
1798
  }) {
1719
- const [isOpen, setIsOpen] = useState3(false);
1799
+ const [isOpen, setIsOpen] = useState4(false);
1720
1800
  const trigger = textLink ? /* @__PURE__ */ jsx20(
1721
1801
  "button",
1722
1802
  {
@@ -1777,6 +1857,8 @@ function Popup({
1777
1857
  export {
1778
1858
  Heading,
1779
1859
  Paragraph,
1860
+ useGtmEvent,
1861
+ useUtmParams,
1780
1862
  Button,
1781
1863
  Image,
1782
1864
  ImageCarousel,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mission-studio/puck",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "A collection of puck components and configurations for internal use at Mission Studio.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -47,7 +47,8 @@
47
47
  "test:build": "yarn run build && jest __tests__/build.test.ts",
48
48
  "build:css": "tailwindcss -i ./styles.css -o ./dist/styles.css --minify",
49
49
  "build:js": "tsup --format cjs,esm --dts --clean",
50
- "build": "yarn run build:js && yarn run build:css"
50
+ "build": "yarn run build:js && yarn run build:css",
51
+ "prepare": "husky"
51
52
  },
52
53
  "dependencies": {
53
54
  "@measured/puck": "^0.20.2",
@@ -82,16 +83,33 @@
82
83
  "@testing-library/react": "^16.3.2",
83
84
  "@types/jest": "^30.0.0",
84
85
  "@types/react": "^19.2.13",
86
+ "@typescript-eslint/eslint-plugin": "^8.55.0",
87
+ "@typescript-eslint/parser": "^8.55.0",
85
88
  "autoprefixer": "^10.4.24",
89
+ "eslint": "^9.39.2",
90
+ "eslint-config-prettier": "^10.1.8",
91
+ "husky": "^9.1.7",
86
92
  "jest": "^30.2.0",
87
93
  "jest-environment-jsdom": "^30.2.0",
94
+ "lint-staged": "^16.2.7",
88
95
  "next": "^16.1.6",
89
96
  "postcss": "^8.5.6",
97
+ "prettier": "^3.8.1",
90
98
  "react": "^19.2.4",
91
99
  "react-dom": "^19.2.4",
92
100
  "tailwindcss": "^4.1.18",
93
101
  "ts-jest": "^29.4.6",
94
102
  "tsup": "^8.5.1",
95
- "typescript": "^5.9.3"
103
+ "typescript": "^5.9.3",
104
+ "typescript-eslint": "^8.55.0"
105
+ },
106
+ "lint-staged": {
107
+ "*.{ts,tsx}": [
108
+ "prettier --write",
109
+ "eslint --fix"
110
+ ],
111
+ "*.{json,css,md}": [
112
+ "prettier --write"
113
+ ]
96
114
  }
97
115
  }
@@ -1,183 +0,0 @@
1
- import * as react_jsx_runtime from 'react/jsx-runtime';
2
- import { ReactNode } from 'react';
3
-
4
- type ColorValue = {
5
- color: string;
6
- opacity: number;
7
- };
8
- type CustomFieldProps<T> = {
9
- value: T;
10
- onChangeAction: (value: T) => void;
11
- disabled?: boolean;
12
- label: string;
13
- };
14
-
15
- type ThemeColors = {
16
- primary: ColorValue;
17
- secondary: ColorValue;
18
- accent: ColorValue;
19
- background: ColorValue;
20
- foreground: ColorValue;
21
- muted: ColorValue;
22
- };
23
- type ThemeTypography = {
24
- fontFamily: {
25
- heading: string;
26
- body: string;
27
- };
28
- fontSize: {
29
- base: string;
30
- heading: string;
31
- };
32
- fontWeight: {
33
- normal: number;
34
- heading: number;
35
- };
36
- };
37
- type ThemeSpacing = {
38
- xs: number;
39
- sm: number;
40
- md: number;
41
- lg: number;
42
- xl: number;
43
- };
44
- type ThemeBorders = {
45
- radiusSmall: number;
46
- radiusMedium: number;
47
- radiusLarge: number;
48
- };
49
- type ThemeShadows = {
50
- small: string;
51
- medium: string;
52
- large: string;
53
- };
54
- type PageTheme = {
55
- id: string;
56
- name: string;
57
- colors: ThemeColors;
58
- typography: ThemeTypography;
59
- spacing: ThemeSpacing;
60
- borders: ThemeBorders;
61
- shadows: ThemeShadows;
62
- };
63
- type ThemeColorKey = keyof ThemeColors;
64
- type ThemeSpacingKey = keyof ThemeSpacing;
65
- type ThemeBorderKey = keyof ThemeBorders;
66
- type ThemeShadowKey = keyof ThemeShadows;
67
- type ThemeableColorValue = {
68
- useTheme: true;
69
- themeKey: ThemeColorKey;
70
- } | {
71
- useTheme: false;
72
- value: ColorValue;
73
- };
74
- type ThemeableSpacingValue = {
75
- useTheme: true;
76
- themeKey: ThemeSpacingKey;
77
- } | {
78
- useTheme: false;
79
- value: number;
80
- };
81
- type ThemeableBorderRadiusValue = {
82
- useTheme: true;
83
- themeKey: ThemeBorderKey;
84
- } | {
85
- useTheme: false;
86
- value: number;
87
- };
88
- type ThemeableShadowValue = {
89
- useTheme: true;
90
- themeKey: ThemeShadowKey;
91
- } | {
92
- useTheme: false;
93
- value: string;
94
- };
95
-
96
- type ThemeContextValue = {
97
- theme: PageTheme;
98
- resolveColor: (key: ThemeColorKey) => ColorValue;
99
- resolveSpacing: (key: ThemeSpacingKey) => number;
100
- resolveBorderRadius: (key: ThemeBorderKey) => number;
101
- resolveShadow: (key: ThemeShadowKey) => string;
102
- };
103
- declare function ThemeProvider({ theme, children, }: {
104
- theme: PageTheme | null;
105
- children: ReactNode;
106
- }): react_jsx_runtime.JSX.Element;
107
- declare function useTheme(): ThemeContextValue;
108
-
109
- declare const DEFAULT_THEME: PageTheme;
110
-
111
- type BorderRadiusPreset = {
112
- label: string;
113
- value: number;
114
- };
115
- declare const borderRadiusScale: BorderRadiusPreset[];
116
- declare const getClosestBorderRadiusValue: (value: number) => number;
117
- declare const getBorderRadiusCSS: (value: number) => string;
118
-
119
- type ColorPreset = {
120
- label: string;
121
- value: string;
122
- };
123
- declare const neutralColors: ColorPreset[];
124
- declare const allColorPresets: ColorPreset[];
125
-
126
- type ShadowPreset = {
127
- label: string;
128
- value: string;
129
- css: string;
130
- };
131
- declare const shadowPresets: ShadowPreset[];
132
- declare const getShadowCSS: (value: string) => string;
133
-
134
- type SpacingPreset = {
135
- label: string;
136
- value: number;
137
- };
138
- declare const spacingScale: SpacingPreset[];
139
- declare const getClosestSpacingValue: (value: number) => number;
140
-
141
- type FontFamilyPreset = {
142
- label: string;
143
- value: string;
144
- };
145
- type FontSizePreset = {
146
- label: string;
147
- value: string;
148
- css: string;
149
- };
150
- type FontWeightPreset = {
151
- label: string;
152
- value: number;
153
- };
154
- declare const fontFamilies: FontFamilyPreset[];
155
- declare const fontSizes: FontSizePreset[];
156
- declare const fontWeights: FontWeightPreset[];
157
- declare const getFontSizeCSS: (value: string) => string;
158
-
159
- type EntryContent = Record<string, string | number | boolean | null>;
160
- type Entry = {
161
- id: string;
162
- venture_id: string;
163
- name: string;
164
- content: EntryContent;
165
- created_at: string | null;
166
- updated_at: string | null;
167
- };
168
- type EntryBoundValue<T> = {
169
- useEntry: true;
170
- entryName: string;
171
- fieldKey: string;
172
- } | {
173
- useEntry: false;
174
- value: T;
175
- };
176
-
177
- type ResponsiveVisibility = {
178
- mobile: boolean;
179
- desktop: boolean;
180
- };
181
- declare function ResponsiveToggleField({ value, onChangeAction, disabled, label, }: CustomFieldProps<ResponsiveVisibility>): react_jsx_runtime.JSX.Element;
182
-
183
- export { getBorderRadiusCSS as A, type BorderRadiusPreset as B, type ColorValue as C, DEFAULT_THEME as D, type Entry as E, type FontFamilyPreset as F, getClosestBorderRadiusValue as G, getClosestSpacingValue as H, getFontSizeCSS as I, getShadowCSS as J, neutralColors as K, shadowPresets as L, spacingScale as M, useTheme as N, type PageTheme as P, type ResponsiveVisibility as R, type ShadowPreset as S, type ThemeableColorValue as T, type EntryBoundValue as a, type ThemeColorKey as b, type CustomFieldProps as c, type ColorPreset as d, type EntryContent as e, type FontSizePreset as f, type FontWeightPreset as g, ResponsiveToggleField as h, type SpacingPreset as i, type ThemeBorderKey as j, type ThemeBorders as k, type ThemeColors as l, ThemeProvider as m, type ThemeShadowKey as n, type ThemeShadows as o, type ThemeSpacing as p, type ThemeSpacingKey as q, type ThemeTypography as r, type ThemeableBorderRadiusValue as s, type ThemeableShadowValue as t, type ThemeableSpacingValue as u, allColorPresets as v, borderRadiusScale as w, fontFamilies as x, fontSizes as y, fontWeights as z };
@@ -1,183 +0,0 @@
1
- import * as react_jsx_runtime from 'react/jsx-runtime';
2
- import { ReactNode } from 'react';
3
-
4
- type ColorValue = {
5
- color: string;
6
- opacity: number;
7
- };
8
- type CustomFieldProps<T> = {
9
- value: T;
10
- onChangeAction: (value: T) => void;
11
- disabled?: boolean;
12
- label: string;
13
- };
14
-
15
- type ThemeColors = {
16
- primary: ColorValue;
17
- secondary: ColorValue;
18
- accent: ColorValue;
19
- background: ColorValue;
20
- foreground: ColorValue;
21
- muted: ColorValue;
22
- };
23
- type ThemeTypography = {
24
- fontFamily: {
25
- heading: string;
26
- body: string;
27
- };
28
- fontSize: {
29
- base: string;
30
- heading: string;
31
- };
32
- fontWeight: {
33
- normal: number;
34
- heading: number;
35
- };
36
- };
37
- type ThemeSpacing = {
38
- xs: number;
39
- sm: number;
40
- md: number;
41
- lg: number;
42
- xl: number;
43
- };
44
- type ThemeBorders = {
45
- radiusSmall: number;
46
- radiusMedium: number;
47
- radiusLarge: number;
48
- };
49
- type ThemeShadows = {
50
- small: string;
51
- medium: string;
52
- large: string;
53
- };
54
- type PageTheme = {
55
- id: string;
56
- name: string;
57
- colors: ThemeColors;
58
- typography: ThemeTypography;
59
- spacing: ThemeSpacing;
60
- borders: ThemeBorders;
61
- shadows: ThemeShadows;
62
- };
63
- type ThemeColorKey = keyof ThemeColors;
64
- type ThemeSpacingKey = keyof ThemeSpacing;
65
- type ThemeBorderKey = keyof ThemeBorders;
66
- type ThemeShadowKey = keyof ThemeShadows;
67
- type ThemeableColorValue = {
68
- useTheme: true;
69
- themeKey: ThemeColorKey;
70
- } | {
71
- useTheme: false;
72
- value: ColorValue;
73
- };
74
- type ThemeableSpacingValue = {
75
- useTheme: true;
76
- themeKey: ThemeSpacingKey;
77
- } | {
78
- useTheme: false;
79
- value: number;
80
- };
81
- type ThemeableBorderRadiusValue = {
82
- useTheme: true;
83
- themeKey: ThemeBorderKey;
84
- } | {
85
- useTheme: false;
86
- value: number;
87
- };
88
- type ThemeableShadowValue = {
89
- useTheme: true;
90
- themeKey: ThemeShadowKey;
91
- } | {
92
- useTheme: false;
93
- value: string;
94
- };
95
-
96
- type ThemeContextValue = {
97
- theme: PageTheme;
98
- resolveColor: (key: ThemeColorKey) => ColorValue;
99
- resolveSpacing: (key: ThemeSpacingKey) => number;
100
- resolveBorderRadius: (key: ThemeBorderKey) => number;
101
- resolveShadow: (key: ThemeShadowKey) => string;
102
- };
103
- declare function ThemeProvider({ theme, children, }: {
104
- theme: PageTheme | null;
105
- children: ReactNode;
106
- }): react_jsx_runtime.JSX.Element;
107
- declare function useTheme(): ThemeContextValue;
108
-
109
- declare const DEFAULT_THEME: PageTheme;
110
-
111
- type BorderRadiusPreset = {
112
- label: string;
113
- value: number;
114
- };
115
- declare const borderRadiusScale: BorderRadiusPreset[];
116
- declare const getClosestBorderRadiusValue: (value: number) => number;
117
- declare const getBorderRadiusCSS: (value: number) => string;
118
-
119
- type ColorPreset = {
120
- label: string;
121
- value: string;
122
- };
123
- declare const neutralColors: ColorPreset[];
124
- declare const allColorPresets: ColorPreset[];
125
-
126
- type ShadowPreset = {
127
- label: string;
128
- value: string;
129
- css: string;
130
- };
131
- declare const shadowPresets: ShadowPreset[];
132
- declare const getShadowCSS: (value: string) => string;
133
-
134
- type SpacingPreset = {
135
- label: string;
136
- value: number;
137
- };
138
- declare const spacingScale: SpacingPreset[];
139
- declare const getClosestSpacingValue: (value: number) => number;
140
-
141
- type FontFamilyPreset = {
142
- label: string;
143
- value: string;
144
- };
145
- type FontSizePreset = {
146
- label: string;
147
- value: string;
148
- css: string;
149
- };
150
- type FontWeightPreset = {
151
- label: string;
152
- value: number;
153
- };
154
- declare const fontFamilies: FontFamilyPreset[];
155
- declare const fontSizes: FontSizePreset[];
156
- declare const fontWeights: FontWeightPreset[];
157
- declare const getFontSizeCSS: (value: string) => string;
158
-
159
- type EntryContent = Record<string, string | number | boolean | null>;
160
- type Entry = {
161
- id: string;
162
- venture_id: string;
163
- name: string;
164
- content: EntryContent;
165
- created_at: string | null;
166
- updated_at: string | null;
167
- };
168
- type EntryBoundValue<T> = {
169
- useEntry: true;
170
- entryName: string;
171
- fieldKey: string;
172
- } | {
173
- useEntry: false;
174
- value: T;
175
- };
176
-
177
- type ResponsiveVisibility = {
178
- mobile: boolean;
179
- desktop: boolean;
180
- };
181
- declare function ResponsiveToggleField({ value, onChangeAction, disabled, label, }: CustomFieldProps<ResponsiveVisibility>): react_jsx_runtime.JSX.Element;
182
-
183
- export { getBorderRadiusCSS as A, type BorderRadiusPreset as B, type ColorValue as C, DEFAULT_THEME as D, type Entry as E, type FontFamilyPreset as F, getClosestBorderRadiusValue as G, getClosestSpacingValue as H, getFontSizeCSS as I, getShadowCSS as J, neutralColors as K, shadowPresets as L, spacingScale as M, useTheme as N, type PageTheme as P, type ResponsiveVisibility as R, type ShadowPreset as S, type ThemeableColorValue as T, type EntryBoundValue as a, type ThemeColorKey as b, type CustomFieldProps as c, type ColorPreset as d, type EntryContent as e, type FontSizePreset as f, type FontWeightPreset as g, ResponsiveToggleField as h, type SpacingPreset as i, type ThemeBorderKey as j, type ThemeBorders as k, type ThemeColors as l, ThemeProvider as m, type ThemeShadowKey as n, type ThemeShadows as o, type ThemeSpacing as p, type ThemeSpacingKey as q, type ThemeTypography as r, type ThemeableBorderRadiusValue as s, type ThemeableShadowValue as t, type ThemeableSpacingValue as u, allColorPresets as v, borderRadiusScale as w, fontFamilies as x, fontSizes as y, fontWeights as z };