@hubspot/cms-component-library 0.3.8 → 0.3.10

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 (48) hide show
  1. package/components/componentLibrary/Accordion/AccordionTitle/AccordionTitleBase.tsx +45 -0
  2. package/components/componentLibrary/Accordion/AccordionTitle/index.tsx +17 -30
  3. package/components/componentLibrary/Accordion/AccordionTitle/islands/AccordionTitleIsland.tsx +29 -0
  4. package/components/componentLibrary/Button/StyleFields.tsx +8 -8
  5. package/components/componentLibrary/Button/index.module.scss +24 -27
  6. package/components/componentLibrary/Button/index.tsx +4 -4
  7. package/components/componentLibrary/Button/llm.txt +51 -64
  8. package/components/componentLibrary/Button/stories/Button.AsButton.stories.tsx +2 -2
  9. package/components/componentLibrary/Button/stories/Button.AsLink.stories.tsx +2 -2
  10. package/components/componentLibrary/Button/stories/ButtonDecorator.module.scss +19 -23
  11. package/components/componentLibrary/Button/types.ts +2 -2
  12. package/components/componentLibrary/Card/StyleFields.tsx +9 -14
  13. package/components/componentLibrary/Card/index.module.scss +7 -7
  14. package/components/componentLibrary/Card/index.tsx +8 -13
  15. package/components/componentLibrary/Card/llm.txt +22 -43
  16. package/components/componentLibrary/Card/stories/Card.stories.tsx +28 -20
  17. package/components/componentLibrary/Card/stories/CardDecorator.module.scss +28 -5
  18. package/components/componentLibrary/Card/types.ts +8 -5
  19. package/components/componentLibrary/Form/StyleFields.tsx +19 -0
  20. package/components/componentLibrary/Form/index.tsx +7 -1
  21. package/components/componentLibrary/Form/islands/FormIsland.tsx +3 -1
  22. package/components/componentLibrary/Form/islands/LegacyFormIsland.tsx +2 -1
  23. package/components/componentLibrary/Form/islands/legacyForm.module.css +251 -0
  24. package/components/componentLibrary/Form/islands/v4Form.module.css +95 -0
  25. package/components/componentLibrary/Form/llm.txt +184 -0
  26. package/components/componentLibrary/Form/types.ts +6 -0
  27. package/components/componentLibrary/Link/ContentFields.tsx +2 -2
  28. package/components/componentLibrary/Link/StyleFields.tsx +10 -17
  29. package/components/componentLibrary/Link/index.module.scss +9 -9
  30. package/components/componentLibrary/Link/index.tsx +3 -8
  31. package/components/componentLibrary/Link/llm.txt +29 -85
  32. package/components/componentLibrary/Link/stories/Link.stories.tsx +4 -11
  33. package/components/componentLibrary/Link/stories/LinkDecorator.module.scss +15 -0
  34. package/components/componentLibrary/Link/stories/LinkDecorator.tsx +2 -11
  35. package/components/componentLibrary/Link/types.ts +11 -8
  36. package/components/componentLibrary/Video/ContentFields.tsx +112 -0
  37. package/components/componentLibrary/Video/StyleFields.tsx +19 -0
  38. package/components/componentLibrary/Video/index.tsx +47 -0
  39. package/components/componentLibrary/Video/islands/HSVideoIsland.tsx +53 -0
  40. package/components/componentLibrary/Video/serverUtils.ts +41 -0
  41. package/components/componentLibrary/Video/types.ts +74 -0
  42. package/components/componentLibrary/_patterns/README.md +11 -7
  43. package/components/componentLibrary/_patterns/checklist-and-examples.md +8 -0
  44. package/components/componentLibrary/_patterns/component-structure.md +5 -1
  45. package/components/componentLibrary/_patterns/field-patterns.md +46 -0
  46. package/components/componentLibrary/_patterns/island-patterns.md +136 -0
  47. package/components/componentLibrary/utils/index.ts +1 -0
  48. package/package.json +4 -3
