diff --git a/.changeset/card-variant-and-type-fixes.md b/.changeset/card-variant-and-type-fixes.md
new file mode 100644
index 00000000..840f3d7a
--- /dev/null
+++ b/.changeset/card-variant-and-type-fixes.md
@@ -0,0 +1,5 @@
+---
+"@tiny-design/react": minor
+---
+
+Add `variant` prop to Card component (`outlined`, `elevated`, `filled`). Fix NativeSelect children type to accept arrays. Make Table ColumnType `dataIndex` optional for action columns.
diff --git a/.github/workflows/deploy-site.yml b/.github/workflows/deploy-site.yml
index b2bdbf8f..9385ada7 100644
--- a/.github/workflows/deploy-site.yml
+++ b/.github/workflows/deploy-site.yml
@@ -43,6 +43,14 @@ jobs:
- name: Copy 404.html for SPA routing
run: cp apps/docs/build/index.html apps/docs/build/404.html
+ - name: Build pro site
+ run: pnpm --filter @tiny-design/pro build
+ env:
+ NEXT_PUBLIC_BASE_PATH: /tiny-design/pro
+
+ - name: Merge pro into docs output
+ run: cp -r apps/pro/out/ apps/docs/build/pro/
+
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
diff --git a/apps/docs/src/containers/theme-editor/components/preview-panel.tsx b/apps/docs/src/containers/theme-editor/components/preview-panel.tsx
index d9fb706a..26589083 100644
--- a/apps/docs/src/containers/theme-editor/components/preview-panel.tsx
+++ b/apps/docs/src/containers/theme-editor/components/preview-panel.tsx
@@ -126,7 +126,10 @@ export const PreviewPanel = (): React.ReactElement => {
- Card content with some text to show how typography looks in a card component.
+
+ Card content with some text to show how typography looks in a card component.
+ Card content with some text to show how typography looks in a card component.
+
Default
diff --git a/apps/docs/src/locale/en_US.ts b/apps/docs/src/locale/en_US.ts
index a567da16..f25bbeb1 100644
--- a/apps/docs/src/locale/en_US.ts
+++ b/apps/docs/src/locale/en_US.ts
@@ -7,6 +7,7 @@ const en_US: SiteLocale = {
guide: 'Guide',
theme: 'Theme',
components: 'Components',
+ pro: 'Pro',
},
home: {
subtitle: 'A Friendly UI Component Set for React',
diff --git a/apps/docs/src/locale/types.ts b/apps/docs/src/locale/types.ts
index 7a85c0d1..cfdbd6fe 100644
--- a/apps/docs/src/locale/types.ts
+++ b/apps/docs/src/locale/types.ts
@@ -5,6 +5,7 @@ export type SiteLocale = {
guide: string;
theme: string;
components: string;
+ pro: string;
};
home: {
subtitle: string;
diff --git a/apps/docs/src/locale/zh_CN.ts b/apps/docs/src/locale/zh_CN.ts
index 2ffe677d..b953b877 100644
--- a/apps/docs/src/locale/zh_CN.ts
+++ b/apps/docs/src/locale/zh_CN.ts
@@ -7,6 +7,7 @@ const zh_CN: SiteLocale = {
guide: '指南',
theme: '主题',
components: '组件',
+ pro: 'Pro',
},
home: {
subtitle: '一套友好的 React UI 组件库',
diff --git a/apps/pro/.gitignore b/apps/pro/.gitignore
new file mode 100644
index 00000000..50fc4bdb
--- /dev/null
+++ b/apps/pro/.gitignore
@@ -0,0 +1,2 @@
+.next/
+out/
\ No newline at end of file
diff --git a/apps/pro/next-env.d.ts b/apps/pro/next-env.d.ts
new file mode 100644
index 00000000..830fb594
--- /dev/null
+++ b/apps/pro/next-env.d.ts
@@ -0,0 +1,6 @@
+///
+///
+///
+
+// NOTE: This file should not be edited
+// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
diff --git a/apps/pro/next.config.ts b/apps/pro/next.config.ts
new file mode 100644
index 00000000..f8129d02
--- /dev/null
+++ b/apps/pro/next.config.ts
@@ -0,0 +1,63 @@
+import type { NextConfig } from 'next';
+import path from 'path';
+import { fileURLToPath } from 'url';
+
+const __dirname = path.dirname(fileURLToPath(import.meta.url));
+const basePath = process.env.NEXT_PUBLIC_BASE_PATH || '';
+
+const nextConfig: NextConfig = {
+ output: 'export',
+ basePath,
+ trailingSlash: true,
+ images: { unoptimized: true },
+ sassOptions: {
+ includePaths: [
+ path.resolve(__dirname, 'node_modules'),
+ path.resolve(__dirname, '../../node_modules'),
+ path.resolve(__dirname, '../../packages/react/src'),
+ ],
+ },
+ webpack(config) {
+ // Source alias: resolve to package source (same pattern as docs app)
+ config.resolve.alias['@tiny-design/react'] = path.resolve(
+ __dirname,
+ '../../packages/react/src'
+ );
+ config.resolve.alias['@tiny-design/icons'] = path.resolve(
+ __dirname,
+ '../../packages/icons/src'
+ );
+
+ // ?raw imports: embed original file contents as strings at build time.
+ // We must exclude ?raw from existing oneOf rules so Next.js/SWC doesn't
+ // compile the TSX before we read it as plain text.
+ const rawQuery = /raw/;
+
+ for (const rule of config.module.rules) {
+ if (rule && typeof rule === 'object' && rule.oneOf) {
+ for (const oneOfRule of rule.oneOf) {
+ if (oneOfRule && typeof oneOfRule === 'object') {
+ // Add resourceQuery exclusion to each sub-rule
+ if (!oneOfRule.resourceQuery) {
+ oneOfRule.resourceQuery = { not: [rawQuery] };
+ } else if (oneOfRule.resourceQuery instanceof RegExp) {
+ oneOfRule.resourceQuery = {
+ and: [oneOfRule.resourceQuery],
+ not: [rawQuery],
+ };
+ }
+ }
+ }
+ }
+ }
+
+ config.module.rules.push({
+ resourceQuery: rawQuery,
+ type: 'asset/source',
+ });
+
+ return config;
+ },
+};
+
+export default nextConfig;
diff --git a/apps/pro/package.json b/apps/pro/package.json
new file mode 100644
index 00000000..71512579
--- /dev/null
+++ b/apps/pro/package.json
@@ -0,0 +1,28 @@
+{
+ "name": "@tiny-design/pro",
+ "version": "0.0.0",
+ "private": true,
+ "scripts": {
+ "dev": "next dev --port 3001",
+ "build": "next build",
+ "start": "next start"
+ },
+ "dependencies": {
+ "@tiny-design/icons": "workspace:*",
+ "@tiny-design/react": "workspace:*",
+ "@tiny-design/tokens": "workspace:*",
+ "classnames": "^2.5.0",
+ "next": "^15.3.4",
+ "prism-react-renderer": "^2.3.0",
+ "react": "^18.2.0",
+ "react-dom": "^18.2.0",
+ "react-runner": "^1.0.5",
+ "sass": "^1.49.9"
+ },
+ "devDependencies": {
+ "@types/node": "^22.0.0",
+ "@types/react": "^18.2.0",
+ "@types/react-dom": "^18.2.0",
+ "typescript": "^5.4.0"
+ }
+}
diff --git a/apps/pro/postcss.config.js b/apps/pro/postcss.config.js
new file mode 100644
index 00000000..9361eff3
--- /dev/null
+++ b/apps/pro/postcss.config.js
@@ -0,0 +1,3 @@
+module.exports = {
+ plugins: {},
+};
diff --git a/apps/pro/src/app/blocks/[category]/category-page-client.tsx b/apps/pro/src/app/blocks/[category]/category-page-client.tsx
new file mode 100644
index 00000000..9c4dfbcf
--- /dev/null
+++ b/apps/pro/src/app/blocks/[category]/category-page-client.tsx
@@ -0,0 +1,33 @@
+'use client';
+
+import { CategoryNav } from '@/components/layout/category-nav';
+import { BlockPreview } from '@/components/block-preview';
+import { getCategory } from '../../../utils/blocks';
+import styles from './category-page.module.scss';
+
+interface CategoryPageClientProps {
+ slug: string;
+ label: string;
+}
+
+export function CategoryPageClient({ slug, label }: CategoryPageClientProps) {
+ const category = getCategory(slug);
+ const blocks = category?.blocks ?? [];
+
+ return (
+
+
+
+
+
{label}
+
+ {blocks.length} {blocks.length === 1 ? 'block' : 'blocks'}
+
+
+ {blocks.map((block) => (
+
+ ))}
+
+
+ );
+}
diff --git a/apps/pro/src/app/blocks/[category]/category-page.module.scss b/apps/pro/src/app/blocks/[category]/category-page.module.scss
new file mode 100644
index 00000000..4b0c06be
--- /dev/null
+++ b/apps/pro/src/app/blocks/[category]/category-page.module.scss
@@ -0,0 +1,33 @@
+.layout {
+ display: flex;
+ padding-top: 60px;
+ min-height: 100vh;
+}
+
+.content {
+ flex: 1;
+ margin-left: 240px;
+ padding: 40px 48px 80px;
+ max-width: calc(100% - 240px);
+}
+
+.pageHeader {
+ margin-bottom: 36px;
+ padding-bottom: 24px;
+ border-bottom: 1px solid var(--ty-color-border-secondary);
+}
+
+.title {
+ font-size: 26px;
+ font-weight: 700;
+ letter-spacing: -0.025em;
+ margin: 0 0 6px;
+ color: var(--ty-color-text);
+}
+
+.count {
+ font-size: 13px;
+ color: var(--ty-color-text-tertiary);
+ margin: 0;
+ font-weight: 500;
+}
diff --git a/apps/pro/src/app/blocks/[category]/page.tsx b/apps/pro/src/app/blocks/[category]/page.tsx
new file mode 100644
index 00000000..a3f19450
--- /dev/null
+++ b/apps/pro/src/app/blocks/[category]/page.tsx
@@ -0,0 +1,18 @@
+import { notFound } from 'next/navigation';
+import { getCategorySlugs, getCategoryInfo } from '../../../utils/blocks';
+import { CategoryPageClient } from './category-page-client';
+
+export function generateStaticParams() {
+ return getCategorySlugs().map((slug) => ({ category: slug }));
+}
+
+interface PageProps {
+ params: Promise<{ category: string }>;
+}
+
+export default async function CategoryPage({ params }: PageProps) {
+ const { category: slug } = await params;
+ const info = getCategoryInfo(slug);
+ if (!info) notFound();
+ return ;
+}
diff --git a/apps/pro/src/app/globals.scss b/apps/pro/src/app/globals.scss
new file mode 100644
index 00000000..3c62bdd0
--- /dev/null
+++ b/apps/pro/src/app/globals.scss
@@ -0,0 +1,22 @@
+@use '@tiny-design/tokens/scss/base' as *;
+@use 'style/component' as *;
+
+*,
+*::before,
+*::after {
+ box-sizing: border-box;
+}
+
+body {
+ margin: 0;
+ padding: 0;
+ font-family: var(--font-body), -apple-system, blinkmacsystemfont, 'Segoe UI', roboto,
+ 'Helvetica Neue', arial, 'Noto Sans', sans-serif;
+ background: var(--ty-color-bg);
+ color: var(--ty-color-text);
+ transition:
+ background-color 0.2s,
+ color 0.2s;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+}
diff --git a/apps/pro/src/app/layout.tsx b/apps/pro/src/app/layout.tsx
new file mode 100644
index 00000000..fe2f40b1
--- /dev/null
+++ b/apps/pro/src/app/layout.tsx
@@ -0,0 +1,36 @@
+import type { Metadata } from 'next';
+import { Bricolage_Grotesque, DM_Sans } from 'next/font/google';
+import { ThemeScript } from '../components/theme-script';
+import { SiteHeader } from '../components/layout/site-header';
+import './globals.scss';
+
+const heading = Bricolage_Grotesque({
+ subsets: ['latin'],
+ variable: '--font-heading',
+ display: 'swap',
+ weight: ['400', '500', '600', '700', '800'],
+});
+
+const body = DM_Sans({
+ subsets: ['latin'],
+ variable: '--font-body',
+ display: 'swap',
+ weight: ['400', '500', '600', '700'],
+});
+
+export const metadata: Metadata = {
+ title: 'Tiny Design Pro',
+ description: 'Beautiful, ready-to-use UI blocks built with Tiny Design components.',
+};
+
+export default function RootLayout({ children }: { children: React.ReactNode }) {
+ return (
+
+
+
+
+ {children}
+
+
+ );
+}
diff --git a/apps/pro/src/app/page.module.scss b/apps/pro/src/app/page.module.scss
new file mode 100644
index 00000000..4c9d1bf7
--- /dev/null
+++ b/apps/pro/src/app/page.module.scss
@@ -0,0 +1,439 @@
+/* ─── Animations ─── */
+@keyframes fadeInUp {
+ from {
+ opacity: 0;
+ transform: translateY(24px);
+ }
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
+}
+
+@keyframes pulseGlow {
+ 0%,
+ 100% {
+ opacity: 0.5;
+ }
+ 50% {
+ opacity: 0.8;
+ }
+}
+
+@keyframes dotPulse {
+ 0%,
+ 100% {
+ opacity: 1;
+ }
+ 50% {
+ opacity: 0.4;
+ }
+}
+
+/* ─── Layout ─── */
+.main {
+ padding-top: 60px;
+}
+
+/* ─── Hero ─── */
+.hero {
+ position: relative;
+ overflow: hidden;
+ padding: 100px 24px 80px;
+ display: flex;
+ justify-content: center;
+ background: var(--ty-color-bg);
+}
+
+.heroGlow {
+ position: absolute;
+ top: -200px;
+ left: 50%;
+ transform: translateX(-50%);
+ width: 800px;
+ height: 600px;
+ background: radial-gradient(
+ ellipse at center,
+ color-mix(in srgb, var(--ty-color-primary) 12%, transparent) 0%,
+ transparent 70%
+ );
+ pointer-events: none;
+ animation: pulseGlow 6s ease-in-out infinite;
+}
+
+.heroDots {
+ position: absolute;
+ inset: 0;
+ background-image: radial-gradient(color-mix(in srgb, var(--ty-color-text) 8%, transparent) 1px, transparent 1px);
+ background-size: 24px 24px;
+ mask-image: radial-gradient(ellipse at 50% 30%, black 20%, transparent 70%);
+ -webkit-mask-image: radial-gradient(ellipse at 50% 30%, black 20%, transparent 70%);
+ pointer-events: none;
+}
+
+.heroContent {
+ position: relative;
+ z-index: 1;
+ max-width: 720px;
+ text-align: center;
+ animation: fadeInUp 0.7s ease both;
+}
+
+.badge {
+ display: inline-flex;
+ align-items: center;
+ gap: 8px;
+ padding: 6px 16px;
+ border-radius: 100px;
+ font-size: 13px;
+ font-weight: 500;
+ letter-spacing: 0.01em;
+ color: var(--ty-color-text-secondary);
+ background: color-mix(in srgb, var(--ty-color-primary) 8%, transparent);
+ border: 1px solid color-mix(in srgb, var(--ty-color-primary) 15%, transparent);
+ margin-bottom: 28px;
+}
+
+.badgeDot {
+ width: 7px;
+ height: 7px;
+ border-radius: 50%;
+ background: var(--ty-color-primary);
+ animation: dotPulse 2s ease-in-out infinite;
+}
+
+.title {
+ font-family: var(--font-heading), system-ui, sans-serif;
+ font-size: clamp(42px, 6.5vw, 72px);
+ font-weight: 800;
+ line-height: 1.05;
+ letter-spacing: -0.04em;
+ margin: 0 0 20px;
+ color: var(--ty-color-text);
+}
+
+.titleAccent {
+ background: linear-gradient(
+ 135deg,
+ var(--ty-color-primary) 0%,
+ color-mix(in srgb, var(--ty-color-primary) 70%, #f59e0b) 50%,
+ #f59e0b 100%
+ );
+ -webkit-background-clip: text;
+ -webkit-text-fill-color: transparent;
+ background-clip: text;
+}
+
+.subtitle {
+ font-size: 18px;
+ line-height: 1.7;
+ color: var(--ty-color-text-secondary);
+ margin: 0 auto 36px;
+ max-width: 520px;
+}
+
+.heroActions {
+ display: flex;
+ gap: 12px;
+ justify-content: center;
+ flex-wrap: wrap;
+}
+
+/* ─── Buttons ─── */
+.btnPrimary {
+ display: inline-flex;
+ align-items: center;
+ gap: 8px;
+ padding: 12px 28px;
+ border-radius: 10px;
+ font-size: 15px;
+ font-weight: 600;
+ text-decoration: none;
+ color: #fff;
+ background: var(--ty-color-primary);
+ box-shadow: 0 2px 8px color-mix(in srgb, var(--ty-color-primary) 30%, transparent);
+ transition: transform 0.2s, box-shadow 0.2s, filter 0.2s;
+
+ &:hover {
+ transform: translateY(-1px);
+ box-shadow: 0 4px 16px color-mix(in srgb, var(--ty-color-primary) 40%, transparent);
+ filter: brightness(1.08);
+ }
+
+ &:active {
+ transform: translateY(0);
+ }
+}
+
+.btnArrow {
+ transition: transform 0.2s;
+
+ .btnPrimary:hover & {
+ transform: translateX(3px);
+ }
+}
+
+.btnSecondary {
+ display: inline-flex;
+ align-items: center;
+ gap: 8px;
+ padding: 12px 28px;
+ border-radius: 10px;
+ font-size: 15px;
+ font-weight: 600;
+ text-decoration: none;
+ color: var(--ty-color-text);
+ background: transparent;
+ border: 1px solid var(--ty-color-border);
+ transition: border-color 0.2s, background 0.2s, transform 0.2s;
+
+ &:hover {
+ background: var(--ty-color-fill-secondary);
+ border-color: var(--ty-color-border-secondary);
+ transform: translateY(-1px);
+ }
+}
+
+/* ─── Stats Ribbon ─── */
+.stats {
+ display: flex;
+ justify-content: center;
+ gap: 60px;
+ flex-wrap: wrap;
+ padding: 48px 24px;
+ border-top: 1px solid var(--ty-color-border-secondary);
+ border-bottom: 1px solid var(--ty-color-border-secondary);
+ background: color-mix(in srgb, var(--ty-color-primary) 3%, transparent);
+}
+
+.stat {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 4px;
+ animation: fadeInUp 0.5s ease both;
+}
+
+.statValue {
+ font-family: var(--font-heading), system-ui, sans-serif;
+ font-size: 36px;
+ font-weight: 700;
+ letter-spacing: -0.03em;
+ color: var(--ty-color-primary);
+}
+
+.statLabel {
+ font-size: 13px;
+ font-weight: 500;
+ text-transform: uppercase;
+ letter-spacing: 0.06em;
+ color: var(--ty-color-text-tertiary);
+}
+
+/* ─── Categories Section ─── */
+.categories {
+ max-width: 1080px;
+ margin: 0 auto;
+ padding: 80px 24px;
+}
+
+.sectionHeader {
+ text-align: center;
+ margin-bottom: 48px;
+}
+
+.sectionTitle {
+ font-family: var(--font-heading), system-ui, sans-serif;
+ font-size: 32px;
+ font-weight: 700;
+ letter-spacing: -0.03em;
+ margin: 0 0 12px;
+ color: var(--ty-color-text);
+}
+
+.sectionDesc {
+ font-size: 16px;
+ color: var(--ty-color-text-secondary);
+ margin: 0;
+ line-height: 1.6;
+}
+
+.grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
+ gap: 14px;
+}
+
+.card {
+ display: flex;
+ align-items: center;
+ gap: 16px;
+ padding: 18px 20px;
+ border-radius: 12px;
+ text-decoration: none;
+ color: var(--ty-color-text);
+ background: var(--ty-color-bg-container);
+ border: 1px solid var(--ty-color-border-secondary);
+ transition: border-color 0.2s, box-shadow 0.25s, transform 0.2s;
+ animation: fadeInUp 0.5s ease both;
+
+ &:hover {
+ border-color: var(--ty-color-primary);
+ transform: translateY(-3px);
+ box-shadow:
+ 0 4px 12px color-mix(in srgb, var(--ty-color-primary) 8%, transparent),
+ 0 12px 32px rgba(0, 0, 0, 0.06);
+
+ .cardArrow {
+ opacity: 1;
+ transform: translateX(0);
+ }
+ }
+}
+
+.cardIcon {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 44px;
+ height: 44px;
+ border-radius: 11px;
+ font-size: 20px;
+ flex-shrink: 0;
+ background: color-mix(in srgb, var(--ty-color-primary) 8%, transparent);
+}
+
+.cardTitle {
+ margin: 0 0 2px;
+ font-size: 15px;
+ font-weight: 600;
+ letter-spacing: -0.01em;
+}
+
+.cardCount {
+ margin: 0;
+ font-size: 13px;
+ color: var(--ty-color-text-tertiary);
+}
+
+.cardArrow {
+ margin-left: auto;
+ font-size: 16px;
+ color: var(--ty-color-primary);
+ opacity: 0;
+ transform: translateX(-6px);
+ transition: opacity 0.2s, transform 0.2s;
+}
+
+/* ─── CTA Section ─── */
+.cta {
+ position: relative;
+ text-align: center;
+ padding: 80px 24px;
+ border-top: 1px solid var(--ty-color-border-secondary);
+ background: color-mix(in srgb, var(--ty-color-primary) 3%, transparent);
+ overflow: hidden;
+}
+
+.ctaTitle {
+ font-family: var(--font-heading), system-ui, sans-serif;
+ font-size: clamp(26px, 4vw, 36px);
+ font-weight: 700;
+ letter-spacing: -0.03em;
+ margin: 0 0 12px;
+ color: var(--ty-color-text);
+}
+
+.ctaDesc {
+ font-size: 16px;
+ color: var(--ty-color-text-secondary);
+ line-height: 1.7;
+ margin: 0 auto 28px;
+ max-width: 480px;
+}
+
+.ctaActions {
+ display: flex;
+ justify-content: center;
+}
+
+/* ─── Footer ─── */
+.footer {
+ padding: 32px 24px;
+ text-align: center;
+ border-top: 1px solid var(--ty-color-border-secondary);
+
+ p {
+ margin: 0;
+ font-size: 13px;
+ color: var(--ty-color-text-tertiary);
+ }
+
+ a {
+ color: var(--ty-color-primary);
+ text-decoration: none;
+ font-weight: 500;
+
+ &:hover {
+ text-decoration: underline;
+ }
+ }
+}
+
+/* ─── Responsive ─── */
+@media (max-width: 768px) {
+ .hero {
+ padding: 70px 20px 60px;
+ }
+
+ .subtitle {
+ font-size: 16px;
+ }
+
+ .stats {
+ gap: 32px 48px;
+ padding: 36px 20px;
+ }
+
+ .statValue {
+ font-size: 28px;
+ }
+
+ .categories {
+ padding: 56px 20px;
+ }
+
+ .grid {
+ grid-template-columns: 1fr;
+ }
+
+ .cta {
+ padding: 56px 20px;
+ }
+}
+
+@media (max-width: 480px) {
+ .hero {
+ padding: 56px 16px 48px;
+ }
+
+ .heroActions {
+ flex-direction: column;
+ align-items: center;
+ }
+
+ .btnPrimary,
+ .btnSecondary {
+ width: 100%;
+ max-width: 280px;
+ justify-content: center;
+ }
+
+ .stats {
+ gap: 24px 36px;
+ }
+
+ .statValue {
+ font-size: 24px;
+ }
+}
diff --git a/apps/pro/src/app/page.tsx b/apps/pro/src/app/page.tsx
new file mode 100644
index 00000000..146474c5
--- /dev/null
+++ b/apps/pro/src/app/page.tsx
@@ -0,0 +1,135 @@
+import Link from 'next/link';
+import { getCategories } from '../utils/blocks';
+import styles from './page.module.scss';
+
+const CATEGORY_ICONS: Record = {
+ authentication: '🔐',
+ banners: '📢',
+ cards: '🃏',
+ 'form-layouts': '📝',
+ lists: '📋',
+ navbars: '🧭',
+ notifications: '🔔',
+ 'page-headers': '📄',
+ 'page-shells': '🏗',
+ 'progress-steps': '📊',
+ sidebars: '📑',
+ stats: '📈',
+ tables: '🗃',
+ 'user-cards': '👤',
+};
+
+export default function HomePage() {
+ const categories = getCategories();
+ const totalBlocks = categories.reduce((sum, cat) => sum + cat.blocks.length, 0);
+
+ return (
+
+ {/* Hero section */}
+
+
+
+
+
+
+ Open Source · Copy & Paste · Free
+
+
+ Production-Ready
+
+ UI Blocks
+
+
+ Beautifully crafted, ready-to-use interface blocks built entirely with
+ Tiny Design components. Browse, preview, and copy into your projects.
+
+
+
+
+
+ {/* Stats ribbon */}
+
+ {[
+ { value: String(totalBlocks), label: 'UI Blocks' },
+ { value: String(categories.length), label: 'Categories' },
+ { value: '80+', label: 'Components Used' },
+ { value: '100%', label: 'TypeScript' },
+ ].map((stat, i) => (
+
+ {stat.value}
+ {stat.label}
+
+ ))}
+
+
+ {/* Categories section */}
+
+
+
Explore by Category
+
+ Each block is a self-contained, composable UI pattern you can drop into any React project.
+
+
+
+ {categories.map((cat, i) => (
+
+
+ {CATEGORY_ICONS[cat.slug] || '📦'}
+
+
+
{cat.label}
+
+ {cat.blocks.length} {cat.blocks.length === 1 ? 'block' : 'blocks'}
+
+
+
→
+
+ ))}
+
+
+
+ {/* CTA section */}
+
+ Start Building Beautiful Interfaces
+
+ Every block uses only Tiny Design components — no extra dependencies.
+ Just copy, paste, and customize.
+
+
+
+ Get Started
+ →
+
+
+
+
+ {/* Footer */}
+
+
+ );
+}
diff --git a/apps/pro/src/blocks/authentication/sign-in-simple.tsx b/apps/pro/src/blocks/authentication/sign-in-simple.tsx
new file mode 100644
index 00000000..37290a8b
--- /dev/null
+++ b/apps/pro/src/blocks/authentication/sign-in-simple.tsx
@@ -0,0 +1,78 @@
+import { Button, Card, Checkbox, Divider, Flex, Form, Input, InputPassword, Typography } from '@tiny-design/react';
+import { IconLock, IconGoogle, IconGithub } from '@tiny-design/icons';
+
+const { Heading, Text } = Typography;
+
+export default function SignInSimple() {
+ return (
+
+
+
+
+
+
+
+ Welcome back
+
+ Sign in to continue to your workspace
+
+
+
+
+
+
+
+ Google
+
+
+
+
+
+ GitHub
+
+
+
+
+
or continue with email
+
+
+
+
+
+
+
+
+ Remember me
+ Forgot password?
+
+
+ Sign in
+
+
+
+
+ Don't have an account? Create one free
+
+
+
+
+ );
+}
diff --git a/apps/pro/src/blocks/authentication/sign-up-simple.tsx b/apps/pro/src/blocks/authentication/sign-up-simple.tsx
new file mode 100644
index 00000000..777e813a
--- /dev/null
+++ b/apps/pro/src/blocks/authentication/sign-up-simple.tsx
@@ -0,0 +1,77 @@
+import { Button, Card, Checkbox, Flex, Form, Input, InputPassword, Progress, Typography } from '@tiny-design/react';
+import { IconAddUser } from '@tiny-design/icons';
+
+const { Heading, Text } = Typography;
+
+export default function SignUpSimple() {
+ return (
+
+
+
+
+
+
+
+ Create your account
+
+ Start your 14-day free trial, no credit card required
+
+
+
+
+
+
+
+
+
+
+
+ Password strength
+ Strong
+
+
+
+
+
+ I agree to the Terms of Service and Privacy Policy
+
+
+
+ Get started free
+
+
+
+
+ Already have an account? Sign in
+
+
+
+
+ );
+}
diff --git a/apps/pro/src/blocks/banners/alert-banner.tsx b/apps/pro/src/blocks/banners/alert-banner.tsx
new file mode 100644
index 00000000..ef12be5d
--- /dev/null
+++ b/apps/pro/src/blocks/banners/alert-banner.tsx
@@ -0,0 +1,143 @@
+import { Alert, Flex, Tag, Typography } from '@tiny-design/react';
+import {
+ IconCheckCircle,
+ IconCalendar,
+ IconWarning,
+ IconCreditCard,
+ IconArrowRight,
+} from '@tiny-design/icons';
+
+const { Text } = Typography;
+
+const iconBox = (gradient: string, shadow: string): React.CSSProperties => ({
+ width: 36,
+ height: 36,
+ borderRadius: 10,
+ marginRight: 12,
+ background: gradient,
+ display: 'inline-flex',
+ alignItems: 'center',
+ justifyContent: 'center',
+ boxShadow: `0 3px 10px ${shadow}`,
+ flexShrink: 0,
+});
+
+const linkStyle = (color: string): React.CSSProperties => ({
+ color,
+ fontWeight: 600,
+ fontSize: 13,
+ cursor: 'pointer',
+ display: 'inline-flex',
+ alignItems: 'center',
+ gap: 3,
+ textDecoration: 'none',
+});
+
+export default function AlertBanner() {
+ return (
+
+
+
+
+ }
+ title={
+
+ Deployment successful
+
+ Live
+
+
+ }
+ >
+
+
+ All 24 checks passed — your app is now live at production.
+
+
+ View logs
+
+
+
+
+
+
+
+ }
+ title={
+ Scheduled maintenance
+ }
+ >
+
+
+ Systems will be briefly unavailable on March 30, 2:00–4:00 AM UTC.
+
+
+ View details
+
+
+
+
+
+
+
+ }
+ title={
+
+ Usage limit approaching
+
+ 89%
+
+
+ }
+ >
+
+
+ You've consumed 89% of your monthly API quota. Upgrade to avoid interruption.
+
+
+ Upgrade plan
+
+
+
+
+
+
+
+ }
+ title={
+
+ Payment failed
+
+ Action required
+
+
+ }
+ >
+
+
+ We couldn't charge your Visa ending in 4242. Please update your billing info.
+
+
+ Update card
+
+
+
+
+ );
+}
diff --git a/apps/pro/src/blocks/banners/promo-banner.tsx b/apps/pro/src/blocks/banners/promo-banner.tsx
new file mode 100644
index 00000000..7f4e4b2d
--- /dev/null
+++ b/apps/pro/src/blocks/banners/promo-banner.tsx
@@ -0,0 +1,56 @@
+import { Button, Flex, Tag, Typography } from '@tiny-design/react';
+import { IconFire } from '@tiny-design/icons';
+
+const { Text } = Typography;
+
+export default function PromoBanner() {
+ return (
+
+
+
+
+
+
+
+ New
+
+
+
+ Tiny Design v2.0 is here — redesigned components, dark mode, and 40+ new blocks.
+
+
+ Explore now
+
+
+
+ );
+}
diff --git a/apps/pro/src/blocks/cards/profile-card.tsx b/apps/pro/src/blocks/cards/profile-card.tsx
new file mode 100644
index 00000000..4f42139f
--- /dev/null
+++ b/apps/pro/src/blocks/cards/profile-card.tsx
@@ -0,0 +1,84 @@
+import { Avatar, Button, Card, Divider, Flex, Tag, Typography } from '@tiny-design/react';
+import { IconBriefcase } from '@tiny-design/icons';
+
+const { Heading, Text } = Typography;
+
+export default function ProfileCard() {
+ return (
+
+
+ {/* Gradient header */}
+
+
+ {/* Avatar overlapping header */}
+
+
+
+ JD
+
+
+ Jane Doe
+
+
+ Software Engineer
+
+
+
+ Building beautiful UIs with Tiny Design. Open-source enthusiast and design systems advocate.
+
+
+ React
+ TypeScript
+ Design Systems
+
+
+
+
+
+
+
+ 128
+ Projects
+
+
+ 1.2k
+ Followers
+
+
+ 384
+ Following
+
+
+
+
+
+ Follow
+
+
+ Message
+
+
+
+
+
+ );
+}
diff --git a/apps/pro/src/blocks/cards/stats-card.tsx b/apps/pro/src/blocks/cards/stats-card.tsx
new file mode 100644
index 00000000..418a89a5
--- /dev/null
+++ b/apps/pro/src/blocks/cards/stats-card.tsx
@@ -0,0 +1,61 @@
+import { Card, Flex, Statistic, Tag, Typography } from '@tiny-design/react';
+import { IconWallet, IconTeam, IconPieChart, IconBullish } from '@tiny-design/icons';
+
+const { Text } = Typography;
+
+const stats = [
+ { title: 'Total Revenue', value: 45231.89, prefix: '$', change: '+20.1%', up: true, icon: IconWallet, color: '#6366f1', bg: '#eef2ff' },
+ { title: 'Subscribers', value: 2350, change: '+180', up: true, icon: IconTeam, color: '#0891b2', bg: '#ecfeff' },
+ { title: 'Conversion', value: 12.5, suffix: '%', change: '+4.3%', up: true, icon: IconBullish, color: '#059669', bg: '#ecfdf5' },
+ { title: 'Bounce Rate', value: 24.5, suffix: '%', change: '-2.1%', up: false, icon: IconPieChart, color: '#e11d48', bg: '#fff1f2' },
+];
+
+export default function StatsCard() {
+ return (
+
+
+ {stats.map((s) => {
+ const Icon = s.icon;
+ return (
+
+
+
+
+
+ {s.title}
+
+
+
+
+
+
+
+
+
+
+ {s.change}
+
+ vs last month
+
+
+
+
+ );
+ })}
+
+
+ );
+}
diff --git a/apps/pro/src/blocks/form-layouts/contact-form.tsx b/apps/pro/src/blocks/form-layouts/contact-form.tsx
new file mode 100644
index 00000000..00a0353c
--- /dev/null
+++ b/apps/pro/src/blocks/form-layouts/contact-form.tsx
@@ -0,0 +1,87 @@
+import { Button, Card, Flex, Form, Input, NativeSelect, Textarea, Typography } from '@tiny-design/react';
+import { IconComment, IconCustomerSupport } from '@tiny-design/icons';
+
+const { Heading, Text } = Typography;
+
+export default function ContactForm() {
+ return (
+
+
+
+
+
+
+
+ Get in touch
+
+ Have a question or need help? Fill out the form below and our team will get back to you within 24 hours.
+
+
+
+
+
+
+
+
+
+
+ Select a topic...
+ General Inquiry
+ Technical Support
+ Billing Question
+ Enterprise Sales
+
+
+
+
+
+
+
+ Cancel
+
+
+
+
+ Send Message
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/apps/pro/src/blocks/lists/user-list.tsx b/apps/pro/src/blocks/lists/user-list.tsx
new file mode 100644
index 00000000..94cad609
--- /dev/null
+++ b/apps/pro/src/blocks/lists/user-list.tsx
@@ -0,0 +1,82 @@
+import { Avatar, Button, Card, Flex, Input, List, Tag, Typography } from '@tiny-design/react';
+import { IconSearch, IconAddUser } from '@tiny-design/icons';
+
+const { Heading, Text } = Typography;
+
+const users = [
+ { name: 'Alice Johnson', email: 'alice@example.com', role: 'Admin', status: 'Active', avatar: 'AJ', color: '#6366f1' },
+ { name: 'Bob Smith', email: 'bob@example.com', role: 'Editor', status: 'Active', avatar: 'BS', color: '#0891b2' },
+ { name: 'Carol White', email: 'carol@example.com', role: 'Viewer', status: 'Inactive', avatar: 'CW', color: '#64748b' },
+ { name: 'David Brown', email: 'david@example.com', role: 'Editor', status: 'Active', avatar: 'DB', color: '#059669' },
+ { name: 'Eva Martinez', email: 'eva@example.com', role: 'Admin', status: 'Active', avatar: 'EM', color: '#e11d48' },
+];
+
+const roleColors: Record = { Admin: 'purple', Editor: 'blue', Viewer: 'default' };
+
+export default function UserList() {
+ return (
+
+
+
+
+
+ Team Members
+
+ Manage your team and their permissions
+
+
+
+ }
+ size="sm"
+ style={{ width: 200, borderRadius: 8 }}
+ />
+
+
+
+ Invite
+
+
+
+
+
+
(
+ Edit,
+ Remove ,
+ ]}
+ >
+
+
+ {user.avatar}
+
+
+ {user.name}
+
+ {user.email}
+
+
+ {user.role}
+
+
+
+ )}
+ />
+
+
+
+ Showing 5 of 5 members
+
+
+ 3 Admin · 2 Editor · 1 Viewer
+
+
+
+
+
+ );
+}
diff --git a/apps/pro/src/blocks/navbars/navbar-simple.tsx b/apps/pro/src/blocks/navbars/navbar-simple.tsx
new file mode 100644
index 00000000..f16f7c61
--- /dev/null
+++ b/apps/pro/src/blocks/navbars/navbar-simple.tsx
@@ -0,0 +1,61 @@
+import { Avatar, Badge, Button, Flex, Menu, Typography } from '@tiny-design/react';
+import { IconFeedback, IconSetting } from '@tiny-design/icons';
+
+const { Text } = Typography;
+
+export default function NavbarSimple() {
+ return (
+
+
+
+
+
+ A
+
+ Acme Inc
+
+
+ Dashboard
+ Projects
+ Team
+ Reports
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ JD
+
+
+ Jane Doe
+ Admin
+
+
+
+
+
+ );
+}
diff --git a/apps/pro/src/blocks/navbars/navbar-with-search.tsx b/apps/pro/src/blocks/navbars/navbar-with-search.tsx
new file mode 100644
index 00000000..eab44ff8
--- /dev/null
+++ b/apps/pro/src/blocks/navbars/navbar-with-search.tsx
@@ -0,0 +1,67 @@
+import { Avatar, Button, Flex, Input, Keyboard, Menu, Tag, Typography } from '@tiny-design/react';
+import { IconSearch, IconBroadcast } from '@tiny-design/icons';
+
+const { Text } = Typography;
+
+export default function NavbarWithSearch() {
+ return (
+
+
+
+
+
+ W
+
+ Workspace
+
+ Pro
+
+
+
+ Home
+ Docs
+ API
+ Changelog
+
+
+
+ }
+ suffix={/ }
+ size="sm"
+ style={{ width: 220, borderRadius: 8 }}
+ />
+
+
+
+ Upgrade
+
+
+
+ U
+
+
+
+
+ );
+}
diff --git a/apps/pro/src/blocks/notifications/notification-list.tsx b/apps/pro/src/blocks/notifications/notification-list.tsx
new file mode 100644
index 00000000..a960080d
--- /dev/null
+++ b/apps/pro/src/blocks/notifications/notification-list.tsx
@@ -0,0 +1,122 @@
+import { Badge, Button, Card, Flex, List, Typography } from '@tiny-design/react';
+import { IconComment, IconCheckCircle, IconTeam, IconStatistics } from '@tiny-design/icons';
+
+const { Heading, Text } = Typography;
+
+const notifications = [
+ {
+ id: 1,
+ title: 'New comment on your pull request',
+ description: 'Alice reviewed: "Looks great! Just one small suggestion on the auth handler."',
+ time: '5 min ago',
+ read: false,
+ avatar: 'AJ',
+ color: '#6366f1',
+ icon: IconComment,
+ },
+ {
+ id: 2,
+ title: 'Deployment succeeded',
+ description: 'tiny-design@2.1.0 deployed to production — all health checks passing.',
+ time: '1 hour ago',
+ read: false,
+ avatar: 'CI',
+ color: '#059669',
+ icon: IconCheckCircle,
+ },
+ {
+ id: 3,
+ title: 'Bob Smith joined Engineering',
+ description: 'New team member added to the engineering team. Say hello!',
+ time: '3 hours ago',
+ read: true,
+ avatar: 'BS',
+ color: '#0891b2',
+ icon: IconTeam,
+ },
+ {
+ id: 4,
+ title: 'Weekly analytics report',
+ description: 'Your weekly report is ready — downloads up 23% this week.',
+ time: 'Yesterday',
+ read: true,
+ avatar: 'WR',
+ color: '#e11d48',
+ icon: IconStatistics,
+ },
+];
+
+export default function NotificationList() {
+ return (
+
+
+
+
+
+ Notifications
+
+
+ Mark all read
+
+
+ You have 2 unread notifications
+
+
+
+
+
(
+
+
+
+
+
+
+ {!item.read && (
+
+ )}
+
+
+
+ {item.title}
+
+ {item.time}
+
+
+
+ {item.description}
+
+
+
+
+ )}
+ />
+
+
+
+ );
+}
diff --git a/apps/pro/src/blocks/page-headers/page-header-with-breadcrumb.tsx b/apps/pro/src/blocks/page-headers/page-header-with-breadcrumb.tsx
new file mode 100644
index 00000000..489aac33
--- /dev/null
+++ b/apps/pro/src/blocks/page-headers/page-header-with-breadcrumb.tsx
@@ -0,0 +1,108 @@
+import { Avatar, Breadcrumb, Button, Divider, Flex, Tag, Typography } from '@tiny-design/react';
+import { IconSetting, IconBranch, IconCheckCircle } from '@tiny-design/icons';
+
+const { Heading, Text } = Typography;
+
+export default function PageHeaderWithBreadcrumb() {
+ return (
+
+
+ Home
+ Projects
+ Tiny Design
+
+
+
+
+
+ T
+
+
+
+ Tiny Design
+
+
+
+ Active
+
+
+
+
+
+ A friendly UI component set for React
+
+
+
+ main
+
+
+
+
+
+ {['JD', 'AJ', 'BS'].map((initials, i) => (
+ 0 ? -8 : 0,
+ }}
+ >
+ {initials}
+
+ ))}
+
+ +5
+
+
+
+ 8 contributors
+
+
+
+
+
+
+
+
+
+ Settings
+
+
+
+ Deploy
+
+
+
+
+
+
+ );
+}
diff --git a/apps/pro/src/blocks/page-shells/dashboard-shell.tsx b/apps/pro/src/blocks/page-shells/dashboard-shell.tsx
new file mode 100644
index 00000000..daabe9ad
--- /dev/null
+++ b/apps/pro/src/blocks/page-shells/dashboard-shell.tsx
@@ -0,0 +1,102 @@
+import { Avatar, Button, Card, Divider, Flex, Layout, Menu, Statistic, Tag, Typography } from '@tiny-design/react';
+import { IconTeam, IconSearch, IconWallet, IconBullish } from '@tiny-design/icons';
+
+const { Heading, Text } = Typography;
+const { Header, Sidebar, Content } = Layout;
+
+export default function DashboardShell() {
+ return (
+
+
+
+
+ A
+
+
+ Acme Inc
+ Enterprise
+
+
+
+
+
+ Main
+
+
+ Overview
+ Analytics
+ Reports
+
+
+ Manage
+
+
+ Users
+ Settings
+
+
+
+
+ JD
+
+ Jane Doe
+ Admin
+
+
+
+
+
+
+ Overview
+
+
+
+
+
+ Create Report
+
+
+
+
+
+ {[
+ { title: 'Total Users', value: 12834, icon: IconTeam, color: '#6366f1', bg: '#eef2ff', change: '+12%' },
+ { title: 'Revenue', value: 45231, prefix: '$', icon: IconWallet, color: '#059669', bg: '#ecfdf5', change: '+8.2%' },
+ { title: 'Growth', value: 23.5, suffix: '%', icon: IconBullish, color: '#0891b2', bg: '#ecfeff', change: '+4.1%' },
+ ].map((s) => (
+
+
+
+
+ {s.title}
+
+
+
+
+
+
+
{s.change}
+
+
+ ))}
+
+
+
+
+ );
+}
diff --git a/apps/pro/src/blocks/progress-steps/steps-basic.tsx b/apps/pro/src/blocks/progress-steps/steps-basic.tsx
new file mode 100644
index 00000000..1b703054
--- /dev/null
+++ b/apps/pro/src/blocks/progress-steps/steps-basic.tsx
@@ -0,0 +1,68 @@
+import { Button, Card, Divider, Flex, Steps, Tag, Typography } from '@tiny-design/react';
+
+const { Heading, Text } = Typography;
+
+export default function StepsBasic() {
+ return (
+
+
+
+
+
+
+ Create New Project
+ Step 2 of 4
+
+
+ Configure your project settings and preferences
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Configuration
+
+ Choose the framework, deployment target, and build options for your project.
+
+
+ {['React', 'Vercel', 'TypeScript'].map((item) => (
+ {item}
+ ))}
+
+
+
+
+
+
+
+
+ Previous
+
+
+ Save Draft
+
+ Next Step
+
+
+
+
+
+
+ );
+}
diff --git a/apps/pro/src/blocks/sidebars/sidebar-with-groups.tsx b/apps/pro/src/blocks/sidebars/sidebar-with-groups.tsx
new file mode 100644
index 00000000..aa20e122
--- /dev/null
+++ b/apps/pro/src/blocks/sidebars/sidebar-with-groups.tsx
@@ -0,0 +1,84 @@
+import { Avatar, Divider, Flex, Menu, Progress, Tag, Typography } from '@tiny-design/react';
+
+const { Text } = Typography;
+
+export default function SidebarWithGroups() {
+ return (
+
+ {/* Organization header */}
+
+
+ A
+
+
+ Acme Inc
+
+ Pro Plan
+
+
+
+
+
+
+ {/* Navigation groups */}
+
+
+ General
+
+
+ Dashboard
+ Projects
+ Tasks
+ Analytics
+
+
+
+ Settings
+
+
+ General
+ Members
+ Billing
+
+
+
+ {/* Usage footer */}
+
+
+ Storage used
+ 7.2 / 10 GB
+
+
+
+
+ {/* User footer */}
+
+
+ JD
+
+ Jane Doe
+ jane@acme.co
+
+
+
+
+ );
+}
diff --git a/apps/pro/src/blocks/stats/stat-with-icon.tsx b/apps/pro/src/blocks/stats/stat-with-icon.tsx
new file mode 100644
index 00000000..9878720e
--- /dev/null
+++ b/apps/pro/src/blocks/stats/stat-with-icon.tsx
@@ -0,0 +1,62 @@
+import { Card, Flex, Progress, Statistic, Tag, Typography } from '@tiny-design/react';
+import { IconWallet, IconTeam, IconStarFill, IconBullish } from '@tiny-design/icons';
+
+const { Text } = Typography;
+
+const metrics = [
+ { title: 'Total Sales', value: 89420, prefix: '$', progress: 72, trend: '+12.5%', icon: IconWallet, color: '#6366f1', bg: '#eef2ff' },
+ { title: 'New Customers', value: 1423, progress: 58, trend: '+8.2%', icon: IconTeam, color: '#0891b2', bg: '#ecfeff' },
+ { title: 'Satisfaction', value: 94.2, suffix: '%', progress: 94, trend: '+2.1%', icon: IconStarFill, color: '#f59e0b', bg: '#fffbeb' },
+ { title: 'Growth Rate', value: 23.5, suffix: '%', progress: 85, trend: '+4.3%', icon: IconBullish, color: '#059669', bg: '#ecfdf5' },
+];
+
+export default function StatWithIcon() {
+ return (
+
+
+ {metrics.map((m) => {
+ const Icon = m.icon;
+ return (
+
+
+
+
+
+
+
+
+ {m.title}
+
+
+
+
+
+
+
+ Target progress
+ {m.progress}%
+
+
+
+
+
+ {m.trend}
+ vs last period
+
+
+
+ );
+ })}
+
+
+ );
+}
diff --git a/apps/pro/src/blocks/tables/data-table.tsx b/apps/pro/src/blocks/tables/data-table.tsx
new file mode 100644
index 00000000..7837b972
--- /dev/null
+++ b/apps/pro/src/blocks/tables/data-table.tsx
@@ -0,0 +1,130 @@
+import { Avatar, Button, Card, Flex, Input, Table, Tag, Typography } from '@tiny-design/react';
+import { IconSearch, IconPlus, IconDownload } from '@tiny-design/icons';
+
+const { Heading, Text } = Typography;
+
+const data = [
+ { key: '1', name: 'Alice Johnson', email: 'alice@acme.co', role: 'Admin', status: 'Active', lastLogin: '2 hours ago', color: '#6366f1' },
+ { key: '2', name: 'Bob Smith', email: 'bob@acme.co', role: 'Engineer', status: 'Active', lastLogin: '1 day ago', color: '#0891b2' },
+ { key: '3', name: 'Carol White', email: 'carol@acme.co', role: 'Designer', status: 'Inactive', lastLogin: '2 weeks ago', color: '#64748b' },
+ { key: '4', name: 'David Brown', email: 'david@acme.co', role: 'Engineer', status: 'Active', lastLogin: '5 hours ago', color: '#059669' },
+ { key: '5', name: 'Eva Martinez', email: 'eva@acme.co', role: 'Admin', status: 'Active', lastLogin: '30 min ago', color: '#e11d48' },
+];
+
+const roleColors: Record = { Admin: 'purple', Engineer: 'blue', Designer: 'cyan', Viewer: 'default' };
+
+const columns = [
+ {
+ title: 'Member',
+ dataIndex: 'name',
+ key: 'name',
+ render: (_: unknown, record: (typeof data)[0]) => (
+
+
+ {record.name.split(' ').map((n) => n[0]).join('')}
+
+
+ {record.name}
+
+ {record.email}
+
+
+ ),
+ },
+ {
+ title: 'Role',
+ dataIndex: 'role',
+ key: 'role',
+ render: (role: string) => (
+ {role}
+ ),
+ },
+ {
+ title: 'Status',
+ dataIndex: 'status',
+ key: 'status',
+ render: (status: string) => (
+
+
+ {status}
+
+ ),
+ },
+ {
+ title: 'Last Active',
+ dataIndex: 'lastLogin',
+ key: 'lastLogin',
+ render: (val: string) => (
+ {val}
+ ),
+ },
+ {
+ title: '',
+ key: 'action',
+ render: () => (
+
+ Edit
+ Remove
+
+ ),
+ },
+];
+
+export default function DataTable() {
+ return (
+
+
+
+
+
+ Team Members
+
+ Manage and review all team members
+
+
+
+ }
+ size="sm"
+ style={{ width: 180, borderRadius: 8 }}
+ />
+
+
+
+ Export
+
+
+
+
+
+ Add Member
+
+
+
+
+
+
+
+ Showing 5 results
+
+
+ 2 Admin
+ 2 Engineer
+ 1 Designer
+
+
+
+
+
+ );
+}
diff --git a/apps/pro/src/blocks/user-cards/user-card-simple.tsx b/apps/pro/src/blocks/user-cards/user-card-simple.tsx
new file mode 100644
index 00000000..ce3eb565
--- /dev/null
+++ b/apps/pro/src/blocks/user-cards/user-card-simple.tsx
@@ -0,0 +1,114 @@
+import { Avatar, Button, Card, Flex, Tag, Typography } from '@tiny-design/react';
+import { IconLink, IconComment } from '@tiny-design/icons';
+
+const { Heading, Text } = Typography;
+
+const users = [
+ {
+ name: 'Alice Johnson',
+ role: 'Engineering Lead',
+ avatar: 'AJ',
+ color: '#6366f1',
+ gradient: 'linear-gradient(135deg, #6366f1 0%, #a855f7 100%)',
+ tags: ['React', 'TypeScript', 'Node.js'],
+ projects: 42,
+ contributions: '1.2k',
+ },
+ {
+ name: 'Bob Smith',
+ role: 'Product Designer',
+ avatar: 'BS',
+ color: '#0891b2',
+ gradient: 'linear-gradient(135deg, #0891b2 0%, #06b6d4 100%)',
+ tags: ['Figma', 'UI/UX', 'Motion'],
+ projects: 38,
+ contributions: '890',
+ },
+ {
+ name: 'Carol White',
+ role: 'Backend Engineer',
+ avatar: 'CW',
+ color: '#059669',
+ gradient: 'linear-gradient(135deg, #059669 0%, #10b981 100%)',
+ tags: ['Go', 'PostgreSQL', 'K8s'],
+ projects: 56,
+ contributions: '2.1k',
+ },
+];
+
+export default function UserCardSimple() {
+ return (
+
+ {users.map((user) => (
+
+
+
+
+ {user.avatar}
+
+
+ {user.name}
+ {user.role}
+
+
+ {user.tags.map((tag) => (
+ {tag}
+ ))}
+
+
+
+ {user.projects}
+ Projects
+
+
+ {user.contributions}
+ Commits
+
+
+
+
+ Connect
+
+
+
+
+ Message
+
+
+
+
+
+ ))}
+
+ );
+}
diff --git a/apps/pro/src/components/block-preview/block-preview.module.scss b/apps/pro/src/components/block-preview/block-preview.module.scss
new file mode 100644
index 00000000..37d85c18
--- /dev/null
+++ b/apps/pro/src/components/block-preview/block-preview.module.scss
@@ -0,0 +1,268 @@
+// ── Block Preview Card ──────────────────────────────────────────────
+.blockPreview {
+ border: 1px solid var(--ty-color-border-secondary);
+ border-radius: 12px;
+ overflow: hidden;
+ background: var(--ty-color-bg-container);
+ box-shadow:
+ 0 1px 2px rgba(0, 0, 0, 0.04),
+ 0 2px 8px rgba(0, 0, 0, 0.02);
+ transition:
+ border-color 0.25s,
+ background-color 0.25s,
+ box-shadow 0.25s;
+
+ & + & {
+ margin-top: 40px;
+ }
+
+ &:hover {
+ box-shadow:
+ 0 2px 4px rgba(0, 0, 0, 0.06),
+ 0 4px 16px rgba(0, 0, 0, 0.04);
+ }
+}
+
+// ── Header ─────────────────────────────────────────────────────────
+.header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 14px 20px;
+ border-bottom: 1px solid var(--ty-color-border-secondary);
+ background: var(--ty-color-bg-elevated);
+ transition:
+ background-color 0.25s,
+ border-color 0.25s;
+}
+
+.title {
+ margin: 0;
+ font-size: 14px;
+ font-weight: 600;
+ letter-spacing: -0.01em;
+ color: var(--ty-color-text);
+}
+
+// ── Toolbar ────────────────────────────────────────────────────────
+.toolbar {
+ display: flex;
+ align-items: center;
+ gap: 6px;
+}
+
+// Segmented viewport switcher
+.viewportGroup {
+ display: flex;
+ align-items: center;
+ background: var(--ty-color-fill-secondary);
+ border-radius: 8px;
+ padding: 3px;
+ gap: 2px;
+}
+
+.viewportBtn {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: 30px;
+ height: 28px;
+ border: none;
+ border-radius: 6px;
+ background: transparent;
+ color: var(--ty-color-text-tertiary);
+ cursor: pointer;
+ font-size: 15px;
+ transition:
+ background-color 0.2s,
+ color 0.2s,
+ box-shadow 0.2s;
+
+ &:hover {
+ color: var(--ty-color-text-secondary);
+ }
+}
+
+.viewportBtnActive {
+ background: var(--ty-color-bg-container);
+ color: var(--ty-color-text);
+ box-shadow:
+ 0 1px 2px rgba(0, 0, 0, 0.06),
+ 0 1px 3px rgba(0, 0, 0, 0.04);
+}
+
+// Divider between viewport and actions
+.toolbarDivider {
+ width: 1px;
+ height: 20px;
+ background: var(--ty-color-border-secondary);
+ margin: 0 4px;
+}
+
+// Action buttons
+.actionGroup {
+ display: flex;
+ align-items: center;
+ gap: 2px;
+}
+
+.actionBtn {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: 30px;
+ height: 30px;
+ border: none;
+ border-radius: 6px;
+ background: transparent;
+ color: var(--ty-color-text-tertiary);
+ cursor: pointer;
+ font-size: 15px;
+ transition:
+ background-color 0.15s,
+ color 0.15s;
+
+ &:hover {
+ color: var(--ty-color-text);
+ background: var(--ty-color-fill-secondary);
+ }
+}
+
+.actionBtnActive {
+ color: var(--ty-color-primary);
+ background: var(--ty-color-primary-bg);
+
+ &:hover {
+ color: var(--ty-color-primary);
+ background: var(--ty-color-primary-bg);
+ }
+}
+
+// ── Preview Frame ──────────────────────────────────────────────────
+.previewOuter {
+ position: relative;
+ overflow: hidden;
+ min-height: 120px;
+ transition: background-color 0.3s;
+}
+
+.previewOuterScaled {
+ // Subtle dot grid to indicate constrained viewport
+ background-image: radial-gradient(circle, var(--ty-color-border-secondary) 0.8px, transparent 0.8px);
+ background-size: 16px 16px;
+ background-position: 8px 8px;
+}
+
+.previewInner {
+ width: 100%;
+}
+
+.previewInnerScaled {
+ margin: 0 auto;
+ box-shadow:
+ -1px 0 0 var(--ty-color-border-secondary),
+ 1px 0 0 var(--ty-color-border-secondary);
+ background: var(--ty-color-bg-container);
+}
+
+// ── Action Bar (between preview and code) ──────────────────────────
+.actionBar {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 6px;
+ padding: 6px 16px;
+ border-top: 1px dashed var(--ty-color-border-secondary);
+ background: var(--ty-color-bg-elevated);
+ transition:
+ background-color 0.25s,
+ border-color 0.25s;
+}
+
+.actionBarBtn {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ height: 28px;
+ border: none;
+ border-radius: 6px;
+ background: transparent;
+ color: var(--ty-color-text-tertiary);
+ cursor: pointer;
+ font-size: 13px;
+ padding: 0 8px;
+ gap: 5px;
+ transition:
+ background-color 0.15s,
+ color 0.15s;
+
+ &:hover {
+ color: var(--ty-color-text);
+ background: var(--ty-color-fill-secondary);
+ }
+
+ svg {
+ width: 14px;
+ height: 14px;
+ }
+}
+
+.actionBarBtnActive {
+ color: var(--ty-color-primary);
+
+ &:hover {
+ color: var(--ty-color-primary);
+ }
+}
+
+.actionBarDivider {
+ width: 1px;
+ height: 16px;
+ background: var(--ty-color-border-secondary);
+ margin: 0 2px;
+}
+
+.actionBarLabel {
+ font-family: inherit;
+ font-size: 12px;
+ font-weight: 500;
+}
+
+// ── Code Panel ─────────────────────────────────────────────────────
+.codePanelWrapper {
+ display: grid;
+ grid-template-rows: 0fr;
+ transition: grid-template-rows 0.3s ease;
+ border-top: 1px solid var(--ty-color-border-secondary);
+}
+
+.codePanelWrapperOpen {
+ grid-template-rows: 1fr;
+}
+
+.codePanelInner {
+ overflow: hidden;
+}
+
+.codePanel {
+ max-height: 480px;
+ overflow: auto;
+ font-size: 13px;
+ line-height: 1.7;
+
+ pre {
+ margin: 0;
+ }
+}
+
+// ── Error ──────────────────────────────────────────────────────────
+.errorDisplay {
+ padding: 16px 20px;
+ font-size: 13px;
+ font-family: 'Menlo', 'Consolas', 'Droid Sans Mono', monospace;
+ line-height: 1.5;
+ color: var(--ty-color-danger);
+ background: var(--ty-color-danger-bg);
+ white-space: pre-wrap;
+ word-break: break-word;
+}
diff --git a/apps/pro/src/components/block-preview/code-panel.tsx b/apps/pro/src/components/block-preview/code-panel.tsx
new file mode 100644
index 00000000..3fde8e4d
--- /dev/null
+++ b/apps/pro/src/components/block-preview/code-panel.tsx
@@ -0,0 +1,39 @@
+'use client';
+
+import { Highlight, themes } from 'prism-react-renderer';
+import { useTheme } from '@tiny-design/react';
+import { LightCodeTheme, DarkCodeTheme } from './code-theme';
+import styles from './block-preview.module.scss';
+
+interface CodePanelProps {
+ source: string;
+}
+
+export function CodePanel({ source }: CodePanelProps) {
+ const { resolvedTheme } = useTheme();
+ const theme = (resolvedTheme === 'dark' ? DarkCodeTheme : LightCodeTheme) as typeof themes.github;
+
+ return (
+
+
+ {({ className, style, tokens, getLineProps, getTokenProps }) => (
+
+
+ {tokens.map((line, i) => {
+ const { key: _lineKey, ...lineProps } = getLineProps({ line }); // eslint-disable-line @typescript-eslint/no-unused-vars
+ return (
+
+ {line.map((token, j) => {
+ const { key: _tokenKey, ...tokenProps } = getTokenProps({ token }); // eslint-disable-line @typescript-eslint/no-unused-vars
+ return ;
+ })}
+
+ );
+ })}
+
+
+ )}
+
+
+ );
+}
diff --git a/apps/pro/src/components/block-preview/code-theme.ts b/apps/pro/src/components/block-preview/code-theme.ts
new file mode 100644
index 00000000..01987e32
--- /dev/null
+++ b/apps/pro/src/components/block-preview/code-theme.ts
@@ -0,0 +1,97 @@
+/* Light theme: based on prism-ghcolors */
+export const LightCodeTheme = {
+ plain: {
+ color: '#393A34',
+ backgroundColor: '#f6f8fa',
+ },
+ styles: [
+ {
+ types: ['comment', 'prolog', 'doctype', 'cdata'],
+ style: { color: '#999988', fontStyle: 'italic' as const },
+ },
+ {
+ types: ['namespace'],
+ style: { opacity: 0.7 },
+ },
+ {
+ types: ['string', 'attr-name'],
+ style: { color: '#0b8235' },
+ },
+ {
+ types: ['punctuation', 'operator'],
+ style: { color: '#999' },
+ },
+ {
+ types: [
+ 'entity', 'url', 'symbol', 'number', 'boolean',
+ 'variable', 'constant', 'property', 'regex', 'inserted',
+ ],
+ style: { color: '#36acaa' },
+ },
+ {
+ types: ['atrule', 'keyword', 'selector', 'attr-value'],
+ style: { color: '#00a4db' },
+ },
+ {
+ types: ['function', 'deleted', 'tag', 'function-variable', 'at-rule', 'class-name'],
+ style: { color: '#f81d22' },
+ },
+ {
+ types: ['function-variable'],
+ style: { color: '#6f42c1' },
+ },
+ {
+ types: ['tag', 'selector', 'keyword'],
+ style: { color: '#6f42c1' },
+ },
+ ],
+};
+
+/* Dark theme: based on VS Code Dark+ */
+export const DarkCodeTheme = {
+ plain: {
+ color: '#d4d4d4',
+ backgroundColor: '#1e1e1e',
+ },
+ styles: [
+ {
+ types: ['comment', 'prolog', 'doctype', 'cdata'],
+ style: { color: '#6a9955', fontStyle: 'italic' as const },
+ },
+ {
+ types: ['namespace'],
+ style: { opacity: 0.7 },
+ },
+ {
+ types: ['string', 'attr-name'],
+ style: { color: '#ce9178' },
+ },
+ {
+ types: ['punctuation', 'operator'],
+ style: { color: '#808080' },
+ },
+ {
+ types: [
+ 'entity', 'url', 'symbol', 'number', 'boolean',
+ 'variable', 'constant', 'property', 'regex', 'inserted',
+ ],
+ style: { color: '#b5cea8' },
+ },
+ {
+ types: ['atrule', 'keyword', 'selector', 'attr-value'],
+ style: { color: '#569cd6' },
+ },
+ {
+ types: ['function', 'deleted', 'tag', 'function-variable', 'at-rule', 'class-name'],
+ style: { color: '#d7ba7d' },
+ },
+ {
+ types: ['function-variable'],
+ style: { color: '#dcdcaa' },
+ },
+ {
+ types: ['tag', 'selector', 'keyword'],
+ style: { color: '#c586c0' },
+ },
+ ],
+};
diff --git a/apps/pro/src/components/block-preview/index.tsx b/apps/pro/src/components/block-preview/index.tsx
new file mode 100644
index 00000000..7aba29a4
--- /dev/null
+++ b/apps/pro/src/components/block-preview/index.tsx
@@ -0,0 +1,94 @@
+'use client';
+
+import React, { useState, useEffect, useCallback } from 'react';
+import { useRunner } from 'react-runner';
+import * as TinyDesign from '@tiny-design/react';
+import * as TinyIcons from '@tiny-design/icons';
+import { Toolbar, CodeIcon, CopyIcon, CheckIcon, type ViewportSize } from './toolbar';
+import { PreviewFrame } from './preview-frame';
+import { CodePanel } from './code-panel';
+import type { BlockMeta } from '../../utils/blocks';
+import styles from './block-preview.module.scss';
+
+// In dev mode, Next.js appends HMR code (import.meta.webpackHot...) to
+// asset/source modules. Strip it so react-runner only sees the TSX source.
+function stripHmr(raw: string): string {
+ const marker = '\n\n;\n // Wrapped in an IIFE';
+ const idx = raw.indexOf(marker);
+ return idx !== -1 ? raw.substring(0, idx).trim() : raw.trim();
+}
+
+const scope = {
+ import: {
+ react: React,
+ '@tiny-design/react': TinyDesign,
+ '@tiny-design/icons': TinyIcons,
+ },
+};
+
+interface BlockPreviewProps {
+ meta: BlockMeta;
+}
+
+export function BlockPreview({ meta }: BlockPreviewProps) {
+ const [viewport, setViewport] = useState('desktop');
+ const [showCode, setShowCode] = useState(false);
+ const [sourceCode, setSourceCode] = useState('');
+ const [copied, setCopied] = useState(false);
+
+ useEffect(() => {
+ meta.rawSource().then((m) => setSourceCode(stripHmr(m.default)));
+ }, [meta]);
+
+ const { element, error } = useRunner({ code: sourceCode, scope });
+
+ const handleCopy = useCallback(async () => {
+ await navigator.clipboard.writeText(sourceCode);
+ setCopied(true);
+ setTimeout(() => setCopied(false), 2000);
+ }, [sourceCode]);
+
+ return (
+
+
+
{meta.title}
+
+
+
+
+ {error ? (
+ {error}
+ ) : (
+ element
+ )}
+
+
+ {/* Docs-inspired action bar */}
+
+ setShowCode((v) => !v)}
+ >
+
+
+ {showCode ? 'Hide Code' : 'Show Code'}
+
+
+
+
+ {copied ? : }
+
+ {copied ? 'Copied!' : 'Copy'}
+
+
+
+
+ {/* Animated code panel */}
+
+
+ );
+}
diff --git a/apps/pro/src/components/block-preview/preview-frame.tsx b/apps/pro/src/components/block-preview/preview-frame.tsx
new file mode 100644
index 00000000..91127179
--- /dev/null
+++ b/apps/pro/src/components/block-preview/preview-frame.tsx
@@ -0,0 +1,76 @@
+'use client';
+
+import { useRef, useState, useEffect } from 'react';
+import type { ViewportSize } from './toolbar';
+import styles from './block-preview.module.scss';
+
+const VIEWPORT_WIDTHS: Record = {
+ desktop: 0, // 0 = fluid / 100%
+ tablet: 768,
+ mobile: 375,
+};
+
+interface PreviewFrameProps {
+ viewport: ViewportSize;
+ children: React.ReactNode;
+}
+
+export function PreviewFrame({ viewport, children }: PreviewFrameProps) {
+ const outerRef = useRef(null);
+ const innerRef = useRef(null);
+ const [scale, setScale] = useState(1);
+ const [innerHeight, setInnerHeight] = useState(undefined);
+ const targetWidth = VIEWPORT_WIDTHS[viewport];
+
+ useEffect(() => {
+ if (!outerRef.current || targetWidth === 0) {
+ setScale(1);
+ setInnerHeight(undefined);
+ return;
+ }
+
+ const observer = new ResizeObserver(() => {
+ if (!outerRef.current) return;
+ const availableWidth = outerRef.current.clientWidth;
+ const newScale = Math.min(1, availableWidth / targetWidth);
+ setScale(newScale);
+
+ if (innerRef.current) {
+ setInnerHeight(innerRef.current.scrollHeight * newScale);
+ }
+ });
+
+ observer.observe(outerRef.current);
+ if (innerRef.current) {
+ observer.observe(innerRef.current);
+ }
+
+ return () => observer.disconnect();
+ }, [viewport, targetWidth]);
+
+ const isScaled = targetWidth > 0;
+
+ return (
+
+ );
+}
diff --git a/apps/pro/src/components/block-preview/toolbar.tsx b/apps/pro/src/components/block-preview/toolbar.tsx
new file mode 100644
index 00000000..72c785e8
--- /dev/null
+++ b/apps/pro/src/components/block-preview/toolbar.tsx
@@ -0,0 +1,75 @@
+'use client';
+
+import styles from './block-preview.module.scss';
+
+export type ViewportSize = 'desktop' | 'tablet' | 'mobile';
+
+const DesktopIcon = () => (
+
+
+
+
+
+);
+
+const TabletIcon = () => (
+
+
+
+
+);
+
+const MobileIcon = () => (
+
+
+
+
+);
+
+export const CodeIcon = () => (
+
+
+
+
+);
+
+export const CopyIcon = () => (
+
+
+
+
+);
+
+export const CheckIcon = () => (
+
+
+
+);
+
+interface ToolbarProps {
+ viewport: ViewportSize;
+ onViewportChange: (v: ViewportSize) => void;
+}
+
+export function Toolbar({ viewport, onViewportChange }: ToolbarProps) {
+ return (
+
+
+ {([
+ ['desktop', DesktopIcon],
+ ['tablet', TabletIcon],
+ ['mobile', MobileIcon],
+ ] as const).map(([size, Icon]) => (
+ onViewportChange(size)}
+ aria-label={`${size} view`}
+ >
+
+
+ ))}
+
+
+ );
+}
diff --git a/apps/pro/src/components/layout/category-nav.module.scss b/apps/pro/src/components/layout/category-nav.module.scss
new file mode 100644
index 00000000..4632dc90
--- /dev/null
+++ b/apps/pro/src/components/layout/category-nav.module.scss
@@ -0,0 +1,49 @@
+.nav {
+ position: fixed;
+ top: 60px;
+ left: 0;
+ bottom: 0;
+ width: 240px;
+ overflow-y: auto;
+ padding: 20px 0;
+ background: var(--ty-color-bg-elevated);
+ border-right: 1px solid var(--ty-color-border-secondary);
+ transition:
+ background-color 0.25s,
+ border-color 0.25s;
+}
+
+.title {
+ padding: 6px 24px 10px;
+ font-size: 11px;
+ font-weight: 700;
+ text-transform: uppercase;
+ letter-spacing: 0.06em;
+ color: var(--ty-color-text-tertiary);
+}
+
+.link {
+ display: block;
+ padding: 7px 24px;
+ font-size: 13px;
+ font-weight: 450;
+ color: var(--ty-color-text-secondary);
+ text-decoration: none;
+ border-left: 2px solid transparent;
+ transition:
+ color 0.15s,
+ background-color 0.15s,
+ border-color 0.15s;
+
+ &:hover {
+ color: var(--ty-color-text);
+ background: var(--ty-color-fill-secondary);
+ }
+
+ &.active {
+ color: var(--ty-color-primary);
+ font-weight: 550;
+ border-left-color: var(--ty-color-primary);
+ background: var(--ty-color-primary-bg);
+ }
+}
diff --git a/apps/pro/src/components/layout/category-nav.tsx b/apps/pro/src/components/layout/category-nav.tsx
new file mode 100644
index 00000000..c5b119ea
--- /dev/null
+++ b/apps/pro/src/components/layout/category-nav.tsx
@@ -0,0 +1,29 @@
+'use client';
+
+import Link from 'next/link';
+import { usePathname } from 'next/navigation';
+import { CATEGORIES } from '../../utils/blocks';
+import styles from './category-nav.module.scss';
+
+export function CategoryNav() {
+ const pathname = usePathname();
+
+ return (
+
+ Application
+ {CATEGORIES.map((cat) => {
+ const href = `/blocks/${cat.slug}/`;
+ const isActive = pathname?.includes(`/blocks/${cat.slug}`);
+ return (
+
+ {cat.label}
+
+ );
+ })}
+
+ );
+}
diff --git a/apps/pro/src/components/layout/site-header.module.scss b/apps/pro/src/components/layout/site-header.module.scss
new file mode 100644
index 00000000..ecc80a13
--- /dev/null
+++ b/apps/pro/src/components/layout/site-header.module.scss
@@ -0,0 +1,60 @@
+.header {
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ z-index: 100;
+ height: 60px;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 0 24px;
+ background: color-mix(in srgb, var(--ty-color-bg-container) 85%, transparent);
+ backdrop-filter: blur(12px) saturate(180%);
+ -webkit-backdrop-filter: blur(12px) saturate(180%);
+ border-bottom: 1px solid var(--ty-color-border-secondary);
+ transition:
+ background-color 0.25s,
+ border-color 0.25s;
+}
+
+.logo {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ text-decoration: none;
+ color: var(--ty-color-text);
+ font-weight: 700;
+ font-size: 16px;
+ letter-spacing: -0.02em;
+}
+
+.actions {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+}
+
+.iconBtn {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: 34px;
+ height: 34px;
+ border: 1px solid var(--ty-color-border-secondary);
+ border-radius: 8px;
+ background: transparent;
+ color: var(--ty-color-text-secondary);
+ cursor: pointer;
+ font-size: 16px;
+ transition:
+ background-color 0.15s,
+ color 0.15s,
+ border-color 0.15s;
+
+ &:hover {
+ color: var(--ty-color-text);
+ background: var(--ty-color-fill-secondary);
+ border-color: var(--ty-color-border);
+ }
+}
diff --git a/apps/pro/src/components/layout/site-header.tsx b/apps/pro/src/components/layout/site-header.tsx
new file mode 100644
index 00000000..31a0d74c
--- /dev/null
+++ b/apps/pro/src/components/layout/site-header.tsx
@@ -0,0 +1,61 @@
+'use client';
+
+import { useState, useEffect } from 'react';
+import Link from 'next/link';
+import { useTheme } from '@tiny-design/react';
+import styles from './site-header.module.scss';
+
+const SunIcon = () => (
+
+
+
+
+
+
+
+
+
+
+
+);
+
+const MoonIcon = () => (
+
+
+
+);
+
+const GitHubIcon = () => (
+
+
+
+);
+
+export function SiteHeader() {
+ const { resolvedTheme, toggle } = useTheme();
+ const [mounted, setMounted] = useState(false);
+
+ useEffect(() => setMounted(true), []);
+
+ return (
+
+
+ Tiny Design Pro
+
+
+
+ {mounted ? (resolvedTheme === 'dark' ? : ) : }
+
+
+
+
+
+
+ );
+}
diff --git a/apps/pro/src/components/theme-script/index.tsx b/apps/pro/src/components/theme-script/index.tsx
new file mode 100644
index 00000000..037e691f
--- /dev/null
+++ b/apps/pro/src/components/theme-script/index.tsx
@@ -0,0 +1,11 @@
+export function ThemeScript() {
+ const script = `
+ (function() {
+ try {
+ var t = localStorage.getItem('ty-theme') || 'light';
+ document.documentElement.setAttribute('data-tiny-theme', t);
+ } catch(e) {}
+ })();
+ `;
+ return ;
+}
diff --git a/apps/pro/src/env.d.ts b/apps/pro/src/env.d.ts
new file mode 100644
index 00000000..ddb8910d
--- /dev/null
+++ b/apps/pro/src/env.d.ts
@@ -0,0 +1,9 @@
+declare module '*?raw' {
+ const content: string;
+ export default content;
+}
+
+declare module '*.module.scss' {
+ const classes: { [key: string]: string };
+ export default classes;
+}
diff --git a/apps/pro/src/utils/blocks.ts b/apps/pro/src/utils/blocks.ts
new file mode 100644
index 00000000..e3248203
--- /dev/null
+++ b/apps/pro/src/utils/blocks.ts
@@ -0,0 +1,164 @@
+export interface BlockMeta {
+ id: string;
+ title: string;
+ category: string;
+ categoryLabel: string;
+ rawSource: () => Promise<{ default: string }>;
+}
+
+export interface CategoryMeta {
+ slug: string;
+ label: string;
+ blocks: BlockMeta[];
+}
+
+function block(
+ category: string,
+ categoryLabel: string,
+ id: string,
+ title: string,
+ rawSource: () => Promise<{ default: string }>
+): BlockMeta {
+ return { id: `${category}/${id}`, title, category, categoryLabel, rawSource };
+}
+
+export const CATEGORIES: CategoryMeta[] = [
+ {
+ slug: 'authentication',
+ label: 'Authentication',
+ blocks: [
+ block('authentication', 'Authentication', 'sign-in-simple', 'Simple Sign In',
+ () => import('../blocks/authentication/sign-in-simple?raw')),
+ block('authentication', 'Authentication', 'sign-up-simple', 'Simple Sign Up',
+ () => import('../blocks/authentication/sign-up-simple?raw')),
+ ],
+ },
+ {
+ slug: 'banners',
+ label: 'Banners',
+ blocks: [
+ block('banners', 'Banners', 'promo-banner', 'Promo Banner',
+ () => import('../blocks/banners/promo-banner?raw')),
+ block('banners', 'Banners', 'alert-banner', 'Alert Banner',
+ () => import('../blocks/banners/alert-banner?raw')),
+ ],
+ },
+ {
+ slug: 'cards',
+ label: 'Cards',
+ blocks: [
+ block('cards', 'Cards', 'stats-card', 'Stats Cards',
+ () => import('../blocks/cards/stats-card?raw')),
+ block('cards', 'Cards', 'profile-card', 'Profile Card',
+ () => import('../blocks/cards/profile-card?raw')),
+ ],
+ },
+ {
+ slug: 'form-layouts',
+ label: 'Form Layouts',
+ blocks: [
+ block('form-layouts', 'Form Layouts', 'contact-form', 'Contact Form',
+ () => import('../blocks/form-layouts/contact-form?raw')),
+ ],
+ },
+ {
+ slug: 'lists',
+ label: 'Lists',
+ blocks: [
+ block('lists', 'Lists', 'user-list', 'User List',
+ () => import('../blocks/lists/user-list?raw')),
+ ],
+ },
+ {
+ slug: 'navbars',
+ label: 'Navbars',
+ blocks: [
+ block('navbars', 'Navbars', 'navbar-simple', 'Simple Navbar',
+ () => import('../blocks/navbars/navbar-simple?raw')),
+ block('navbars', 'Navbars', 'navbar-with-search', 'Navbar with Search',
+ () => import('../blocks/navbars/navbar-with-search?raw')),
+ ],
+ },
+ {
+ slug: 'notifications',
+ label: 'Notifications',
+ blocks: [
+ block('notifications', 'Notifications', 'notification-list', 'Notification List',
+ () => import('../blocks/notifications/notification-list?raw')),
+ ],
+ },
+ {
+ slug: 'page-headers',
+ label: 'Page Headers',
+ blocks: [
+ block('page-headers', 'Page Headers', 'page-header-with-breadcrumb', 'Page Header with Breadcrumb',
+ () => import('../blocks/page-headers/page-header-with-breadcrumb?raw')),
+ ],
+ },
+ {
+ slug: 'page-shells',
+ label: 'Page Shells',
+ blocks: [
+ block('page-shells', 'Page Shells', 'dashboard-shell', 'Dashboard Shell',
+ () => import('../blocks/page-shells/dashboard-shell?raw')),
+ ],
+ },
+ {
+ slug: 'progress-steps',
+ label: 'Progress Steps',
+ blocks: [
+ block('progress-steps', 'Progress Steps', 'steps-basic', 'Basic Steps',
+ () => import('../blocks/progress-steps/steps-basic?raw')),
+ ],
+ },
+ {
+ slug: 'sidebars',
+ label: 'Sidebars',
+ blocks: [
+ block('sidebars', 'Sidebars', 'sidebar-with-groups', 'Sidebar with Groups',
+ () => import('../blocks/sidebars/sidebar-with-groups?raw')),
+ ],
+ },
+ {
+ slug: 'stats',
+ label: 'Stats',
+ blocks: [
+ block('stats', 'Stats', 'stat-with-icon', 'Stats with Progress',
+ () => import('../blocks/stats/stat-with-icon?raw')),
+ ],
+ },
+ {
+ slug: 'tables',
+ label: 'Tables',
+ blocks: [
+ block('tables', 'Tables', 'data-table', 'Data Table',
+ () => import('../blocks/tables/data-table?raw')),
+ ],
+ },
+ {
+ slug: 'user-cards',
+ label: 'User Cards',
+ blocks: [
+ block('user-cards', 'User Cards', 'user-card-simple', 'Simple User Cards',
+ () => import('../blocks/user-cards/user-card-simple?raw')),
+ ],
+ },
+];
+
+export function getCategories(): CategoryMeta[] {
+ return CATEGORIES;
+}
+
+export function getCategory(slug: string): CategoryMeta | undefined {
+ return CATEGORIES.find((c) => c.slug === slug);
+}
+
+export function getCategorySlugs(): string[] {
+ return CATEGORIES.map((c) => c.slug);
+}
+
+export function getCategoryInfo(slug: string): { label: string; blockCount: number } | undefined {
+ const cat = CATEGORIES.find((c) => c.slug === slug);
+ if (!cat) return undefined;
+ return { label: cat.label, blockCount: cat.blocks.length };
+}
diff --git a/apps/pro/tsconfig.json b/apps/pro/tsconfig.json
new file mode 100644
index 00000000..68c950c4
--- /dev/null
+++ b/apps/pro/tsconfig.json
@@ -0,0 +1,43 @@
+{
+ "extends": "../../tsconfig.base.json",
+ "compilerOptions": {
+ "baseUrl": ".",
+ "module": "esnext",
+ "noUnusedLocals": false,
+ "plugins": [
+ {
+ "name": "next"
+ }
+ ],
+ "paths": {
+ "@/*": [
+ "./src/*"
+ ],
+ "@tiny-design/react": [
+ "../../packages/react/src"
+ ],
+ "@tiny-design/react/*": [
+ "../../packages/react/src/*"
+ ],
+ "@tiny-design/icons": [
+ "../../packages/icons/src"
+ ],
+ "@tiny-design/icons/*": [
+ "../../packages/icons/src/*"
+ ]
+ },
+ "skipLibCheck": true,
+ "incremental": true,
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "jsx": "preserve"
+ },
+ "include": [
+ "next-env.d.ts",
+ "src",
+ ".next/types/**/*.ts"
+ ],
+ "exclude": [
+ "node_modules"
+ ]
+}
diff --git a/packages/react/src/alert/alert.tsx b/packages/react/src/alert/alert.tsx
index f868c8de..85c67c0f 100755
--- a/packages/react/src/alert/alert.tsx
+++ b/packages/react/src/alert/alert.tsx
@@ -77,7 +77,7 @@ const Alert = React.forwardRef((props, forwardedRef)
}}>
{icon && renderIcon()}
- {title &&
{title}
}
+ {title &&
{title}
}
{children}
{closeIcon}
diff --git a/packages/react/src/card/__tests__/__snapshots__/card.test.tsx.snap b/packages/react/src/card/__tests__/__snapshots__/card.test.tsx.snap
index e3cb23d2..5d862955 100644
--- a/packages/react/src/card/__tests__/__snapshots__/card.test.tsx.snap
+++ b/packages/react/src/card/__tests__/__snapshots__/card.test.tsx.snap
@@ -3,7 +3,7 @@
exports[` should match the snapshot 1`] = `
Content
diff --git a/packages/react/src/card/__tests__/card.test.tsx b/packages/react/src/card/__tests__/card.test.tsx
index 42a7e42d..03a359f4 100644
--- a/packages/react/src/card/__tests__/card.test.tsx
+++ b/packages/react/src/card/__tests__/card.test.tsx
@@ -13,9 +13,37 @@ describe(' ', () => {
expect(container.firstChild).toHaveClass('ty-card');
});
- it('should render bordered by default', () => {
+ it('should render outlined by default (bordered backward compat)', () => {
const { container } = render(Content );
- expect(container.firstChild).toHaveClass('ty-card_bordered');
+ expect(container.firstChild).toHaveClass('ty-card_outlined');
+ });
+
+ it('should render elevated variant', () => {
+ const { container } = render(Content );
+ expect(container.firstChild).toHaveClass('ty-card_elevated');
+ expect(container.firstChild).not.toHaveClass('ty-card_outlined');
+ });
+
+ it('should render filled variant', () => {
+ const { container } = render(Content );
+ expect(container.firstChild).toHaveClass('ty-card_filled');
+ expect(container.firstChild).not.toHaveClass('ty-card_outlined');
+ });
+
+ it('should render outlined variant explicitly', () => {
+ const { container } = render(Content );
+ expect(container.firstChild).toHaveClass('ty-card_outlined');
+ });
+
+ it('variant should take precedence over bordered', () => {
+ const { container } = render(Content );
+ expect(container.firstChild).toHaveClass('ty-card_elevated');
+ expect(container.firstChild).not.toHaveClass('ty-card_outlined');
+ });
+
+ it('should render without border when bordered is false', () => {
+ const { container } = render(Content );
+ expect(container.firstChild).not.toHaveClass('ty-card_outlined');
});
it('should render hoverable', () => {
diff --git a/packages/react/src/card/card.tsx b/packages/react/src/card/card.tsx
index a93c1d32..d07be8f1 100644
--- a/packages/react/src/card/card.tsx
+++ b/packages/react/src/card/card.tsx
@@ -2,10 +2,11 @@ import React, { ReactNode, useContext } from 'react';
import classNames from 'classnames';
import { ConfigContext } from '../config-provider/config-context';
import { getPrefixCls } from '../_utils/general';
-import { CardContentProps, CardProps } from './types';
+import { CardContentProps, CardProps, CardVariant } from './types';
const Card = React.forwardRef((props, ref) => {
const {
+ variant,
bordered = true,
active = false,
hoverable = false,
@@ -24,8 +25,11 @@ const Card = React.forwardRef((props, ref) => {
} = props;
const configContext = useContext(ConfigContext);
const prefixCls = getPrefixCls('card', configContext.prefixCls, customisedCls);
+ // When variant is set, it takes precedence over the legacy bordered prop
+ const resolvedVariant: CardVariant | undefined = variant ?? (bordered ? 'outlined' : undefined);
+
const cls = classNames(prefixCls, className, {
- [`${prefixCls}_bordered`]: bordered,
+ [`${prefixCls}_${resolvedVariant}`]: resolvedVariant,
[`${prefixCls}_active`]: active,
[`${prefixCls}_hoverable`]: hoverable,
});
diff --git a/packages/react/src/card/demo/Variant.tsx b/packages/react/src/card/demo/Variant.tsx
new file mode 100644
index 00000000..1d4f4bec
--- /dev/null
+++ b/packages/react/src/card/demo/Variant.tsx
@@ -0,0 +1,18 @@
+import React from 'react';
+import { Card, Flex } from '@tiny-design/react';
+
+export default function VariantDemo() {
+ return (
+
+
+ Default style with a border.
+
+
+ Shadow without border.
+
+
+ Subtle fill background.
+
+
+ );
+}
diff --git a/packages/react/src/card/index.md b/packages/react/src/card/index.md
index fdd0c114..4c3d75f7 100644
--- a/packages/react/src/card/index.md
+++ b/packages/react/src/card/index.md
@@ -8,6 +8,8 @@ import HoverableDemo from './demo/Hoverable';
import HoverableSource from './demo/Hoverable.tsx?raw';
import ActiveDemo from './demo/Active';
import ActiveSource from './demo/Active.tsx?raw';
+import VariantDemo from './demo/Variant';
+import VariantSource from './demo/Variant.tsx?raw';
import InnerCardDemo from './demo/InnerCard';
import InnerCardSource from './demo/InnerCard.tsx?raw';
import ImageDemo from './demo/Image';
@@ -44,6 +46,15 @@ A basic card containing a title, content and an extra corner content.
+### Variants
+
+Use `variant` to control the card surface style: `outlined` (default), `elevated`, or `filled`.
+
+
+
+
+
+
### No border
A borderless card on a gray background.
@@ -103,13 +114,14 @@ A card using an image to reinforce the content.
## Props
-| Property | Description | Type | Default |
-| ----------- | -------------------------------------------- | ------------------------ | ------- |
-| title | card title | ReactNode | - |
-| extra | content to render in the top-right corner | ReactNode | - |
-| hoverable | lift up when hovering card | boolean | false |
-| active | display card with elevation shadow | boolean | false |
-| bordered | toggles rendering of the border | boolean | true |
+| Property | Description | Type | Default |
+| ----------- | -------------------------------------------- | -------------------------------------- | ----------- |
+| variant | card surface style | `outlined` \| `elevated` \| `filled` | `outlined` |
+| title | card title | ReactNode | - |
+| extra | content to render in the top-right corner | ReactNode | - |
+| hoverable | lift up when hovering card | boolean | false |
+| active | display card with elevation shadow | boolean | false |
+| bordered | toggles rendering of the border (deprecated) | boolean | true |
| actions | the action list at the bottom of the card | ReactNode[] | - |
| header | custom header content | ReactNode | - |
| footer | custom footer content | ReactNode | - |
diff --git a/packages/react/src/card/index.tsx b/packages/react/src/card/index.tsx
index 7b330f42..8a9a485c 100755
--- a/packages/react/src/card/index.tsx
+++ b/packages/react/src/card/index.tsx
@@ -1,6 +1,8 @@
import Card from './card';
import CardContent from './card-content';
+export type { CardVariant, CardProps } from './types';
+
type ICard = typeof Card & {
Content: typeof CardContent;
};
diff --git a/packages/react/src/card/index.zh_CN.md b/packages/react/src/card/index.zh_CN.md
index feb2660f..012fc583 100644
--- a/packages/react/src/card/index.zh_CN.md
+++ b/packages/react/src/card/index.zh_CN.md
@@ -8,6 +8,8 @@ import HoverableDemo from './demo/Hoverable';
import HoverableSource from './demo/Hoverable.tsx?raw';
import ActiveDemo from './demo/Active';
import ActiveSource from './demo/Active.tsx?raw';
+import VariantDemo from './demo/Variant';
+import VariantSource from './demo/Variant.tsx?raw';
import InnerCardDemo from './demo/InnerCard';
import InnerCardSource from './demo/InnerCard.tsx?raw';
import ImageDemo from './demo/Image';
@@ -44,6 +46,15 @@ const { Content } = Card;
+### 变体样式
+
+使用 `variant` 控制卡片表面样式:`outlined`(默认)、`elevated` 或 `filled`。
+
+
+
+
+
+
### 无边框
灰色背景上的无边框卡片。
@@ -103,13 +114,14 @@ const { Content } = Card;
## Props
-| 属性 | 说明 | 类型 | 默认值 |
-| ----------- | -------------------------------------------- | ------------------------ | ------- |
-| title | 卡片标题 | ReactNode | - |
-| extra | 右上角额外内容 | ReactNode | - |
-| hoverable | 鼠标悬停时浮起 | boolean | false |
-| active | 显示带有阴影的卡片 | boolean | false |
-| bordered | 是否显示边框 | boolean | true |
+| 属性 | 说明 | 类型 | 默认值 |
+| ----------- | -------------------------------------------- | -------------------------------------- | ----------- |
+| variant | 卡片表面样式 | `outlined` \| `elevated` \| `filled` | `outlined` |
+| title | 卡片标题 | ReactNode | - |
+| extra | 右上角额外内容 | ReactNode | - |
+| hoverable | 鼠标悬停时浮起 | boolean | false |
+| active | 显示带有阴影的卡片 | boolean | false |
+| bordered | 是否显示边框(已弃用) | boolean | true |
| actions | 卡片底部的操作列表 | ReactNode[] | - |
| header | 自定义头部内容 | ReactNode | - |
| footer | 自定义底部内容 | ReactNode | - |
diff --git a/packages/react/src/card/style/_index.scss b/packages/react/src/card/style/_index.scss
index 23cd4036..46897992 100644
--- a/packages/react/src/card/style/_index.scss
+++ b/packages/react/src/card/style/_index.scss
@@ -9,27 +9,32 @@
transition: all 0.3s;
background-color: var(--ty-card-bg);
- & + & {
- margin-top: 15px;
- }
-
& > img:first-child {
border-radius: $card-border-radius $card-border-radius 0 0;
}
- &_bordered {
+ &_outlined {
border: 1px solid var(--ty-card-border);
}
+ &_elevated {
+ box-shadow: var(--ty-shadow-card);
+ }
+
+ &_filled {
+ background-color: var(--ty-color-fill);
+ }
+
&_hoverable {
cursor: pointer;
+
&:hover {
- @include card_elevation();
+ @include card_elevation;
}
}
&_active {
- @include card_elevation();
+ @include card_elevation;
}
&__header {
diff --git a/packages/react/src/card/types.ts b/packages/react/src/card/types.ts
index e9bec693..9dd335a9 100644
--- a/packages/react/src/card/types.ts
+++ b/packages/react/src/card/types.ts
@@ -1,9 +1,11 @@
import React, { CSSProperties, ReactNode } from 'react';
import { BaseProps } from '../_utils/props';
+export type CardVariant = 'outlined' | 'elevated' | 'filled';
+
export interface CardContentProps extends React.PropsWithoutRef {
prefixCls?: string;
- children: string;
+ children: ReactNode;
}
export interface CardProps
@@ -11,8 +13,11 @@ export interface CardProps
Omit, 'title'> {
title?: ReactNode;
extra?: ReactNode;
+ /** Card surface style */
+ variant?: CardVariant;
hoverable?: boolean;
active?: boolean;
+ /** @deprecated Use `variant="outlined"` instead */
bordered?: boolean;
actions?: ReactNode[];
header?: ReactNode;
diff --git a/packages/react/src/icon/demo/svg-icons.tsx b/packages/react/src/icon/demo/svg-icons.tsx
index d25200bd..3dbd1d66 100644
--- a/packages/react/src/icon/demo/svg-icons.tsx
+++ b/packages/react/src/icon/demo/svg-icons.tsx
@@ -4,7 +4,7 @@ import * as Icons from '@tiny-design/icons';
import type { IconProps } from '@tiny-design/icons';
import Input from '../../input';
import Message from '../../message';
-import { useTheme } from '../../_utils/use-theme'
+import { useTheme } from '../../_utils/use-theme';
const iconEntries = Object.entries(Icons).filter(
([key]) => key.startsWith('Icon')
@@ -38,7 +38,7 @@ const SvgIconList = (): JSX.Element => {
const [keyword, setKeyword] = useState('');
const [activeIconName, setActiveIconName] = useState('');
const deferredKeyword = useDeferredValue(keyword);
- const { resolvedTheme } = useTheme()
+ const { resolvedTheme } = useTheme();
const normalizedKeyword = deferredKeyword.trim().toLowerCase();
const filteredIconEntries = iconEntries.filter(([name]) =>
diff --git a/packages/react/src/native-select/types.ts b/packages/react/src/native-select/types.ts
index 94cba8ae..0656ee0f 100644
--- a/packages/react/src/native-select/types.ts
+++ b/packages/react/src/native-select/types.ts
@@ -9,5 +9,7 @@ export interface NativeSelectProps
extends BaseProps,
Omit, 'size'> {
size?: SizeType;
- children: React.ReactElement;
+ children:
+ | React.ReactElement
+ | React.ReactElement[];
}
diff --git a/packages/react/src/table/table.tsx b/packages/react/src/table/table.tsx
index 1cb2015f..299173e4 100644
--- a/packages/react/src/table/table.tsx
+++ b/packages/react/src/table/table.tsx
@@ -14,8 +14,8 @@ const getRowKey = (record: T, rowKey: string | ((record: T) => React.Key), i
return key !== undefined ? key : index;
};
-const getValue = (record: T, dataIndex: string): any => {
- return (record as any)[dataIndex];
+const getValue = (record: T, dataIndex?: string): any => {
+ return dataIndex ? (record as any)[dataIndex] : undefined;
};
const Table = React.forwardRef((props, ref) => {
diff --git a/packages/react/src/table/types.ts b/packages/react/src/table/types.ts
index 4afd4376..fa28caa2 100644
--- a/packages/react/src/table/types.ts
+++ b/packages/react/src/table/types.ts
@@ -7,7 +7,7 @@ export type ColumnAlign = 'left' | 'center' | 'right';
export interface ColumnType {
title: React.ReactNode;
- dataIndex: string;
+ dataIndex?: string;
key?: string;
width?: number | string;
align?: ColumnAlign;
diff --git a/packages/react/src/tag/style/_index.scss b/packages/react/src/tag/style/_index.scss
index e7e2a373..7228242b 100755
--- a/packages/react/src/tag/style/_index.scss
+++ b/packages/react/src/tag/style/_index.scss
@@ -1,6 +1,7 @@
@use '../../style/variables' as *;
-$tag-preset-colors: magenta, red, volcano, orange, gold, lime, green, cyan, blue, geekblue, purple;
+$tag-preset-colors: 'magenta', 'red', 'volcano', 'orange', 'gold', 'lime', 'green', 'cyan', 'blue',
+ 'geekblue', 'purple';
$tag-status-colors: success, info, warning, danger;
.#{$prefix}-tag {
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 7677f02d..2e1d4677 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -127,6 +127,52 @@ importers:
specifier: ^7.3.1
version: 7.3.1(@types/node@25.4.0)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.2)
+ apps/pro:
+ dependencies:
+ '@tiny-design/icons':
+ specifier: workspace:*
+ version: link:../../packages/icons
+ '@tiny-design/react':
+ specifier: workspace:*
+ version: link:../../packages/react
+ '@tiny-design/tokens':
+ specifier: workspace:*
+ version: link:../../packages/tokens
+ classnames:
+ specifier: ^2.5.0
+ version: 2.5.1
+ next:
+ specifier: ^15.3.4
+ version: 15.5.14(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.98.0)
+ prism-react-renderer:
+ specifier: ^2.3.0
+ version: 2.4.1(react@18.3.1)
+ react:
+ specifier: ^18.2.0
+ version: 18.3.1
+ react-dom:
+ specifier: ^18.2.0
+ version: 18.3.1(react@18.3.1)
+ react-runner:
+ specifier: ^1.0.5
+ version: 1.0.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ sass:
+ specifier: ^1.49.9
+ version: 1.98.0
+ devDependencies:
+ '@types/node':
+ specifier: ^22.0.0
+ version: 22.19.15
+ '@types/react':
+ specifier: ^18.2.0
+ version: 18.3.28
+ '@types/react-dom':
+ specifier: ^18.2.0
+ version: 18.3.7(@types/react@18.3.28)
+ typescript:
+ specifier: ^5.4.0
+ version: 5.9.3
+
packages/charts:
dependencies:
'@tiny-design/tokens':
@@ -875,6 +921,159 @@ packages:
resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==}
engines: {node: '>=18.18'}
+ '@img/colour@1.1.0':
+ resolution: {integrity: sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==}
+ engines: {node: '>=18'}
+
+ '@img/sharp-darwin-arm64@0.34.5':
+ resolution: {integrity: sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@img/sharp-darwin-x64@0.34.5':
+ resolution: {integrity: sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [x64]
+ os: [darwin]
+
+ '@img/sharp-libvips-darwin-arm64@1.2.4':
+ resolution: {integrity: sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@img/sharp-libvips-darwin-x64@1.2.4':
+ resolution: {integrity: sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==}
+ cpu: [x64]
+ os: [darwin]
+
+ '@img/sharp-libvips-linux-arm64@1.2.4':
+ resolution: {integrity: sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==}
+ cpu: [arm64]
+ os: [linux]
+ libc: [glibc]
+
+ '@img/sharp-libvips-linux-arm@1.2.4':
+ resolution: {integrity: sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==}
+ cpu: [arm]
+ os: [linux]
+ libc: [glibc]
+
+ '@img/sharp-libvips-linux-ppc64@1.2.4':
+ resolution: {integrity: sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==}
+ cpu: [ppc64]
+ os: [linux]
+ libc: [glibc]
+
+ '@img/sharp-libvips-linux-riscv64@1.2.4':
+ resolution: {integrity: sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==}
+ cpu: [riscv64]
+ os: [linux]
+ libc: [glibc]
+
+ '@img/sharp-libvips-linux-s390x@1.2.4':
+ resolution: {integrity: sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==}
+ cpu: [s390x]
+ os: [linux]
+ libc: [glibc]
+
+ '@img/sharp-libvips-linux-x64@1.2.4':
+ resolution: {integrity: sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==}
+ cpu: [x64]
+ os: [linux]
+ libc: [glibc]
+
+ '@img/sharp-libvips-linuxmusl-arm64@1.2.4':
+ resolution: {integrity: sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==}
+ cpu: [arm64]
+ os: [linux]
+ libc: [musl]
+
+ '@img/sharp-libvips-linuxmusl-x64@1.2.4':
+ resolution: {integrity: sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==}
+ cpu: [x64]
+ os: [linux]
+ libc: [musl]
+
+ '@img/sharp-linux-arm64@0.34.5':
+ resolution: {integrity: sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [arm64]
+ os: [linux]
+ libc: [glibc]
+
+ '@img/sharp-linux-arm@0.34.5':
+ resolution: {integrity: sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [arm]
+ os: [linux]
+ libc: [glibc]
+
+ '@img/sharp-linux-ppc64@0.34.5':
+ resolution: {integrity: sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [ppc64]
+ os: [linux]
+ libc: [glibc]
+
+ '@img/sharp-linux-riscv64@0.34.5':
+ resolution: {integrity: sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [riscv64]
+ os: [linux]
+ libc: [glibc]
+
+ '@img/sharp-linux-s390x@0.34.5':
+ resolution: {integrity: sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [s390x]
+ os: [linux]
+ libc: [glibc]
+
+ '@img/sharp-linux-x64@0.34.5':
+ resolution: {integrity: sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [x64]
+ os: [linux]
+ libc: [glibc]
+
+ '@img/sharp-linuxmusl-arm64@0.34.5':
+ resolution: {integrity: sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [arm64]
+ os: [linux]
+ libc: [musl]
+
+ '@img/sharp-linuxmusl-x64@0.34.5':
+ resolution: {integrity: sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [x64]
+ os: [linux]
+ libc: [musl]
+
+ '@img/sharp-wasm32@0.34.5':
+ resolution: {integrity: sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [wasm32]
+
+ '@img/sharp-win32-arm64@0.34.5':
+ resolution: {integrity: sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [arm64]
+ os: [win32]
+
+ '@img/sharp-win32-ia32@0.34.5':
+ resolution: {integrity: sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [ia32]
+ os: [win32]
+
+ '@img/sharp-win32-x64@0.34.5':
+ resolution: {integrity: sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [x64]
+ os: [win32]
+
'@inquirer/external-editor@1.0.3':
resolution: {integrity: sha512-RWbSrDiYmO4LbejWY7ttpxczuwQyZLBUyygsA9Nsv95hpzUWwnNTVQmAq3xuh7vNwCp07UTmE5i11XAEExx4RA==}
engines: {node: '>=18'}
@@ -1016,6 +1215,61 @@ packages:
'@napi-rs/wasm-runtime@1.1.1':
resolution: {integrity: sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A==}
+ '@next/env@15.5.14':
+ resolution: {integrity: sha512-aXeirLYuASxEgi4X4WhfXsShCFxWDfNn/8ZeC5YXAS2BB4A8FJi1kwwGL6nvMVboE7fZCzmJPNdMvVHc8JpaiA==}
+
+ '@next/swc-darwin-arm64@15.5.14':
+ resolution: {integrity: sha512-Y9K6SPzobnZvrRDPO2s0grgzC+Egf0CqfbdvYmQVaztV890zicw8Z8+4Vqw8oPck8r1TjUHxVh8299Cg4TrxXg==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@next/swc-darwin-x64@15.5.14':
+ resolution: {integrity: sha512-aNnkSMjSFRTOmkd7qoNI2/rETQm/vKD6c/Ac9BZGa9CtoOzy3c2njgz7LvebQJ8iPxdeTuGnAjagyis8a9ifBw==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [darwin]
+
+ '@next/swc-linux-arm64-gnu@15.5.14':
+ resolution: {integrity: sha512-tjlpia+yStPRS//6sdmlVwuO1Rioern4u2onafa5n+h2hCS9MAvMXqpVbSrjgiEOoCs0nJy7oPOmWgtRRNSM5Q==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [linux]
+ libc: [glibc]
+
+ '@next/swc-linux-arm64-musl@15.5.14':
+ resolution: {integrity: sha512-8B8cngBaLadl5lbDRdxGCP1Lef8ipD6KlxS3v0ElDAGil6lafrAM3B258p1KJOglInCVFUjk751IXMr2ixeQOQ==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [linux]
+ libc: [musl]
+
+ '@next/swc-linux-x64-gnu@15.5.14':
+ resolution: {integrity: sha512-bAS6tIAg8u4Gn3Nz7fCPpSoKAexEt2d5vn1mzokcqdqyov6ZJ6gu6GdF9l8ORFrBuRHgv3go/RfzYz5BkZ6YSQ==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [linux]
+ libc: [glibc]
+
+ '@next/swc-linux-x64-musl@15.5.14':
+ resolution: {integrity: sha512-mMxv/FcrT7Gfaq4tsR22l17oKWXZmH/lVqcvjX0kfp5I0lKodHYLICKPoX1KRnnE+ci6oIUdriUhuA3rBCDiSw==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [linux]
+ libc: [musl]
+
+ '@next/swc-win32-arm64-msvc@15.5.14':
+ resolution: {integrity: sha512-OTmiBlYThppnvnsqx0rBqjDRemlmIeZ8/o4zI7veaXoeO1PVHoyj2lfTfXTiiGjCyRDhA10y4h6ZvZvBiynr2g==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [win32]
+
+ '@next/swc-win32-x64-msvc@15.5.14':
+ resolution: {integrity: sha512-+W7eFf3RS7m4G6tppVTOSyP9Y6FsJXfOuKzav1qKniiFm3KFByQfPEcouHdjlZmysl4zJGuGLQ/M9XyVeyeNEg==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [win32]
+
'@nodelib/fs.scandir@2.1.5':
resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
engines: {node: '>= 8'}
@@ -1406,6 +1660,9 @@ packages:
'@standard-schema/utils@0.3.0':
resolution: {integrity: sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==}
+ '@swc/helpers@0.5.15':
+ resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==}
+
'@testing-library/dom@9.3.4':
resolution: {integrity: sha512-FlS4ZWlp97iiNWig0Muq8p+3rVDjRiYE+YKGbAqXOu9nwJFFOdL00kFpz42M+4huzYi86vAK1sOOfyOG45muIQ==}
engines: {node: '>=14'}
@@ -2082,6 +2339,9 @@ packages:
cli-width@2.2.1:
resolution: {integrity: sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw==}
+ client-only@0.0.1:
+ resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==}
+
cliui@8.0.1:
resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==}
engines: {node: '>=12'}
@@ -4206,6 +4466,27 @@ packages:
neo-async@2.6.2:
resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==}
+ next@15.5.14:
+ resolution: {integrity: sha512-M6S+4JyRjmKic2Ssm7jHUPkE6YUJ6lv4507jprsSZLulubz0ihO2E+S4zmQK3JZ2ov81JrugukKU4Tz0ivgqqQ==}
+ engines: {node: ^18.18.0 || ^19.8.0 || >= 20.0.0}
+ hasBin: true
+ peerDependencies:
+ '@opentelemetry/api': ^1.1.0
+ '@playwright/test': ^1.51.1
+ babel-plugin-react-compiler: '*'
+ react: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0
+ react-dom: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0
+ sass: ^1.3.0
+ peerDependenciesMeta:
+ '@opentelemetry/api':
+ optional: true
+ '@playwright/test':
+ optional: true
+ babel-plugin-react-compiler:
+ optional: true
+ sass:
+ optional: true
+
node-addon-api@7.1.1:
resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==}
@@ -4550,6 +4831,10 @@ packages:
postcss-value-parser@4.2.0:
resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==}
+ postcss@8.4.31:
+ resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==}
+ engines: {node: ^10 || ^12 || >=14}
+
postcss@8.5.8:
resolution: {integrity: sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==}
engines: {node: ^10 || ^12 || >=14}
@@ -5037,6 +5322,10 @@ packages:
setprototypeof@1.2.0:
resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==}
+ sharp@0.34.5:
+ resolution: {integrity: sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+
shebang-command@1.2.0:
resolution: {integrity: sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==}
engines: {node: '>=0.10.0'}
@@ -5269,6 +5558,19 @@ packages:
style-to-object@1.0.14:
resolution: {integrity: sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==}
+ styled-jsx@5.1.6:
+ resolution: {integrity: sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==}
+ engines: {node: '>= 12.0.0'}
+ peerDependencies:
+ '@babel/core': '*'
+ babel-plugin-macros: '*'
+ react: '>= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0'
+ peerDependenciesMeta:
+ '@babel/core':
+ optional: true
+ babel-plugin-macros:
+ optional: true
+
stylelint-config-recommended-scss@14.1.0:
resolution: {integrity: sha512-bhaMhh1u5dQqSsf6ri2GVWWQW5iUjBYgcHkh7SgDDn92ijoItC/cfO/W+fpXshgTQWhwFkP1rVcewcv4jaftRg==}
engines: {node: '>=18.12.0'}
@@ -6525,6 +6827,103 @@ snapshots:
'@humanwhocodes/retry@0.4.3': {}
+ '@img/colour@1.1.0':
+ optional: true
+
+ '@img/sharp-darwin-arm64@0.34.5':
+ optionalDependencies:
+ '@img/sharp-libvips-darwin-arm64': 1.2.4
+ optional: true
+
+ '@img/sharp-darwin-x64@0.34.5':
+ optionalDependencies:
+ '@img/sharp-libvips-darwin-x64': 1.2.4
+ optional: true
+
+ '@img/sharp-libvips-darwin-arm64@1.2.4':
+ optional: true
+
+ '@img/sharp-libvips-darwin-x64@1.2.4':
+ optional: true
+
+ '@img/sharp-libvips-linux-arm64@1.2.4':
+ optional: true
+
+ '@img/sharp-libvips-linux-arm@1.2.4':
+ optional: true
+
+ '@img/sharp-libvips-linux-ppc64@1.2.4':
+ optional: true
+
+ '@img/sharp-libvips-linux-riscv64@1.2.4':
+ optional: true
+
+ '@img/sharp-libvips-linux-s390x@1.2.4':
+ optional: true
+
+ '@img/sharp-libvips-linux-x64@1.2.4':
+ optional: true
+
+ '@img/sharp-libvips-linuxmusl-arm64@1.2.4':
+ optional: true
+
+ '@img/sharp-libvips-linuxmusl-x64@1.2.4':
+ optional: true
+
+ '@img/sharp-linux-arm64@0.34.5':
+ optionalDependencies:
+ '@img/sharp-libvips-linux-arm64': 1.2.4
+ optional: true
+
+ '@img/sharp-linux-arm@0.34.5':
+ optionalDependencies:
+ '@img/sharp-libvips-linux-arm': 1.2.4
+ optional: true
+
+ '@img/sharp-linux-ppc64@0.34.5':
+ optionalDependencies:
+ '@img/sharp-libvips-linux-ppc64': 1.2.4
+ optional: true
+
+ '@img/sharp-linux-riscv64@0.34.5':
+ optionalDependencies:
+ '@img/sharp-libvips-linux-riscv64': 1.2.4
+ optional: true
+
+ '@img/sharp-linux-s390x@0.34.5':
+ optionalDependencies:
+ '@img/sharp-libvips-linux-s390x': 1.2.4
+ optional: true
+
+ '@img/sharp-linux-x64@0.34.5':
+ optionalDependencies:
+ '@img/sharp-libvips-linux-x64': 1.2.4
+ optional: true
+
+ '@img/sharp-linuxmusl-arm64@0.34.5':
+ optionalDependencies:
+ '@img/sharp-libvips-linuxmusl-arm64': 1.2.4
+ optional: true
+
+ '@img/sharp-linuxmusl-x64@0.34.5':
+ optionalDependencies:
+ '@img/sharp-libvips-linuxmusl-x64': 1.2.4
+ optional: true
+
+ '@img/sharp-wasm32@0.34.5':
+ dependencies:
+ '@emnapi/runtime': 1.8.1
+ optional: true
+
+ '@img/sharp-win32-arm64@0.34.5':
+ optional: true
+
+ '@img/sharp-win32-ia32@0.34.5':
+ optional: true
+
+ '@img/sharp-win32-x64@0.34.5':
+ optional: true
+
'@inquirer/external-editor@1.0.3(@types/node@25.4.0)':
dependencies:
chardet: 2.1.1
@@ -6822,6 +7221,32 @@ snapshots:
'@tybys/wasm-util': 0.10.1
optional: true
+ '@next/env@15.5.14': {}
+
+ '@next/swc-darwin-arm64@15.5.14':
+ optional: true
+
+ '@next/swc-darwin-x64@15.5.14':
+ optional: true
+
+ '@next/swc-linux-arm64-gnu@15.5.14':
+ optional: true
+
+ '@next/swc-linux-arm64-musl@15.5.14':
+ optional: true
+
+ '@next/swc-linux-x64-gnu@15.5.14':
+ optional: true
+
+ '@next/swc-linux-x64-musl@15.5.14':
+ optional: true
+
+ '@next/swc-win32-arm64-msvc@15.5.14':
+ optional: true
+
+ '@next/swc-win32-x64-msvc@15.5.14':
+ optional: true
+
'@nodelib/fs.scandir@2.1.5':
dependencies:
'@nodelib/fs.stat': 2.0.5
@@ -7067,6 +7492,10 @@ snapshots:
'@standard-schema/utils@0.3.0': {}
+ '@swc/helpers@0.5.15':
+ dependencies:
+ tslib: 2.8.1
+
'@testing-library/dom@9.3.4':
dependencies:
'@babel/code-frame': 7.29.0
@@ -7859,6 +8288,8 @@ snapshots:
cli-width@2.2.1: {}
+ client-only@0.0.1: {}
+
cliui@8.0.1:
dependencies:
string-width: 4.2.3
@@ -10761,6 +11192,30 @@ snapshots:
neo-async@2.6.2: {}
+ next@15.5.14(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.98.0):
+ dependencies:
+ '@next/env': 15.5.14
+ '@swc/helpers': 0.5.15
+ caniuse-lite: 1.0.30001777
+ postcss: 8.4.31
+ react: 18.3.1
+ react-dom: 18.3.1(react@18.3.1)
+ styled-jsx: 5.1.6(react@18.3.1)
+ optionalDependencies:
+ '@next/swc-darwin-arm64': 15.5.14
+ '@next/swc-darwin-x64': 15.5.14
+ '@next/swc-linux-arm64-gnu': 15.5.14
+ '@next/swc-linux-arm64-musl': 15.5.14
+ '@next/swc-linux-x64-gnu': 15.5.14
+ '@next/swc-linux-x64-musl': 15.5.14
+ '@next/swc-win32-arm64-msvc': 15.5.14
+ '@next/swc-win32-x64-msvc': 15.5.14
+ sass: 1.98.0
+ sharp: 0.34.5
+ transitivePeerDependencies:
+ - '@babel/core'
+ - babel-plugin-macros
+
node-addon-api@7.1.1:
optional: true
@@ -11113,6 +11568,12 @@ snapshots:
postcss-value-parser@4.2.0: {}
+ postcss@8.4.31:
+ dependencies:
+ nanoid: 3.3.11
+ picocolors: 1.1.1
+ source-map-js: 1.2.1
+
postcss@8.5.8:
dependencies:
nanoid: 3.3.11
@@ -11755,6 +12216,38 @@ snapshots:
setprototypeof@1.2.0: {}
+ sharp@0.34.5:
+ dependencies:
+ '@img/colour': 1.1.0
+ detect-libc: 2.1.2
+ semver: 7.7.4
+ optionalDependencies:
+ '@img/sharp-darwin-arm64': 0.34.5
+ '@img/sharp-darwin-x64': 0.34.5
+ '@img/sharp-libvips-darwin-arm64': 1.2.4
+ '@img/sharp-libvips-darwin-x64': 1.2.4
+ '@img/sharp-libvips-linux-arm': 1.2.4
+ '@img/sharp-libvips-linux-arm64': 1.2.4
+ '@img/sharp-libvips-linux-ppc64': 1.2.4
+ '@img/sharp-libvips-linux-riscv64': 1.2.4
+ '@img/sharp-libvips-linux-s390x': 1.2.4
+ '@img/sharp-libvips-linux-x64': 1.2.4
+ '@img/sharp-libvips-linuxmusl-arm64': 1.2.4
+ '@img/sharp-libvips-linuxmusl-x64': 1.2.4
+ '@img/sharp-linux-arm': 0.34.5
+ '@img/sharp-linux-arm64': 0.34.5
+ '@img/sharp-linux-ppc64': 0.34.5
+ '@img/sharp-linux-riscv64': 0.34.5
+ '@img/sharp-linux-s390x': 0.34.5
+ '@img/sharp-linux-x64': 0.34.5
+ '@img/sharp-linuxmusl-arm64': 0.34.5
+ '@img/sharp-linuxmusl-x64': 0.34.5
+ '@img/sharp-wasm32': 0.34.5
+ '@img/sharp-win32-arm64': 0.34.5
+ '@img/sharp-win32-ia32': 0.34.5
+ '@img/sharp-win32-x64': 0.34.5
+ optional: true
+
shebang-command@1.2.0:
dependencies:
shebang-regex: 1.0.0
@@ -12014,6 +12507,11 @@ snapshots:
dependencies:
inline-style-parser: 0.2.7
+ styled-jsx@5.1.6(react@18.3.1):
+ dependencies:
+ client-only: 0.0.1
+ react: 18.3.1
+
stylelint-config-recommended-scss@14.1.0(postcss@8.5.8)(stylelint@16.26.1(typescript@5.9.3)):
dependencies:
postcss-scss: 4.0.9(postcss@8.5.8)