Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 35 additions & 6 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,19 +60,48 @@ Barrel exports in `src/components/index.ts` must match content type keys used in
### Component Pattern

```typescript
import { Infer } from '@optimizely/cms-sdk';
import { ContentProps } from '@optimizely/cms-sdk';
import { getPreviewUtils } from '@optimizely/cms-sdk/react/server';

type Props = {
opti: Infer<typeof SomeContentTypeCT>;
displaySettings?: Infer<typeof SomeDisplayTemplate>;
content: ContentProps<typeof SomeContentTypeCT>;
displaySettings?: ContentProps<typeof SomeDisplayTemplate>;
};

export default function MyComponent({ opti, displaySettings }: Props) {
const { pa, src } = getPreviewUtils(opti);
export default function MyComponent({ content, displaySettings }: Props) {
const { pa, src } = getPreviewUtils(content);
// pa('propertyName') — adds preview attributes for Visual Builder editing
// src(opti.image) — returns optimized image URL from CMS CDN
return <div {...pa('title')}>{opti.title}</div>;
return <div {...pa('title')}>{content.title}</div>;
}
```
### Experience Pattern
```typescript
import { BlankExperienceContentType, ContentProps } from '@optimizely/cms-sdk';
import {
ComponentContainerProps,
OptimizelyComposition,
getPreviewUtils,
} from '@optimizely/cms-sdk/react/server';

type Props = {
content: ContentProps<typeof BlankExperienceContentType>;
};

function ComponentWrapper({ children, node }: ComponentContainerProps) {
const { pa } = getPreviewUtils(node);
return <div className="mb-8" {...pa(node)}>{children}</div>;
}

export default function BlankExperience({ content }: Props) {
return (
<main className="blank-experience">
<OptimizelyComposition
nodes={content.composition?.nodes ?? []}
ComponentWrapper={ComponentWrapper}
/>
</main>
);
}
```

Expand Down
115 changes: 59 additions & 56 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@
"cms:pull-config": "npx @optimizely/cms-cli@latest config pull --output ./content-types.json"
},
"dependencies": {
"@optimizely/cms-sdk": "^0.1.0-alpha.16",
"@optimizely/cms-sdk": "^1.0.0",
"@tailwindcss/typography": "^0.5.19",
"next": "16.1.1",
"next": "16.1.6",
"react": "19.2.3",
"react-dom": "19.2.3"
},
Expand All @@ -28,7 +28,7 @@
"@types/react": "^19",
"@types/react-dom": "^19",
"eslint": "^9",
"eslint-config-next": "16.1.1",
"eslint-config-next": "16.1.6",
"tailwindcss": "^4",
"typescript": "^5"
}
Expand Down
26 changes: 13 additions & 13 deletions src/components/blocks/CardBlock.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,23 @@
import { Infer } from '@optimizely/cms-sdk';
import { ContentProps } from '@optimizely/cms-sdk';
import { RichText } from '@optimizely/cms-sdk/react/richText';
import { getPreviewUtils } from '@optimizely/cms-sdk/react/server';
import { CardBlockCT } from '@/content-types/CardBlock';
import Image from 'next/image';

type Props = {
opti: Infer<typeof CardBlockCT>;
content: ContentProps<typeof CardBlockCT>;
};

export default function CardBlock({ opti }: Props) {
const { pa, src } = getPreviewUtils(opti);
export default function CardBlock({ content }: Props) {
const { pa, src } = getPreviewUtils(content);

return (
<div className="bg-white rounded-xl shadow-md overflow-hidden hover:shadow-lg transition-shadow duration-300">
{(opti.image?.url?.default || opti.image?.item?.Url) && (
{(content.image?.url?.default || content.image?.item?.Url) && (
<div className="relative h-48 w-full">
<Image
src={src(opti.image)}
alt={opti.title || 'Card image'}
src={src(content.image)}
alt={content.title || 'Card image'}
fill
className="object-cover"
{...pa('image')}
Expand All @@ -30,22 +30,22 @@ export default function CardBlock({ opti }: Props) {
className="text-xl font-semibold text-gray-900 mb-2"
{...pa('title')}
>
{opti.title}
{content.title}
</h3>

{opti.text && (
{content.text && (
<div className="prose prose-sm text-gray-600 mb-4" {...pa('text')}>
<RichText content={opti.text?.json} />
<RichText content={content.text?.json} />
</div>
)}

{opti.linkUrl && opti.linkText && (
{content.linkUrl && content.linkText && (
<a
href={String(opti.linkUrl)}
href={String(content.linkUrl)}
className="inline-flex items-center text-blue-600 hover:text-blue-800 font-medium"
{...pa('linkUrl')}
>
{opti.linkText}
{content.linkText}
<svg
className="ml-2 w-4 h-4"
fill="none"
Expand Down
Loading