@@ -58,9 +58,9 @@ Pass a `linkField` prop containing HubSpot link field data. The component automa
58
58
  open_in_new_tab?: boolean; // Open in new tab
59
59
  no_follow?: boolean; // Add nofollow rel
60
60
  };
61
- variant?: 'primary' | 'secondary' | 'tertiary'; // Visual style variant (default: 'primary')
61
+ variant?: 'primaryLink' | 'secondaryLink'; // Visual style variant (default: 'primaryLink')
62
62
  className?: string; // Additional CSS classes
63
- style?: React.CSSProperties; // Inline styles (including CSS variables)
63
+ style?: React.CSSProperties; // Inline styles
64
64
  children?: React.ReactNode; // Link text/content
65
65
  }
66
66
  ```
@@ -74,9 +74,9 @@ Pass individual `href`, `target`, and `rel` props directly.
74
74
  href: string; // Link destination URL (required)
75
75
  target?: '_self' | '_blank' | '_parent' | '_top'; // Link target behavior
76
76
  rel?: string; // Link relationship (e.g., 'noopener noreferrer')
77
- variant?: 'primary' | 'secondary' | 'tertiary'; // Visual style variant (default: 'primary')
77
+ variant?: 'primaryLink' | 'secondaryLink'; // Visual style variant (default: 'primaryLink')
78
78
  className?: string; // Additional CSS classes
79
- style?: React.CSSProperties; // Inline styles (including CSS variables)
79
+ style?: React.CSSProperties; // Inline styles
80
80
  children?: React.ReactNode; // Link text/content
81
81
  }
82
82
  ```
