Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions .changeset/config-provider-infra-upgrade.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
---
"@tiny-design/react": minor
---

Upgrade the global configuration infrastructure around `ConfigProvider` and align popup, scroll, and static layer behavior across the component library.

Highlights:

- Reworked `ConfigProvider` to use provider-scoped theme containers instead of mutating global HTML styles.
- Added `ConfigProvider.useConfig()` and `ConfigProvider.config({ holderRender })` support for a wider set of static APIs.
- Added static `Modal.open()` and `Modal.confirm()` APIs that participate in the shared static host pipeline.
- Unified popup container resolution across `Portal`, `Popup`, and `Cascader`.
- Unified target container resolution across `Anchor`, `Sticky`, `BackTop`, `Overlay`, and `Tour`.
- Improved `Sticky` container observation with `ResizeObserver`.
- Improved `useTheme()` to sync with DOM state, localStorage, system preference changes, and cross-tab storage events.
- Added `onCopy` to `CopyToClipboard` so copy results can be observed by consumers.

Notes for consumers:

- `Anchor` and `BackTop` now accept and resolve `Window` as a first-class target container shape.
- `BackTop` now defaults to `ConfigProvider.getTargetContainer()` when present.
- `ConfigProvider` only renders an internal scope node when scoped theme behavior is required.
- Static APIs such as `Message.*`, `Notification.*`, `LoadingBar.*`, and `Modal.open()` can now be wrapped consistently through `ConfigProvider.config({ holderRender })`.
12 changes: 12 additions & 0 deletions .changeset/migrate-scss-to-css-custom-properties.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
"@tiny-design/react": minor
"@tiny-design/tokens": minor
---

Migrate component styles from SCSS variables to CSS custom properties (`--ty-*`) for better runtime theming support.

- Migrate ~80 structural SCSS constants (padding, sizing, transitions) to runtime-customizable CSS custom properties
- Tokenize hardcoded values in Button, Input, Card, Select, and Notification components
- Add component-scoped CSS variable fallback chains (e.g., `--ty-btn-border-radius` falls back to `--ty-border-radius`)
- Add `ThemeConfig` API to `ConfigProvider` for programmatic token and component-level overrides
- Three-level customization: global tokens, component tokens, and scoped instance overrides via CSS
13 changes: 13 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,19 @@ your-component/
3. Export the component from `packages/react/src/index.ts`
4. Add the route in `apps/docs/src/routers.tsx`

### Public Type Exports

Every component module must export its public TypeScript types from its own `index.tsx`.

- If the component has a `types.ts` file, re-export its public types from `packages/react/src/your-component/index.tsx`
- If the component exposes public types from related files, re-export them from the same module barrel as well
- Keep component-level type exports consistent with the runtime component export instead of creating one-off type exceptions elsewhere

The package root barrel must also re-export those public component types from `packages/react/src/index.ts`.

- Do not add root-level type exports for only one or two special-case components
- Do not rely on consumers importing public component types from internal file paths when the type is part of the supported API surface

## Code Style

- TypeScript strict mode is enabled
Expand Down
78 changes: 44 additions & 34 deletions apps/docs/guides/customise-theme.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Tiny UI provides three ways to customise the look and feel:

1. **Theme Editor** — a visual, no-code tool for real-time theming (great for exploration and quick customisation).
2. **Design tokens** — CSS custom properties that power light and dark mode. These are the runtime values every component reads.
3. **SCSS variables** — compile-time variables (sizes, font stacks, border radii, etc.) that can be overridden when you build your own stylesheet.
3. **SCSS constants** — compile-time structural constants (padding, transitions, arrow sizes, etc.) that can be overridden when you build your own stylesheet.

## Theme Editor

Expand All @@ -14,7 +14,7 @@ The built-in [Theme Editor](/theme/theme-editor) lets you visually customise des
- Adjust primary, success, warning, danger, and info colours, background, text, and border colours.
- Tweak typography (font size, line height, font weight) and details (border radius, spacing, sizing).
- Preview changes live on real components.
- Export your customised tokens as CSS or SCSS to use in your project.
- Export your customised tokens as CSS or JSON to use in your project.

Changes are applied instantly via CSS custom properties — no rebuild required.

Expand Down Expand Up @@ -55,7 +55,7 @@ The hook returns:

## Design tokens (CSS custom properties)

