@wwog/react 1.1.7 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +43 -9
- package/dist/index.d.mts +123 -26
- package/dist/index.js +1 -1
- package/package.json +2 -3
- package/src/Common/SizeBox.tsx +8 -2
- package/src/ProcessControl/Toggle.tsx +105 -0
- package/src/ProcessControl/index.ts +2 -1
- package/src/hooks/index.ts +1 -0
- package/src/hooks/useControlled.ts +73 -0
- package/src/index.ts +1 -0
package/README.md
CHANGED
|
@@ -141,11 +141,38 @@ function Example({ isActive }) {
|
|
|
141
141
|
- `<True condition={...}>`:当 condition 为 true 时渲染子内容。
|
|
142
142
|
- `<False condition={...}>`:当 condition 为 false 时渲染子内容。
|
|
143
143
|
|
|
144
|
+
#### `<Toggle>`
|
|
145
|
+
|
|
146
|
+
声明式切换组件,在预定义选项中切换值,并通过指定属性传递给子组件,支持自定义切换逻辑。
|
|
147
|
+
|
|
148
|
+
```tsx
|
|
149
|
+
import { Toggle } from "@wwog/react";
|
|
150
|
+
|
|
151
|
+
interface ThemeChildProps {
|
|
152
|
+
theme: string;
|
|
153
|
+
toggleTheme: () => void;
|
|
154
|
+
}
|
|
155
|
+
const ThemeChild: React.FC<ThemeChildProps> = ({ theme, toggleTheme }) => (
|
|
156
|
+
<div onClick={toggleTheme}>当前主题: {theme}</div>
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
<Toggle source="light" options={["light", "dark"]} target="theme" toggleTarget="toggleTheme">
|
|
160
|
+
<ThemeChild />
|
|
161
|
+
</Toggle>
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
- `source`:初始切换值。
|
|
165
|
+
- `options`:可切换的值数组。
|
|
166
|
+
- `target`:传递切换值给子节点的属性名,默认 value。
|
|
167
|
+
- `toggleTarget`:传递切换函数给子节点的属性名,默认 toggle。
|
|
168
|
+
- `next`:自定义切换逻辑函数。
|
|
169
|
+
- `children`:渲染内容。
|
|
170
|
+
|
|
144
171
|
### 通用组件
|
|
145
172
|
|
|
146
173
|
#### `<ArrayRender>`
|
|
147
174
|
|
|
148
|
-
|
|
175
|
+
内部仅单次循环。高效渲染数组数据的工具组件,支持过滤和自定义渲染。
|
|
149
176
|
|
|
150
177
|
```tsx
|
|
151
178
|
import { ArrayRender } from "@wwog/react";
|
|
@@ -181,8 +208,8 @@ function Example({ users }) {
|
|
|
181
208
|
<Pipe
|
|
182
209
|
data={users}
|
|
183
210
|
transform={[
|
|
184
|
-
(data) => data.filter(user => user.active),
|
|
185
|
-
(data) => data.map(user => user.name)
|
|
211
|
+
(data) => data.filter((user) => user.active),
|
|
212
|
+
(data) => data.map((user) => user.name),
|
|
186
213
|
]}
|
|
187
214
|
render={(names) => <div>{names.join(", ")}</div>}
|
|
188
215
|
fallback={<div>No Data</div>}
|
|
@@ -210,15 +237,23 @@ import { Scope } from "@wwog/react";
|
|
|
210
237
|
function Example() {
|
|
211
238
|
return (
|
|
212
239
|
<Scope let={{ count: 1, text: "Hello" }}>
|
|
213
|
-
{({ count, text }) =>
|
|
240
|
+
{({ count, text }) => (
|
|
241
|
+
<div>
|
|
242
|
+
{text} {count}
|
|
243
|
+
</div>
|
|
244
|
+
)}
|
|
214
245
|
</Scope>
|
|
215
246
|
);
|
|
216
247
|
}
|
|
217
248
|
|
|
218
249
|
// 支持函数式 let
|
|
219
|
-
<Scope
|
|
250
|
+
<Scope
|
|
251
|
+
let={(props) => ({ total: props.items.length })}
|
|
252
|
+
props={{ items: [1, 2] }}
|
|
253
|
+
fallback={<div>Empty</div>}
|
|
254
|
+
>
|
|
220
255
|
{({ total }) => <div>Total: {total}</div>}
|
|
221
|
-
</Scope
|
|
256
|
+
</Scope>;
|
|
222
257
|
```
|
|
223
258
|
|
|
224
259
|
- `let`:对象或函数,定义作用域变量。
|
|
@@ -230,6 +265,8 @@ function Example() {
|
|
|
230
265
|
|
|
231
266
|
创建固定尺寸的容器,用于布局调整和间距控制。
|
|
232
267
|
|
|
268
|
+
> v1.1.8: Fixed SizeBox not working in 'flex' layouts, add classname props
|
|
269
|
+
|
|
233
270
|
```tsx
|
|
234
271
|
import { SizeBox } from "@wwog/react";
|
|
235
272
|
|
|
@@ -261,9 +298,6 @@ function Layout() {
|
|
|
261
298
|
- Once:确保内容仅渲染一次,适合初始化。
|
|
262
299
|
- Each:增强列表渲染,支持过滤和排序。
|
|
263
300
|
|
|
264
|
-
|
|
265
301
|
## License
|
|
266
302
|
|
|
267
303
|
MIT
|
|
268
|
-
|
|
269
|
-
|
package/dist/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { ReactNode, FC } from 'react';
|
|
1
|
+
import React$1, { ReactNode, FC, Dispatch } from 'react';
|
|
2
2
|
|
|
3
3
|
interface SwitchProps<T> {
|
|
4
4
|
value: T;
|
|
@@ -7,7 +7,7 @@ interface SwitchProps<T> {
|
|
|
7
7
|
* @description_en optional compare function, default to () => a === b
|
|
8
8
|
**/
|
|
9
9
|
compare?: (a: T, b: T) => boolean;
|
|
10
|
-
children?: React.ReactNode;
|
|
10
|
+
children?: React$1.ReactNode;
|
|
11
11
|
/**
|
|
12
12
|
* @description 是否严格模式,默认 false.建议跟随开发环境变化,严格模式下,会循环所有节点来提供更多的错误提示
|
|
13
13
|
* @description_en strict mode, default to false. It is recommended to follow the development environment changes. In strict mode, all nodes will be looped to provide more error prompts
|
|
@@ -22,58 +22,58 @@ interface SwitchProps<T> {
|
|
|
22
22
|
}
|
|
23
23
|
interface SwitchCaseProps<T> {
|
|
24
24
|
value: T;
|
|
25
|
-
children?: React.ReactNode;
|
|
25
|
+
children?: React$1.ReactNode;
|
|
26
26
|
}
|
|
27
27
|
interface SwitchDefaultProps {
|
|
28
|
-
children?: React.ReactNode;
|
|
28
|
+
children?: React$1.ReactNode;
|
|
29
29
|
}
|
|
30
30
|
/**
|
|
31
31
|
* @description Switch 组件用于根据传入的 value 渲染不同的子组件
|
|
32
32
|
* @description_en The Switch component is used to render different child components based on the passed value
|
|
33
33
|
*/
|
|
34
34
|
declare const Switch: {
|
|
35
|
-
<T>(props: SwitchProps<T>): React.ReactElement | null;
|
|
35
|
+
<T>(props: SwitchProps<T>): React$1.ReactElement | null;
|
|
36
36
|
displayName: string;
|
|
37
37
|
Case: {
|
|
38
|
-
<T>(props: SwitchCaseProps<T>): React.ReactElement;
|
|
38
|
+
<T>(props: SwitchCaseProps<T>): React$1.ReactElement;
|
|
39
39
|
displayName: string;
|
|
40
40
|
};
|
|
41
41
|
Default: {
|
|
42
|
-
(props: SwitchDefaultProps): React.ReactElement;
|
|
42
|
+
(props: SwitchDefaultProps): React$1.ReactElement;
|
|
43
43
|
displayName: string;
|
|
44
44
|
};
|
|
45
45
|
createTyped<T>(): {
|
|
46
|
-
Switch: (props: SwitchProps<T>) => React.ReactElement | null;
|
|
47
|
-
Case: (props: SwitchCaseProps<T>) => React.ReactElement;
|
|
48
|
-
Default: (props: SwitchDefaultProps) => React.ReactElement;
|
|
46
|
+
Switch: (props: SwitchProps<T>) => React$1.ReactElement | null;
|
|
47
|
+
Case: (props: SwitchCaseProps<T>) => React$1.ReactElement;
|
|
48
|
+
Default: (props: SwitchDefaultProps) => React$1.ReactElement;
|
|
49
49
|
};
|
|
50
50
|
};
|
|
51
51
|
|
|
52
52
|
interface IfProps {
|
|
53
53
|
condition: boolean;
|
|
54
|
-
children?: React.ReactNode;
|
|
54
|
+
children?: React$1.ReactNode;
|
|
55
55
|
}
|
|
56
56
|
interface ThenProps {
|
|
57
|
-
children?: React.ReactNode;
|
|
57
|
+
children?: React$1.ReactNode;
|
|
58
58
|
}
|
|
59
59
|
interface ElseProps {
|
|
60
|
-
children?: React.ReactNode;
|
|
60
|
+
children?: React$1.ReactNode;
|
|
61
61
|
}
|
|
62
62
|
interface ElseIfProps {
|
|
63
63
|
condition: boolean;
|
|
64
|
-
children?: React.ReactNode;
|
|
64
|
+
children?: React$1.ReactNode;
|
|
65
65
|
}
|
|
66
66
|
declare const If: {
|
|
67
|
-
({ condition, children, }: IfProps): React.ReactElement | null;
|
|
67
|
+
({ condition, children, }: IfProps): React$1.ReactElement | null;
|
|
68
68
|
displayName: string;
|
|
69
|
-
Then: React.FC<ThenProps>;
|
|
70
|
-
ElseIf: React.FC<ElseIfProps>;
|
|
71
|
-
Else: React.FC<ElseProps>;
|
|
69
|
+
Then: React$1.FC<ThenProps>;
|
|
70
|
+
ElseIf: React$1.FC<ElseIfProps>;
|
|
71
|
+
Else: React$1.FC<ElseProps>;
|
|
72
72
|
createTyped(): {
|
|
73
|
-
If: (props: IfProps) => React.ReactElement | null;
|
|
74
|
-
Then: (props: ThenProps) => React.ReactElement;
|
|
75
|
-
ElseIf: (props: ElseIfProps) => React.ReactElement;
|
|
76
|
-
Else: (props: ElseProps) => React.ReactElement;
|
|
73
|
+
If: (props: IfProps) => React$1.ReactElement | null;
|
|
74
|
+
Then: (props: ThenProps) => React$1.ReactElement;
|
|
75
|
+
ElseIf: (props: ElseIfProps) => React$1.ReactElement;
|
|
76
|
+
Else: (props: ElseProps) => React$1.ReactElement;
|
|
77
77
|
};
|
|
78
78
|
};
|
|
79
79
|
interface TrueProps {
|
|
@@ -190,6 +190,75 @@ interface PipeProps {
|
|
|
190
190
|
*/
|
|
191
191
|
declare const Pipe: FC<PipeProps>;
|
|
192
192
|
|
|
193
|
+
interface ToggleProps<T = boolean> {
|
|
194
|
+
/**
|
|
195
|
+
* @description_en The initial value to toggle.
|
|
196
|
+
* @description_zh 初始切换值。
|
|
197
|
+
*/
|
|
198
|
+
source: T;
|
|
199
|
+
/**
|
|
200
|
+
* @description_en Array of values to toggle between.
|
|
201
|
+
* @description_zh 可切换的值数组。
|
|
202
|
+
*/
|
|
203
|
+
options: T[];
|
|
204
|
+
/**
|
|
205
|
+
* @description_en The prop name to pass the toggled value to children.
|
|
206
|
+
* @description_zh 将切换后的值传递给子节点的属性名。
|
|
207
|
+
* @default 'value'
|
|
208
|
+
*/
|
|
209
|
+
target?: string;
|
|
210
|
+
/**
|
|
211
|
+
* @description_en The prop name to pass the toggle function to children.
|
|
212
|
+
* @description_zh 将切换函数传递给子节点的属性名。
|
|
213
|
+
* @default 'toggle'
|
|
214
|
+
*/
|
|
215
|
+
toggleTarget?: string;
|
|
216
|
+
/**
|
|
217
|
+
* @description_en Function to determine the next value in the toggle sequence.
|
|
218
|
+
* @description_zh 确定切换序列中下一个值的函数。
|
|
219
|
+
* @optional
|
|
220
|
+
*/
|
|
221
|
+
next?: (current: T, options: T[]) => T;
|
|
222
|
+
/**
|
|
223
|
+
* @description_en Content to render, receiving the toggled value and toggle function via the target and toggleTarget props.
|
|
224
|
+
* @description_zh 渲染的内容,通过 target 和 toggleTarget 属性接收切换后的值和切换函数。
|
|
225
|
+
*/
|
|
226
|
+
children: ReactNode;
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* @description_zh 一个声明式组件,用于在预定义选项中切换值并通过指定属性传递给子组件,支持自定义切换逻辑。
|
|
230
|
+
* @description_en A declarative component for toggling between predefined values and passing them to children via specified props, supporting custom toggle logic.
|
|
231
|
+
* @component
|
|
232
|
+
* @example
|
|
233
|
+
* ```tsx
|
|
234
|
+
* interface ThemeChildProps {
|
|
235
|
+
* theme: string;
|
|
236
|
+
* toggleTheme: () => void;
|
|
237
|
+
* }
|
|
238
|
+
* const ThemeChild: FC<ThemeChildProps> = ({ theme, toggleTheme }) => (
|
|
239
|
+
* <div onClick={toggleTheme}>当前主题: {theme}</div>
|
|
240
|
+
* );
|
|
241
|
+
*
|
|
242
|
+
* <Toggle source="light" options={["light", "dark"]} target="theme" toggleTarget="toggleTheme">
|
|
243
|
+
* <ThemeChild />
|
|
244
|
+
* </Toggle>
|
|
245
|
+
* ```
|
|
246
|
+
*
|
|
247
|
+
* @example
|
|
248
|
+
* ```tsx
|
|
249
|
+
* <Toggle
|
|
250
|
+
* source={1}
|
|
251
|
+
* options={[1, 2, 3]}
|
|
252
|
+
* target="value"
|
|
253
|
+
* toggleTarget="toggleValue"
|
|
254
|
+
* next={(current, options) => options[(options.indexOf(current) + 1) % options.length]}
|
|
255
|
+
* >
|
|
256
|
+
* <ValueChild />
|
|
257
|
+
* </Toggle>
|
|
258
|
+
* ```
|
|
259
|
+
*/
|
|
260
|
+
declare const Toggle: <T>(props: ToggleProps<T>) => (string | number | bigint | React$1.ReactElement<unknown, string | React$1.JSXElementConstructor<any>> | Iterable<React$1.ReactNode> | Promise<string | number | bigint | boolean | React$1.ReactPortal | React$1.ReactElement<unknown, string | React$1.JSXElementConstructor<any>> | Iterable<React$1.ReactNode> | null | undefined>)[] | null | undefined;
|
|
261
|
+
|
|
193
262
|
interface SizeBoxProps {
|
|
194
263
|
size?: number | string;
|
|
195
264
|
height?: number | string;
|
|
@@ -197,6 +266,7 @@ interface SizeBoxProps {
|
|
|
197
266
|
h?: number | string;
|
|
198
267
|
w?: number | string;
|
|
199
268
|
children?: ReactNode;
|
|
269
|
+
className?: string;
|
|
200
270
|
}
|
|
201
271
|
/**
|
|
202
272
|
* @description SizeBox 组件用于设置一个固定大小的盒子
|
|
@@ -205,7 +275,7 @@ declare const SizeBox: FC<SizeBoxProps>;
|
|
|
205
275
|
|
|
206
276
|
interface ArrayRenderProps<T> {
|
|
207
277
|
items: T[];
|
|
208
|
-
renderItem: (item: T, index: number) => React.ReactNode;
|
|
278
|
+
renderItem: (item: T, index: number) => React$1.ReactNode;
|
|
209
279
|
filter?: (item: T) => boolean;
|
|
210
280
|
}
|
|
211
281
|
declare function ArrayRender<T>(props: ArrayRenderProps<T>): ReactNode;
|
|
@@ -269,7 +339,34 @@ declare const Scope: FC<ScopeProps>;
|
|
|
269
339
|
* @description 性能优化,替代 React.Children.forEach, 回调可以返回 false 来中断循环
|
|
270
340
|
* @description_en Replace React.Children.forEach, the callback can return false to interrupt the loop
|
|
271
341
|
*/
|
|
272
|
-
declare function childrenLoop(children: React.ReactNode | undefined, callback: (child: React.ReactNode, index: number) => boolean | void): void;
|
|
342
|
+
declare function childrenLoop(children: React$1.ReactNode | undefined, callback: (child: React$1.ReactNode, index: number) => boolean | void): void;
|
|
343
|
+
|
|
344
|
+
interface UseControlledOptions<T> {
|
|
345
|
+
/**
|
|
346
|
+
* @description - 非受控模式下的默认值,会被受控模式下的值覆盖
|
|
347
|
+
* @description_en - Default value in uncontrolled mode, will be overridden by the value in controlled mode
|
|
348
|
+
*/
|
|
349
|
+
defaultValue: T;
|
|
350
|
+
/**
|
|
351
|
+
* @description - 值变更前的回调函数,可用于拦截或修改新值
|
|
352
|
+
* @description_en - Callback function before the value changes, can be used to intercept or modify the new value
|
|
353
|
+
*/
|
|
354
|
+
onBeforeChange?: (newValue: T, currentValue: T) => boolean | void;
|
|
355
|
+
/**
|
|
356
|
+
* @description - 当值发生变化时触发的回调函数名
|
|
357
|
+
* @description_en - Callback function name triggered when the value changes
|
|
358
|
+
* @default - onChange
|
|
359
|
+
*/
|
|
360
|
+
trigger?: string;
|
|
361
|
+
/**
|
|
362
|
+
* @description - 值的属性名
|
|
363
|
+
* @description_en - Property name of the value
|
|
364
|
+
* @default - value
|
|
365
|
+
*/
|
|
366
|
+
valuePropName?: string;
|
|
367
|
+
props: Record<string, any>;
|
|
368
|
+
}
|
|
369
|
+
declare function useControlled<T>(options: UseControlledOptions<T>): [T, Dispatch<React.SetStateAction<T>>];
|
|
273
370
|
|
|
274
|
-
export { ArrayRender, False, If, Pipe, Scope, SizeBox, Switch, True, When, childrenLoop };
|
|
275
|
-
export type { ArrayRenderProps, ElseIfProps, ElseProps, FalseProps, IfProps, PipeProps, ScopeProps, SwitchCaseProps, SwitchDefaultProps, SwitchProps, ThenProps, TrueProps, WhenProps };
|
|
371
|
+
export { ArrayRender, False, If, Pipe, Scope, SizeBox, Switch, Toggle, True, When, childrenLoop, useControlled };
|
|
372
|
+
export type { ArrayRenderProps, ElseIfProps, ElseProps, FalseProps, IfProps, PipeProps, ScopeProps, SwitchCaseProps, SwitchDefaultProps, SwitchProps, ThenProps, ToggleProps, TrueProps, UseControlledOptions, WhenProps };
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import n,{useMemo as
|
|
1
|
+
import n,{useMemo as y,useState as S,Fragment as I,useCallback as T}from"react";function C(e,t){if(e===void 0)return;let l=0;if(Array.isArray(e)){for(const a of e)if(t(a,l++)===!1)break}else t(e,l)}const x=(e,t)=>e===t,g=e=>n.createElement(n.Fragment,null,e.children);g.displayName="Switch_Case";const E=e=>n.createElement(n.Fragment,null,e.children);E.displayName="Switch_Default";const p=e=>{const{value:t,compare:l=x,children:a,strict:o=!1}=e,r=new Set;let c=null,m=null,f=!1;return C(a,(s,d)=>{if(!n.isValidElement(s))throw new Error(`Switch Children only accepts valid React elements at index ${d}`);const i=s.type;if(i.displayName===g.displayName){const u=s.props;if(r.has(u.value))throw new Error(`Switch found duplicate Case value at index ${d}: ${JSON.stringify(u.value)}${o?" (detected in strict mode)":""}`);if(r.add(u.value),!c&&l(t,u.value)&&(c=u.children,o===!1))return!1}else if(i.displayName===E.displayName){if(f)throw new Error(`Switch can only have one Default child at index ${d}`);if(f=!0,m=s.props.children,!o&&c)return!1}else throw new Error(`Switch Children only accepts 'Case' or 'Default' elements, found: ${String(i.displayName||i.name||i)} at index ${d}`)}),n.createElement(n.Fragment,null,c??m)};p.displayName="Switch",p.Case=g,p.Default=E,p.createTyped=function(){return{Switch:p,Case:g,Default:E}};const w=e=>n.createElement(n.Fragment,null,e.children),N=({children:e})=>n.createElement(n.Fragment,null,e),v=e=>n.createElement(n.Fragment,null,e.children);w.displayName="If_Then",N.displayName="If_Else",v.displayName="If_ElseIf";const h=({condition:e,children:t})=>{let l=null,a=null;const o=[];if(n.Children.forEach(t,r=>{if(!n.isValidElement(r))throw new Error("If component only accepts valid React elements");const c=r.type;if(c.displayName===w.displayName){if(l)throw new Error("If component can only have one Then child");l=r}else if(c.displayName===v.displayName)o.push(r);else if(c.displayName===N.displayName){if(a)throw new Error("If component can only have one Else child");a=r}else throw new Error(`If component only accepts 'Then', 'ElseIf', or 'Else' elements as children, found: ${String(c.displayName||c.name||c)}`)}),e)return l?n.createElement(n.Fragment,null,l.props.children):null;for(const r of o)if(r.props.condition)return n.createElement(n.Fragment,null,r.props.children);return a?n.createElement(n.Fragment,null,a.props.children):null};h.displayName="If",h.Then=w,h.ElseIf=v,h.Else=N,h.createTyped=function(){return{If:h,Then:w,ElseIf:v,Else:N}};const k=({condition:e,children:t})=>e?n.createElement(n.Fragment,null,t):null,b=({condition:e,children:t})=>e===!1?n.createElement(n.Fragment,null,t):null,$=({all:e,any:t,none:l,children:a,fallback:o})=>y(()=>(e&&(t||l)&&console.warn('When: Multiple condition types (all, any, none) provided; "all" takes precedence.'),!!(e&&e.length>0&&e.every(Boolean)||t&&t.length>0&&t.some(Boolean)||l&&l.length>0&&l.every(r=>!r))),[e,t,l])?n.createElement(n.Fragment,null,a):n.createElement(n.Fragment,null,o||null),D=({data:e,transform:t,render:l,fallback:a})=>{const o=y(()=>t.reduce((r,c)=>c(r),e),[e,t]);return o==null?n.createElement(n.Fragment,null,a||null):n.createElement(n.Fragment,null,l(o))},O=e=>{const{source:t,options:l,target:a="value",toggleTarget:o="toggle",next:r,children:c}=e,m=y(()=>l.includes(t)?t:l[0],[t,l]),[f,s]=S(m),d=()=>{s(i=>l.length?r?r(i,l):l[(l.indexOf(i)+1)%l.length]||l[0]:i)};return n.Children.map(c,i=>n.isValidElement(i)?n.cloneElement(i,{[a]:f,[o]:d}):i)},_=e=>{const{children:t,h:l,w:a,size:o,height:r,width:c,className:m}=e;return n.createElement("div",{style:{width:o||a||c,height:o||l||r,flexShrink:0},className:m},t)};function A(e){const{items:t,renderItem:l,filter:a}=e;return t?n.createElement(I,null,t.map((o,r)=>a&&!a(o)?null:l(o,r))):(console.error("ArrayRender: items is null"),null)}const B=({let:e,props:t,children:l,fallback:a})=>{const o=y(()=>typeof e=="function"?e(t):e,[e,t]);return!l||!Object.keys(o).length?n.createElement(n.Fragment,null,a||null):n.createElement(n.Fragment,null,l(o))},R="onChange",V="value";function P(e){const{defaultValue:t,onBeforeChange:l,trigger:a=R,valuePropName:o=V,props:r}=e,c=Object.prototype.hasOwnProperty.call(r,o),[m,f]=S(t),s=c?r[o]:m,d=y(()=>r[a],[r,a]),i=T(u=>{const F=typeof u=="function"?u(s):u;l&&l(F,s)===!1||(c||f(F),d&&d(F))},[c,l,a,s,d]);return[s,i]}export{A as ArrayRender,b as False,h as If,D as Pipe,B as Scope,_ as SizeBox,p as Switch,O as Toggle,k as True,$ as When,C as childrenLoop,P as useControlled};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wwog/react",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"keywords": [],
|
|
6
6
|
"author": "wwog",
|
|
@@ -19,8 +19,7 @@
|
|
|
19
19
|
"format": "biome format --write src",
|
|
20
20
|
"check": "biome check --apply src",
|
|
21
21
|
"test": "pnpm check && pnpm test:types && vitest run --coverage",
|
|
22
|
-
"test:types": "tsc --noEmit --skipLibCheck"
|
|
23
|
-
"preinstall": "npx only-allow pnpm"
|
|
22
|
+
"test:types": "tsc --noEmit --skipLibCheck"
|
|
24
23
|
},
|
|
25
24
|
"devDependencies": {
|
|
26
25
|
"@biomejs/biome": "^1.9.4",
|
package/src/Common/SizeBox.tsx
CHANGED
|
@@ -7,16 +7,22 @@ interface SizeBoxProps {
|
|
|
7
7
|
h?: number | string;
|
|
8
8
|
w?: number | string;
|
|
9
9
|
children?: ReactNode;
|
|
10
|
+
className?: string;
|
|
10
11
|
}
|
|
11
12
|
/**
|
|
12
13
|
* @description SizeBox 组件用于设置一个固定大小的盒子
|
|
13
14
|
*/
|
|
14
15
|
export const SizeBox: FC<SizeBoxProps> = (props) => {
|
|
15
|
-
const { children, h, w, size, height, width } = props;
|
|
16
|
+
const { children, h, w, size, height, width, className } = props;
|
|
16
17
|
|
|
17
18
|
const widthValue = size || w || width;
|
|
18
19
|
const heightValue = size || h || height;
|
|
19
20
|
return (
|
|
20
|
-
<div
|
|
21
|
+
<div
|
|
22
|
+
style={{ width: widthValue, height: heightValue, flexShrink: 0 }}
|
|
23
|
+
className={className}
|
|
24
|
+
>
|
|
25
|
+
{children}
|
|
26
|
+
</div>
|
|
21
27
|
);
|
|
22
28
|
};
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import React, { ReactNode, useState, useMemo } from "react";
|
|
2
|
+
|
|
3
|
+
export interface ToggleProps<T = boolean> {
|
|
4
|
+
/**
|
|
5
|
+
* @description_en The initial value to toggle.
|
|
6
|
+
* @description_zh 初始切换值。
|
|
7
|
+
*/
|
|
8
|
+
source: T;
|
|
9
|
+
/**
|
|
10
|
+
* @description_en Array of values to toggle between.
|
|
11
|
+
* @description_zh 可切换的值数组。
|
|
12
|
+
*/
|
|
13
|
+
options: T[];
|
|
14
|
+
/**
|
|
15
|
+
* @description_en The prop name to pass the toggled value to children.
|
|
16
|
+
* @description_zh 将切换后的值传递给子节点的属性名。
|
|
17
|
+
* @default 'value'
|
|
18
|
+
*/
|
|
19
|
+
target?: string;
|
|
20
|
+
/**
|
|
21
|
+
* @description_en The prop name to pass the toggle function to children.
|
|
22
|
+
* @description_zh 将切换函数传递给子节点的属性名。
|
|
23
|
+
* @default 'toggle'
|
|
24
|
+
*/
|
|
25
|
+
toggleTarget?: string;
|
|
26
|
+
/**
|
|
27
|
+
* @description_en Function to determine the next value in the toggle sequence.
|
|
28
|
+
* @description_zh 确定切换序列中下一个值的函数。
|
|
29
|
+
* @optional
|
|
30
|
+
*/
|
|
31
|
+
next?: (current: T, options: T[]) => T;
|
|
32
|
+
/**
|
|
33
|
+
* @description_en Content to render, receiving the toggled value and toggle function via the target and toggleTarget props.
|
|
34
|
+
* @description_zh 渲染的内容,通过 target 和 toggleTarget 属性接收切换后的值和切换函数。
|
|
35
|
+
*/
|
|
36
|
+
children: ReactNode;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* @description_zh 一个声明式组件,用于在预定义选项中切换值并通过指定属性传递给子组件,支持自定义切换逻辑。
|
|
41
|
+
* @description_en A declarative component for toggling between predefined values and passing them to children via specified props, supporting custom toggle logic.
|
|
42
|
+
* @component
|
|
43
|
+
* @example
|
|
44
|
+
* ```tsx
|
|
45
|
+
* interface ThemeChildProps {
|
|
46
|
+
* theme: string;
|
|
47
|
+
* toggleTheme: () => void;
|
|
48
|
+
* }
|
|
49
|
+
* const ThemeChild: FC<ThemeChildProps> = ({ theme, toggleTheme }) => (
|
|
50
|
+
* <div onClick={toggleTheme}>当前主题: {theme}</div>
|
|
51
|
+
* );
|
|
52
|
+
*
|
|
53
|
+
* <Toggle source="light" options={["light", "dark"]} target="theme" toggleTarget="toggleTheme">
|
|
54
|
+
* <ThemeChild />
|
|
55
|
+
* </Toggle>
|
|
56
|
+
* ```
|
|
57
|
+
*
|
|
58
|
+
* @example
|
|
59
|
+
* ```tsx
|
|
60
|
+
* <Toggle
|
|
61
|
+
* source={1}
|
|
62
|
+
* options={[1, 2, 3]}
|
|
63
|
+
* target="value"
|
|
64
|
+
* toggleTarget="toggleValue"
|
|
65
|
+
* next={(current, options) => options[(options.indexOf(current) + 1) % options.length]}
|
|
66
|
+
* >
|
|
67
|
+
* <ValueChild />
|
|
68
|
+
* </Toggle>
|
|
69
|
+
* ```
|
|
70
|
+
*/
|
|
71
|
+
export const Toggle = <T,>(props: ToggleProps<T>) => {
|
|
72
|
+
const {
|
|
73
|
+
source,
|
|
74
|
+
options,
|
|
75
|
+
target = "value",
|
|
76
|
+
toggleTarget = "toggle",
|
|
77
|
+
next,
|
|
78
|
+
children,
|
|
79
|
+
} = props;
|
|
80
|
+
const defaultValue = useMemo(() => {
|
|
81
|
+
return options.includes(source) ? source : options[0];
|
|
82
|
+
}, [source, options]);
|
|
83
|
+
|
|
84
|
+
const [currentValue, setCurrentValue] = useState<T>(defaultValue);
|
|
85
|
+
|
|
86
|
+
const toggle = () => {
|
|
87
|
+
setCurrentValue((prev) => {
|
|
88
|
+
if (!options.length) return prev;
|
|
89
|
+
const nextValue = next
|
|
90
|
+
? next(prev, options)
|
|
91
|
+
: options[(options.indexOf(prev) + 1) % options.length] || options[0];
|
|
92
|
+
return nextValue;
|
|
93
|
+
});
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
return React.Children.map(children, (child) => {
|
|
97
|
+
if (React.isValidElement(child)) {
|
|
98
|
+
return React.cloneElement(child, {
|
|
99
|
+
[target]: currentValue,
|
|
100
|
+
[toggleTarget]: toggle,
|
|
101
|
+
} as { [key: string]: any });
|
|
102
|
+
}
|
|
103
|
+
return child;
|
|
104
|
+
});
|
|
105
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./useControlled";
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { useCallback, useMemo, useState, type Dispatch } from "react";
|
|
2
|
+
|
|
3
|
+
const DEFAULT_TRIGGER = "onChange";
|
|
4
|
+
const DEFAULT_VALUE_PROP_NAME = "value";
|
|
5
|
+
|
|
6
|
+
export interface UseControlledOptions<T> {
|
|
7
|
+
/**
|
|
8
|
+
* @description - 非受控模式下的默认值,会被受控模式下的值覆盖
|
|
9
|
+
* @description_en - Default value in uncontrolled mode, will be overridden by the value in controlled mode
|
|
10
|
+
*/
|
|
11
|
+
defaultValue: T;
|
|
12
|
+
/**
|
|
13
|
+
* @description - 值变更前的回调函数,可用于拦截或修改新值
|
|
14
|
+
* @description_en - Callback function before the value changes, can be used to intercept or modify the new value
|
|
15
|
+
*/
|
|
16
|
+
onBeforeChange?: (newValue: T, currentValue: T) => boolean | void;
|
|
17
|
+
/**
|
|
18
|
+
* @description - 当值发生变化时触发的回调函数名
|
|
19
|
+
* @description_en - Callback function name triggered when the value changes
|
|
20
|
+
* @default - onChange
|
|
21
|
+
*/
|
|
22
|
+
trigger?: string;
|
|
23
|
+
/**
|
|
24
|
+
* @description - 值的属性名
|
|
25
|
+
* @description_en - Property name of the value
|
|
26
|
+
* @default - value
|
|
27
|
+
*/
|
|
28
|
+
valuePropName?: string;
|
|
29
|
+
props: Record<string, any>;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function useControlled<T>(
|
|
33
|
+
options: UseControlledOptions<T>
|
|
34
|
+
): [T, Dispatch<React.SetStateAction<T>>] {
|
|
35
|
+
const {
|
|
36
|
+
defaultValue,
|
|
37
|
+
onBeforeChange,
|
|
38
|
+
trigger = DEFAULT_TRIGGER,
|
|
39
|
+
valuePropName = DEFAULT_VALUE_PROP_NAME,
|
|
40
|
+
props,
|
|
41
|
+
} = options;
|
|
42
|
+
const isControlled = Object.prototype.hasOwnProperty.call(
|
|
43
|
+
props,
|
|
44
|
+
valuePropName
|
|
45
|
+
);
|
|
46
|
+
const [internalValue, setInternalValue] = useState<T>(defaultValue);
|
|
47
|
+
const value = isControlled ? props[valuePropName] : internalValue;
|
|
48
|
+
const onChange = useMemo(() => props[trigger], [props, trigger]);
|
|
49
|
+
|
|
50
|
+
const setValue = useCallback<Dispatch<React.SetStateAction<T>>>(
|
|
51
|
+
(newValue) => {
|
|
52
|
+
const resolvedValue =
|
|
53
|
+
typeof newValue === "function"
|
|
54
|
+
? (newValue as (prev: T) => T)(value)
|
|
55
|
+
: newValue;
|
|
56
|
+
if (onBeforeChange) {
|
|
57
|
+
const shouldProceed = onBeforeChange(resolvedValue, value);
|
|
58
|
+
if (shouldProceed === false) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
if (!isControlled) {
|
|
63
|
+
setInternalValue(resolvedValue);
|
|
64
|
+
}
|
|
65
|
+
if (onChange) {
|
|
66
|
+
onChange(resolvedValue);
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
[isControlled, onBeforeChange, trigger, value, onChange]
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
return [value, setValue];
|
|
73
|
+
}
|
package/src/index.ts
CHANGED