Skip to content

Commit 0032a49

Browse files
committed
refactor(pro): use react-runner for block previews
Replace direct component imports with react-runner's useRunner hook. Blocks are now rendered from their raw source strings, making the source code the single source of truth for both preview and code view. This removes the duplicate dynamic import per block.
1 parent 366617a commit 0032a49

4 files changed

Lines changed: 225 additions & 4 deletions

File tree

apps/pro/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
"prism-react-renderer": "^2.3.0",
1717
"react": "^18.2.0",
1818
"react-dom": "^18.2.0",
19+
"react-runner": "^1.0.5",
1920
"sass": "^1.49.9"
2021
},
2122
"devDependencies": {

apps/pro/src/components/block-preview/index.tsx

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,39 @@
11
'use client';
22

3-
import { useState, useEffect, useCallback } from 'react';
3+
import React, { useState, useEffect, useCallback } from 'react';
4+
import { useRunner } from 'react-runner';
5+
import * as TinyDesign from '@tiny-design/react';
6+
import * as TinyIcons from '@tiny-design/icons';
47
import { Toolbar, type ViewportSize } from './toolbar';
58
import { PreviewFrame } from './preview-frame';
69
import { CodePanel } from './code-panel';
710
import type { BlockMeta } from '@/lib/blocks';
811
import styles from './block-preview.module.scss';
912

13+
const scope = {
14+
import: {
15+
react: React,
16+
'@tiny-design/react': TinyDesign,
17+
'@tiny-design/icons': TinyIcons,
18+
},
19+
};
20+
1021
interface BlockPreviewProps {
1122
meta: BlockMeta;
1223
}
1324

1425
export function BlockPreview({ meta }: BlockPreviewProps) {
1526
const [viewport, setViewport] = useState<ViewportSize>('desktop');
1627
const [showCode, setShowCode] = useState(false);
17-
const [BlockComponent, setBlockComponent] = useState<React.ComponentType | null>(null);
1828
const [sourceCode, setSourceCode] = useState('');
1929
const [copied, setCopied] = useState(false);
2030

2131
useEffect(() => {
22-
meta.component().then((m) => setBlockComponent(() => m.default));
2332
meta.rawSource().then((m) => setSourceCode(m.default));
2433
}, [meta]);
2534

35+
const { element, error } = useRunner({ code: sourceCode, scope });
36+
2637
const handleCopy = useCallback(async () => {
2738
await navigator.clipboard.writeText(sourceCode);
2839
setCopied(true);
@@ -43,7 +54,11 @@ export function BlockPreview({ meta }: BlockPreviewProps) {
4354
/>
4455
</div>
4556
<PreviewFrame viewport={viewport}>
46-
{BlockComponent ? <BlockComponent /> : null}
57+
{error ? (
58+
<pre style={{ color: 'red', padding: 16, fontSize: 13 }}>{error}</pre>
59+
) : (
60+
element
61+
)}
4762
</PreviewFrame>
4863
{showCode && <CodePanel source={sourceCode} />}
4964
</div>

apps/pro/src/lib/blocks.ts

Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
export interface BlockMeta {
2+
id: string;
3+
title: string;
4+
category: string;
5+
categoryLabel: string;
6+
rawSource: () => Promise<{ default: string }>;
7+
}
8+
9+
export interface CategoryMeta {
10+
slug: string;
11+
label: string;
12+
blocks: BlockMeta[];
13+
}
14+
15+
function block(
16+
category: string,
17+
categoryLabel: string,
18+
id: string,
19+
title: string,
20+
rawSource: () => Promise<{ default: string }>
21+
): BlockMeta {
22+
return { id: `${category}/${id}`, title, category, categoryLabel, rawSource };
23+
}
24+
25+
export const CATEGORIES: CategoryMeta[] = [
26+
{
27+
slug: 'authentication',
28+
label: 'Authentication',
29+
blocks: [
30+
block('authentication', 'Authentication', 'sign-in-simple', 'Simple Sign In',
31+
() => import('../blocks/authentication/sign-in-simple?raw')),
32+
block('authentication', 'Authentication', 'sign-up-simple', 'Simple Sign Up',
33+
() => import('../blocks/authentication/sign-up-simple?raw')),
34+
],
35+
},
36+
{
37+
slug: 'banners',
38+
label: 'Banners',
39+
blocks: [
40+
block('banners', 'Banners', 'promo-banner', 'Promo Banner',
41+
() => import('../blocks/banners/promo-banner?raw')),
42+
block('banners', 'Banners', 'alert-banner', 'Alert Banner',
43+
() => import('../blocks/banners/alert-banner?raw')),
44+
],
45+
},
46+
{
47+
slug: 'card-headers',
48+
label: 'Card Headers',
49+
blocks: [
50+
block('card-headers', 'Card Headers', 'card-header-with-actions', 'Card Header with Actions',
51+
() => import('../blocks/card-headers/card-header-with-actions?raw')),
52+
],
53+
},
54+
{
55+
slug: 'cards',
56+
label: 'Cards',
57+
blocks: [
58+
block('cards', 'Cards', 'stats-card', 'Stats Cards',
59+
() => import('../blocks/cards/stats-card?raw')),
60+
block('cards', 'Cards', 'profile-card', 'Profile Card',
61+
() => import('../blocks/cards/profile-card?raw')),
62+
],
63+
},
64+
{
65+
slug: 'dividers',
66+
label: 'Dividers',
67+
blocks: [
68+
block('dividers', 'Dividers', 'divider-variants', 'Divider Variants',
69+
() => import('../blocks/dividers/divider-variants?raw')),
70+
],
71+
},
72+
{
73+
slug: 'form-elements',
74+
label: 'Form Elements',
75+
blocks: [
76+
block('form-elements', 'Form Elements', 'input-variants', 'Input Variants',
77+
() => import('../blocks/form-elements/input-variants?raw')),
78+
],
79+
},
80+
{
81+
slug: 'form-layouts',
82+
label: 'Form Layouts',
83+
blocks: [
84+
block('form-layouts', 'Form Layouts', 'contact-form', 'Contact Form',
85+
() => import('../blocks/form-layouts/contact-form?raw')),
86+
],
87+
},
88+
{
89+
slug: 'lists',
90+
label: 'Lists',
91+
blocks: [
92+
block('lists', 'Lists', 'user-list', 'User List',
93+
() => import('../blocks/lists/user-list?raw')),
94+
],
95+
},
96+
{
97+
slug: 'navbars',
98+
label: 'Navbars',
99+
blocks: [
100+
block('navbars', 'Navbars', 'navbar-simple', 'Simple Navbar',
101+
() => import('../blocks/navbars/navbar-simple?raw')),
102+
block('navbars', 'Navbars', 'navbar-with-search', 'Navbar with Search',
103+
() => import('../blocks/navbars/navbar-with-search?raw')),
104+
],
105+
},
106+
{
107+
slug: 'notifications',
108+
label: 'Notifications',
109+
blocks: [
110+
block('notifications', 'Notifications', 'notification-list', 'Notification List',
111+
() => import('../blocks/notifications/notification-list?raw')),
112+
],
113+
},
114+
{
115+
slug: 'page-headers',
116+
label: 'Page Headers',
117+
blocks: [
118+
block('page-headers', 'Page Headers', 'page-header-with-breadcrumb', 'Page Header with Breadcrumb',
119+
() => import('../blocks/page-headers/page-header-with-breadcrumb?raw')),
120+
],
121+
},
122+
{
123+
slug: 'page-shells',
124+
label: 'Page Shells',
125+
blocks: [
126+
block('page-shells', 'Page Shells', 'dashboard-shell', 'Dashboard Shell',
127+
() => import('../blocks/page-shells/dashboard-shell?raw')),
128+
],
129+
},
130+
{
131+
slug: 'pagination',
132+
label: 'Pagination',
133+
blocks: [
134+
block('pagination', 'Pagination', 'pagination-variants', 'Pagination Variants',
135+
() => import('../blocks/pagination/pagination-variants?raw')),
136+
],
137+
},
138+
{
139+
slug: 'progress-steps',
140+
label: 'Progress Steps',
141+
blocks: [
142+
block('progress-steps', 'Progress Steps', 'steps-basic', 'Basic Steps',
143+
() => import('../blocks/progress-steps/steps-basic?raw')),
144+
],
145+
},
146+
{
147+
slug: 'section-headers',
148+
label: 'Section Headers',
149+
blocks: [
150+
block('section-headers', 'Section Headers', 'section-header-simple', 'Simple Section Header',
151+
() => import('../blocks/section-headers/section-header-simple?raw')),
152+
],
153+
},
154+
{
155+
slug: 'sidebars',
156+
label: 'Sidebars',
157+
blocks: [
158+
block('sidebars', 'Sidebars', 'sidebar-with-groups', 'Sidebar with Groups',
159+
() => import('../blocks/sidebars/sidebar-with-groups?raw')),
160+
],
161+
},
162+
{
163+
slug: 'stats',
164+
label: 'Stats',
165+
blocks: [
166+
block('stats', 'Stats', 'stat-with-icon', 'Stats with Progress',
167+
() => import('../blocks/stats/stat-with-icon?raw')),
168+
],
169+
},
170+
{
171+
slug: 'tables',
172+
label: 'Tables',
173+
blocks: [
174+
block('tables', 'Tables', 'data-table', 'Data Table',
175+
() => import('../blocks/tables/data-table?raw')),
176+
],
177+
},
178+
{
179+
slug: 'tabs',
180+
label: 'Tabs',
181+
blocks: [
182+
block('tabs', 'Tabs', 'tabs-with-content', 'Tabs with Content',
183+
() => import('../blocks/tabs/tabs-with-content?raw')),
184+
],
185+
},
186+
{
187+
slug: 'user-cards',
188+
label: 'User Cards',
189+
blocks: [
190+
block('user-cards', 'User Cards', 'user-card-simple', 'Simple User Cards',
191+
() => import('../blocks/user-cards/user-card-simple?raw')),
192+
],
193+
},
194+
];
195+
196+
export function getCategories(): CategoryMeta[] {
197+
return CATEGORIES;
198+
}
199+
200+
export function getCategory(slug: string): CategoryMeta | undefined {
201+
return CATEGORIES.find((c) => c.slug === slug);
202+
}

pnpm-lock.yaml

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)