Every colour, shadow, and visual state is exposed as a `--ty-*` CSS custom property on `:root`. You can override any token in your own stylesheet:
Every colour, shadow, and visual state is exposed as a `--ty-*` CSS custom property on `:root`. This is the **primary way** to customise Tiny UI. You can override any token in your own stylesheet:

```css
:root {
Expand All @@ -65,6 +65,16 @@ Every colour, shadow, and visual state is exposed as a `--ty-*` CSS custom prope
}
```

For dark mode overrides, target the dark theme selector:

```css
html[data-tiny-theme='dark'] {
--ty-color-primary: #3d9bff;
--ty-color-primary-hover: #66b3ff;
--ty-color-primary-active: #007bff;
}
```

### Commonly used tokens

| Token | Light default | Description |
Expand All @@ -76,16 +86,21 @@ Every colour, shadow, and visual state is exposed as a `--ty-*` CSS custom prope
| `--ty-color-text` | `rgba(0,0,0,0.85)` | Primary text colour |
| `--ty-color-text-secondary` | `rgba(0,0,0,0.65)` | Secondary text colour |
| `--ty-color-border` | `#d9d9d9` | Default border colour |
| `--ty-border-radius` | `2px` | Global border radius |
| `--ty-font-size-base` | `1rem` | Base font size |
| `--ty-height-sm` | `24px` | Small control height |
| `--ty-height-md` | `32px` | Medium control height |
| `--ty-height-lg` | `42px` | Large control height |

