Skip to content

Commit 9e36d33

Browse files
authored
chore: improve-react-playgrounds (#119)
- Added Tailwind CSS and related plugins for typography and Vite support. - Updated navigation and layout styles using Tailwind utility classes. - Introduced a new EmojiRandomizer component for interactive emoji display. - Removed the deprecated IFrameEmbed component. - Updated index.html to include the new styles.css for Tailwind integration.
1 parent d82033f commit 9e36d33

File tree

17 files changed

+411
-131
lines changed

17 files changed

+411
-131
lines changed

โ€Žpackages/react/playground/next15/.gitignoreโ€Ž

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,3 +38,5 @@ yarn-error.log*
3838
# typescript
3939
*.tsbuildinfo
4040
next-env.d.ts
41+
42+
certificates

โ€Žpackages/react/playground/next15/package.jsonโ€Ž

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,25 @@
33
"version": "0.1.0",
44
"private": true,
55
"scripts": {
6-
"dev": "next dev",
6+
"dev": "next dev --experimental-https",
77
"build": "next build",
88
"start": "next start",
99
"lint": "next lint"
1010
},
1111
"dependencies": {
1212
"@storyblok/react": "workspace:*",
13+
"@tailwindcss/typography": "^0.5.16",
1314
"next": "15.3.2",
1415
"react": "19.1.0",
1516
"react-dom": "19.1.0"
1617
},
1718
"devDependencies": {
19+
"@tailwindcss/postcss": "^4.1.10",
1820
"@types/node": "^20",
1921
"@types/react": "^19",
20-
"@types/react-dom": "^19"
22+
"@types/react-dom": "^19",
23+
"postcss": "^8.5.6",
24+
"tailwindcss": "^4.1.10"
2125
},
2226
"nx": {
2327
"projectType": "application"
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
/** @type {import('tailwindcss').Config} */
2+
export default {
3+
plugins: {
4+
'@tailwindcss/postcss': {},
5+
},
6+
};
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
'use client';
2+
3+
import React, { type FC, useState } from 'react';
4+
import type { SbBlokData } from '@storyblok/react';
5+
6+
interface EmojiRandomizerProps {
7+
blok: SbBlokData & {
8+
label?: string;
9+
};
10+
}
11+
12+
/**
13+
* A component that displays a label and a random emoji that changes on click
14+
*/
15+
const EmojiRandomizer: FC<EmojiRandomizerProps> = ({ blok }) => {
16+
// List of fun emojis to randomly choose from
17+
const emojis = ['๐Ÿ˜Š', '๐ŸŽ‰', '๐Ÿš€', 'โœจ', '๐ŸŒˆ', '๐ŸŽจ', '๐ŸŽธ', '๐ŸŽฎ', '๐Ÿ•', '๐ŸŒบ'];
18+
19+
// State to track current emoji
20+
const [currentEmoji, setCurrentEmoji] = useState(() =>
21+
emojis[Math.floor(Math.random() * emojis.length)],
22+
);
23+
24+
/**
25+
* Generates a new random emoji different from the current one
26+
*/
27+
const randomizeEmoji = () => {
28+
let newEmoji;
29+
do {
30+
newEmoji = emojis[Math.floor(Math.random() * emojis.length)];
31+
} while (newEmoji === currentEmoji);
32+
33+
setCurrentEmoji(newEmoji);
34+
};
35+
36+
return (
37+
<div className="flex flex-col items-center gap-6 p-4 bg-gray-100 dark:bg-gray-900 rounded-lg">
38+
<div className="text-6xl">
39+
{currentEmoji}
40+
</div>
41+
<button
42+
onClick={randomizeEmoji}
43+
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"
44+
>
45+
{blok.label || 'Randomize Emoji'}
46+
</button>
47+
</div>
48+
);
49+
};
50+
51+
export default EmojiRandomizer;

โ€Žpackages/react/playground/next15/src/app/globals.cssโ€Ž

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
@import 'tailwindcss';
2+
@plugin '@tailwindcss/typography';
3+
4+
span[data-type='emoji'] img {
5+
@apply m-0;
6+
}
7+
18
:root {
29
--background: #ffffff;
310
--foreground: #171717;
@@ -24,10 +31,13 @@ body {
2431
-moz-osx-font-smoothing: grayscale;
2532
}
2633

27-
* {
28-
box-sizing: border-box;
29-
padding: 0;
30-
margin: 0;
34+
@layer base {
35+
* {
36+
margin: 0;
37+
padding: 0;
38+
box-sizing: border-box;
39+
scroll-behavior: smooth;
40+
}
3141
}
3242

3343
a {

โ€Žpackages/react/playground/next15/src/app/layout.tsxโ€Ž

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import StoryblokProvider from '@/components/StoryblokProvider';
2+
import './globals.css';
23

34
export const metadata = {
45
title: 'Create Next App',
Lines changed: 26 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -1,111 +1,45 @@
11
import type {
22
ISbStoriesParams,
33
StoryblokClient,
4-
StoryblokRichTextNode,
54
} from '@storyblok/react/rsc';
6-
import { MarkTypes, StoryblokRichText, StoryblokStory,
5+
import { StoryblokStory,
76
} from '@storyblok/react/rsc';
87
import { getStoryblokApi } from '@/lib/storyblok';
9-
import Link from 'next/link';
10-
import type { ReactElement } from 'react';
8+
// import Link from 'next/link';
119

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

15-
const doc = {
16-
type: 'doc',
17-
content: [
18-
{
19-
type: 'paragraph',
20-
content: [
21-
{
22-
type: 'text',
23-
text: 'This is a test of the StoryblokRichText component.',
24-
},
25-
],
26-
},
27-
{
28-
type: 'paragraph',
29-
content: [
30-
{
31-
text: 'Internal Link',
32-
type: 'text',
33-
marks: [
34-
{
35-
type: 'link',
36-
attrs: {
37-
href: '/',
38-
uuid: '8489bed8-d86f-4fde-965c-e3d748e12147',
39-
anchor: null,
40-
target: '_self',
41-
linktype: 'story',
42-
},
43-
},
44-
],
45-
},
46-
],
47-
},
48-
{
49-
type: 'paragraph',
50-
content: [
51-
{
52-
text: 'External link',
53-
type: 'text',
54-
marks: [
55-
{
56-
type: 'link',
57-
attrs: {
58-
href: 'https://alvarosaburido.dev',
59-
uuid: null,
60-
anchor: null,
61-
target: '_blank',
62-
linktype: 'url',
63-
},
64-
},
65-
],
66-
},
67-
],
68-
},
69-
],
70-
};
71-
const resolvers = {
72-
// custom resolvers
73-
[MarkTypes.LINK]: (node: StoryblokRichTextNode<ReactElement>) => {
74-
return node.attrs?.linktype === 'story'
75-
? (
76-
<Link
77-
href={node.attrs?.href}
78-
target={node.attrs?.target}
79-
>
80-
{node.text}
81-
</Link>
82-
)
83-
: (
84-
<a
85-
href={node.attrs?.href}
86-
target={node.attrs?.target}
87-
>
88-
{node.text}
89-
</a>
90-
);
91-
},
92-
};
93-
9413
return (
95-
<div>
96-
<h1>
97-
Story:
98-
{data.story.id}
99-
</h1>
100-
<StoryblokStory story={data.story} />
101-
<StoryblokRichText doc={doc} resolvers={resolvers} />
102-
</div>
14+
<main className="container mx-auto px-4 py-8">
15+
<div className="max-w-4xl mx-auto clas prose">
16+
<h1 className="text-4xl font-bold mb-8 dark:text-white">
17+
Storyblok Next.js 15 Example
18+
</h1>
19+
20+
{ // TODO: Enable for https://github.com/storyblok/monoblok/issues/35
21+
/* <nav className="space-y-4">
22+
<Link
23+
href="/richtext"
24+
className="block p-4 bg-blue-500 text-white rounded-lg hover:bg-blue-600 transition-colors"
25+
>
26+
Go to Rich Text Example
27+
</Link>
28+
</nav> */}
29+
30+
{data.story && (
31+
<div>
32+
<StoryblokStory story={data.story} />
33+
</div>
34+
)}
35+
</div>
36+
</main>
10337
);
10438
}
10539

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

10943
const storyblokApi: StoryblokClient = getStoryblokApi();
110-
return storyblokApi.get(`cdn/stories/home`, sbParams);
44+
return storyblokApi.get(`cdn/stories/react`, sbParams);
11145
}

โ€Žpackages/react/playground/next15/src/lib/storyblok.tsโ€Ž

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1+
import EmojiRandomizer from '@/app/components/EmojiRandomizer';
12
import Grid from '@/components/Grid';
2-
import IFrameEmbed from '@/components/IFrameEmbed';
33
import Page from '@/components/Page';
44
import Teaser from '@/components/Teaser';
55
import { apiPlugin, storyblokInit } from '@storyblok/react/rsc';
@@ -11,6 +11,6 @@ export const getStoryblokApi = storyblokInit({
1111
'teaser': Teaser,
1212
'page': Page,
1313
'grid': Grid,
14-
'iframe-embed': IFrameEmbed,
14+
'emoji-randomizer': EmojiRandomizer,
1515
},
1616
});

โ€Žpackages/react/playground/react/App.tsxโ€Ž

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ function App() {
77
return (
88
<BrowserRouter>
99
<div>
10-
<nav>
11-
<ul>
10+
<nav className=" py-8 container mx-auto mb-8">
11+
<ul className="flex gap-4">
1212
<li>
1313
<Link to="/react">Home</Link>
1414
</li>
@@ -17,12 +17,13 @@ function App() {
1717
</li>
1818
</ul>
1919
</nav>
20-
21-
<Routes>
22-
<Route path="/" element={<Home />} />
23-
<Route path="react" element={<Home />} />
24-
<Route path="react/test-richtext" element={<RichtextPage />} />
25-
</Routes>
20+
<div className="prose mx-auto">
21+
<Routes>
22+
<Route path="/" element={<Home />} />
23+
<Route path="react" element={<Home />} />
24+
<Route path="react/test-richtext" element={<RichtextPage />} />
25+
</Routes>
26+
</div>
2627
</div>
2728
</BrowserRouter>
2829
);
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import React, { type FC, useState } from 'react';
2+
import type { SbBlokData } from '@storyblok/react';
3+
4+
interface EmojiRandomizerProps {
5+
blok: SbBlokData & {
6+
label?: string;
7+
};
8+
}
9+
10+
/**
11+
* A component that displays a label and a random emoji that changes on click
12+
*/
13+
const EmojiRandomizer: FC<EmojiRandomizerProps> = ({ blok }) => {
14+
// List of fun emojis to randomly choose from
15+
const emojis = ['๐Ÿ˜Š', '๐ŸŽ‰', '๐Ÿš€', 'โœจ', '๐ŸŒˆ', '๐ŸŽจ', '๐ŸŽธ', '๐ŸŽฎ', '๐Ÿ•', '๐ŸŒบ'];
16+
17+
// State to track current emoji
18+
const [currentEmoji, setCurrentEmoji] = useState(() =>
19+
emojis[Math.floor(Math.random() * emojis.length)],
20+
);
21+
22+
/**
23+
* Generates a new random emoji different from the current one
24+
*/
25+
const randomizeEmoji = () => {
26+
let newEmoji;
27+
do {
28+
newEmoji = emojis[Math.floor(Math.random() * emojis.length)];
29+
} while (newEmoji === currentEmoji);
30+
31+
setCurrentEmoji(newEmoji);
32+
};
33+
34+
return (
35+
<div className="flex flex-col items-center gap-6 p-4 bg-gray-100 dark:bg-gray-900 rounded-lg">
36+
<div className="text-6xl">
37+
{currentEmoji}
38+
</div>
39+
<button
40+
onClick={randomizeEmoji}
41+
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"
42+
>
43+
{blok.label || 'Randomize Emoji'}
44+
</button>
45+
</div>
46+
);
47+
};
48+
49+
export default EmojiRandomizer;

0 commit comments

Comments
ย (0)