@wwog/react 1.2.1 → 1.2.3
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 +46 -27
- package/dist/index.d.mts +86 -45
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/src/Common/DateRender.tsx +72 -0
- package/src/Common/index.ts +1 -0
- package/src/ProcessControl/Toggle.tsx +14 -63
- package/src/utils/index.ts +119 -0
package/README.md
CHANGED
|
@@ -150,25 +150,18 @@ function Example({ isActive }) {
|
|
|
150
150
|
```tsx
|
|
151
151
|
import { Toggle } from "@wwog/react";
|
|
152
152
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
);
|
|
160
|
-
|
|
161
|
-
<Toggle source="light" options={["light", "dark"]} target="theme" toggleTarget="toggleTheme">
|
|
162
|
-
<ThemeChild />
|
|
163
|
-
</Toggle>
|
|
153
|
+
<Toggle
|
|
154
|
+
options={["light", "dark"]}
|
|
155
|
+
render={(value, toggle) => {
|
|
156
|
+
/* xxx */
|
|
157
|
+
}}
|
|
158
|
+
/>;
|
|
164
159
|
```
|
|
165
160
|
|
|
166
161
|
- `options`:可切换的值数组。
|
|
167
162
|
- `index`:默认:0。
|
|
168
|
-
- `target`:传递切换值给子节点的属性名,默认 value。
|
|
169
|
-
- `toggleTarget`:传递切换函数给子节点的属性名,默认 toggle。
|
|
170
163
|
- `next`:自定义切换逻辑函数。
|
|
171
|
-
- `
|
|
164
|
+
- `render`:渲染函数。
|
|
172
165
|
|
|
173
166
|
### 通用组件
|
|
174
167
|
|
|
@@ -263,6 +256,37 @@ function Example() {
|
|
|
263
256
|
- `children`:作用域变量的渲染函数。
|
|
264
257
|
- `fallback`:无内容时的兜底渲染。
|
|
265
258
|
|
|
259
|
+
#### `<DateRender>` (v1.2.3+)
|
|
260
|
+
|
|
261
|
+
一个声明式组件,用于格式化并渲染日期,简单易用且支持自定义格式化。
|
|
262
|
+
|
|
263
|
+
```tsx
|
|
264
|
+
import { DateRender } from "@wwog/react";
|
|
265
|
+
|
|
266
|
+
function Example() {
|
|
267
|
+
return (
|
|
268
|
+
<>
|
|
269
|
+
{/* 使用默认格式化 */}
|
|
270
|
+
<DateRender source="2025-05-06">
|
|
271
|
+
{(formatted) => <div>日期: {formatted}</div>}
|
|
272
|
+
</DateRender>
|
|
273
|
+
|
|
274
|
+
{/* 使用自定义格式化 */}
|
|
275
|
+
<DateRender
|
|
276
|
+
source={new Date()}
|
|
277
|
+
format={(date) => date.toLocaleDateString("zh-CN")}
|
|
278
|
+
>
|
|
279
|
+
{(formatted) => <div>日期: {formatted}</div>}
|
|
280
|
+
</DateRender>
|
|
281
|
+
</>
|
|
282
|
+
);
|
|
283
|
+
}
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
- `source`:要渲染的输入日期(Date 对象、ISO 字符串或时间戳)。
|
|
287
|
+
- `format`:可选的格式化日期的函数,默认使用 `toLocaleString()`。
|
|
288
|
+
- `children`:渲染格式化后日期的函数,接收格式化后的日期作为参数。
|
|
289
|
+
|
|
266
290
|
#### `<SizeBox>`
|
|
267
291
|
|
|
268
292
|
创建固定尺寸的容器,用于布局调整和间距控制。
|
|
@@ -288,25 +312,20 @@ function Layout() {
|
|
|
288
312
|
}
|
|
289
313
|
```
|
|
290
314
|
|
|
291
|
-
### Ideas
|
|
292
|
-
|
|
293
|
-
> 需求不高,但有用的组件
|
|
294
|
-
|
|
295
|
-
- Loop:灵活的迭代渲染,支持数组、对象和范围。
|
|
296
|
-
- Try:封装异步逻辑,处理 Promise 状态。
|
|
297
|
-
- Pick:轻量版值选择渲染,类似枚举匹配。
|
|
298
|
-
- Render:动态渲染函数,简化复杂渲染逻辑。
|
|
299
|
-
- Once:确保内容仅渲染一次,适合初始化。
|
|
300
|
-
- Each:增强列表渲染,支持过滤和排序。
|
|
301
|
-
|
|
302
315
|
### hooks
|
|
303
316
|
|
|
304
|
-
- 一些常用的hooks的封装
|
|
317
|
+
- 一些常用的 hooks 的封装
|
|
305
318
|
|
|
306
|
-
|
|
319
|
+
#### useControlled (v1.2.0+)
|
|
307
320
|
|
|
308
321
|
- 受控组件和非受控组件的切换,方便组件开发
|
|
309
322
|
|
|
323
|
+
### method
|
|
324
|
+
|
|
325
|
+
- 用于部分组件的内部函数,如需要也可使用
|
|
326
|
+
|
|
327
|
+
formatDate 比较标准的格式化时间函数
|
|
328
|
+
childrenLoop 可以中断的子节点遍历,让一些分支流程拥有极致性能
|
|
310
329
|
|
|
311
330
|
## License
|
|
312
331
|
|
package/dist/index.d.mts
CHANGED
|
@@ -202,18 +202,6 @@ interface ToggleProps<T = boolean> {
|
|
|
202
202
|
* @description_zh 可切换的值数组。
|
|
203
203
|
*/
|
|
204
204
|
options: T[];
|
|
205
|
-
/**
|
|
206
|
-
* @description_en The prop name to pass the toggled value to children.
|
|
207
|
-
* @description_zh 将切换后的值传递给子节点的属性名。
|
|
208
|
-
* @default 'value'
|
|
209
|
-
*/
|
|
210
|
-
target?: string;
|
|
211
|
-
/**
|
|
212
|
-
* @description_en The prop name to pass the toggle function to children.
|
|
213
|
-
* @description_zh 将切换函数传递给子节点的属性名。
|
|
214
|
-
* @default 'toggle'
|
|
215
|
-
*/
|
|
216
|
-
toggleTarget?: string;
|
|
217
205
|
/**
|
|
218
206
|
* @description_en Function to determine the next value index in the toggle sequence.
|
|
219
207
|
* @description_zh 确定切换序列中下一个值索引的函数。
|
|
@@ -221,46 +209,26 @@ interface ToggleProps<T = boolean> {
|
|
|
221
209
|
*/
|
|
222
210
|
next?: (curIndex: number, options: T[]) => number;
|
|
223
211
|
/**
|
|
224
|
-
* @description_en
|
|
225
|
-
* @description_zh
|
|
212
|
+
* @description_en Render function, receiving the toggled value and toggle function.
|
|
213
|
+
* @description_zh 渲染函数,接收切换后的值和切换函数。
|
|
226
214
|
*/
|
|
227
|
-
|
|
215
|
+
render: (value: T, toggle: () => void) => ReactNode;
|
|
228
216
|
}
|
|
229
217
|
/**
|
|
230
|
-
* @description_zh
|
|
231
|
-
* @description_en A declarative component for toggling between predefined values and passing them to children via
|
|
218
|
+
* @description_zh 一个声明式组件,用于在预定义选项中切换值并通过 render 函数传递给子组件,支持自定义切换逻辑。
|
|
219
|
+
* @description_en A declarative component for toggling between predefined values and passing them to children via a render function, supporting custom toggle logic.
|
|
232
220
|
* @component
|
|
233
221
|
* @example
|
|
234
222
|
* ```tsx
|
|
235
|
-
* interface ThemeChildProps {
|
|
236
|
-
* theme: string;
|
|
237
|
-
* toggleTheme: () => void;
|
|
238
|
-
* }
|
|
239
|
-
* const ThemeChild: FC<ThemeChildProps> = ({ theme, toggleTheme }) => (
|
|
240
|
-
* <div onClick={toggleTheme}>当前主题: {theme}</div>
|
|
241
|
-
* );
|
|
242
|
-
*
|
|
243
|
-
* <Toggle source="light" options={["light", "dark"]} target="theme" toggleTarget="toggleTheme">
|
|
244
|
-
* <ThemeChild />
|
|
245
|
-
* </Toggle>
|
|
246
|
-
* ```
|
|
247
|
-
*
|
|
248
|
-
* @example
|
|
249
|
-
* ```tsx
|
|
250
223
|
* <Toggle
|
|
251
|
-
*
|
|
252
|
-
*
|
|
253
|
-
*
|
|
254
|
-
*
|
|
255
|
-
*
|
|
256
|
-
* return (currentIndex + 1) % options.length;
|
|
257
|
-
* }}
|
|
258
|
-
* >
|
|
259
|
-
* <ValueChild />
|
|
260
|
-
* </Toggle>
|
|
224
|
+
* options={["light", "dark"]}
|
|
225
|
+
* render={(theme, toggleTheme) => (
|
|
226
|
+
* <div onClick={toggleTheme}>当前主题: {theme}</div>
|
|
227
|
+
* )}
|
|
228
|
+
* />
|
|
261
229
|
* ```
|
|
262
230
|
*/
|
|
263
|
-
declare const Toggle: <T>(props: ToggleProps<T>) =>
|
|
231
|
+
declare const Toggle: <T>(props: ToggleProps<T>) => React$1.ReactNode;
|
|
264
232
|
|
|
265
233
|
interface SizeBoxProps {
|
|
266
234
|
size?: number | string;
|
|
@@ -338,11 +306,84 @@ interface ScopeProps {
|
|
|
338
306
|
*/
|
|
339
307
|
declare const Scope: FC<ScopeProps>;
|
|
340
308
|
|
|
309
|
+
interface DateRenderProps<T = string> {
|
|
310
|
+
/**
|
|
311
|
+
* @description_en The input date to render (Date object, ISO string, or timestamp).
|
|
312
|
+
* @description_zh 要渲染的输入日期(Date 对象、ISO 字符串或时间戳)。
|
|
313
|
+
*/
|
|
314
|
+
source: Date | string | number;
|
|
315
|
+
/**
|
|
316
|
+
* @description_en Function to format the date.
|
|
317
|
+
* @description_zh 格式化日期的函数。
|
|
318
|
+
* @optional
|
|
319
|
+
* @default toLocaleString
|
|
320
|
+
*/
|
|
321
|
+
format?: (date: Date) => T;
|
|
322
|
+
/**
|
|
323
|
+
* @description_en Function to render the formatted date.
|
|
324
|
+
* @description_zh 渲染格式化后日期的函数。
|
|
325
|
+
*/
|
|
326
|
+
children: (formatted: T) => React$1.ReactNode;
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* @description_zh 一个声明式组件,用于格式化并渲染日期,简单易用且支持自定义格式化。
|
|
330
|
+
* @description_en A declarative component for formatting and rendering dates, simple to use with support for custom formatting.
|
|
331
|
+
* @component
|
|
332
|
+
* @template T - The type of the formatted date value
|
|
333
|
+
* @example
|
|
334
|
+
* ```tsx
|
|
335
|
+
* <DateRender source="2025-05-06">
|
|
336
|
+
* {(formatted) => <div>日期: {formatted}</div>}
|
|
337
|
+
* </DateRender>
|
|
338
|
+
* ```
|
|
339
|
+
*
|
|
340
|
+
* @example
|
|
341
|
+
* ```tsx
|
|
342
|
+
* <DateRender<string>
|
|
343
|
+
* source={new Date()}
|
|
344
|
+
* format={(date) => date.toLocaleDateString("zh-CN")}
|
|
345
|
+
* >
|
|
346
|
+
* {(formatted) => <div>日期: {formatted}</div>}
|
|
347
|
+
* </DateRender>
|
|
348
|
+
* ```
|
|
349
|
+
*/
|
|
350
|
+
declare function DateRender<T = string>({ source, format, children, }: DateRenderProps<T>): React$1.JSX.Element | null;
|
|
351
|
+
|
|
341
352
|
/**
|
|
342
353
|
* @description 性能优化,替代 React.Children.forEach, 回调可以返回 false 来中断循环
|
|
343
354
|
* @description_en Replace React.Children.forEach, the callback can return false to interrupt the loop
|
|
344
355
|
*/
|
|
345
356
|
declare function childrenLoop(children: React$1.ReactNode | undefined, callback: (child: React$1.ReactNode, index: number) => boolean | void): void;
|
|
357
|
+
/**
|
|
358
|
+
* @param schema
|
|
359
|
+
* @example
|
|
360
|
+
* YY | 18 | Two-digit year
|
|
361
|
+
* YYYY | 2018 | Four-digit year
|
|
362
|
+
* M | 1-12 | The month, beginning at 1
|
|
363
|
+
* MM | 01-12 | The month, 2-digits
|
|
364
|
+
* MMM | Jan-Dec | The abbreviated month name
|
|
365
|
+
* MMMM | January-December | The full month name
|
|
366
|
+
* D | 1-31 | The day of the month
|
|
367
|
+
* DD | 01-31 | The day of the month, 2-digits
|
|
368
|
+
* d | 0-6 | The day of the week, with Sunday as 0
|
|
369
|
+
* dd | Su-Sa | The min name of the day of the week
|
|
370
|
+
* ddd | Sun-Sat | The short name of the day of the week
|
|
371
|
+
* dddd | Sunday-Saturday | The name of the day of the week
|
|
372
|
+
* H | 0-23 | The hour
|
|
373
|
+
* HH | 00-23 | The hour, 2-digits
|
|
374
|
+
* h | 1-12 | The hour, 12-hour clock
|
|
375
|
+
* hh | 01-12 | The hour, 12-hour clock, 2-digits
|
|
376
|
+
* m | 0-59 | The minute
|
|
377
|
+
* mm | 00-59 | The minute, 2-digits
|
|
378
|
+
* s | 0-59 | The second
|
|
379
|
+
* ss | 00-59 | The second, 2-digits
|
|
380
|
+
* SSS | 000-999 | The millisecond, 3-digits
|
|
381
|
+
* Z | +05:00 | The offset from UTC, ±HH:mm
|
|
382
|
+
* ZZ | +0500 | The offset from UTC, ±HHmm
|
|
383
|
+
* A | AM | PM
|
|
384
|
+
* a | am | pm
|
|
385
|
+
*/
|
|
386
|
+
declare function formatDate(schema: string, date?: Date): string;
|
|
346
387
|
|
|
347
388
|
interface UseControlledOptions<T> {
|
|
348
389
|
/**
|
|
@@ -371,5 +412,5 @@ interface UseControlledOptions<T> {
|
|
|
371
412
|
}
|
|
372
413
|
declare function useControlled<T>(options: UseControlledOptions<T>): [T, Dispatch<React.SetStateAction<T>>];
|
|
373
414
|
|
|
374
|
-
export { ArrayRender, False, If, Pipe, Scope, SizeBox, Switch, Toggle, True, When, childrenLoop, useControlled };
|
|
375
|
-
export type { ArrayRenderProps, ElseIfProps, ElseProps, FalseProps, IfProps, PipeProps, ScopeProps, SwitchCaseProps, SwitchDefaultProps, SwitchProps, ThenProps, ToggleProps, TrueProps, UseControlledOptions, WhenProps };
|
|
415
|
+
export { ArrayRender, DateRender, False, If, Pipe, Scope, SizeBox, Switch, Toggle, True, When, childrenLoop, formatDate, useControlled };
|
|
416
|
+
export type { ArrayRenderProps, DateRenderProps, ElseIfProps, ElseProps, FalseProps, IfProps, PipeProps, ScopeProps, SwitchCaseProps, SwitchDefaultProps, SwitchProps, ThenProps, ToggleProps, TrueProps, UseControlledOptions, WhenProps };
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import
|
|
1
|
+
import r,{useMemo as h,useEffect as J,useState as b,Fragment as O,useCallback as H}from"react";function I(e,t){if(e===void 0)return;let n=0;if(Array.isArray(e)){for(const l of e)if(t(l,n++)===!1)break}else t(e,n)}function R(e,t){const n=t||new Date,l=n.getFullYear(),a=n.getMonth()+1,o=n.getDate(),i=n.getHours(),c=n.getMinutes(),u=n.getSeconds(),d=n.getMilliseconds(),s=n.getDay(),f=["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],m=["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],y=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],T=["January","February","March","April","May","June","July","August","September","October","November","December"],M=s===0?6:s-1,Y=m[M],v=f[M],D=a-1,C=T[D],A=y[D],x={YY:l.toString().slice(2),YYYY:l.toString(),M:a.toString(),MM:a.toString().padStart(2,"0"),MMM:A,MMMM:C,D:o.toString(),DD:o.toString().padStart(2,"0"),d:s.toString(),dd:v,ddd:v,dddd:Y,H:i.toString(),HH:i.toString().padStart(2,"0"),h:(i%12).toString(),hh:(i%12).toString().padStart(2,"0"),m:c.toString(),mm:c.toString().padStart(2,"0"),s:u.toString(),ss:u.toString().padStart(2,"0"),SSS:d.toString().padStart(3,"0"),Z:"+08:00",ZZ:"+0800",A:i<12?"AM":"PM",a:i<12?"am":"pm"};return e.replace(/YYYY|YY|M{1,4}|D{1,2}|d{1,4}|H{1,2}|h{1,2}|m{1,2}|s{1,2}|SSS|Z{1,2}|A|a/g,k=>x[k])}const _=(e,t)=>e===t,S=e=>r.createElement(r.Fragment,null,e.children);S.displayName="Switch_Case";const E=e=>r.createElement(r.Fragment,null,e.children);E.displayName="Switch_Default";const g=e=>{const{value:t,compare:n=_,children:l,strict:a=!1}=e,o=new Set;let i=null,c=null,u=!1;return I(l,(d,s)=>{if(!r.isValidElement(d))throw new Error(`Switch Children only accepts valid React elements at index ${s}`);const f=d.type;if(f.displayName===S.displayName){const m=d.props;if(o.has(m.value))throw new Error(`Switch found duplicate Case value at index ${s}: ${JSON.stringify(m.value)}${a?" (detected in strict mode)":""}`);if(o.add(m.value),!i&&n(t,m.value)&&(i=m.children,a===!1))return!1}else if(f.displayName===E.displayName){if(u)throw new Error(`Switch can only have one Default child at index ${s}`);if(u=!0,c=d.props.children,!a&&i)return!1}else throw new Error(`Switch Children only accepts 'Case' or 'Default' elements, found: ${String(f.displayName||f.name||f)} at index ${s}`)}),r.createElement(r.Fragment,null,i??c)};g.displayName="Switch",g.Case=S,g.Default=E,g.createTyped=function(){return{Switch:g,Case:S,Default:E}};const w=e=>r.createElement(r.Fragment,null,e.children),N=({children:e})=>r.createElement(r.Fragment,null,e),F=e=>r.createElement(r.Fragment,null,e.children);w.displayName="If_Then",N.displayName="If_Else",F.displayName="If_ElseIf";const p=({condition:e,children:t})=>{let n=null,l=null;const a=[];if(r.Children.forEach(t,o=>{if(!r.isValidElement(o))throw new Error("If component only accepts valid React elements");const i=o.type;if(i.displayName===w.displayName){if(n)throw new Error("If component can only have one Then child");n=o}else if(i.displayName===F.displayName)a.push(o);else if(i.displayName===N.displayName){if(l)throw new Error("If component can only have one Else child");l=o}else throw new Error(`If component only accepts 'Then', 'ElseIf', or 'Else' elements as children, found: ${String(i.displayName||i.name||i)}`)}),e)return n?r.createElement(r.Fragment,null,n.props.children):null;for(const o of a)if(o.props.condition)return r.createElement(r.Fragment,null,o.props.children);return l?r.createElement(r.Fragment,null,l.props.children):null};p.displayName="If",p.Then=w,p.ElseIf=F,p.Else=N,p.createTyped=function(){return{If:p,Then:w,ElseIf:F,Else:N}};const B=({condition:e,children:t})=>e?r.createElement(r.Fragment,null,t):null,P=({condition:e,children:t})=>e===!1?r.createElement(r.Fragment,null,t):null,W=({all:e,any:t,none:n,children:l,fallback:a})=>h(()=>(e&&(t||n)&&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)||n&&n.length>0&&n.every(o=>!o))),[e,t,n])?r.createElement(r.Fragment,null,l):r.createElement(r.Fragment,null,a||null),Z=({data:e,transform:t,render:n,fallback:l})=>{const a=h(()=>t.reduce((o,i)=>i(o),e),[e,t]);return a==null?r.createElement(r.Fragment,null,l||null):r.createElement(r.Fragment,null,n(a))},V=e=>{const{index:t=0,options:n,next:l,render:a}=e;J(()=>{if(n.length<t+1)throw new Error(`Index ${t} is out of bounds for options array of length ${n.length}. Defaulting to first option.`)},[t,n]);const[o,i]=b(t),c=()=>{i(u=>n.length?l?l(u,n):(u+1)%n.length:u)};return a(n[o],c)},$=e=>{const{children:t,h:n,w:l,size:a,height:o,width:i,className:c}=e;return r.createElement("div",{style:{width:a||l||i,height:a||n||o,flexShrink:0},className:c},t)};function j(e){const{items:t,renderItem:n,filter:l}=e;return t?r.createElement(O,null,t.map((a,o)=>l&&!l(a)?null:n(a,o))):(console.error("ArrayRender: items is null"),null)}const z=({let:e,props:t,children:n,fallback:l})=>{const a=h(()=>typeof e=="function"?e(t):e,[e,t]);return!n||!Object.keys(a).length?r.createElement(r.Fragment,null,l||null):r.createElement(r.Fragment,null,n(a))};function L({source:e,format:t,children:n}){const l=h(()=>{if(e instanceof Date)return e;if(typeof e=="string"||typeof e=="number"){const o=new Date(e);return isNaN(o.getTime())?null:o}return null},[e]),a=h(()=>l?t?t(l):l.toLocaleString():null,[l,t]);return!a||!n?null:r.createElement(r.Fragment,null,n(a))}const q="onChange",G="value";function K(e){const{defaultValue:t,onBeforeChange:n,trigger:l=q,valuePropName:a=G,props:o}=e,i=Object.prototype.hasOwnProperty.call(o,a),[c,u]=b(t),d=i?o[a]:c,s=h(()=>o[l],[o,l]),f=H(m=>{const y=typeof m=="function"?m(d):m;n&&n(y,d)===!1||(i||u(y),s&&s(y))},[i,n,l,d,s]);return[d,f]}export{j as ArrayRender,L as DateRender,P as False,p as If,Z as Pipe,z as Scope,$ as SizeBox,g as Switch,V as Toggle,B as True,W as When,I as childrenLoop,R as formatDate,K as useControlled};
|
package/package.json
CHANGED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import React, { useMemo } from "react";
|
|
2
|
+
|
|
3
|
+
export interface DateRenderProps<T = string> {
|
|
4
|
+
/**
|
|
5
|
+
* @description_en The input date to render (Date object, ISO string, or timestamp).
|
|
6
|
+
* @description_zh 要渲染的输入日期(Date 对象、ISO 字符串或时间戳)。
|
|
7
|
+
*/
|
|
8
|
+
source: Date | string | number;
|
|
9
|
+
/**
|
|
10
|
+
* @description_en Function to format the date.
|
|
11
|
+
* @description_zh 格式化日期的函数。
|
|
12
|
+
* @optional
|
|
13
|
+
* @default toLocaleString
|
|
14
|
+
*/
|
|
15
|
+
format?: (date: Date) => T;
|
|
16
|
+
/**
|
|
17
|
+
* @description_en Function to render the formatted date.
|
|
18
|
+
* @description_zh 渲染格式化后日期的函数。
|
|
19
|
+
*/
|
|
20
|
+
children: (formatted: T) => React.ReactNode;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* @description_zh 一个声明式组件,用于格式化并渲染日期,简单易用且支持自定义格式化。
|
|
25
|
+
* @description_en A declarative component for formatting and rendering dates, simple to use with support for custom formatting.
|
|
26
|
+
* @component
|
|
27
|
+
* @template T - The type of the formatted date value
|
|
28
|
+
* @example
|
|
29
|
+
* ```tsx
|
|
30
|
+
* <DateRender source="2025-05-06">
|
|
31
|
+
* {(formatted) => <div>日期: {formatted}</div>}
|
|
32
|
+
* </DateRender>
|
|
33
|
+
* ```
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* ```tsx
|
|
37
|
+
* <DateRender<string>
|
|
38
|
+
* source={new Date()}
|
|
39
|
+
* format={(date) => date.toLocaleDateString("zh-CN")}
|
|
40
|
+
* >
|
|
41
|
+
* {(formatted) => <div>日期: {formatted}</div>}
|
|
42
|
+
* </DateRender>
|
|
43
|
+
* ```
|
|
44
|
+
*/
|
|
45
|
+
export function DateRender<T = string>({
|
|
46
|
+
source,
|
|
47
|
+
format,
|
|
48
|
+
children,
|
|
49
|
+
}: DateRenderProps<T>) {
|
|
50
|
+
const date = useMemo(() => {
|
|
51
|
+
if (source instanceof Date) return source;
|
|
52
|
+
if (typeof source === "string" || typeof source === "number") {
|
|
53
|
+
const parsed = new Date(source);
|
|
54
|
+
return isNaN(parsed.getTime()) ? null : parsed;
|
|
55
|
+
}
|
|
56
|
+
return null;
|
|
57
|
+
}, [source]);
|
|
58
|
+
|
|
59
|
+
const formattedDate = useMemo(() => {
|
|
60
|
+
if (!date) return null;
|
|
61
|
+
if (format) {
|
|
62
|
+
return format(date);
|
|
63
|
+
}
|
|
64
|
+
return date.toLocaleString() as unknown as T;
|
|
65
|
+
}, [date, format]);
|
|
66
|
+
|
|
67
|
+
if (!formattedDate || !children) {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return <>{children(formattedDate)}</>;
|
|
72
|
+
}
|
package/src/Common/index.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, {
|
|
1
|
+
import React, { useState, useEffect, ReactNode } from "react";
|
|
2
2
|
|
|
3
3
|
export interface ToggleProps<T = boolean> {
|
|
4
4
|
/**
|
|
@@ -12,18 +12,6 @@ export interface ToggleProps<T = boolean> {
|
|
|
12
12
|
* @description_zh 可切换的值数组。
|
|
13
13
|
*/
|
|
14
14
|
options: T[];
|
|
15
|
-
/**
|
|
16
|
-
* @description_en The prop name to pass the toggled value to children.
|
|
17
|
-
* @description_zh 将切换后的值传递给子节点的属性名。
|
|
18
|
-
* @default 'value'
|
|
19
|
-
*/
|
|
20
|
-
target?: string;
|
|
21
|
-
/**
|
|
22
|
-
* @description_en The prop name to pass the toggle function to children.
|
|
23
|
-
* @description_zh 将切换函数传递给子节点的属性名。
|
|
24
|
-
* @default 'toggle'
|
|
25
|
-
*/
|
|
26
|
-
toggleTarget?: string;
|
|
27
15
|
/**
|
|
28
16
|
* @description_en Function to determine the next value index in the toggle sequence.
|
|
29
17
|
* @description_zh 确定切换序列中下一个值索引的函数。
|
|
@@ -31,54 +19,32 @@ export interface ToggleProps<T = boolean> {
|
|
|
31
19
|
*/
|
|
32
20
|
next?: (curIndex: number, options: T[]) => number;
|
|
33
21
|
/**
|
|
34
|
-
* @description_en
|
|
35
|
-
* @description_zh
|
|
22
|
+
* @description_en Render function, receiving the toggled value and toggle function.
|
|
23
|
+
* @description_zh 渲染函数,接收切换后的值和切换函数。
|
|
36
24
|
*/
|
|
37
|
-
|
|
25
|
+
render: (value: T, toggle: () => void) => ReactNode;
|
|
38
26
|
}
|
|
39
27
|
|
|
40
28
|
/**
|
|
41
|
-
* @description_zh
|
|
42
|
-
* @description_en A declarative component for toggling between predefined values and passing them to children via
|
|
29
|
+
* @description_zh 一个声明式组件,用于在预定义选项中切换值并通过 render 函数传递给子组件,支持自定义切换逻辑。
|
|
30
|
+
* @description_en A declarative component for toggling between predefined values and passing them to children via a render function, supporting custom toggle logic.
|
|
43
31
|
* @component
|
|
44
32
|
* @example
|
|
45
33
|
* ```tsx
|
|
46
|
-
* interface ThemeChildProps {
|
|
47
|
-
* theme: string;
|
|
48
|
-
* toggleTheme: () => void;
|
|
49
|
-
* }
|
|
50
|
-
* const ThemeChild: FC<ThemeChildProps> = ({ theme, toggleTheme }) => (
|
|
51
|
-
* <div onClick={toggleTheme}>当前主题: {theme}</div>
|
|
52
|
-
* );
|
|
53
|
-
*
|
|
54
|
-
* <Toggle source="light" options={["light", "dark"]} target="theme" toggleTarget="toggleTheme">
|
|
55
|
-
* <ThemeChild />
|
|
56
|
-
* </Toggle>
|
|
57
|
-
* ```
|
|
58
|
-
*
|
|
59
|
-
* @example
|
|
60
|
-
* ```tsx
|
|
61
34
|
* <Toggle
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
* return (currentIndex + 1) % options.length;
|
|
68
|
-
* }}
|
|
69
|
-
* >
|
|
70
|
-
* <ValueChild />
|
|
71
|
-
* </Toggle>
|
|
35
|
+
* options={["light", "dark"]}
|
|
36
|
+
* render={(theme, toggleTheme) => (
|
|
37
|
+
* <div onClick={toggleTheme}>当前主题: {theme}</div>
|
|
38
|
+
* )}
|
|
39
|
+
* />
|
|
72
40
|
* ```
|
|
73
41
|
*/
|
|
74
42
|
export const Toggle = <T,>(props: ToggleProps<T>) => {
|
|
75
43
|
const {
|
|
76
44
|
index = 0,
|
|
77
45
|
options,
|
|
78
|
-
target = "value",
|
|
79
|
-
toggleTarget = "toggle",
|
|
80
46
|
next,
|
|
81
|
-
|
|
47
|
+
render,
|
|
82
48
|
} = props;
|
|
83
49
|
|
|
84
50
|
useEffect(() => {
|
|
@@ -90,27 +56,12 @@ export const Toggle = <T,>(props: ToggleProps<T>) => {
|
|
|
90
56
|
}, [index, options]);
|
|
91
57
|
|
|
92
58
|
const [curIndex, setCurIndex] = useState<number>(index);
|
|
93
|
-
const nextIndex = () => {
|
|
94
|
-
if (next) {
|
|
95
|
-
return next(curIndex, options);
|
|
96
|
-
}
|
|
97
|
-
return (curIndex + 1) % options.length;
|
|
98
|
-
};
|
|
99
|
-
|
|
100
59
|
const toggle = () => {
|
|
101
60
|
setCurIndex((prev) => {
|
|
102
61
|
if (!options.length) return prev;
|
|
103
|
-
return
|
|
62
|
+
return next ? next(prev, options) : (prev + 1) % options.length;
|
|
104
63
|
});
|
|
105
64
|
};
|
|
106
65
|
|
|
107
|
-
return
|
|
108
|
-
if (React.isValidElement(child)) {
|
|
109
|
-
return React.cloneElement(child, {
|
|
110
|
-
[target]: curIndex,
|
|
111
|
-
[toggleTarget]: toggle,
|
|
112
|
-
} as { [key: string]: any });
|
|
113
|
-
}
|
|
114
|
-
return child;
|
|
115
|
-
});
|
|
66
|
+
return render(options[curIndex], toggle);
|
|
116
67
|
};
|
package/src/utils/index.ts
CHANGED
|
@@ -20,3 +20,122 @@ export function childrenLoop(
|
|
|
20
20
|
callback(children, index);
|
|
21
21
|
}
|
|
22
22
|
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* @param schema
|
|
26
|
+
* @example
|
|
27
|
+
* YY | 18 | Two-digit year
|
|
28
|
+
* YYYY | 2018 | Four-digit year
|
|
29
|
+
* M | 1-12 | The month, beginning at 1
|
|
30
|
+
* MM | 01-12 | The month, 2-digits
|
|
31
|
+
* MMM | Jan-Dec | The abbreviated month name
|
|
32
|
+
* MMMM | January-December | The full month name
|
|
33
|
+
* D | 1-31 | The day of the month
|
|
34
|
+
* DD | 01-31 | The day of the month, 2-digits
|
|
35
|
+
* d | 0-6 | The day of the week, with Sunday as 0
|
|
36
|
+
* dd | Su-Sa | The min name of the day of the week
|
|
37
|
+
* ddd | Sun-Sat | The short name of the day of the week
|
|
38
|
+
* dddd | Sunday-Saturday | The name of the day of the week
|
|
39
|
+
* H | 0-23 | The hour
|
|
40
|
+
* HH | 00-23 | The hour, 2-digits
|
|
41
|
+
* h | 1-12 | The hour, 12-hour clock
|
|
42
|
+
* hh | 01-12 | The hour, 12-hour clock, 2-digits
|
|
43
|
+
* m | 0-59 | The minute
|
|
44
|
+
* mm | 00-59 | The minute, 2-digits
|
|
45
|
+
* s | 0-59 | The second
|
|
46
|
+
* ss | 00-59 | The second, 2-digits
|
|
47
|
+
* SSS | 000-999 | The millisecond, 3-digits
|
|
48
|
+
* Z | +05:00 | The offset from UTC, ±HH:mm
|
|
49
|
+
* ZZ | +0500 | The offset from UTC, ±HHmm
|
|
50
|
+
* A | AM | PM
|
|
51
|
+
* a | am | pm
|
|
52
|
+
*/
|
|
53
|
+
export function formatDate(schema: string, date?: Date): string {
|
|
54
|
+
const d = date || new Date();
|
|
55
|
+
const year = d.getFullYear();
|
|
56
|
+
const month = d.getMonth() + 1;
|
|
57
|
+
const day = d.getDate();
|
|
58
|
+
const hour = d.getHours();
|
|
59
|
+
const minute = d.getMinutes();
|
|
60
|
+
const second = d.getSeconds();
|
|
61
|
+
const millisecond = d.getMilliseconds();
|
|
62
|
+
const week = d.getDay();
|
|
63
|
+
const weekName = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
|
|
64
|
+
const weekFullName = [
|
|
65
|
+
"Sunday",
|
|
66
|
+
"Monday",
|
|
67
|
+
"Tuesday",
|
|
68
|
+
"Wednesday",
|
|
69
|
+
"Thursday",
|
|
70
|
+
"Friday",
|
|
71
|
+
"Saturday",
|
|
72
|
+
];
|
|
73
|
+
const monthName = [
|
|
74
|
+
"Jan",
|
|
75
|
+
"Feb",
|
|
76
|
+
"Mar",
|
|
77
|
+
"Apr",
|
|
78
|
+
"May",
|
|
79
|
+
"Jun",
|
|
80
|
+
"Jul",
|
|
81
|
+
"Aug",
|
|
82
|
+
"Sep",
|
|
83
|
+
"Oct",
|
|
84
|
+
"Nov",
|
|
85
|
+
"Dec",
|
|
86
|
+
];
|
|
87
|
+
const monthFullName = [
|
|
88
|
+
"January",
|
|
89
|
+
"February",
|
|
90
|
+
"March",
|
|
91
|
+
"April",
|
|
92
|
+
"May",
|
|
93
|
+
"June",
|
|
94
|
+
"July",
|
|
95
|
+
"August",
|
|
96
|
+
"September",
|
|
97
|
+
"October",
|
|
98
|
+
"November",
|
|
99
|
+
"December",
|
|
100
|
+
];
|
|
101
|
+
const weekIndex = week === 0 ? 6 : week - 1;
|
|
102
|
+
const weekFull = weekFullName[weekIndex]!;
|
|
103
|
+
const weekShort = weekName[weekIndex]!;
|
|
104
|
+
const monthIndex = month - 1;
|
|
105
|
+
const monthFull = monthFullName[monthIndex]!;
|
|
106
|
+
const monthShort = monthName[monthIndex]!;
|
|
107
|
+
const map: Record<string, string> = {
|
|
108
|
+
YY: year.toString().slice(2),
|
|
109
|
+
YYYY: year.toString(),
|
|
110
|
+
M: month.toString(),
|
|
111
|
+
MM: month.toString().padStart(2, "0"),
|
|
112
|
+
MMM: monthShort,
|
|
113
|
+
MMMM: monthFull,
|
|
114
|
+
D: day.toString(),
|
|
115
|
+
DD: day.toString().padStart(2, "0"),
|
|
116
|
+
d: week.toString(),
|
|
117
|
+
dd: weekShort,
|
|
118
|
+
ddd: weekShort,
|
|
119
|
+
dddd: weekFull,
|
|
120
|
+
H: hour.toString(),
|
|
121
|
+
HH: hour.toString().padStart(2, "0"),
|
|
122
|
+
h: (hour % 12).toString(),
|
|
123
|
+
hh: (hour % 12).toString().padStart(2, "0"),
|
|
124
|
+
m: minute.toString(),
|
|
125
|
+
mm: minute.toString().padStart(2, "0"),
|
|
126
|
+
s: second.toString(),
|
|
127
|
+
ss: second.toString().padStart(2, "0"),
|
|
128
|
+
SSS: millisecond.toString().padStart(3, "0"),
|
|
129
|
+
Z: "+08:00",
|
|
130
|
+
ZZ: "+0800",
|
|
131
|
+
A: hour < 12 ? "AM" : "PM",
|
|
132
|
+
a: hour < 12 ? "am" : "pm",
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
return schema.replace(
|
|
136
|
+
/YYYY|YY|M{1,4}|D{1,2}|d{1,4}|H{1,2}|h{1,2}|m{1,2}|s{1,2}|SSS|Z{1,2}|A|a/g,
|
|
137
|
+
(match) => {
|
|
138
|
+
return map[match]!;
|
|
139
|
+
}
|
|
140
|
+
);
|
|
141
|
+
}
|