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. +

+
+ + Browse Blocks + + + + View on GitHub + +
+
+
+ + {/* 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 + +
+ + + + + + + or continue with email + +
+ + + + + + + + Remember me + Forgot password? + + + + + + 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 + + + + + + + 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. + + + +
+ ); +} 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 + + + + + + + +
+ + + ); +} 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 + + + +