Skip to content
Merged
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
2 changes: 2 additions & 0 deletions packages/react/playground/next15/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,5 @@ yarn-error.log*
# typescript
*.tsbuildinfo
next-env.d.ts

certificates
8 changes: 6 additions & 2 deletions packages/react/playground/next15/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,25 @@
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"dev": "next dev --experimental-https",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"@storyblok/react": "workspace:*",
"@tailwindcss/typography": "^0.5.16",
"next": "15.3.2",
"react": "19.1.0",
"react-dom": "19.1.0"
},
"devDependencies": {
"@tailwindcss/postcss": "^4.1.10",
"@types/node": "^20",
"@types/react": "^19",
"@types/react-dom": "^19"
"@types/react-dom": "^19",
"postcss": "^8.5.6",
"tailwindcss": "^4.1.10"
},
"nx": {
"projectType": "application"
Expand Down
6 changes: 6 additions & 0 deletions packages/react/playground/next15/postcss.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/** @type {import('tailwindcss').Config} */
export default {
plugins: {
'@tailwindcss/postcss': {},
},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
'use client';

import React, { type FC, useState } from 'react';
import type { SbBlokData } from '@storyblok/react';

interface EmojiRandomizerProps {
blok: SbBlokData & {
label?: string;
};
}

/**
* A component that displays a label and a random emoji that changes on click
*/
const EmojiRandomizer: FC<EmojiRandomizerProps> = ({ blok }) => {
// List of fun emojis to randomly choose from
const emojis = ['😊', '🎉', '🚀', '✨', '🌈', '🎨', '🎸', '🎮', '🍕', '🌺'];

// State to track current emoji
const [currentEmoji, setCurrentEmoji] = useState(() =>
emojis[Math.floor(Math.random() * emojis.length)],
);

/**
* Generates a new random emoji different from the current one
*/
const randomizeEmoji = () => {
let newEmoji;
do {
newEmoji = emojis[Math.floor(Math.random() * emojis.length)];
} while (newEmoji === currentEmoji);

setCurrentEmoji(newEmoji);
};

return (
<div className="flex flex-col items-center gap-6 p-4 bg-gray-100 dark:bg-gray-900 rounded-lg">
<div className="text-6xl">
{currentEmoji}
</div>
<button
onClick={randomizeEmoji}
className="px-6 py-3 rounded-lg bg-blue-500 hover:bg-blue-600 active:bg-blue-700 text-white font-small transition-colors duration-200 dark:bg-blue-600 dark:hover:bg-blue-700 dark:active:bg-blue-800"
>
{blok.label || 'Randomize Emoji'}
</button>
</div>
);
};

export default EmojiRandomizer;
18 changes: 14 additions & 4 deletions packages/react/playground/next15/src/app/globals.css
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
@import 'tailwindcss';
@plugin '@tailwindcss/typography';

span[data-type='emoji'] img {
@apply m-0;
}

:root {
--background: #ffffff;
--foreground: #171717;
Expand All @@ -24,10 +31,13 @@ body {
-moz-osx-font-smoothing: grayscale;
}

* {
box-sizing: border-box;
padding: 0;
margin: 0;
@layer base {
* {
margin: 0;
padding: 0;
box-sizing: border-box;
scroll-behavior: smooth;
}
}

a {
Expand Down
1 change: 1 addition & 0 deletions packages/react/playground/next15/src/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import StoryblokProvider from '@/components/StoryblokProvider';
import './globals.css';

export const metadata = {
title: 'Create Next App',
Expand Down
118 changes: 26 additions & 92 deletions packages/react/playground/next15/src/app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,111 +1,45 @@
import type {
ISbStoriesParams,
StoryblokClient,
StoryblokRichTextNode,
} from '@storyblok/react/rsc';
import { MarkTypes, StoryblokRichText, StoryblokStory,
import { StoryblokStory,
} from '@storyblok/react/rsc';
import { getStoryblokApi } from '@/lib/storyblok';
import Link from 'next/link';
import type { ReactElement } from 'react';
// import Link from 'next/link';

export default async function Home() {
const { data } = await fetchData();

const doc = {
type: 'doc',
content: [
{
type: 'paragraph',
content: [
{
type: 'text',
text: 'This is a test of the StoryblokRichText component.',
},
],
},
{
type: 'paragraph',
content: [
{
text: 'Internal Link',
type: 'text',
marks: [
{
type: 'link',
attrs: {
href: '/',
uuid: '8489bed8-d86f-4fde-965c-e3d748e12147',
anchor: null,
target: '_self',
linktype: 'story',
},
},
],
},
],
},
{
type: 'paragraph',
content: [
{
text: 'External link',
type: 'text',
marks: [
{
type: 'link',
attrs: {
href: 'https://alvarosaburido.dev',
uuid: null,
anchor: null,
target: '_blank',
linktype: 'url',
},
},
],
},
],
},
],
};
const resolvers = {
// custom resolvers
[MarkTypes.LINK]: (node: StoryblokRichTextNode<ReactElement>) => {
return node.attrs?.linktype === 'story'
? (
<Link
href={node.attrs?.href}
target={node.attrs?.target}
>
{node.text}
</Link>
)
: (
<a
href={node.attrs?.href}
target={node.attrs?.target}
>
{node.text}
</a>
);
},
};

return (
<div>
<h1>
Story:
{data.story.id}
</h1>
<StoryblokStory story={data.story} />
<StoryblokRichText doc={doc} resolvers={resolvers} />
</div>
<main className="container mx-auto px-4 py-8">
<div className="max-w-4xl mx-auto clas prose">
<h1 className="text-4xl font-bold mb-8 dark:text-white">
Storyblok Next.js 15 Example
</h1>

{ // TODO: Enable for https://github.com/storyblok/monoblok/issues/35
/* <nav className="space-y-4">
<Link
href="/richtext"
className="block p-4 bg-blue-500 text-white rounded-lg hover:bg-blue-600 transition-colors"
>
Go to Rich Text Example
</Link>
</nav> */}

{data.story && (
<div>
<StoryblokStory story={data.story} />
</div>
)}
</div>
</main>
);
}

export async function fetchData() {
const sbParams: ISbStoriesParams = { version: 'draft' };

const storyblokApi: StoryblokClient = getStoryblokApi();
return storyblokApi.get(`cdn/stories/home`, sbParams);
return storyblokApi.get(`cdn/stories/react`, sbParams);
}
4 changes: 2 additions & 2 deletions packages/react/playground/next15/src/lib/storyblok.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import EmojiRandomizer from '@/app/components/EmojiRandomizer';
import Grid from '@/components/Grid';
import IFrameEmbed from '@/components/IFrameEmbed';
import Page from '@/components/Page';
import Teaser from '@/components/Teaser';
import { apiPlugin, storyblokInit } from '@storyblok/react/rsc';
Expand All @@ -11,6 +11,6 @@ export const getStoryblokApi = storyblokInit({
'teaser': Teaser,
'page': Page,
'grid': Grid,
'iframe-embed': IFrameEmbed,
'emoji-randomizer': EmojiRandomizer,
},
});
17 changes: 9 additions & 8 deletions packages/react/playground/react/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ function App() {
return (
<BrowserRouter>
<div>
<nav>
<ul>
<nav className=" py-8 container mx-auto mb-8">
<ul className="flex gap-4">
<li>
<Link to="/react">Home</Link>
</li>
Expand All @@ -17,12 +17,13 @@ function App() {
</li>
</ul>
</nav>

<Routes>
<Route path="/" element={<Home />} />
<Route path="react" element={<Home />} />
<Route path="react/test-richtext" element={<RichtextPage />} />
</Routes>
<div className="prose mx-auto">
<Routes>
<Route path="/" element={<Home />} />
<Route path="react" element={<Home />} />
<Route path="react/test-richtext" element={<RichtextPage />} />
</Routes>
</div>
</div>
</BrowserRouter>
);
Expand Down
49 changes: 49 additions & 0 deletions packages/react/playground/react/components/emiji-randomizer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import React, { type FC, useState } from 'react';
import type { SbBlokData } from '@storyblok/react';

interface EmojiRandomizerProps {
blok: SbBlokData & {
label?: string;
};
}

/**
* A component that displays a label and a random emoji that changes on click
*/
const EmojiRandomizer: FC<EmojiRandomizerProps> = ({ blok }) => {
// List of fun emojis to randomly choose from
const emojis = ['😊', '🎉', '🚀', '✨', '🌈', '🎨', '🎸', '🎮', '🍕', '🌺'];

// State to track current emoji
const [currentEmoji, setCurrentEmoji] = useState(() =>
emojis[Math.floor(Math.random() * emojis.length)],
);

/**
* Generates a new random emoji different from the current one
*/
const randomizeEmoji = () => {
let newEmoji;
do {
newEmoji = emojis[Math.floor(Math.random() * emojis.length)];
} while (newEmoji === currentEmoji);

setCurrentEmoji(newEmoji);
};

return (
<div className="flex flex-col items-center gap-6 p-4 bg-gray-100 dark:bg-gray-900 rounded-lg">
<div className="text-6xl">
{currentEmoji}
</div>
<button
onClick={randomizeEmoji}
className="px-6 py-3 rounded-lg bg-blue-500 hover:bg-blue-600 active:bg-blue-700 text-white font-small transition-colors duration-200 dark:bg-blue-600 dark:hover:bg-blue-700 dark:active:bg-blue-800"
>
{blok.label || 'Randomize Emoji'}
</button>
</div>
);
};

export default EmojiRandomizer;
19 changes: 0 additions & 19 deletions packages/react/playground/react/components/iframe-embed.tsx

This file was deleted.

1 change: 1 addition & 0 deletions packages/react/playground/react/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>React App</title>
<link rel="stylesheet" href="./styles.css" />
</head>

<body>
Expand Down
Loading