@@ -107,21 +107,7 @@ export default function MyModule({ fieldValues }) {
107
107
  ```tsx
108
108
  <Link
109
109
  linkField={fieldValues.link}
110
- variant="secondary"
111
- >
112
- Visit HubSpot
113
- </Link>
114
- ```
115
-
116
- #### Field-based Link with Style Override
117
-
118
- ```tsx
119
- <Link
120
- linkField={fieldValues.link}
121
- style={{
122
- '--hscl-link-textDecoration': 'none',
123
- '--hscl-link-textDecoration-hover': 'underline',
124
- }}
110
+ variant="secondaryLink"
125
111
  >
126
112
  Visit HubSpot
127
113
  </Link>
@@ -163,17 +149,13 @@ import Link from '@hubspot/cms-component-library/Link';
163
149
  ### Using Variants
164
150
 
165
151
  ```tsx
166
- <Link href="https://www.hubspot.com" variant="primary">
152
+ <Link href="https://www.hubspot.com" variant="primaryLink">
167
153
  Primary Link
168
154
  </Link>
169
155
 
170
- <Link href="https://www.hubspot.com" variant="secondary">
156
+ <Link href="https://www.hubspot.com" variant="secondaryLink">
171
157
  Secondary Link
172
158
  </Link>
173
-
174
- <Link href="https://www.hubspot.com" variant="tertiary">
175
- Tertiary Link
176
- </Link>
177
159
  ```
178
160
 
179
161
  ## HubSpot CMS Integration
@@ -220,12 +202,12 @@ The Link component provides field definitions for easy integration with HubSpot
220
202
  <Link.StyleFields
221
203
  variantLabel="Link style"
222
204
  variantName="linkVariant"
223
- variantDefault="primary"
205
+ variantDefault={{ variant_name: 'primaryLink' }}
224
206
  />
225
207
  ```
226
208
 
227
209
  **Fields:**
228
- - `linkVariant`: Choice field for variant selection (primary, secondary, tertiary)
210
+ - `linkVariant`: VariantSelectionField for theme variant selection (primaryLink, secondaryLink)
229
211
 
230
212
  ### Module Usage Example
231
213
 
@@ -238,7 +220,7 @@ export default function NavigationModule({ fieldValues }) {
238
220
  return (
239
221
  <Link
240
222
  linkField={fieldValues.link}
241
- variant={fieldValues.linkVariant}
223
+ variant={fieldValues.style.linkVariant?.variantName}
242
224
  >
243
225
  {fieldValues.linkText}
244
226
  </Link>
@@ -255,7 +237,7 @@ export default function NavigationModule({ fieldValues }) {
255
237
  href="https://www.hubspot.com"
256
238
  target="_blank"
257
239
  rel="nofollow"
258
- variant="primary"
240
+ variant="primaryLink"
259
241
  >
260
242
  Go to HubSpot
261
243
  </Link>
@@ -269,69 +251,32 @@ Note: The field-based approach is simpler and less error-prone.
269
251
 
270
252
  ### Variants
271
253
 
272
- The Link component supports three visual style variants through the `variant` prop:
254
+ The Link component supports two visual style variants through the `variant` prop:
273
255
 
274
- - **primary** (default): Default link styling using `--hscl-link-color-primary` and `--hscl-link-color-hover-primary`
275
- - **secondary**: Alternative styling using `--hscl-link-color-secondary` and `--hscl-link-color-hover-secondary`
276
- - **tertiary**: Third alternative using `--hscl-link-color-tertiary` and `--hscl-link-color-hover-tertiary`
256
+ - **primaryLink** (default): Default link styling
257
+ - **secondaryLink**: Alternative link styling
277
258
 
278
- The component internally sets:
279
- ```tsx
280
- style={{
281
- '--hscl-link-color': `var(--hscl-link-color-${variant})`,
282
- '--hscl-link-color-hover': `var(--hscl-link-color-hover-${variant})`,
283
- }}
284
- ```
259
+ The component renders a `data-link-variant` attribute on the `<a>` element. The theme provides CSS variables scoped to `[data-link-variant]` selectors.
285
260
 
286
- ### CSS Variables
261
+ ### CSS Variables (Theme-provided)
287
262
 
288
- The Link component uses CSS variables for theming and customization:
263
+ The theme provides these CSS variables via `[data-link-variant]` selectors:
289
264
 
290
265
  **Base Styles:**
291
- - `--hscl-link-display`: Display property (default: inline-block)
292
- - `--hscl-link-color`: Text color (set by variant, no fallback)
293
- - `--hscl-link-textDecoration`: Text decoration (default: underline)
266
+ - `--hs-link-color`: Text color
267
+ - `--hs-link-textDecoration`: Text decoration
294
268
 
295
269
  **Hover States:**
296
- - `--hscl-link-color-hover`: Hover text color (set by variant, no fallback)
297
- - `--hscl-link-textDecoration-hover`: Hover text decoration (default: underline)
298
-
299
- **Variant-specific Variables (must be defined globally):**
300
- - `--hscl-link-color-primary`: Primary variant text color
301
- - `--hscl-link-color-hover-primary`: Primary variant hover text color
302
- - `--hscl-link-color-secondary`: Secondary variant text color
303
- - `--hscl-link-color-hover-secondary`: Secondary variant hover text color
304
- - `--hscl-link-color-tertiary`: Tertiary variant text color
305
- - `--hscl-link-color-hover-tertiary`: Tertiary variant hover text color
306
-
307
- ### Custom Styling Examples
270
+ - `--hs-link-color-hover`: Hover text color
271
+ - `--hs-link-textDecoration-hover`: Hover text decoration
308
272
 
309
- #### Override Text Decoration
273
+ **Active States:**
274
+ - `--hs-link-color-active`: Active text color (falls back to `--hs-link-color`)
275
+ - `--hs-link-textDecoration-active`: Active text decoration (falls back to `--hs-link-textDecoration`)
310
276
 
311
- ```tsx
312
- <Link
313
- href="https://www.hubspot.com"
314
- style={{
315
- '--hscl-link-textDecoration': 'none',
316
- '--hscl-link-textDecoration-hover': 'underline',
317
- }}
318
- >
319
- Hover for underline
320
- </Link>
321
- ```
322
-
323
- #### Override Display
324
-
325
- ```tsx
326
- <Link
327
- href="https://www.hubspot.com"
328
- style={{
329
- '--hscl-link-display': 'block',
330
- }}
331
- >
332
- Block-level Link
333
- </Link>
334
- ```
277
+ **Hardcoded Styles (not theme-configurable):**
278
+ - `display: inline-block`
279
+ - Focus outline: `2px solid currentColor` with `2px` offset
335
280
 
336
281
  ## Accessibility
337
282
 
@@ -349,9 +294,8 @@ The Link component follows accessibility best practices:
349
294
 
350
295
  - **Use linkField prop when available**: If you have HubSpot field data, use the `linkField` prop instead of manually extracting href/target/rel
351
296
  - **Do NOT mix approaches**: TypeScript will prevent you from using both `linkField` and `href` props together
352
- - **Use variants for consistency**: Choose the appropriate variant (primary/secondary/tertiary) rather than overriding colors directly
297
+ - **Use variants for consistency**: Choose the appropriate variant (primaryLink/secondaryLink) rather than overriding colors directly
353
298
  - **Security**: The component automatically adds `rel="noopener noreferrer"` when target is `_blank`
354
- - **CSS Variables**: Override design tokens using CSS variables for advanced customization
355
299
  - **Minimal styling**: Keep link styling simple; use Button component for prominent CTAs
356
300
 
357
301
  ## Common Patterns
@@ -359,7 +303,7 @@ The Link component follows accessibility best practices:
359
303
  ### Navigation Menu Item
360
304
 
361
305
  ```tsx
362
- <Link href="/dashboard" variant="secondary">
306
+ <Link href="/dashboard" variant="secondaryLink">
363
307
  Dashboard
364
308
  </Link>
365
309
  ```
@@ -4,12 +4,6 @@ import { LinkProps } from '../types.js';
4
4
  import { withLinkStyles } from './LinkDecorator.js';
5
5
  import { SBContainer, SBFocusWrapper } from '@sb-utils';
6
6
  import { SBCard } from '@sb-utils/SBCard.js';
7
- import type { CSSVariables } from '../../utils/types.js';
8
-
9
- const removeUnderlineStyle: CSSVariables = {
10
- '--hscl-link-textDecoration': 'none',
11
- '--hscl-link-textDecoration-hover': 'none',
12
- };
13
7
 
14
8
  const meta: Meta<LinkProps> = {
15
9
  title: 'Component Library/Link',
@@ -34,7 +28,7 @@ export const Default: Story = {
34
28
  href: 'https://www.hubspot.com',
35
29
  target: '_blank',
36
30
  rel: 'noopener noreferrer',
37
- variant: 'primary',
31
+ variant: 'primaryLink',
38
32
  className: 'test-class',
39
33
  },
40
34
  };
@@ -90,7 +84,7 @@ export const InContext: Story = {
90
84
  gap="medium"
91
85
  style={{ flexWrap: 'wrap' }}
92
86
  >
93
- <Link href="https://www.hubspot.com" style={removeUnderlineStyle}>
87
+ <Link href="https://www.hubspot.com">
94
88
  <SBCard
95
89
  title="Clickable Card"
96
90
  description="The entire card is wrapped in a link, making it clickable."
@@ -143,7 +137,6 @@ export const InContext: Story = {
143
137
  <Link
144
138
  key={key}
145
139
  href={`/image/${key}`}
146
- style={removeUnderlineStyle}
147
140
  >
148
141
  <div
149
142
  style={{
@@ -179,7 +172,7 @@ export const InteractionStates: Story = {
179
172
  <SBContainer addBackground>
180
173
  <h4>Hover State</h4>
181
174
  <p>Hover over the link to see the hover effect</p>
182
- <Link href="https://www.hubspot.com" variant="primary">
175
+ <Link href="https://www.hubspot.com" variant="primaryLink">
183
176
  Hover me
184
177
  </Link>
185
178
  </SBContainer>
@@ -192,7 +185,7 @@ export const InteractionStates: Story = {
192
185
  to show how it looks.
193
186
  </p>
194
187
  <SBFocusWrapper>
195
- <Link href="https://www.hubspot.com" variant="primary">
188
+ <Link href="https://www.hubspot.com" variant="primaryLink">
196
189
  Focused link
197
190
  </Link>
198
191
  </SBFocusWrapper>
@@ -0,0 +1,15 @@
1
+ .linkContainer {
2
+ [data-link-variant='primaryLink'] {
3
+ --hs-link-color: #0066cc;
4
+ --hs-link-textDecoration: none;
5
+ --hs-link-color-hover: #0052a3;
6
+ --hs-link-textDecoration-hover: underline;
7
+ }
8
+
9
+ [data-link-variant='secondaryLink'] {
10
+ --hs-link-color: #516f90;
11
+ --hs-link-textDecoration: none;
12
+ --hs-link-color-hover: #33475b;
13
+ --hs-link-textDecoration-hover: underline;
14
+ }
15
+ }
@@ -1,17 +1,8 @@
1
1
  import type { Decorator } from '@storybook/react';
2
- import type { CSSVariables } from '../../utils/types.js';
3
-
4
- const defaultLinkStyles: CSSVariables = {
5
- '--hscl-link-color-primary': '#0066cc',
6
- '--hscl-link-color-secondary': '#516f90',
7
- '--hscl-link-color-tertiary': '#7c98b6',
8
- '--hscl-link-color-hover-primary': '#0052a3',
9
- '--hscl-link-color-hover-secondary': '#33475b',
10
- '--hscl-link-color-hover-tertiary': '#516f90',
11
- };
2
+ import styles from './LinkDecorator.module.scss';
12
3
 
13
4
  export const withLinkStyles: Decorator = Story => (
14
- <div style={defaultLinkStyles}>
5
+ <div className={styles.linkContainer}>
15
6
  <Story />
16
7
  </div>
17
8
  );
@@ -1,8 +1,10 @@
1
1
  import { LinkFieldDefaults, Visibility } from '@hubspot/cms-components/fields';
2
2
  import { LinkFieldValue } from '../utils/linkField.js';
3
3
 
4
+ export type LinkVariant = 'primaryLink' | 'secondaryLink';
5
+
4
6
  export type BaseLinkProps = {
5
- variant?: 'primary' | 'secondary' | 'tertiary';
7
+ variant?: LinkVariant;
6
8
  className?: string;
7
9
  style?: React.CSSProperties;
8
10
  children?: React.ReactNode;
@@ -33,15 +35,16 @@ type LinkWithoutField = BaseLinkProps &
33
35
 
34
36
  export type LinkProps = LinkWithField | LinkWithoutField;
35
37
 
36
- export type ContentFieldsProps = {
38
+ export type ContentFieldsProps<TLinkName extends string = 'link'> = {
37
39
  linkLabel?: string;
38
- linkName?: string;
39
40
  linkDefault?: typeof LinkFieldDefaults;
40
- fieldVisibility?: Partial<Record<string, Visibility>>;
41
- };
41
+ } & (
42
+ | { linkName?: never; fieldVisibility?: Partial<Record<'link', Visibility>> }
43
+ | { linkName: TLinkName; fieldVisibility?: Partial<Record<TLinkName, Visibility>> }
44
+ );
42
45
 
43
46
  export type StyleFieldsProps = {
44
- linkVariantLabel?: string;
45
- linkVariantName?: string;
46
- linkVariantDefault?: string;
47
+ variantLabel?: string;
48
+ variantName?: string;
49
+ variantDefault?: { variant_name: LinkVariant };
47
50
  };
@@ -0,0 +1,112 @@
1
+ import {
2
+ ChoiceField,
3
+ EmbedField,
4
+ ImageField,
5
+ VideoField,
6
+ } from '@hubspot/cms-components/fields';
7
+ import { ContentFieldsProps } from './types.js';
8
+
9
+ const ContentFields = ({
10
+ videoTypeLabel = 'Video type',
11
+ videoTypeName = 'videoType',
12
+ hsVideoLabel = 'HubSpot video',
13
+ hsVideoName = 'hubspotVideo',
14
+ hsVideoDefault,
15
+ embedVideoLabel = 'Embed video',
16
+ embedVideoName = 'embedVideo',
17
+ embedVideoDefault,
18
+ oembedThumbnailLabel = 'Custom thumbnail',
19
+ oembedThumbnailName = 'oembedThumbnail',
20
+ oembedThumbnailDefault,
21
+ }: ContentFieldsProps) => {
22
+ return (
23
+ <>
24
+ <ChoiceField
25
+ name={videoTypeName}
26
+ label={videoTypeLabel}
27
+ id="videoType"
28
+ display="radio"
29
+ choices={[
30
+ ['embed', 'Embed'],
31
+ ['hubspot_video', 'HubSpot video'],
32
+ ]}
33
+ default="embed"
34
+ visibilityRules="ADVANCED"
35
+ advancedVisibility={{
36
+ boolean_operator: 'OR',
37
+ criteria: [
38
+ {
39
+ access: {
40
+ operator: 'HAS_ALL',
41
+ scopes: ['marketing-video'],
42
+ },
43
+ },
44
+ {
45
+ controlling_field: 'videoType',
46
+ operator: 'EQUAL',
47
+ controlling_value_regex: 'hubspot_video',
48
+ },
49
+ ],
50
+ }}
51
+ />
52
+
53
+ <VideoField
54
+ name={hsVideoName}
55
+ label={hsVideoLabel}
56
+ showAdvancedOptions={true}
57
+ default={hsVideoDefault}
58
+ visibility={{
59
+ controlling_field: 'videoType',
60
+ operator: 'EQUAL',
61
+ controlling_value_regex: 'hubspot_video',
62
+ }}
63
+ />
64
+
65
+ <EmbedField
66
+ name={embedVideoName}
67
+ label={embedVideoLabel}
68
+ id="embedVideo"
69
+ supportedSourceTypes={['oembed', 'html']}
70
+ supportedOembedTypes={['video']}
71
+ supportedMediaBridgeProviders={[]}
72
+ default={embedVideoDefault}
73
+ visibility={{
74
+ controlling_field: 'videoType',
75
+ operator: 'EQUAL',
76
+ controlling_value_regex: 'embed',
77
+ }}
78
+ />
79
+
80
+ <ImageField
81
+ name={oembedThumbnailName}
82
+ label={oembedThumbnailLabel}
83
+ default={oembedThumbnailDefault}
84
+ responsive={true}
85
+ resizable={false}
86
+ visibilityRules="ADVANCED"
87
+ advancedVisibility={{
88
+ boolean_operator: 'AND',
89
+ criteria: [
90
+ {
91
+ controlling_field: 'videoType',
92
+ operator: 'EQUAL',
93
+ controlling_value_regex: 'embed',
94
+ },
95
+ {
96
+ controlling_field: 'embedVideo',
97
+ operator: 'MATCHES_REGEX',
98
+ controlling_value_regex: '(?=.*"source_type":"oembed")',
99
+ },
100
+ {
101
+ controlling_field: 'embedVideo',
102
+ operator: 'MATCHES_REGEX',
103
+ controlling_value_regex: '(?=.*"oembed_url":"(?!")+)',
104
+ },
105
+ ],
106
+ }}
107
+ />
108
+ </>
109
+ );
110
+ };
111
+
112
+ export default ContentFields;
@@ -0,0 +1,19 @@
1
+ import { ColorField } from '@hubspot/cms-components/fields';
2
+ import { StyleFieldsProps } from './types.js';
3
+
4
+ const StyleFields = ({
5
+ playButtonColorLabel = 'Play button color',
6
+ playButtonColorName = 'playButtonColor',
7
+ playButtonColorDefault = { color: '#F7761F', opacity: 100 },
8
+ }: StyleFieldsProps) => {
9
+ return (
10
+ <ColorField
11
+ label={playButtonColorLabel}
12
+ name={playButtonColorName}
13
+ default={playButtonColorDefault}
14
+ showOpacity={false}
15
+ />
16
+ );
17
+ };
18
+
19
+ export default StyleFields;
@@ -0,0 +1,47 @@
1
+ import { Island } from '@hubspot/cms-components';
2
+ import ContentFields from './ContentFields.js';
3
+ import StyleFields from './StyleFields.js';
4
+ // @ts-expect-error -- ?island not typed
5
+ import HSVideoIsland from './islands/HSVideoIsland.js?island';
6
+ import { VideoProps } from './types.js';
7
+
8
+ const VideoComponent = ({
9
+ videoType,
10
+ hubspotVideoParams,
11
+ embedVideoParams,
12
+ playButtonColor,
13
+ video,
14
+ }: VideoProps) => {
15
+ if (videoType === 'hubspot_video' && hubspotVideoParams?.player_id) {
16
+ return (
17
+ <Island
18
+ module={HSVideoIsland}
19
+ hydrateOn="visible"
20
+ hubspotVideoParams={hubspotVideoParams}
21
+ playButtonColor={playButtonColor}
22
+ video={video}
23
+ />
24
+ );
25
+ }
26
+
27
+ if (
28
+ videoType === 'embed' &&
29
+ Boolean(embedVideoParams?.oembed_url || embedVideoParams?.embed_html)
30
+ ) {
31
+ // TODO: implement embed video
32
+ return <div />;
33
+ }
34
+
35
+ return null;
36
+ };
37
+
38
+ type VideoComponentType = typeof VideoComponent & {
39
+ ContentFields: typeof ContentFields;
40
+ StyleFields: typeof StyleFields;
41
+ };
42
+
43
+ const Video = VideoComponent as VideoComponentType;
44
+ Video.ContentFields = ContentFields;
45
+ Video.StyleFields = StyleFields;
46
+
47
+ export default Video;
@@ -0,0 +1,53 @@
1
+ import { getHSEnv, getHubID, getHublet } from '@hubspot/cms-components';
2
+ import {
3
+ // @ts-expect-error -- provider type not flowing through properly
4
+ TranslationsProvider,
5
+ useVideo,
6
+ VideoFetchProvider,
7
+ VideoPlayer,
8
+ // @ts-expect-error -- provider type not flowing through properly
9
+ VideoPlayerProvider,
10
+ } from '@hubspot/video-player-core';
11
+
12
+ import { HSVideoIslandProps } from '../types.js';
13
+
14
+ const HSVideoIsland = ({
15
+ video,
16
+ hubspotVideoParams,
17
+ playButtonColor,
18
+ }: HSVideoIslandProps) => {
19
+ const portalId = getHubID();
20
+ const hsEnv = getHSEnv();
21
+ const hublet = getHublet();
22
+ const { video: clientFetchedVideo, error: videoFetchError } = useVideo(
23
+ hubspotVideoParams.player_id,
24
+ portalId,
25
+ hsEnv,
26
+ hublet,
27
+ Boolean(video)
28
+ );
29
+ const resolvedVideo = video || clientFetchedVideo;
30
+
31
+ return (
32
+ <VideoFetchProvider
33
+ env={hsEnv}
34
+ hublet={hublet}
35
+ videoId={hubspotVideoParams.player_id}
36
+ video={resolvedVideo}
37
+ videoFetchError={videoFetchError}
38
+ hubspotVideoParams={hubspotVideoParams}
39
+ >
40
+ <VideoPlayerProvider video={resolvedVideo}>
41
+ <TranslationsProvider translations={{}} language="en">
42
+ <VideoPlayer
43
+ portalId={portalId}
44
+ playButtonColor={playButtonColor}
45
+ theme="DARK"
46
+ />
47
+ </TranslationsProvider>
48
+ </VideoPlayerProvider>
49
+ </VideoFetchProvider>
50
+ );
51
+ };
52
+
53
+ export default HSVideoIsland;
@@ -0,0 +1,41 @@
1
+ import {
2
+ getHubID,
3
+ getHublet,
4
+ useServerRenderContext,
5
+ } from '@hubspot/cms-components';
6
+ import {
7
+ // @ts-expect-error -- types not flowing through properly
8
+ fetchVideo,
9
+ // @ts-expect-error -- types not flowing through properly
10
+ HubSpotVideoParams,
11
+ // @ts-expect-error -- types not flowing through properly
12
+ VideoCrmObject,
13
+ } from '@hubspot/video-player-core';
14
+
15
+ type FetchHSVideoResult = VideoCrmObject | undefined;
16
+
17
+ export async function fetchHSVideoServerSide(
18
+ videoType: 'hubspot_video' | 'embed',
19
+ hubspotVideoParams?: HubSpotVideoParams
20
+ ): Promise<FetchHSVideoResult> {
21
+ if (videoType !== 'hubspot_video' || !hubspotVideoParams?.player_id) {
22
+ return undefined;
23
+ }
24
+
25
+ const portalId = getHubID();
26
+ const { environmentConfig } = useServerRenderContext();
27
+ const env = environmentConfig.getInternalHSEnv();
28
+
29
+ try {
30
+ const response = await fetchVideo(
31
+ hubspotVideoParams.player_id,
32
+ portalId,
33
+ env,
34
+ getHublet()
35
+ );
36
+ return response.video;
37
+ } catch (err) {
38
+ console.error(err);
39
+ return undefined;
40
+ }
41
+ }
@@ -0,0 +1,74 @@
1
+ import {
2
+ ColorFieldDefaults,
3
+ EmbedFieldDefaults,
4
+ ImageFieldDefaults,
5
+ VideoFieldDefaults,
6
+ } from '@hubspot/cms-components/fields';
7
+ // @ts-expect-error -- types not flowing through properly
8
+ import { VideoCrmObject, HubSpotVideoParams } from '@hubspot/video-player-core';
9
+
10
+ type ColorFieldValue = typeof ColorFieldDefaults;
11
+
12
+ export type ContentFieldsProps = {
13
+ videoTypeLabel?: string;
14
+ videoTypeName?: string;
15
+ hsVideoLabel?: string;
16
+ hsVideoName?: string;
17
+ hsVideoDefault?: typeof VideoFieldDefaults;
18
+ embedVideoLabel?: string;
19
+ embedVideoName?: string;
20
+ embedVideoDefault?: typeof EmbedFieldDefaults;
21
+ oembedThumbnailLabel?: string;
22
+ oembedThumbnailName?: string;
23
+ oembedThumbnailDefault?: typeof ImageFieldDefaults;
24
+ };
25
+
26
+ export type StyleFieldsProps = {
27
+ playButtonColorLabel?: string;
28
+ playButtonColorName?: string;
29
+ playButtonColorDefault?: ColorFieldValue;
30
+ };
31
+
32
+ export type VideoProps = {
33
+ videoType: 'embed' | 'hubspot_video';
34
+ video?: VideoCrmObject;
35
+ hubspotVideoParams?: HubSpotVideoParams;
36
+ embedVideoParams?: EmbedVideoParams;
37
+ oembedThumbnail?: { src: string; alt?: string };
38
+ playButtonColor?: ColorFieldValue;
39
+ };
40
+
41
+ export type HSVideoIslandProps = {
42
+ video?: VideoCrmObject;
43
+ hubspotVideoParams: HubSpotVideoParams;
44
+ playButtonColor?: ColorFieldValue;
45
+ };
46
+
47
+ export type EmbedVideoIslandProps = {
48
+ embedVideoParams: EmbedVideoParams;
49
+ oembedThumbnail?: { src: string; alt?: string };
50
+ playButtonColor?: ColorFieldValue;
51
+ };
52
+
53
+ export type EmbedVideoParams = {
54
+ source_type: 'oembed' | 'html';
55
+ supported_oembed_types: string[];
56
+ // fields for 'oembed' source_type
57
+ size_type?: 'auto' | 'auto_custom_max' | 'auto_full_width' | 'exact';
58
+ width?: number;
59
+ height?: number;
60
+ max_width?: number;
61
+ max_height?: number;
62
+ oembed_url?: string;
63
+ oembed_response?: {
64
+ type: string;
65
+ html: string;
66
+ width: string;
67
+ height: string;
68
+ title?: string;
69
+ provider_name?: string;
70
+ provider_url?: string;
71
+ };
72
+ // fields for 'html' source_type
73
+ embed_html?: string;
74
+ };