Skip to content

Commit afdece7

Browse files
authored
EDU-Hydrogen: Custom components - child blocks (#3971)
## Description This is the final custom component for the child blocks PR. With it, we can update the custom components—child blocks doc. @samijaber Please review this as well.
1 parent 782424f commit afdece7

File tree

9 files changed

+229
-5
lines changed

9 files changed

+229
-5
lines changed

packages/sdks-tests/src/snippet-tests/advanced-child.spec.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { test, testClickAndVerifyVisibility } from '../helpers/index.js';
44
test.describe('Advanced child sub components', () => {
55
test('Display two buttons with label Tab 1 and Tab 2', async ({ page, packageName }) => {
66
test.skip(
7-
!['react', 'angular-16', 'angular-16-ssr', 'gen1-remix', 'gen1-react', 'qwik-city'].includes(
7+
!['react', 'angular-16', 'angular-16-ssr', 'gen1-remix', 'gen1-react', 'hydrogen', 'qwik-city'].includes(
88
packageName
99
)
1010
);
@@ -30,7 +30,8 @@ test.describe('Advanced child sub components', () => {
3030

3131
test('Display content for the clicked tab and hide the other', async ({ page, packageName }) => {
3232
test.skip(
33-
!['react', 'angular-16', 'angular-16-ssr', 'gen1-remix', 'gen1-react', 'qwik-city'].includes(
33+
34+
!['react', 'angular-16', 'angular-16-ssr', 'gen1-remix', 'gen1-react', 'hydrogen', 'qwik-city'].includes(
3435
packageName
3536
)
3637
);

packages/sdks-tests/src/snippet-tests/custom-child.spec.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { test } from '../helpers/index.js';
44
test.describe('Div with Hero class, and text', () => {
55
test('should render the page without 404', async ({ page, packageName }) => {
66
test.skip(
7-
!['react', 'angular-16', 'angular-16-ssr', 'gen1-remix', 'gen1-react', 'qwik-city'].includes(
7+
!['react', 'angular-16', 'angular-16-ssr', 'gen1-remix', 'gen1-react', 'hydrogen', 'qwik-city'].includes(
88
packageName
99
)
1010
);
@@ -15,7 +15,7 @@ test.describe('Div with Hero class, and text', () => {
1515

1616
test('should verify builder-block with specific text', async ({ page, packageName }) => {
1717
test.skip(
18-
!['react', 'angular-16', 'angular-16-ssr', 'gen1-remix', 'gen1-react', 'qwik-city'].includes(
18+
!['react', 'angular-16', 'angular-16-ssr', 'gen1-remix', 'gen1-react', 'hydrogen', 'qwik-city'].includes(
1919
packageName
2020
)
2121
);

packages/sdks-tests/src/snippet-tests/editable-regions.spec.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ test.describe('Editable regions in custom components', () => {
2323
packageName,
2424
}) => {
2525
test.skip(
26-
!['react', 'angular-16', 'angular-16-ssr', 'gen1-remix', 'gen1-react', 'qwik-city'].includes(
26+
!['react', 'angular-16', 'angular-16-ssr', 'gen1-remix', 'gen1-react', 'hydrogen', 'qwik-city'].includes(
2727
packageName
2828
)
2929
);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import {
2+
Blocks,
3+
type BuilderBlock,
4+
type RegisteredComponent,
5+
} from '@builder.io/sdk-react';
6+
7+
interface CustomColumnsProps {
8+
column1: BuilderBlock[];
9+
column2: BuilderBlock[];
10+
builderBlock: BuilderBlock;
11+
}
12+
13+
export function CustomColumns({
14+
column1,
15+
column2,
16+
builderBlock,
17+
}: CustomColumnsProps) {
18+
return (
19+
<>
20+
<Blocks blocks={column1} path="column1" parent={builderBlock.id} />
21+
<Blocks blocks={column2} path="column2" parent={builderBlock.id} />
22+
</>
23+
);
24+
}
25+
26+
export const customColumnsInfo: RegisteredComponent = {
27+
name: 'MyColumns',
28+
component: CustomColumns,
29+
shouldReceiveBuilderProps: {
30+
builderBlock: true,
31+
},
32+
inputs: [
33+
{name: 'column1', type: 'uiBlocks', defaultValue: []},
34+
{name: 'column2', type: 'uiBlocks', defaultValue: []},
35+
],
36+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// app/components/CustomHero.tsx
2+
import {type PropsWithChildren} from 'react';
3+
import type {RegisteredComponent} from '@builder.io/sdk-react';
4+
5+
export function CustomHero({children}: PropsWithChildren) {
6+
return (
7+
<>
8+
<div>This is text from your component</div>
9+
{children}
10+
</>
11+
);
12+
}
13+
14+
export const customHeroInfo: RegisteredComponent = {
15+
name: 'CustomHero',
16+
component: CustomHero,
17+
canHaveChildren: true,
18+
defaultChildren: [
19+
{
20+
'@type': '@builder.io/sdk:Element',
21+
component: {
22+
name: 'Text',
23+
options: {text: 'This is Builder text'},
24+
},
25+
},
26+
],
27+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import {useState} from 'react';
2+
import {
3+
Blocks,
4+
type BuilderBlock,
5+
type RegisteredComponent,
6+
} from '@builder.io/sdk-react';
7+
8+
interface CustomTabsProps {
9+
tabList: Array<{
10+
tabName: string;
11+
blocks: BuilderBlock[];
12+
}>;
13+
builderBlock: BuilderBlock;
14+
}
15+
16+
export function CustomTabs({tabList, builderBlock}: CustomTabsProps) {
17+
const [activeTab, setActiveTab] = useState(0);
18+
19+
if (!tabList?.length) return null;
20+
21+
return (
22+
<>
23+
{tabList.map((tab, i) => (
24+
<button
25+
key={i}
26+
onClick={() => setActiveTab(i)}
27+
className={activeTab === i ? 'active' : ''}
28+
>
29+
{tab.tabName}
30+
</button>
31+
))}
32+
33+
<Blocks
34+
parent={builderBlock.id}
35+
path={`tabList.${activeTab}.blocks`}
36+
blocks={tabList[activeTab].blocks}
37+
/>
38+
</>
39+
);
40+
}
41+
42+
export const customTabsInfo: RegisteredComponent = {
43+
name: 'TabFields',
44+
component: CustomTabs,
45+
shouldReceiveBuilderProps: {
46+
builderBlock: true,
47+
},
48+
inputs: [
49+
{
50+
name: 'tabList',
51+
type: 'list',
52+
subFields: [
53+
{name: 'tabName', type: 'string'},
54+
{name: 'blocks', type: 'uiBlocks', defaultValue: []},
55+
],
56+
},
57+
],
58+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import type {LoaderFunction} from '@remix-run/node';
2+
import {useLoaderData} from '@remix-run/react';
3+
import {Content, fetchOneEntry, isPreviewing} from '@builder.io/sdk-react';
4+
import {customTabsInfo} from '~/components/CustomTabs';
5+
6+
const MODEL_NAME = 'advanced-child';
7+
const API_KEY = 'ee9f13b4981e489a9a1209887695ef2b';
8+
9+
export const loader: LoaderFunction = async ({params}) => {
10+
const content = await fetchOneEntry({
11+
model: MODEL_NAME,
12+
apiKey: API_KEY,
13+
userAttributes: {urlPath: params.pathname},
14+
});
15+
16+
return {content};
17+
};
18+
19+
export default function AdvancedChildRoute() {
20+
const {content} = useLoaderData<typeof loader>();
21+
22+
if (!content && !isPreviewing()) {
23+
return <div>404</div>;
24+
}
25+
26+
return (
27+
<Content
28+
content={content}
29+
model={MODEL_NAME}
30+
apiKey={API_KEY}
31+
customComponents={[customTabsInfo]}
32+
/>
33+
);
34+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import type {LoaderFunction, LoaderFunctionArgs} from '@remix-run/node';
2+
import {useLoaderData} from '@remix-run/react';
3+
import {Content, fetchOneEntry, isPreviewing} from '@builder.io/sdk-react';
4+
import {customHeroInfo} from '~/components/CustomHero';
5+
6+
const MODEL_NAME = 'custom-child';
7+
const API_KEY = 'ee9f13b4981e489a9a1209887695ef2b';
8+
9+
export const loader: LoaderFunction = async ({params}) => {
10+
const content = await fetchOneEntry({
11+
model: MODEL_NAME,
12+
apiKey: API_KEY,
13+
userAttributes: {
14+
urlPath: params.pathname,
15+
},
16+
});
17+
return {content};
18+
};
19+
export default function CustomChildRoute() {
20+
const {content} = useLoaderData<typeof loader>();
21+
22+
if (!content && !isPreviewing()) {
23+
return <div>404</div>;
24+
}
25+
26+
return (
27+
<Content
28+
content={content}
29+
model={MODEL_NAME}
30+
apiKey={API_KEY}
31+
customComponents={[customHeroInfo]}
32+
/>
33+
);
34+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import type {LoaderFunction} from '@remix-run/node';
2+
import {useLoaderData} from '@remix-run/react';
3+
import {Content, fetchOneEntry, isPreviewing} from '@builder.io/sdk-react';
4+
import {customColumnsInfo} from '~/components/CustomColumns';
5+
6+
const MODEL_NAME = 'editable-regions';
7+
const API_KEY = 'ee9f13b4981e489a9a1209887695ef2b';
8+
9+
export const loader: LoaderFunction = async ({params}) => {
10+
const content = await fetchOneEntry({
11+
model: MODEL_NAME,
12+
apiKey: API_KEY,
13+
userAttributes: {urlPath: params.pathname},
14+
});
15+
16+
return {content};
17+
};
18+
19+
export default function EditableRegionsRoute() {
20+
const {content} = useLoaderData<typeof loader>();
21+
22+
if (!content && !isPreviewing()) {
23+
return <div>404</div>;
24+
}
25+
26+
return (
27+
<Content
28+
content={content}
29+
model={MODEL_NAME}
30+
apiKey={API_KEY}
31+
customComponents={[customColumnsInfo]}
32+
/>
33+
);
34+
}

0 commit comments

Comments
 (0)