The full list of tokens can be found in the source:
Every component also has its own tokens for fine-grained control. For example, Button uses `--ty-btn-default-bg`, `--ty-btn-default-color`, etc. The full list of tokens can be found in the source:
- [Light theme tokens](https://github.com/wangdicoder/tiny-design/blob/master/packages/tokens/scss/themes/_light.scss)
- [Dark theme tokens](https://github.com/wangdicoder/tiny-design/blob/master/packages/tokens/scss/themes/_dark.scss)

## SCSS variables
## SCSS constants

If you import Tiny UI's SCSS source instead of the pre-built CSS, you can override compile-time variables such as sizes, spacing, font stacks, and border radii. Every variable uses the `!default` flag, so your overrides take precedence.
If you import Tiny UI's SCSS source instead of the pre-built CSS, you can override compile-time structural constants such as padding, transitions, and arrow sizes. These are values that don't need to change at runtime.

> **What's `!default`?** A Sass variable with `!default` is only assigned if it hasn't already been defined. By declaring your value *before* importing Tiny UI's styles, your value wins.
Every constant uses the `!default` flag, so your overrides take precedence.

### 1. Install Sass

Expand All @@ -95,13 +110,13 @@ $ npm install sass --save-dev

### 2. Create your overrides file

Create a file, e.g. `theme-variables.scss`. Your overrides **must come before** the Tiny UI import:
Create a file, e.g. `theme-overrides.scss`. Your overrides **must come before** the Tiny UI import:

```scss
// Your overrides
$primary-color: #007bff;
$border-radius: 4px;
$font-size-base: 14px;
// Override structural constants
$btn-padding-md: 0 20px;
$card-body-padding: 20px;
$tooltip-arrow-size: 6px;

// Import Tiny UI styles (applies your overrides via !default)
@use "@tiny-design/react/es/style/index" as *;
Expand All @@ -110,32 +125,27 @@ $font-size-base: 14px;
### 3. Import in your entry file

```js
import './theme-variables.scss';
import './theme-overrides.scss';
```

The full list of SCSS variables can be found in [_variables.scss](https://github.com/wangdicoder/tiny-design/blob/master/packages/tokens/scss/_variables.scss).
The full list of SCSS constants can be found in [_constants.scss](https://github.com/wangdicoder/tiny-design/blob/master/packages/tokens/scss/_constants.scss).

Some commonly overridden variables:
Some commonly overridden constants:

```scss
// Color
$primary-color: #6e41bf !default;

// Font
$font-size-base: 1rem !default;
$font-size-lg: $font-size-base * 1.25 !default;
$font-size-sm: $font-size-base * 0.875 !default;
$font-weight: 400 !default;

// Border
$border-radius: 2px !default;
$border-width: 1px !default;
$border-color: $gray-300 !default;

// Component sizes
$height-sm: 24px !default;
$height-md: 32px !default;
$height-lg: 42px !default;
// Button
$btn-padding-sm: 0 10px !default;
$btn-padding-md: 0 15px !default;
$btn-padding-lg: 0 28px !default;

// Card
$card-header-padding: 13px 16px !default;
$card-body-padding: 16px !default;

// Notification
$notification-width: 380px !default;
```

Please report an issue if the existing list of variables is not enough for you.
> **Note:** Colours, font sizes, border radii, shadows, and all other visual tokens should be customised via CSS custom properties (see above), not SCSS variables. SCSS constants are only for structural values like padding and sizing.

Please report an issue if the existing list of tokens or constants is not enough for you.
78 changes: 44 additions & 34 deletions apps/docs/guides/customise-theme.zh_CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Tiny UI 提供三种方式来定制外观:

1. **主题编辑器** — 一个可视化的实时主题工具,无需编写代码(非常适合探索和快速定制)。
2. **设计令牌(Design tokens)** — 驱动亮色/暗色模式的 CSS 自定义属性,所有组件在运行时读取这些值。
3. **SCSS 变量** — 编译时变量(尺寸、字体、圆角等),可在构建自定义样式表时覆盖。
3. **SCSS 常量** — 编译时结构常量(内边距、过渡动画、箭头尺寸等),可在构建自定义样式表时覆盖。

## 主题编辑器

Expand All @@ -14,7 +14,7 @@ Tiny UI 提供三种方式来定制外观:
- 调整主色、成功色、警告色、危险色和信息色,以及背景色、文本色和边框色。
- 调整排版(字号、行高、字重)和细节(圆角、间距、尺寸)。
- 在真实组件上实时预览更改效果。
- 导出自定义的令牌为 CSS 或 SCSS,在你的项目中使用。
- 导出自定义的令牌为 CSS 或 JSON,在你的项目中使用。

更改通过 CSS 自定义属性即时生效 — 无需重新构建。

Expand Down Expand Up @@ -55,7 +55,7 @@ const App = () => {

## 设计令牌(CSS 自定义属性)

所有颜色、阴影和视觉状态都以 `--ty-*` CSS 自定义属性的形式暴露在 `:root` 上。你可以在自己的样式表中覆盖任意令牌:
所有颜色、阴影和视觉状态都以 `--ty-*` CSS 自定义属性的形式暴露在 `:root` 上。这是定制 Tiny UI 的**主要方式**。你可以在自己的样式表中覆盖任意令牌:

```css
:root {
Expand All @@ -65,6 +65,16 @@ const App = () => {
}
```

暗色模式下的覆盖,使用暗色主题选择器:

```css
html[data-tiny-theme='dark'] {
--ty-color-primary: #3d9bff;
--ty-color-primary-hover: #66b3ff;
--ty-color-primary-active: #007bff;
}
```

### 常用令牌

| 令牌 | 亮色默认值 | 说明 |
Expand All @@ -76,16 +86,21 @@ const App = () => {
| `--ty-color-text` | `rgba(0,0,0,0.85)` | 主文本色 |
| `--ty-color-text-secondary` | `rgba(0,0,0,0.65)` | 次要文本色 |
| `--ty-color-border` | `#d9d9d9` | 默认边框色 |
| `--ty-border-radius` | `2px` | 全局圆角 |
| `--ty-font-size-base` | `1rem` | 基础字号 |
| `--ty-height-sm` | `24px` | 小尺寸控件高度 |
| `--ty-height-md` | `32px` | 中尺寸控件高度 |
| `--ty-height-lg` | `42px` | 大尺寸控件高度 |

完整的令牌列表请参考源码:
每个组件也有自己的令牌,用于细粒度控制。例如,Button 使用 `--ty-btn-default-bg`、`--ty-btn-default-color` 等。完整的令牌列表请参考源码:
- [亮色主题令牌](https://github.com/wangdicoder/tiny-design/blob/master/packages/tokens/scss/themes/_light.scss)
- [暗色主题令牌](https://github.com/wangdicoder/tiny-design/blob/master/packages/tokens/scss/themes/_dark.scss)

## SCSS 变量
## SCSS 常量

如果你引入的是 Tiny UI 的 SCSS 源文件而非预编译的 CSS,可以覆盖编译时变量,如尺寸、间距、字体和圆角等。每个变量都使用了 `!default` 标志,因此你的覆盖值会优先生效
如果你引入的是 Tiny UI 的 SCSS 源文件而非预编译的 CSS,可以覆盖编译时结构常量,如内边距、过渡动画和箭头尺寸。这些是不需要在运行时变化的值

> **什么是 `!default`?** 带有 `!default` 的 Sass 变量仅在尚未定义时才会赋值。在引入 Tiny UI 样式*之前*声明你的值,你的值就会生效
每个常量都使用了 `!default` 标志,因此你的覆盖值会优先生效

### 1. 安装 Sass

Expand All @@ -95,13 +110,13 @@ $ npm install sass --save-dev

### 2. 创建覆盖文件

创建一个文件,例如 `theme-variables.scss`。你的覆盖值**必须写在** Tiny UI 引入语句之前:
创建一个文件,例如 `theme-overrides.scss`。你的覆盖值**必须写在** Tiny UI 引入语句之前:

```scss
// 你的覆盖值
$primary-color: #007bff;
$border-radius: 4px;
$font-size-base: 14px;
// 覆盖结构常量
$btn-padding-md: 0 20px;
$card-body-padding: 20px;
$tooltip-arrow-size: 6px;

// 引入 Tiny UI 样式(通过 !default 应用你的覆盖值)
@use "@tiny-design/react/es/style/index" as *;
Expand All @@ -110,32 +125,27 @@ $font-size-base: 14px;
### 3. 在入口文件中引入

```js
import './theme-variables.scss';
import './theme-overrides.scss';
```

完整的 SCSS 变量列表请参考 [_variables.scss](https://github.com/wangdicoder/tiny-design/blob/master/packages/tokens/scss/_variables.scss)。
完整的 SCSS 常量列表请参考 [_constants.scss](https://github.com/wangdicoder/tiny-design/blob/master/packages/tokens/scss/_constants.scss)。

以下是一些常用的可覆盖变量
以下是一些常用的可覆盖常量

```scss
// 颜色
$primary-color: #6e41bf !default;

// 字体
$font-size-base: 1rem !default;
$font-size-lg: $font-size-base * 1.25 !default;
$font-size-sm: $font-size-base * 0.875 !default;
$font-weight: 400 !default;

// 边框
$border-radius: 2px !default;
$border-width: 1px !default;
$border-color: $gray-300 !default;

// 组件尺寸
$height-sm: 24px !default;
$height-md: 32px !default;
$height-lg: 42px !default;
// 按钮
$btn-padding-sm: 0 10px !default;
$btn-padding-md: 0 15px !default;
$btn-padding-lg: 0 28px !default;

// 卡片
$card-header-padding: 13px 16px !default;
$card-body-padding: 16px !default;

// 通知
$notification-width: 380px !default;
```

如果现有的变量列表无法满足你的需求,请提交 issue 反馈。
> **注意:** 颜色、字号、圆角、阴影等所有视觉令牌应通过 CSS 自定义属性定制(见上方),而非 SCSS 变量。SCSS 常量仅用于内边距、尺寸等结构性值。

如果现有的令牌或常量列表无法满足你的需求,请提交 issue 反馈。
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { useState } from 'react';
import { Button, Modal, Tabs } from '@tiny-design/react';
import { generateCSS, generateSCSS, generateJSON } from '../utils/export-theme';
import { generateCSS, generateJSON } from '../utils/export-theme';

interface ExportDialogProps {
visible: boolean;
Expand Down Expand Up @@ -28,7 +28,6 @@ export const ExportDialog = ({
const [copied, setCopied] = useState(false);

const cssCode = generateCSS(appliedTokens);
const scssCode = generateSCSS(seeds);
const jsonCode = generateJSON(seeds);

const handleCopy = (code: string) => {
Expand Down Expand Up @@ -71,9 +70,6 @@ export const ExportDialog = ({
<Tabs.Panel tab="CSS Variables" tabKey="css">
{renderBlock(cssCode, 'tiny-theme.css', 'text/css')}
</Tabs.Panel>
<Tabs.Panel tab="SCSS Variables" tabKey="scss">
{renderBlock(scssCode, 'tiny-theme.scss', 'text/x-scss')}
</Tabs.Panel>
<Tabs.Panel tab="JSON Tokens" tabKey="json">
{renderBlock(jsonCode, 'tiny-theme.json', 'application/json')}
</Tabs.Panel>
Expand Down
Loading
Loading