Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
113 changes: 24 additions & 89 deletions packages/react/playground/next15/src/app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,111 +1,46 @@
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}
<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>
<StoryblokStory story={data.story} />
<StoryblokRichText doc={doc} resolvers={resolvers} />

{ // 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,
},
});
9 changes: 5 additions & 4 deletions packages/react/playground/react/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,23 @@ 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>
<Link to="/react" >Home</Link>
</li>
<li>
<Link to="/react/test-richtext">Richtext</Link>
</li>
</ul>
</nav>

<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
Loading