@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.
- package/components/componentLibrary/Accordion/AccordionTitle/AccordionTitleBase.tsx +45 -0
- package/components/componentLibrary/Accordion/AccordionTitle/index.tsx +17 -30
- package/components/componentLibrary/Accordion/AccordionTitle/islands/AccordionTitleIsland.tsx +29 -0
- package/components/componentLibrary/Button/StyleFields.tsx +8 -8
- package/components/componentLibrary/Button/index.module.scss +24 -27
- package/components/componentLibrary/Button/index.tsx +4 -4
- package/components/componentLibrary/Button/llm.txt +51 -64
- package/components/componentLibrary/Button/stories/Button.AsButton.stories.tsx +2 -2
- package/components/componentLibrary/Button/stories/Button.AsLink.stories.tsx +2 -2
- package/components/componentLibrary/Button/stories/ButtonDecorator.module.scss +19 -23
- package/components/componentLibrary/Button/types.ts +2 -2
- package/components/componentLibrary/Card/StyleFields.tsx +9 -14
- package/components/componentLibrary/Card/index.module.scss +7 -7
- package/components/componentLibrary/Card/index.tsx +8 -13
- package/components/componentLibrary/Card/llm.txt +22 -43
- package/components/componentLibrary/Card/stories/Card.stories.tsx +28 -20
- package/components/componentLibrary/Card/stories/CardDecorator.module.scss +28 -5
- package/components/componentLibrary/Card/types.ts +8 -5
- package/components/componentLibrary/Form/StyleFields.tsx +19 -0
- package/components/componentLibrary/Form/index.tsx +7 -1
- package/components/componentLibrary/Form/islands/FormIsland.tsx +3 -1
- package/components/componentLibrary/Form/islands/LegacyFormIsland.tsx +2 -1
- package/components/componentLibrary/Form/islands/legacyForm.module.css +251 -0
- package/components/componentLibrary/Form/islands/v4Form.module.css +95 -0
- package/components/componentLibrary/Form/llm.txt +184 -0
- package/components/componentLibrary/Form/types.ts +6 -0
- package/components/componentLibrary/Link/ContentFields.tsx +2 -2
- package/components/componentLibrary/Link/StyleFields.tsx +10 -17
- package/components/componentLibrary/Link/index.module.scss +9 -9
- package/components/componentLibrary/Link/index.tsx +3 -8
- package/components/componentLibrary/Link/llm.txt +29 -85
- package/components/componentLibrary/Link/stories/Link.stories.tsx +4 -11
- package/components/componentLibrary/Link/stories/LinkDecorator.module.scss +15 -0
- package/components/componentLibrary/Link/stories/LinkDecorator.tsx +2 -11
- package/components/componentLibrary/Link/types.ts +11 -8
- package/components/componentLibrary/Video/ContentFields.tsx +112 -0
- package/components/componentLibrary/Video/StyleFields.tsx +19 -0
- package/components/componentLibrary/Video/index.tsx +47 -0
- package/components/componentLibrary/Video/islands/HSVideoIsland.tsx +53 -0
- package/components/componentLibrary/Video/serverUtils.ts +41 -0
- package/components/componentLibrary/Video/types.ts +74 -0
- package/components/componentLibrary/_patterns/README.md +11 -7
- package/components/componentLibrary/_patterns/checklist-and-examples.md +8 -0
- package/components/componentLibrary/_patterns/component-structure.md +5 -1
- package/components/componentLibrary/_patterns/field-patterns.md +46 -0
- package/components/componentLibrary/_patterns/island-patterns.md +136 -0
- package/components/componentLibrary/utils/index.ts +1 -0
- 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?: '
|
|
61
|
+
variant?: 'primaryLink' | 'secondaryLink'; // Visual style variant (default: 'primaryLink')
|
|
62
62
|
className?: string; // Additional CSS classes
|
|
63
|
-
style?: React.CSSProperties; // Inline styles
|
|
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?: '
|
|
77
|
+
variant?: 'primaryLink' | 'secondaryLink'; // Visual style variant (default: 'primaryLink')
|
|
78
78
|
className?: string; // Additional CSS classes
|
|
79
|
-
style?: React.CSSProperties; // Inline styles
|
|
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="
|
|
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="
|
|
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="
|
|
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=
|
|
205
|
+
variantDefault={{ variant_name: 'primaryLink' }}
|
|
224
206
|
/>
|
|
225
207
|
```
|
|
226
208
|
|
|
227
209
|
**Fields:**
|
|
228
|
-
- `linkVariant`:
|
|
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="
|
|
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
|
|
254
|
+
The Link component supports two visual style variants through the `variant` prop:
|
|
273
255
|
|
|
274
|
-
- **
|
|
275
|
-
- **
|
|
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
|
|
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
|
|
263
|
+
The theme provides these CSS variables via `[data-link-variant]` selectors:
|
|
289
264
|
|
|
290
265
|
**Base Styles:**
|
|
291
|
-
- `--
|
|
292
|
-
- `--
|
|
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
|
-
- `--
|
|
297
|
-
- `--
|
|
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
|
-
|
|
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
|
-
|
|
312
|
-
|
|
313
|
-
|
|
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 (
|
|
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="
|
|
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: '
|
|
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"
|
|
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="
|
|
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="
|
|
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
|
|
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
|
|
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?:
|
|
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
|
-
|
|
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
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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
|
+
};
|