Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
59ba21b
docs(components): extract all component docs into .md via ?raw
shijistar Aug 3, 2026
3ee561f
refactor(storybook): move component docs into per-component folders w…
shijistar Aug 4, 2026
c8eefcf
refactor(storybook): split bilingual docs into locale-specific markdo…
shijistar Aug 4, 2026
72be43c
refactor(storybook): split component docs into per-locale markdown files
shijistar Aug 4, 2026
6355399
docs: improve storybook preview
shijistar Aug 4, 2026
dd36155
refactor(storybook): extract argTypes/story descriptions into i18n re…
shijistar Aug 4, 2026
2314aaf
refactor(storybook): update component descriptions and improve locali…
shijistar Aug 4, 2026
bdb23a1
refactor(storybook): update imports to include storyT for localizatio…
shijistar Aug 4, 2026
b82b5e7
refactor(storybook): enhance description localization in jsdocArgType…
shijistar Aug 4, 2026
5a0b66c
refactor(storybook): move Description column to second position in ap…
shijistar Aug 4, 2026
54b0fc3
docs(storybook): enrich component docs (batch 1: BreakLines, ConfirmA…
shijistar Aug 4, 2026
e76aadd
docs(storybook): enrich all 16 component docs (introduce + api-doc)
shijistar Aug 4, 2026
23ecffc
docs(storybook): add newline between intro and api-doc in component d…
shijistar Aug 5, 2026
25c8e8c
refactor(storybook): remove subcomponents from ConfirmAction story me…
shijistar Aug 5, 2026
582f844
docs(storybook): migrate hooks/utils stories to component-standard ma…
shijistar Aug 5, 2026
e7ae3c5
docs(storybook): update descriptions and fix wording in useStreamDown…
shijistar Aug 5, 2026
5fdff0d
docs: add Storybook stories + bilingual markdown docs for 13 remainin…
shijistar Aug 5, 2026
048e0fb
feat: enhance useMovable hook with viewport support and onMove callback
shijistar Aug 5, 2026
24cdcfd
test(hooks): fix useMovable and useSplitter tests for viewport/enhanc…
shijistar Aug 5, 2026
6608838
docs(story): add Sample code section to all story introduce docs
shijistar Aug 6, 2026
b1a2a85
feat(hooks): simplify useMovable docs and fix empty theme/language ch…
shijistar Aug 6, 2026
e19cdfb
fix(story): rework useRowSelection demo to showcase cross-page selection
shijistar Aug 6, 2026
f3620c4
feat(css): add easy-full-height-table-v6
shijistar Aug 6, 2026
25aa765
feat: allow space in name validator
shijistar Aug 6, 2026
cfd43e1
feat: enhance documentation and improve useValidator and useValidator…
shijistar Aug 6, 2026
6c1b21a
docs(storybook): add bilingual stories for 7 utils modules
shijistar Aug 6, 2026
e5a5bce
docs(storybook): clarify no-arg random() as secure Math.random() repl…
shijistar Aug 7, 2026
cc1574c
feat(utils): split utils into modular implementations, and change Flo…
shijistar Aug 7, 2026
3a63ed9
docs: update changelog for version 2.2.0 with new features, bug fixes…
shijistar Aug 7, 2026
8982601
test(FloatDrawer): replace mouse events with pointer events in dragRe…
shijistar Aug 7, 2026
184dd83
fix(storybook): migrate antd v5 deprecated props to v6 APIs
shijistar Aug 7, 2026
d916cb4
fix(ModalAction): improve destroyOnClose logic
shijistar Aug 7, 2026
ca93b0b
fix(ModalAction): ensure default values for destroyOnClose and destro…
shijistar Aug 7, 2026
6d2a2e3
fix(ModalAction): set default value for destroyOnCloseRef to true
shijistar Aug 7, 2026
be7ed28
fix(randomChars): use custom random function for character selection
shijistar Aug 7, 2026
074a19c
docs(changelog): update changelog
shijistar Aug 7, 2026
e8e1619
fix(useSplitter): replace mouse events with pointer events for better…
shijistar Aug 7, 2026
8893b24
test: align useSplitter/string tests with pointer events and custom r…
shijistar Aug 7, 2026
0972011
test(utils/string): import math utils types for mock module
shijistar Aug 7, 2026
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
478 changes: 475 additions & 3 deletions .storybook/locales/langs/en-US.ts

Large diffs are not rendered by default.

433 changes: 430 additions & 3 deletions .storybook/locales/langs/zh-CN.ts

Large diffs are not rendered by default.

6 changes: 6 additions & 0 deletions .storybook/preview-head.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<style>
#storybook-docs > .sbdocs-wrapper {
padding-top: 3rem;
padding-bottom: 3rem;
}
</style>
124 changes: 26 additions & 98 deletions .storybook/preview.tsx
Original file line number Diff line number Diff line change
@@ -1,29 +1,21 @@
import type { ComponentType, PropsWithChildren } from 'react';
import { useMemo, useState } from 'react';
import type { Control, DocsContainerProps } from '@storybook/addon-docs/blocks';
import {
Controls,
DocsContainer,
Markdown,
Primary,
Stories,
Subtitle,
Title,
useOf,
} from '@storybook/addon-docs/blocks';
import { Controls, DocsContainer, Markdown, Primary, Subtitle, Title, useOf } from '@storybook/addon-docs/blocks';
import type { Preview, ReactRenderer } from '@storybook/react-vite';
import { App as AntdApp, ConfigProvider as AntdConfigProvider, theme as antThemes } from 'antd';
import enUS from 'antd/es/locale/en_US';
import zhCN from 'antd/es/locale/zh_CN';
import { FORCE_RE_RENDER } from 'storybook/internal/core-events';
import type { StoryContext, StoryContextForEnhancers } from 'storybook/internal/csf';
import type { ResolvedModuleExportFromType } from 'storybook/internal/types';
import { addons, useStoryContext } from 'storybook/preview-api';
import { themes } from 'storybook/theming';
import { App as AntdApp, ConfigProvider as AntdConfigProvider, theme as antThemes } from 'antd';
import enUS from 'antd/es/locale/en_US';
import zhCN from 'antd/es/locale/zh_CN';
import ConfigProvider from '../src/components/ConfigProvider';
import { useRefValue } from '../src/hooks';
import type { Langs } from '../src/locales';
import storyI18n, { storyT } from './locales';
import { pickLangDoc } from './utils/doc';
import { getGlobalValueFromUrl } from './utils/global';
import { inferControlFromDocgenType, standardizeJsDocDefaultValue } from './utils/jsdoc';

