From 747729069c27a081c17fac105645b8429929f5aa Mon Sep 17 00:00:00 2001 From: wangdicoder Date: Sat, 28 Mar 2026 11:31:34 +1100 Subject: [PATCH 01/16] fix(react): fix NativeSelect children type and Table optional dataIndex - NativeSelect children type now accepts arrays of ReactElements - Table ColumnType.dataIndex is now optional for action columns - Updated getValue helper to handle undefined dataIndex gracefully --- packages/react/src/native-select/types.ts | 4 +++- packages/react/src/table/table.tsx | 4 ++-- packages/react/src/table/types.ts | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) 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; From 1c4062e90c539a10585316c52dcf202147ad4c4a Mon Sep 17 00:00:00 2001 From: wangdicoder Date: Sat, 28 Mar 2026 11:31:46 +1100 Subject: [PATCH 02/16] feat(card): add variant prop with outlined, elevated, and filled styles - Add CardVariant type: 'outlined' | 'elevated' | 'filled' - outlined: border (default, same as bordered=true) - elevated: box-shadow using --ty-shadow-card token - filled: subtle background using --ty-color-fill token - variant takes precedence over deprecated bordered prop - Full backward compatibility: existing bordered prop still works - Added Variant demo and updated docs (EN + CN) --- .../__snapshots__/card.test.tsx.snap | 2 +- .../react/src/card/__tests__/card.test.tsx | 32 +++++++++++++++++-- packages/react/src/card/card.tsx | 8 +++-- packages/react/src/card/demo/Variant.tsx | 18 +++++++++++ packages/react/src/card/index.md | 26 +++++++++++---- packages/react/src/card/index.tsx | 2 ++ packages/react/src/card/index.zh_CN.md | 26 +++++++++++---- packages/react/src/card/style/_index.scss | 19 +++++++---- packages/react/src/card/types.ts | 5 +++ 9 files changed, 112 insertions(+), 26 deletions(-) create mode 100644 packages/react/src/card/demo/Variant.tsx 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..1e7f0a27 100644 --- a/packages/react/src/card/types.ts +++ b/packages/react/src/card/types.ts @@ -1,6 +1,8 @@ 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; @@ -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; From 8ce91760719520f3d921b9b3dbc32fc883f4b715 Mon Sep 17 00:00:00 2001 From: wangdicoder Date: Sat, 28 Mar 2026 11:32:03 +1100 Subject: [PATCH 03/16] feat(pro): add Tiny Design Pro app with UI block examples Next.js 15 static site with 20 block categories and 25 blocks showcasing @tiny-design/react components. Features include: - Live preview with responsive viewport toggle (desktop/tablet/mobile) - Source code view with syntax highlighting and copy button - Light/dark theme support with anti-FOUC script - Static export for GitHub Pages deployment at /tiny-design/pro/ --- apps/pro/.gitignore | 2 + apps/pro/next-env.d.ts | 6 ++ apps/pro/next.config.ts | 41 ++++++++ apps/pro/package.json | 27 +++++ apps/pro/postcss.config.js | 3 + .../[category]/category-page-client.tsx | 31 ++++++ .../[category]/category-page.module.scss | 25 +++++ apps/pro/src/app/blocks/[category]/page.tsx | 18 ++++ apps/pro/src/app/globals.scss | 23 +++++ apps/pro/src/app/layout.tsx | 21 ++++ apps/pro/src/app/page.module.scss | 64 ++++++++++++ apps/pro/src/app/page.tsx | 30 ++++++ .../blocks/authentication/sign-in-simple.tsx | 38 +++++++ .../blocks/authentication/sign-up-simple.tsx | 43 ++++++++ apps/pro/src/blocks/banners/alert-banner.tsx | 12 +++ apps/pro/src/blocks/banners/promo-banner.tsx | 22 +++++ .../card-headers/card-header-with-actions.tsx | 21 ++++ apps/pro/src/blocks/cards/profile-card.tsx | 44 +++++++++ apps/pro/src/blocks/cards/stats-card.tsx | 36 +++++++ .../src/blocks/dividers/divider-variants.tsx | 33 +++++++ .../blocks/form-elements/input-variants.tsx | 45 +++++++++ .../src/blocks/form-layouts/contact-form.tsx | 45 +++++++++ apps/pro/src/blocks/lists/user-list.tsx | 43 ++++++++ apps/pro/src/blocks/navbars/navbar-simple.tsx | 33 +++++++ .../src/blocks/navbars/navbar-with-search.tsx | 31 ++++++ .../notifications/notification-list.tsx | 74 ++++++++++++++ .../page-header-with-breadcrumb.tsx | 31 ++++++ .../blocks/page-shells/dashboard-shell.tsx | 43 ++++++++ .../blocks/pagination/pagination-variants.tsx | 27 +++++ .../src/blocks/progress-steps/steps-basic.tsx | 24 +++++ .../section-headers/section-header-simple.tsx | 34 +++++++ .../blocks/sidebars/sidebar-with-groups.tsx | 46 +++++++++ apps/pro/src/blocks/stats/stat-with-icon.tsx | 31 ++++++ apps/pro/src/blocks/tables/data-table.tsx | 68 +++++++++++++ .../pro/src/blocks/tabs/tabs-with-content.tsx | 38 +++++++ .../blocks/user-cards/user-card-simple.tsx | 38 +++++++ .../block-preview/block-preview.module.scss | 95 ++++++++++++++++++ .../components/block-preview/code-panel.tsx | 39 ++++++++ .../components/block-preview/code-theme.ts | 97 ++++++++++++++++++ .../src/components/block-preview/index.tsx | 51 ++++++++++ .../block-preview/preview-frame.tsx | 77 +++++++++++++++ .../src/components/block-preview/toolbar.tsx | 98 +++++++++++++++++++ .../layout/category-nav.module.scss | 44 +++++++++ .../src/components/layout/category-nav.tsx | 29 ++++++ .../components/layout/site-header.module.scss | 54 ++++++++++ .../pro/src/components/layout/site-header.tsx | 57 +++++++++++ .../pro/src/components/theme-script/index.tsx | 11 +++ apps/pro/src/env.d.ts | 9 ++ apps/pro/tsconfig.json | 43 ++++++++ 49 files changed, 1895 insertions(+) create mode 100644 apps/pro/.gitignore create mode 100644 apps/pro/next-env.d.ts create mode 100644 apps/pro/next.config.ts create mode 100644 apps/pro/package.json create mode 100644 apps/pro/postcss.config.js create mode 100644 apps/pro/src/app/blocks/[category]/category-page-client.tsx create mode 100644 apps/pro/src/app/blocks/[category]/category-page.module.scss create mode 100644 apps/pro/src/app/blocks/[category]/page.tsx create mode 100644 apps/pro/src/app/globals.scss create mode 100644 apps/pro/src/app/layout.tsx create mode 100644 apps/pro/src/app/page.module.scss create mode 100644 apps/pro/src/app/page.tsx create mode 100644 apps/pro/src/blocks/authentication/sign-in-simple.tsx create mode 100644 apps/pro/src/blocks/authentication/sign-up-simple.tsx create mode 100644 apps/pro/src/blocks/banners/alert-banner.tsx create mode 100644 apps/pro/src/blocks/banners/promo-banner.tsx create mode 100644 apps/pro/src/blocks/card-headers/card-header-with-actions.tsx create mode 100644 apps/pro/src/blocks/cards/profile-card.tsx create mode 100644 apps/pro/src/blocks/cards/stats-card.tsx create mode 100644 apps/pro/src/blocks/dividers/divider-variants.tsx create mode 100644 apps/pro/src/blocks/form-elements/input-variants.tsx create mode 100644 apps/pro/src/blocks/form-layouts/contact-form.tsx create mode 100644 apps/pro/src/blocks/lists/user-list.tsx create mode 100644 apps/pro/src/blocks/navbars/navbar-simple.tsx create mode 100644 apps/pro/src/blocks/navbars/navbar-with-search.tsx create mode 100644 apps/pro/src/blocks/notifications/notification-list.tsx create mode 100644 apps/pro/src/blocks/page-headers/page-header-with-breadcrumb.tsx create mode 100644 apps/pro/src/blocks/page-shells/dashboard-shell.tsx create mode 100644 apps/pro/src/blocks/pagination/pagination-variants.tsx create mode 100644 apps/pro/src/blocks/progress-steps/steps-basic.tsx create mode 100644 apps/pro/src/blocks/section-headers/section-header-simple.tsx create mode 100644 apps/pro/src/blocks/sidebars/sidebar-with-groups.tsx create mode 100644 apps/pro/src/blocks/stats/stat-with-icon.tsx create mode 100644 apps/pro/src/blocks/tables/data-table.tsx create mode 100644 apps/pro/src/blocks/tabs/tabs-with-content.tsx create mode 100644 apps/pro/src/blocks/user-cards/user-card-simple.tsx create mode 100644 apps/pro/src/components/block-preview/block-preview.module.scss create mode 100644 apps/pro/src/components/block-preview/code-panel.tsx create mode 100644 apps/pro/src/components/block-preview/code-theme.ts create mode 100644 apps/pro/src/components/block-preview/index.tsx create mode 100644 apps/pro/src/components/block-preview/preview-frame.tsx create mode 100644 apps/pro/src/components/block-preview/toolbar.tsx create mode 100644 apps/pro/src/components/layout/category-nav.module.scss create mode 100644 apps/pro/src/components/layout/category-nav.tsx create mode 100644 apps/pro/src/components/layout/site-header.module.scss create mode 100644 apps/pro/src/components/layout/site-header.tsx create mode 100644 apps/pro/src/components/theme-script/index.tsx create mode 100644 apps/pro/src/env.d.ts create mode 100644 apps/pro/tsconfig.json 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..b18c6cac --- /dev/null +++ b/apps/pro/next.config.ts @@ -0,0 +1,41 @@ +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 file contents as strings at build time + config.module.rules.push({ + resourceQuery: /raw/, + 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..fb48ddba --- /dev/null +++ b/apps/pro/package.json @@ -0,0 +1,27 @@ +{ + "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", + "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..5963e540 --- /dev/null +++ b/apps/pro/src/app/blocks/[category]/category-page-client.tsx @@ -0,0 +1,31 @@ +'use client'; + +import { CategoryNav } from '@/components/layout/category-nav'; +import { BlockPreview } from '@/components/block-preview'; +import { getCategory } from '@/lib/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..3aa3f59d --- /dev/null +++ b/apps/pro/src/app/blocks/[category]/category-page.module.scss @@ -0,0 +1,25 @@ +.layout { + display: flex; + padding-top: 60px; + min-height: 100vh; +} + +.content { + flex: 1; + margin-left: 240px; + padding: 32px 40px 64px; + max-width: calc(100% - 240px); +} + +.title { + font-size: 28px; + font-weight: 700; + margin: 0 0 4px; + color: var(--ty-color-text); +} + +.count { + font-size: 14px; + color: var(--ty-color-text-tertiary); + margin: 0 0 32px; +} 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..078b977d --- /dev/null +++ b/apps/pro/src/app/blocks/[category]/page.tsx @@ -0,0 +1,18 @@ +import { notFound } from 'next/navigation'; +import { getCategorySlugs, getCategoryInfo } from '@/lib/categories'; +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..2d938388 --- /dev/null +++ b/apps/pro/src/app/globals.scss @@ -0,0 +1,23 @@ +@use '@tiny-design/tokens/scss/base' as *; +@use 'style/component' as *; + +$pro-font-family: -apple-system, blinkmacsystemfont, 'Segoe UI', roboto, 'Helvetica Neue', arial, + 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', + 'Noto Color Emoji'; + +*, +*::before, +*::after { + box-sizing: border-box; +} + +body { + margin: 0; + padding: 0; + font-family: $pro-font-family; + background: var(--ty-color-bg); + color: var(--ty-color-text); + transition: + background-color 0.2s, + color 0.2s; +} diff --git a/apps/pro/src/app/layout.tsx b/apps/pro/src/app/layout.tsx new file mode 100644 index 00000000..549eb9bf --- /dev/null +++ b/apps/pro/src/app/layout.tsx @@ -0,0 +1,21 @@ +import type { Metadata } from 'next'; +import { ThemeScript } from '../components/theme-script'; +import { SiteHeader } from '../components/layout/site-header'; +import './globals.scss'; + +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..6b7a03ae --- /dev/null +++ b/apps/pro/src/app/page.module.scss @@ -0,0 +1,64 @@ +.main { + padding-top: 60px; + max-width: 1100px; + margin: 0 auto; + padding-left: 24px; + padding-right: 24px; +} + +.hero { + text-align: center; + padding: 64px 0 48px; +} + +.title { + font-size: 40px; + font-weight: 700; + margin: 0 0 12px; + color: var(--ty-color-text); +} + +.subtitle { + font-size: 16px; + color: var(--ty-color-text-secondary); + line-height: 1.6; + margin: 0; +} + +.grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); + gap: 16px; + padding-bottom: 64px; +} + +.card { + display: block; + padding: 20px; + border: 1px solid var(--ty-color-border); + border-radius: 8px; + text-decoration: none; + color: var(--ty-color-text); + background: var(--ty-color-bg-container); + transition: + border-color 0.15s, + box-shadow 0.15s, + background-color 0.2s; + + &:hover { + border-color: var(--ty-color-primary); + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06); + } +} + +.cardTitle { + margin: 0 0 4px; + font-size: 15px; + font-weight: 600; +} + +.cardCount { + margin: 0; + font-size: 13px; + color: var(--ty-color-text-tertiary); +} diff --git a/apps/pro/src/app/page.tsx b/apps/pro/src/app/page.tsx new file mode 100644 index 00000000..8c88100f --- /dev/null +++ b/apps/pro/src/app/page.tsx @@ -0,0 +1,30 @@ +import Link from 'next/link'; +import { getCategories } from '@/lib/categories'; +import styles from './page.module.scss'; + +export default function HomePage() { + const categories = getCategories(); + + return ( +
+
+

Tiny Design Pro

+

+ Beautiful, production-ready UI blocks built with Tiny Design components. +
+ Copy and paste into your projects. +

+
+
+ {categories.map((cat) => ( + +

{cat.label}

+

+ {cat.blockCount} {cat.blockCount === 1 ? 'block' : 'blocks'} +

+ + ))} +
+
+ ); +} 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..310c80cf --- /dev/null +++ b/apps/pro/src/blocks/authentication/sign-in-simple.tsx @@ -0,0 +1,38 @@ +import { Button, Card, Checkbox, Divider, Flex, Form, Input, InputPassword, Typography } from '@tiny-design/react'; + +const { Heading, Text } = Typography; + +export default function SignInSimple() { + return ( + + + + Sign in to your account + + + Don't have an account? Sign up + +
+ + + + + + + + Remember me + Forgot password? + + +
+ or continue with + + + + +
+
+ ); +} 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..5949468e --- /dev/null +++ b/apps/pro/src/blocks/authentication/sign-up-simple.tsx @@ -0,0 +1,43 @@ +import { Button, Card, Checkbox, Flex, Form, Input, InputPassword, Typography } from '@tiny-design/react'; + +const { Heading, Text } = Typography; + +export default function SignUpSimple() { + return ( + + + + Create your account + + + Already have an account? Sign in + +
+ + + + + + + + + + + + + + + + + + + I agree to the Terms of Service and Privacy Policy + + +
+
+
+ ); +} 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..3a00acd7 --- /dev/null +++ b/apps/pro/src/blocks/banners/alert-banner.tsx @@ -0,0 +1,12 @@ +import { Alert, Flex } from '@tiny-design/react'; + +export default function AlertBanner() { + return ( + + + + + + + ); +} 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..54966799 --- /dev/null +++ b/apps/pro/src/blocks/banners/promo-banner.tsx @@ -0,0 +1,22 @@ +import { Alert, Button, Flex, Typography } from '@tiny-design/react'; + +const { Text } = Typography; + +export default function PromoBanner() { + return ( + + + Tiny Design v2.0 is here! Check out the new features and improvements. + + + + } + /> + ); +} diff --git a/apps/pro/src/blocks/card-headers/card-header-with-actions.tsx b/apps/pro/src/blocks/card-headers/card-header-with-actions.tsx new file mode 100644 index 00000000..51a123a5 --- /dev/null +++ b/apps/pro/src/blocks/card-headers/card-header-with-actions.tsx @@ -0,0 +1,21 @@ +import { Button, Card, Flex, Typography } from '@tiny-design/react'; + +const { Text } = Typography; + +export default function CardHeaderWithActions() { + return ( +
+ + + + + } + > + Manage your team members and their roles. + +
+ ); +} 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..18db7a75 --- /dev/null +++ b/apps/pro/src/blocks/cards/profile-card.tsx @@ -0,0 +1,44 @@ +import { Avatar, Button, Card, Divider, Flex, Typography } from '@tiny-design/react'; + +const { Heading, Text } = Typography; + +export default function ProfileCard() { + return ( + + + + + DW + +
+ Di Wang + Software Engineer +
+ + Building beautiful UIs with Tiny Design. Open-source enthusiast. + +
+ + + + 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..665b4b12 --- /dev/null +++ b/apps/pro/src/blocks/cards/stats-card.tsx @@ -0,0 +1,36 @@ +import { Card, Flex, Statistic, Tag, Typography } from '@tiny-design/react'; + +const { Text } = Typography; + +const stats = [ + { title: 'Total Revenue', value: 45231.89, prefix: '$', change: '+20.1%', up: true }, + { title: 'Subscriptions', value: 2350, change: '+180.1%', up: true }, + { title: 'Active Users', value: 12234, change: '+19%', up: true }, + { title: 'Bounce Rate', value: 24.5, suffix: '%', change: '-4.3%', up: false }, +]; + +export default function StatsCard() { + return ( +
+ + {stats.map((s) => ( + + + {s.title} + + + + + + {s.change} from last month + + + ))} + +
+ ); +} diff --git a/apps/pro/src/blocks/dividers/divider-variants.tsx b/apps/pro/src/blocks/dividers/divider-variants.tsx new file mode 100644 index 00000000..2d2d9cc2 --- /dev/null +++ b/apps/pro/src/blocks/dividers/divider-variants.tsx @@ -0,0 +1,33 @@ +import { Divider, Flex, Typography } from '@tiny-design/react'; + +const { Heading, Text } = Typography; + +export default function DividerVariants() { + return ( +
+ Horizontal dividers + Section above the divider + + Default divider above + With Text + Divider with centered text above + Left Text + Divider with left-aligned text above + Right Text + Divider with right-aligned text above + Dashed + Dashed divider above + + Vertical dividers + + Home + + Products + + About + + Contact + +
+ ); +} diff --git a/apps/pro/src/blocks/form-elements/input-variants.tsx b/apps/pro/src/blocks/form-elements/input-variants.tsx new file mode 100644 index 00000000..f074c846 --- /dev/null +++ b/apps/pro/src/blocks/form-elements/input-variants.tsx @@ -0,0 +1,45 @@ +import { Flex, Form, Input, InputPassword, InputNumber, AutoComplete, Textarea, Typography } from '@tiny-design/react'; + +const { Heading } = Typography; + +const suggestions = [ + { value: 'React' }, + { value: 'Vue' }, + { value: 'Angular' }, + { value: 'Svelte' }, + { value: 'Next.js' }, +]; + +export default function InputVariants() { + return ( +
+ Input Types +
+ + + + + + + + + + + + + +