Expand All @@ -35,7 +27,7 @@ const isPreferDark = window.matchMedia('(prefers-color-scheme: dark)').matches;

const preview: Preview = {
initialGlobals: {
lang: 'en-US',
lang: '',
backgrounds: {
value: themeFromUrl ?? (isPreferDark ? 'dark' : 'light'),
grid: false,
Expand Down Expand Up @@ -85,23 +77,27 @@ const preview: Preview = {
extractComponentDescription: (
component: ComponentType & {
__docgenInfo?: { description?: string };
}
},
) => {
const raw = component?.__docgenInfo?.description ?? '';
let result = stripExampleBlock(raw);
result = removeOtherLang(result);
result = pickLangDoc(result);
return result;
},
page: () => (
<>
<Title />
<Subtitle />
<CustomComponentDescription />
<Primary />
<Controls />
<Stories />
</>
),
page: () => {
const langFromUrl = getGlobalValueFromUrl('lang');
return (
<>
<Title />
<Subtitle />
<CustomComponentDescription />
<h2>{langFromUrl === 'zh-CN' ? '演示' : 'Demo'}</h2>
<Primary />
<Controls />
{/* <Stories /> */}
</>
);
},
},
},
tags: ['autodocs'],
Expand All @@ -117,15 +113,15 @@ const preview: Preview = {
const isDark = theme === 'dark' || (!theme && isPreferDark);
// Reload the page if the theme changes.
useMemo(() => {
if (themeFromGlobal !== prevTheme) {
if (themeFromGlobal && themeFromGlobal !== prevTheme) {
setPrevTheme(themeFromGlobal);
(window.top ?? window.parent ?? window).location.reload();
}
}, [themeFromGlobal, prevTheme]);

// Reload the page if the language changes.
useMemo(() => {
if (storyI18n.language !== lang) {
if (lang && storyI18n.language !== lang) {
storyI18n.changeLanguage(lang).then(() => {
if (viewModeRef.current === 'docs') {
addons.getChannel().emit(FORCE_RE_RENDER);
Expand Down Expand Up @@ -153,74 +149,6 @@ const preview: Preview = {
argTypesEnhancers: [jsdocArgTypesEnhancer],
};

function removeOtherLang(input = '') {
const langFromUrl = getGlobalValueFromUrl('lang');
const currentLang = langFromUrl === 'zh-CN' ? 'zh-CN' : 'en-US';
return keepCurrentLangContent(input, currentLang);
}

function keepCurrentLangContent(input = '', lang: Langs = 'en-US') {
const targetLang = lang === 'zh-CN' ? 'CN' : 'EN';

// Compatible with JSDoc original text (with *) and plain text extracted by docgen
const lines = input.split(/\r?\n/).map((line) => line.replace(/^\s*\*\s?/, ''));

const result: string[] = [];
let blockLang: 'EN' | 'CN' | null = null;
let blockLines: string[] = [];

// Language block: - **EN:** xxx or - **CN:** xxx
const langHeaderReg = /^-\s*\*\*(EN|CN):\*\*\s*(.*)$/;
// JSDoc 标签:@param @returns ...
const jsdocTagReg = /^@\w+/;

const flushBlock = () => {
if (blockLang === targetLang) {
result.push(...blockLines);
}
blockLang = null;
blockLines = [];
};

for (const line of lines) {
const headerMatch = line.match(langHeaderReg);

if (headerMatch) {
if (blockLang) {
flushBlock();
}
const [, langFlag, firstContent = ''] = headerMatch;
blockLang = langFlag as 'EN' | 'CN';
blockLines = firstContent ? [firstContent] : [];
continue;
}

if (blockLang) {
// Encounter @param/@returns indicating the end of the language block, tag content should be retained
if (jsdocTagReg.test(line)) {
flushBlock();
result.push(line);
} else {
blockLines.push(line);
}
continue;
}

// Non-internationalized content remains unchanged.
result.push(line);
}

if (blockLang) {
flushBlock();
}

// 压缩多余空行
return result
.join('\n')
.replace(/\n{3,}/g, '\n\n')
.trim();
}

function stripExampleBlock(input = '') {
return (
input
Expand Down Expand Up @@ -248,7 +176,7 @@ function jsdocArgTypesEnhancer(context: StoryContextForEnhancers) {
control: { type: argType?.control ?? inferred.control } as Control,
options: argType?.options ?? inferred.options,
// The handwritten description will not be overwritten.
description: argType?.description ?? docProp?.description ?? '',
description: pickLangDoc(argType?.description ?? docProp?.description ?? ''),
table: {
...(argType?.table || {}),
defaultValue: {
Expand Down Expand Up @@ -285,7 +213,7 @@ function CustomComponentDescription() {
function processDescription(content: string | undefined) {
const raw = content ?? '';
let result = stripExampleBlock(raw);
result = removeOtherLang(result);
result = pickLangDoc(result);
return result;
}

Expand Down
25 changes: 0 additions & 25 deletions .storybook/stories/components/BreakLines.stories.tsx

This file was deleted.

13 changes: 13 additions & 0 deletions .storybook/stories/components/BreakLines/api-doc.en-US.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
## API

| Prop | Description | Type | Default |
| ----------- | ------------------------------------------------------------------------------------------ | ----------------------------------------------------------- | ------- |
| `value` | Text content to render | `string \| undefined` | - |
| `enabled` | Whether line-break conversion is enabled | `boolean` | `true` |
| `EOL` | The end-of-line character used to split lines | `string` | `'\n'` |
| `tagName` | HTML tag used to render the content. When `false`, content is rendered as a React fragment | `false \| 'span' \| 'div' \| 'i' \| 'pre' \| (string & {})` | `false` |
| `className` | CSS class of the DOM node. Ignored when `tagName` is `false` | `string` | - |

## Notes

When `tagName` is `false` (default), the content is returned as a fragment with `<br/>` between segments, so it can be embedded inline.
13 changes: 13 additions & 0 deletions .storybook/stories/components/BreakLines/api-doc.zh-CN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
## API

| Prop | Description | Type | Default |
| ----------- | ------------------------------------------------------------------------------------------ | ----------------------------------------------------------- | ------- |
| `value` | Text content to render | `string \| undefined` | - |
| `enabled` | Whether line-break conversion is enabled | `boolean` | `true` |
| `EOL` | The end-of-line character used to split lines | `string` | `'\n'` |
| `tagName` | HTML tag used to render the content. When `false`, content is rendered as a React fragment | `false \| 'span' \| 'div' \| 'i' \| 'pre' \| (string & {})` | `false` |
| `className` | CSS class of the DOM node. Ignored when `tagName` is `false` | `string` | - |

## 说明

`tagName` 为 `false`(默认)时,内容以 React fragment 返回,段间用 `<br/>`,可内联嵌入其它文本中。
33 changes: 33 additions & 0 deletions .storybook/stories/components/BreakLines/index.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import type { Meta, StoryObj } from '@storybook/react-vite';
import BreakLines, { type BreakLinesProps } from '../../../../src/components/BreakLines';
import storyI18n, { storyT } from '../../../locales';
import apiDocEN from './api-doc.en-US.md?raw';
import apiDocCN from './api-doc.zh-CN.md?raw';
import introduceEN from './introduce.en-US.md?raw';
import introduceCN from './introduce.zh-CN.md?raw';

const meta: Meta<typeof BreakLines> = {
title: 'Components/BreakLines',
component: BreakLines,
parameters: {
docs: {
description: {
component: storyI18n.language === 'zh-CN' ? `${introduceCN}\n${apiDocCN}` : `${introduceEN}\n${apiDocEN}`,
},
},
},
args: {
value: storyT('storybook.stories.BreakLines.args.value'),
enabled: true,
EOL: '\n',
tagName: 'div',
className: '',
},
argTypes: {},
};
type BreakLinesStoryArgs = BreakLinesProps;

export default meta;
type Story = StoryObj<BreakLinesStoryArgs>;

export const Playground: Story = {};
29 changes: 29 additions & 0 deletions .storybook/stories/components/BreakLines/introduce.en-US.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
Renders plain text with preserved line breaks by splitting with a configurable EOL marker and output tag.

## When to use

Display backend-provided text that contains `\n` (or another EOL marker) and you want it shown as multiple visual lines instead of a single wrapped paragraph. This is common for multi-line addresses, log snippets, poetry, or any string whose line structure carries meaning.

## Key features

- **Line break preservation** — splits the input by a configurable end-of-line character and renders each segment on its own line, joined by `<br />`.
- **Pluggable output tag** — when `tagName` is set, the whole content is wrapped in that HTML element (e.g. `div`, `span`, `pre`); when `tagName` is `false` (default) it renders a React fragment so it can be embedded inline.
- **Toggle at runtime** — the `enabled` prop turns conversion on/off without changing the source value.
- **Custom EOL** — `EOL` lets you split on any delimiter (e.g. `\r\n`, `|`, a custom token), not just `\n`.

## Sample code

```tsx
import { BreakLines } from '@tiny-codes/react-easy';

export function Demo() {
const text = 'Line 1\nLine 2\nLine 3';
return <BreakLines value={text} tagName="div" />;
}
```

## Usage notes

- When `tagName` is `false` (default), the result is a fragment with `<br />` between segments, so it can be placed inside running text without introducing an extra DOM node. `className` is ignored in this mode.
- `EOL` only affects splitting; it is not stripped from the rendered output — each segment keeps its own content.
- Long lines are still subject to the parent container's normal text wrapping; `BreakLines` does not force horizontal overflow.
29 changes: 29 additions & 0 deletions .storybook/stories/components/BreakLines/introduce.zh-CN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
按指定换行符拆分文本并保留换行展示,可自定义输出标签。

## 适用场景

展示后端返回、包含 `\n`(或其它换行符)的文本,希望按原换行分段显示,而不是被当作一整段自动折行。常见于多行地址、日志片段、诗文,或任何"换行本身具有语义"的字符串。

## 核心特性

- **保留换行** —— 按可配置的换行符拆分输入,并将每一段渲染为独立的一行,段间以 `<br />` 连接。
- **可选输出标签** —— 设置 `tagName` 后,整段内容会被包裹到该 HTML 元素中(如 `div`、`span`、`pre`);为 `false`(默认)时以 React 片段渲染,可内联嵌入。
- **运行时可切换** —— `enabled` 属性可在不修改源数据的情况下开关换行转换。
- **自定义换行符** —— `EOL` 支持任意分隔符(如 `\r\n`、`|`、自定义标记),不局限于 `\n`。

## 示例代码

```tsx
import { BreakLines } from '@tiny-codes/react-easy';

export function Demo() {
const text = '第一行\n第二行\n第三行';
return <BreakLines value={text} tagName="div" />;
}
```

## 使用注意

- 当 `tagName` 为 `false`(默认)时,结果为片段且段间使用 `<br />`,可放入行内文本而不会额外产生 DOM 节点;此模式下 `className` 不生效。
- `EOL` 仅影响拆分,不会被从渲染结果中剔除——每段保留自身内容。
- 长行仍受父容器正常文本折行约束,`BreakLines` 不会强制横向溢出。
27 changes: 27 additions & 0 deletions .storybook/stories/components/ColumnSetting/api-doc.en-US.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
## API

| Prop | Description | Type | Default |
| ------------------- | ----------------------------------------------------------------------------------- | ------------------------------------------------------ | ------- |
| `columns` | Column definitions including visibility, ordering, and `disabled` state | `ColumnSettingItem<T>[]` | - |
| `onChange` | Called when the selected columns change; returns columns with `hidden` updated | `(nextColumns: T[]) => void` | - |
| `storageKey` | Local storage key for persisting column settings. If unset, persistence is disabled | `string` | - |
| `renderColumnTitle` | Custom renderer for column titles | `(col: ColumnSettingItem, index: number) => ReactNode` | - |
| `triggerProps` | Props for the button that opens the dropdown | `ButtonProps` | - |
| `dropdownProps` | Props for the dropdown component | `DropdownProps` | - |
| `popupProps` | Props for the dropdown popup container | `React.HTMLAttributes<HTMLDivElement>` | - |
| `checkAllProps` | Props for the "Check All" button | `ButtonProps` | - |
| `resetProps` | Props for the "Reset" button | `ButtonProps` | - |
| `prefixCls` | Custom CSS class prefix | `string` | - |

### `ColumnSettingItem`

Extends Ant Design `ColumnType<T>` and adds:

| Field | Description | Type |
| ---------- | ------------------------------------------- | --------- |
| `disabled` | Disable toggling visibility for this column | `boolean` |

## Notes

At least one column always remains visible — the last visible column's checkbox is disabled to prevent hiding everything.
When `storageKey` is set, the selection is read from `localStorage` on mount and written back on every change.
Loading