@wwog/react 1.2.4 → 1.2.6

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 CHANGED
@@ -312,6 +312,48 @@ function Layout() {
312
312
  }
313
313
  ```
314
314
 
315
+ #### `<ClassName>` (v1.2.5+)
316
+
317
+ 用于将 CSS 类名分类编写的组件,内置类似`clsx`的功能,并且可以去除重复的 className。
318
+
319
+ ```tsx
320
+ import { ClassName } from "@wwog/react";
321
+
322
+ function Example() {
323
+ return (
324
+ <ClassName
325
+ className={{
326
+ base: "p-2 bg-white",
327
+ hover: "hover:bg-gray-100",
328
+ active: "active:bg-gray-200",
329
+ focus: "focus:ring-2",
330
+ other: "button",
331
+ }}
332
+ >
333
+ <button>点击我</button>
334
+ </ClassName>
335
+ );
336
+ }
337
+ ```
338
+
339
+ 还可以使用容器包装元素:
340
+
341
+ ```tsx
342
+ <ClassName
343
+ className={{
344
+ base: ["p-2", { "bg-red-500": isError }],
345
+ hover: { "hover:bg-blue-500": true },
346
+ }}
347
+ asWrapper="span"
348
+ >
349
+ 内容
350
+ </ClassName>
351
+ ```
352
+
353
+ - `className`:分类的类名对象,支持各种状态的类名(base, hover, active, focus, disabled 等)
354
+ - `asWrapper`:是否生成包含所有 className 的 wrapper,默认 false,传递标签名如'div'或'span'
355
+ - `children`:子元素,通常是一个 React 元素
356
+
315
357
  ### hooks
316
358
 
317
359
  - 一些常用的 hooks 的封装
@@ -324,11 +366,45 @@ function Layout() {
324
366
 
325
367
  - 用于部分组件的内部函数,如需要也可使用
326
368
 
327
- formatDate 比较标准的格式化时间函数
369
+ #### `formatDate`
370
+
371
+ 比较标准的格式化时间函数
372
+
373
+ #### `childrenLoop`
374
+
375
+ 可以中断的子节点遍历,让一些分支流程拥有极致性能
376
+
377
+ #### `Counter`
378
+
379
+ 计数器
380
+
381
+ #### `cn` (v1.2.5+)
382
+
383
+ 一个高效的 CSS 类名合并工具函数,类似于`clsx`或`classnames`,但能自动去除重复的类名。
384
+
385
+ ```tsx
386
+ import { cn } from "@wwog/react";
387
+
388
+ function Example({ isActive, isDisabled }) {
389
+ return (
390
+ <div
391
+ className={cn("base-class", ["array-class-1", "array-class-2"], {
392
+ "active-class": isActive,
393
+ "disabled-class": isDisabled,
394
+ })}
395
+ >
396
+ 内容
397
+ </div>
398
+ );
399
+ }
400
+ ```
328
401
 
329
- childrenLoop 可以中断的子节点遍历,让一些分支流程拥有极致性能
402
+ 支持多种参数类型:
330
403
 
331
- Counter 计数器
404
+ - 字符串: `"class1 class2"`
405
+ - 字符串数组: `["class1", "class2"]`
406
+ - 对象: `{ "class1": true, "class2": false }`
407
+ - 以上类型的任意组合
332
408
 
333
409
  ## License
334
410
 
package/dist/index.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import React$1, { ReactNode, FC, Dispatch } from 'react';
1
+ import React$1, { ReactNode, FC, HTMLElementType, Dispatch } from 'react';
2
2
 
3
3
  interface SwitchProps<T> {
4
4
  value: T;
@@ -190,46 +190,6 @@ 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
- * @default 0
198
- */
199
- index?: number;
200
- /**
201
- * @description_en Array of values to toggle between.
202
- * @description_zh 可切换的值数组。
203
- */
204
- options: T[];
205
- /**
206
- * @description_en Function to determine the next value index in the toggle sequence.
207
- * @description_zh 确定切换序列中下一个值索引的函数。
208
- * @optional
209
- */
210
- next?: (curIndex: number, options: T[]) => number;
211
- /**
212
- * @description_en Render function, receiving the toggled value and toggle function.
213
- * @description_zh 渲染函数,接收切换后的值和切换函数。
214
- */
215
- render: (value: T, toggle: () => void) => ReactNode;
216
- }
217
- /**
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.
220
- * @component
221
- * @example
222
- * ```tsx
223
- * <Toggle
224
- * options={["light", "dark"]}
225
- * render={(theme, toggleTheme) => (
226
- * <div onClick={toggleTheme}>当前主题: {theme}</div>
227
- * )}
228
- * />
229
- * ```
230
- */
231
- declare const Toggle: <T>(props: ToggleProps<T>) => React$1.ReactNode;
232
-
233
193
  interface SizeBoxProps {
234
194
  size?: number | string;
235
195
  height?: number | string;
@@ -244,13 +204,6 @@ interface SizeBoxProps {
244
204
  */
245
205
  declare const SizeBox: FC<SizeBoxProps>;
246
206
 
247
- interface ArrayRenderProps<T> {
248
- items: T[];
249
- renderItem: (item: T, index: number) => React$1.ReactNode;
250
- filter?: (item: T) => boolean;
251
- }
252
- declare function ArrayRender<T>(props: ArrayRenderProps<T>): ReactNode;
253
-
254
207
  /**
255
208
  * Props for the `Scope` component.
256
209
  *
@@ -306,6 +259,98 @@ interface ScopeProps {
306
259
  */
307
260
  declare const Scope: FC<ScopeProps>;
308
261
 
262
+ type CxInput = string | string[] | Record<string, boolean> | undefined | null | false;
263
+ declare function cx(...args: CxInput[]): string;
264
+
265
+ interface ClassNameProps {
266
+ className?: {
267
+ base?: CxInput;
268
+ hover?: CxInput;
269
+ active?: CxInput;
270
+ focus?: CxInput;
271
+ disabled?: CxInput;
272
+ [key: string]: CxInput;
273
+ };
274
+ /**
275
+ * @description 传入容器标签名.是否生成包含所有 `className` 的 `wrapper`, 默认 false, 传递 `true` 为 `div。`
276
+ * @description_en Whether to generate a `wrapper` containing all `className`, default is false, and pass the container tag name, if `true` will be `div`.
277
+ */
278
+ asWrapper?: boolean | HTMLElementType;
279
+ children?: React$1.ReactNode;
280
+ }
281
+ /**
282
+ * @description 用于将 `className` 分类编写的组件,内置了类似`clsx`的功能,并且去除重复的 className。
283
+ * @description_en A component for `className` classification, built-in similar to `clsx` functionality, and removes duplicate className.
284
+ * @component
285
+ * @example
286
+ * ```tsx
287
+ * <ClassName className={{ base: "p-2 bg-red", hover: ["hover:bg-blue", { "hover:text-white": true }] }}>
288
+ * <button>Click me</button>
289
+ * </ClassName>
290
+ * ```
291
+ *
292
+ * @example
293
+ * ```tsx
294
+ * <ClassName
295
+ * className={{
296
+ * base: ["p-2", { "bg-red": condition }],
297
+ * hover: { "hover:bg-blue": true },
298
+ * }}
299
+ * asWrapper="span"
300
+ * >
301
+ * <button>Click me</button>
302
+ * </ClassName>
303
+ * ```
304
+ */
305
+ declare const ClassName: FC<ClassNameProps>;
306
+
307
+ interface ToggleProps<T = boolean> {
308
+ /**
309
+ * @description_en The initial value to toggle.
310
+ * @description_zh 初始切换值。
311
+ * @default 0
312
+ */
313
+ index?: number;
314
+ /**
315
+ * @description_en Array of values to toggle between.
316
+ * @description_zh 可切换的值数组。
317
+ */
318
+ options: T[];
319
+ /**
320
+ * @description_en Function to determine the next value index in the toggle sequence.
321
+ * @description_zh 确定切换序列中下一个值索引的函数。
322
+ * @optional
323
+ */
324
+ next?: (curIndex: number, options: T[]) => number;
325
+ /**
326
+ * @description_en Render function, receiving the toggled value and toggle function.
327
+ * @description_zh 渲染函数,接收切换后的值和切换函数。
328
+ */
329
+ render: (value: T, toggle: () => void) => ReactNode;
330
+ }
331
+ /**
332
+ * @description_zh 一个声明式组件,用于在预定义选项中切换值并通过 render 函数传递给子组件,支持自定义切换逻辑。
333
+ * @description_en A declarative component for toggling between predefined values and passing them to children via a render function, supporting custom toggle logic.
334
+ * @component
335
+ * @example
336
+ * ```tsx
337
+ * <Toggle
338
+ * options={["light", "dark"]}
339
+ * render={(theme, toggleTheme) => (
340
+ * <div onClick={toggleTheme}>当前主题: {theme}</div>
341
+ * )}
342
+ * />
343
+ * ```
344
+ */
345
+ declare const Toggle: <T>(props: ToggleProps<T>) => React$1.ReactNode;
346
+
347
+ interface ArrayRenderProps<T> {
348
+ items: T[];
349
+ renderItem: (item: T, index: number) => React$1.ReactNode;
350
+ filter?: (item: T) => boolean;
351
+ }
352
+ declare function ArrayRender<T>(props: ArrayRenderProps<T>): ReactNode;
353
+
309
354
  interface DateRenderProps<T = string> {
310
355
  /**
311
356
  * @description_en The input date to render (Date object, ISO string, or timestamp).
@@ -349,11 +394,33 @@ interface DateRenderProps<T = string> {
349
394
  */
350
395
  declare function DateRender<T = string>({ source, format, children, }: DateRenderProps<T>): React$1.JSX.Element | null;
351
396
 
352
- /**
353
- * @description 性能优化,替代 React.Children.forEach, 回调可以返回 false 来中断循环
354
- * @description_en Replace React.Children.forEach, the callback can return false to interrupt the loop
355
- */
356
- declare function childrenLoop(children: React$1.ReactNode | undefined, callback: (child: React$1.ReactNode, index: number) => boolean | void): void;
397
+ interface UseControlledOptions<T> {
398
+ /**
399
+ * @description - 非受控模式下的默认值,会被受控模式下的值覆盖
400
+ * @description_en - Default value in uncontrolled mode, will be overridden by the value in controlled mode
401
+ */
402
+ defaultValue: T;
403
+ /**
404
+ * @description - 值变更前的回调函数,可用于拦截或修改新值
405
+ * @description_en - Callback function before the value changes, can be used to intercept or modify the new value
406
+ */
407
+ onBeforeChange?: (newValue: T, currentValue: T) => boolean | void;
408
+ /**
409
+ * @description - 当值发生变化时触发的回调函数名
410
+ * @description_en - Callback function name triggered when the value changes
411
+ * @default - onChange
412
+ */
413
+ trigger?: string;
414
+ /**
415
+ * @description - 值的属性名
416
+ * @description_en - Property name of the value
417
+ * @default - value
418
+ */
419
+ valuePropName?: string;
420
+ props: Record<string, any>;
421
+ }
422
+ declare function useControlled<T>(options: UseControlledOptions<T>): [T, Dispatch<React.SetStateAction<T>>];
423
+
357
424
  /**
358
425
  * @param schema
359
426
  * @example
@@ -393,32 +460,11 @@ declare class Counter {
393
460
  next(): number;
394
461
  }
395
462
 
396
- interface UseControlledOptions<T> {
397
- /**
398
- * @description - 非受控模式下的默认值,会被受控模式下的值覆盖
399
- * @description_en - Default value in uncontrolled mode, will be overridden by the value in controlled mode
400
- */
401
- defaultValue: T;
402
- /**
403
- * @description - 值变更前的回调函数,可用于拦截或修改新值
404
- * @description_en - Callback function before the value changes, can be used to intercept or modify the new value
405
- */
406
- onBeforeChange?: (newValue: T, currentValue: T) => boolean | void;
407
- /**
408
- * @description - 当值发生变化时触发的回调函数名
409
- * @description_en - Callback function name triggered when the value changes
410
- * @default - onChange
411
- */
412
- trigger?: string;
413
- /**
414
- * @description - 值的属性名
415
- * @description_en - Property name of the value
416
- * @default - value
417
- */
418
- valuePropName?: string;
419
- props: Record<string, any>;
420
- }
421
- declare function useControlled<T>(options: UseControlledOptions<T>): [T, Dispatch<React.SetStateAction<T>>];
463
+ /**
464
+ * @description 性能优化,替代 React.Children.forEach, 回调可以返回 false 来中断循环
465
+ * @description_en Replace React.Children.forEach, the callback can return false to interrupt the loop
466
+ */
467
+ declare function childrenLoop(children: React$1.ReactNode | undefined, callback: (child: React$1.ReactNode, index: number) => boolean | void): void;
422
468
 
423
- export { ArrayRender, Counter, DateRender, False, If, Pipe, Scope, SizeBox, Switch, Toggle, True, When, childrenLoop, formatDate, useControlled };
424
- export type { ArrayRenderProps, DateRenderProps, ElseIfProps, ElseProps, FalseProps, IfProps, PipeProps, ScopeProps, SwitchCaseProps, SwitchDefaultProps, SwitchProps, ThenProps, ToggleProps, TrueProps, UseControlledOptions, WhenProps };
469
+ export { ArrayRender, ClassName, Counter, DateRender, False, If, Pipe, Scope, SizeBox, Switch, Toggle, True, When, childrenLoop, cx, formatDate, useControlled };
470
+ export type { ArrayRenderProps, ClassNameProps, CxInput, DateRenderProps, ElseIfProps, ElseProps, FalseProps, IfProps, PipeProps, ScopeProps, SwitchCaseProps, SwitchDefaultProps, SwitchProps, ThenProps, ToggleProps, TrueProps, UseControlledOptions, WhenProps };
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- import r,{useMemo as p,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,C=m[M],v=f[M],D=a-1,Y=T[D],x=y[D],A={YY:l.toString().slice(2),YYYY:l.toString(),M:a.toString(),MM:a.toString().padStart(2,"0"),MMM:x,MMMM:Y,D:o.toString(),DD:o.toString().padStart(2,"0"),d:s.toString(),dd:v,ddd:v,dddd:C,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=>A[k])}class _{count=0;next(){return this.count++}}const B=(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=B,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 h=({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};h.displayName="If",h.Then=w,h.ElseIf=F,h.Else=N,h.createTyped=function(){return{If:h,Then:w,ElseIf:F,Else:N}};const P=({condition:e,children:t})=>e?r.createElement(r.Fragment,null,t):null,W=({condition:e,children:t})=>e===!1?r.createElement(r.Fragment,null,t):null,Z=({all:e,any:t,none:n,children:l,fallback:a})=>p(()=>(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),V=({data:e,transform:t,render:n,fallback:l})=>{const a=p(()=>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))},$=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)},j=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 z(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 L=({let:e,props:t,children:n,fallback:l})=>{const a=p(()=>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 q({source:e,format:t,children:n}){const l=p(()=>{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=p(()=>l?t?t(l):l.toLocaleString():null,[l,t]);return!a||!n?null:r.createElement(r.Fragment,null,n(a))}const G="onChange",K="value";function Q(e){const{defaultValue:t,onBeforeChange:n,trigger:l=G,valuePropName:a=K,props:o}=e,i=Object.prototype.hasOwnProperty.call(o,a),[c,u]=b(t),d=i?o[a]:c,s=p(()=>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{z as ArrayRender,_ as Counter,q as DateRender,W as False,h as If,V as Pipe,L as Scope,j as SizeBox,g as Switch,$ as Toggle,P as True,Z as When,I as childrenLoop,R as formatDate,Q as useControlled};
1
+ import r,{useMemo as h,useEffect as C,isValidElement as I,Fragment as M,cloneElement as R,useState as T,useCallback as H}from"react";function A(n,t){if(n===void 0)return;let e=0;if(Array.isArray(n)){for(const l of n)if(t(l,e++)===!1)break}else t(n,e)}const P=(n,t)=>n===t,S=n=>r.createElement(r.Fragment,null,n.children);S.displayName="Switch_Case";const E=n=>r.createElement(r.Fragment,null,n.children);E.displayName="Switch_Default";const g=n=>{const{value:t,compare:e=P,children:l,strict:a=!1}=n,o=new Set;let s=null,c=null,u=!1;return A(l,(d,i)=>{if(!r.isValidElement(d))throw new Error(`Switch Children only accepts valid React elements at index ${i}`);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 ${i}: ${JSON.stringify(m.value)}${a?" (detected in strict mode)":""}`);if(o.add(m.value),!s&&e(t,m.value)&&(s=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 ${i}`);if(u=!0,c=d.props.children,!a&&s)return!1}else throw new Error(`Switch Children only accepts 'Case' or 'Default' elements, found: ${String(f.displayName||f.name||f)} at index ${i}`)}),r.createElement(r.Fragment,null,s??c)};g.displayName="Switch",g.Case=S,g.Default=E,g.createTyped=function(){return{Switch:g,Case:S,Default:E}};const w=n=>r.createElement(r.Fragment,null,n.children),N=({children:n})=>r.createElement(r.Fragment,null,n),F=n=>r.createElement(r.Fragment,null,n.children);w.displayName="If_Then",N.displayName="If_Else",F.displayName="If_ElseIf";const p=({condition:n,children:t})=>{let e=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 s=o.type;if(s.displayName===w.displayName){if(e)throw new Error("If component can only have one Then child");e=o}else if(s.displayName===F.displayName)a.push(o);else if(s.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(s.displayName||s.name||s)}`)}),n)return e?r.createElement(r.Fragment,null,e.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 W=({condition:n,children:t})=>n?r.createElement(r.Fragment,null,t):null,_=({condition:n,children:t})=>n===!1?r.createElement(r.Fragment,null,t):null,B=({all:n,any:t,none:e,children:l,fallback:a})=>h(()=>(n&&(t||e)&&console.warn('When: Multiple condition types (all, any, none) provided; "all" takes precedence.'),!!(n&&n.length>0&&n.every(Boolean)||t&&t.length>0&&t.some(Boolean)||e&&e.length>0&&e.every(o=>!o))),[n,t,e])?r.createElement(r.Fragment,null,l):r.createElement(r.Fragment,null,a||null),V=({data:n,transform:t,render:e,fallback:l})=>{const a=h(()=>t.reduce((o,s)=>s(o),n),[n,t]);return a==null?r.createElement(r.Fragment,null,l||null):r.createElement(r.Fragment,null,e(a))},Z=n=>{const{children:t,h:e,w:l,size:a,height:o,width:s,className:c}=n;return r.createElement("div",{style:{width:a||l||s,height:a||e||o,flexShrink:0},className:c},t)},$=({let:n,props:t,children:e,fallback:l})=>{const a=h(()=>typeof n=="function"?n(t):n,[n,t]);return!e||!Object.keys(a).length?r.createElement(r.Fragment,null,l||null):r.createElement(r.Fragment,null,e(a))};function v(...n){const t=new Set;for(const e of n)if(e){if(typeof e=="string")t.add(e);else if(Array.isArray(e))e.forEach(l=>t.add(l));else if(typeof e=="object")for(const[l,a]of Object.entries(e))a&&t.add(l)}return Array.from(t).join(" ")}const z=n=>{const{className:t,children:e,asWrapper:l}=n;if(C(()=>{I(e)===!1&&console.warn("<ClassName>: children is not a valid React element. Please check your code.")},[e]),!e)return null;if(!t)return r.createElement(M,null,e);const a=v(...Object.values(t));return l?r.createElement(typeof l=="string"?l:"div",{className:a},e):I(e)?R(e,{className:v(e.props.className,a)}):r.createElement(M,null,e)},L=n=>{const{index:t=0,options:e,next:l,render:a}=n;C(()=>{if(e.length<t+1)throw new Error(`Index ${t} is out of bounds for options array of length ${e.length}. Defaulting to first option.`)},[t,e]);const[o,s]=T(t),c=()=>{s(u=>e.length?l?l(u,e):(u+1)%e.length:u)};return a(e[o],c)};function q(n){const{items:t,renderItem:e,filter:l}=n;return t?r.createElement(M,null,t.map((a,o)=>l&&!l(a)?null:e(a,o))):(console.error("ArrayRender: items is null"),null)}function G({source:n,format:t,children:e}){const l=h(()=>{if(n instanceof Date)return n;if(typeof n=="string"||typeof n=="number"){const o=new Date(n);return isNaN(o.getTime())?null:o}return null},[n]),a=h(()=>l?t?t(l):l.toLocaleString():null,[l,t]);return!a||!e?null:r.createElement(r.Fragment,null,e(a))}const K="onChange",Q="value";function U(n){const{defaultValue:t,onBeforeChange:e,trigger:l=K,valuePropName:a=Q,props:o}=n,s=Object.prototype.hasOwnProperty.call(o,a),[c,u]=T(t),d=s?o[a]:c,i=h(()=>o[l],[o,l]),f=H(m=>{const y=typeof m=="function"?m(d):m;e&&e(y,d)===!1||(s||u(y),i&&i(y))},[s,e,d,i]);return[d,f]}function X(n,t){const e=t||new Date,l=e.getFullYear(),a=e.getMonth()+1,o=e.getDate(),s=e.getHours(),c=e.getMinutes(),u=e.getSeconds(),d=e.getMilliseconds(),i=e.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"],Y=["January","February","March","April","May","June","July","August","September","October","November","December"],x=m[i],D=f[i],b=a-1,k=Y[b],O=y[b],J={YY:l.toString().slice(2),YYYY:l.toString(),M:a.toString(),MM:a.toString().padStart(2,"0"),MMM:O,MMMM:k,D:o.toString(),DD:o.toString().padStart(2,"0"),d:i.toString(),dd:D,ddd:D,dddd:x,H:s.toString(),HH:s.toString().padStart(2,"0"),h:(s%12).toString(),hh:(s%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:s<12?"AM":"PM",a:s<12?"am":"pm"};return n.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,j=>J[j])}class ee{count=0;next(){return this.count++}}export{q as ArrayRender,z as ClassName,ee as Counter,G as DateRender,_ as False,p as If,V as Pipe,$ as Scope,Z as SizeBox,g as Switch,L as Toggle,W as True,B as When,A as childrenLoop,v as cx,X as formatDate,U as useControlled};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wwog/react",
3
- "version": "1.2.4",
3
+ "version": "1.2.6",
4
4
  "description": "",
5
5
  "keywords": [],
6
6
  "author": "wwog",
@@ -17,9 +17,10 @@
17
17
  "scripts": {
18
18
  "build": "unbuild",
19
19
  "format": "biome format --write src",
20
- "check": "biome check --apply src",
21
- "test": "pnpm check && pnpm test:types && vitest run --coverage",
22
- "test:types": "tsc --noEmit --skipLibCheck"
20
+ "check": "biome check --write src",
21
+ "test": "vitest run",
22
+ "test:types": "tsc --noEmit --skipLibCheck",
23
+ "all-suites": "pnpm run format && pnpm run check && pnpm run test:types && pnpm run test"
23
24
  },
24
25
  "devDependencies": {
25
26
  "@biomejs/biome": "^1.9.4",
@@ -1,5 +1,5 @@
1
1
  import React from "react";
2
- import { childrenLoop } from "../utils";
2
+ import { childrenLoop } from "../../utils/reactUtils";
3
3
 
4
4
  export interface SwitchProps<T> {
5
5
  value: T;
@@ -120,5 +120,5 @@ Switch.createTyped = function <T>() {
120
120
  Switch: (props: SwitchProps<T>) => React.ReactElement | null;
121
121
  Case: (props: SwitchCaseProps<T>) => React.ReactElement;
122
122
  Default: (props: SwitchDefaultProps) => React.ReactElement;
123
- }
123
+ };
124
124
  };
@@ -0,0 +1,4 @@
1
+ export * from './Switch'
2
+ export * from './If'
3
+ export * from './When'
4
+ export * from './Pipe'
@@ -0,0 +1,2 @@
1
+ export * from './ArrayRender'
2
+ export * from './DateRender'
@@ -0,0 +1,86 @@
1
+ import React, {
2
+ cloneElement,
3
+ FC,
4
+ Fragment,
5
+ isValidElement,
6
+ useEffect,
7
+ type HTMLElementType,
8
+ } from "react";
9
+ import { cx, type CxInput } from "../../utils/cx";
10
+
11
+ export interface ClassNameProps {
12
+ className?: {
13
+ base?: CxInput;
14
+ hover?: CxInput;
15
+ active?: CxInput;
16
+ focus?: CxInput;
17
+ disabled?: CxInput;
18
+ [key: string]: CxInput;
19
+ };
20
+ /**
21
+ * @description 传入容器标签名.是否生成包含所有 `className` 的 `wrapper`, 默认 false, 传递 `true` 为 `div。`
22
+ * @description_en Whether to generate a `wrapper` containing all `className`, default is false, and pass the container tag name, if `true` will be `div`.
23
+ */
24
+ asWrapper?: boolean | HTMLElementType;
25
+ children?: React.ReactNode;
26
+ }
27
+
28
+ /**
29
+ * @description 用于将 `className` 分类编写的组件,内置了类似`clsx`的功能,并且去除重复的 className。
30
+ * @description_en A component for `className` classification, built-in similar to `clsx` functionality, and removes duplicate className.
31
+ * @component
32
+ * @example
33
+ * ```tsx
34
+ * <ClassName className={{ base: "p-2 bg-red", hover: ["hover:bg-blue", { "hover:text-white": true }] }}>
35
+ * <button>Click me</button>
36
+ * </ClassName>
37
+ * ```
38
+ *
39
+ * @example
40
+ * ```tsx
41
+ * <ClassName
42
+ * className={{
43
+ * base: ["p-2", { "bg-red": condition }],
44
+ * hover: { "hover:bg-blue": true },
45
+ * }}
46
+ * asWrapper="span"
47
+ * >
48
+ * <button>Click me</button>
49
+ * </ClassName>
50
+ * ```
51
+ */
52
+ export const ClassName: FC<ClassNameProps> = (props) => {
53
+ const { className, children, asWrapper } = props;
54
+
55
+ useEffect(() => {
56
+ if (isValidElement(children) === false) {
57
+ console.warn(
58
+ "<ClassName>: children is not a valid React element. Please check your code."
59
+ );
60
+ }
61
+ }, [children]);
62
+
63
+ if (!children) {
64
+ return null;
65
+ }
66
+
67
+ if (!className) {
68
+ return <Fragment>{children}</Fragment>;
69
+ }
70
+
71
+ const generatedCls = cx(...Object.values(className));
72
+
73
+ if (asWrapper) {
74
+ const Wrapper = typeof asWrapper === "string" ? asWrapper : "div";
75
+ return <Wrapper className={generatedCls}>{children}</Wrapper>;
76
+ }
77
+
78
+ if (isValidElement(children)) {
79
+ return cloneElement(children, {
80
+ //@ts-expect-error type error
81
+ className: cx(children.props.className, generatedCls),
82
+ } as any);
83
+ }
84
+
85
+ return <Fragment>{children}</Fragment>;
86
+ };
@@ -0,0 +1,4 @@
1
+ export * from './SizeBox'
2
+ export * from './Scope'
3
+ export * from './ClassName'
4
+ export * from './Toggle'
@@ -1 +1 @@
1
- export * from "./useControlled";
1
+ export * from './useControlled'
@@ -1,36 +1,36 @@
1
- import { useCallback, useMemo, useState, type Dispatch } from "react";
1
+ import {type Dispatch, useCallback, useMemo, useState} from 'react'
2
2
 
3
- const DEFAULT_TRIGGER = "onChange";
4
- const DEFAULT_VALUE_PROP_NAME = "value";
3
+ const DEFAULT_TRIGGER = 'onChange'
4
+ const DEFAULT_VALUE_PROP_NAME = 'value'
5
5
 
6
6
  export interface UseControlledOptions<T> {
7
7
  /**
8
8
  * @description - 非受控模式下的默认值,会被受控模式下的值覆盖
9
9
  * @description_en - Default value in uncontrolled mode, will be overridden by the value in controlled mode
10
10
  */
11
- defaultValue: T;
11
+ defaultValue: T
12
12
  /**
13
13
  * @description - 值变更前的回调函数,可用于拦截或修改新值
14
14
  * @description_en - Callback function before the value changes, can be used to intercept or modify the new value
15
15
  */
16
- onBeforeChange?: (newValue: T, currentValue: T) => boolean | void;
16
+ onBeforeChange?: (newValue: T, currentValue: T) => boolean | void
17
17
  /**
18
18
  * @description - 当值发生变化时触发的回调函数名
19
19
  * @description_en - Callback function name triggered when the value changes
20
20
  * @default - onChange
21
21
  */
22
- trigger?: string;
22
+ trigger?: string
23
23
  /**
24
24
  * @description - 值的属性名
25
25
  * @description_en - Property name of the value
26
26
  * @default - value
27
27
  */
28
- valuePropName?: string;
29
- props: Record<string, any>;
28
+ valuePropName?: string
29
+ props: Record<string, any>
30
30
  }
31
31
 
32
32
  export function useControlled<T>(
33
- options: UseControlledOptions<T>
33
+ options: UseControlledOptions<T>,
34
34
  ): [T, Dispatch<React.SetStateAction<T>>] {
35
35
  const {
36
36
  defaultValue,
@@ -38,36 +38,31 @@ export function useControlled<T>(
38
38
  trigger = DEFAULT_TRIGGER,
39
39
  valuePropName = DEFAULT_VALUE_PROP_NAME,
40
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]);
41
+ } = options
42
+ const isControlled = Object.prototype.hasOwnProperty.call(props, valuePropName)
43
+ const [internalValue, setInternalValue] = useState<T>(defaultValue)
44
+ const value = isControlled ? props[valuePropName] : internalValue
45
+ const onChange = useMemo(() => props[trigger], [props, trigger])
49
46
 
50
47
  const setValue = useCallback<Dispatch<React.SetStateAction<T>>>(
51
48
  (newValue) => {
52
49
  const resolvedValue =
53
- typeof newValue === "function"
54
- ? (newValue as (prev: T) => T)(value)
55
- : newValue;
50
+ typeof newValue === 'function' ? (newValue as (prev: T) => T)(value) : newValue
56
51
  if (onBeforeChange) {
57
- const shouldProceed = onBeforeChange(resolvedValue, value);
52
+ const shouldProceed = onBeforeChange(resolvedValue, value)
58
53
  if (shouldProceed === false) {
59
- return;
54
+ return
60
55
  }
61
56
  }
62
57
  if (!isControlled) {
63
- setInternalValue(resolvedValue);
58
+ setInternalValue(resolvedValue)
64
59
  }
65
60
  if (onChange) {
66
- onChange(resolvedValue);
61
+ onChange(resolvedValue)
67
62
  }
68
63
  },
69
- [isControlled, onBeforeChange, trigger, value, onChange]
70
- );
64
+ [isControlled, onBeforeChange, value, onChange],
65
+ )
71
66
 
72
- return [value, setValue];
67
+ return [value, setValue]
73
68
  }
package/src/index.ts CHANGED
@@ -1,4 +1,9 @@
1
- export * from "./ProcessControl";
2
- export * from "./Common";
3
- export * from "./utils";
4
- export * from "./hooks";
1
+ export * from './components/ProcessControl'
2
+ export * from './components/Sundry'
3
+ export * from './components/Struct'
4
+
5
+ export * from './hooks'
6
+
7
+ export * from './utils/sundry'
8
+ export * from './utils/cx'
9
+ export * from './utils/reactUtils'
@@ -0,0 +1,33 @@
1
+ import {describe, expect, it} from 'vitest'
2
+ import {cx} from './cx'
3
+
4
+ describe('cx', () => {
5
+ it('应该正确合并字符串类名', () => {
6
+ expect(cx('foo', 'bar')).toBe('foo bar')
7
+ expect(cx('foo', 'bar', 'baz')).toBe('foo bar baz')
8
+ })
9
+
10
+ it('应该过滤掉 falsy 值', () => {
11
+ expect(cx('foo', null, 'bar', undefined, false)).toBe('foo bar')
12
+ })
13
+
14
+ it('应该正确处理数组', () => {
15
+ expect(cx(['foo', 'bar'])).toBe('foo bar')
16
+ expect(cx('baz', ['foo', 'bar'])).toBe('baz foo bar')
17
+ })
18
+
19
+ it('应该正确处理对象', () => {
20
+ expect(cx({foo: true, bar: false})).toBe('foo')
21
+ expect(cx({foo: true, bar: true})).toBe('foo bar')
22
+ })
23
+
24
+ it('应该正确处理混合输入', () => {
25
+ expect(cx('foo', ['bar', 'baz'], {qux: true, quux: false})).toBe('foo bar baz qux')
26
+ })
27
+
28
+ it('应该去除重复的类名', () => {
29
+ expect(cx('foo', 'foo', 'bar')).toBe('foo bar')
30
+ expect(cx('foo', ['foo', 'bar'])).toBe('foo bar')
31
+ expect(cx('foo', {foo: true})).toBe('foo')
32
+ })
33
+ })
@@ -0,0 +1,25 @@
1
+ export type CxInput = string | string[] | Record<string, boolean> | undefined | null | false
2
+
3
+ export function cx(...args: CxInput[]): string {
4
+ const classes = new Set<string>()
5
+
6
+ for (const arg of args) {
7
+ if (!arg) {
8
+ continue
9
+ }
10
+
11
+ if (typeof arg === 'string') {
12
+ classes.add(arg)
13
+ } else if (Array.isArray(arg)) {
14
+ arg.forEach((item) => classes.add(item))
15
+ } else if (typeof arg === 'object') {
16
+ for (const [key, value] of Object.entries(arg)) {
17
+ if (value) {
18
+ classes.add(key)
19
+ }
20
+ }
21
+ }
22
+ }
23
+
24
+ return Array.from(classes).join(' ')
25
+ }
@@ -0,0 +1,22 @@
1
+ import type React from 'react'
2
+ /**
3
+ * @description 性能优化,替代 React.Children.forEach, 回调可以返回 false 来中断循环
4
+ * @description_en Replace React.Children.forEach, the callback can return false to interrupt the loop
5
+ */
6
+ export function childrenLoop(
7
+ children: React.ReactNode | undefined,
8
+ callback: (child: React.ReactNode, index: number) => boolean | void,
9
+ ): void {
10
+ if (children === undefined) return
11
+ let index = 0
12
+ if (Array.isArray(children)) {
13
+ for (const child of children) {
14
+ const shouldContinue = callback(child, index++)
15
+ if (shouldContinue === false) {
16
+ break
17
+ }
18
+ }
19
+ } else {
20
+ callback(children, index)
21
+ }
22
+ }
@@ -0,0 +1,130 @@
1
+ import {describe, expect, it} from 'vitest'
2
+ import {Counter, formatDate} from './sundry'
3
+
4
+ describe('formatDate', () => {
5
+ // 使用一个固定的日期来测试,以避免时间差异导致的测试失败
6
+ // 2023-04-15 14:30:45.678 星期六
7
+ const testDate = new Date(2023, 3, 15, 14, 30, 45, 678)
8
+
9
+ it('应该正确格式化年份', () => {
10
+ expect(formatDate('YY', testDate)).toBe('23')
11
+ expect(formatDate('YYYY', testDate)).toBe('2023')
12
+ })
13
+
14
+ it('应该正确格式化月份', () => {
15
+ expect(formatDate('M', testDate)).toBe('4')
16
+ expect(formatDate('MM', testDate)).toBe('04')
17
+ expect(formatDate('MMM', testDate)).toBe('Apr')
18
+ expect(formatDate('MMMM', testDate)).toBe('April')
19
+ })
20
+
21
+ it('应该正确格式化日期', () => {
22
+ expect(formatDate('D', testDate)).toBe('15')
23
+ expect(formatDate('DD', testDate)).toBe('15')
24
+ })
25
+
26
+ it('应该正确格式化星期', () => {
27
+ expect(formatDate('d', testDate)).toBe('6')
28
+ expect(formatDate('dd', testDate)).toBe('Sat')
29
+ expect(formatDate('ddd', testDate)).toBe('Sat')
30
+ expect(formatDate('dddd', testDate)).toBe('Saturday')
31
+ })
32
+
33
+ it('应该正确格式化小时', () => {
34
+ expect(formatDate('H', testDate)).toBe('14')
35
+ expect(formatDate('HH', testDate)).toBe('14')
36
+ expect(formatDate('h', testDate)).toBe('2')
37
+ expect(formatDate('hh', testDate)).toBe('02')
38
+ })
39
+
40
+ it('应该正确格式化分钟和秒', () => {
41
+ expect(formatDate('m', testDate)).toBe('30')
42
+ expect(formatDate('mm', testDate)).toBe('30')
43
+ expect(formatDate('s', testDate)).toBe('45')
44
+ expect(formatDate('ss', testDate)).toBe('45')
45
+ })
46
+
47
+ it('应该正确格式化毫秒', () => {
48
+ expect(formatDate('SSS', testDate)).toBe('678')
49
+ })
50
+
51
+ it('应该正确格式化上午/下午', () => {
52
+ expect(formatDate('A', testDate)).toBe('PM')
53
+ expect(formatDate('a', testDate)).toBe('pm')
54
+
55
+ const morningDate = new Date(2023, 3, 15, 9, 30, 45, 678)
56
+ expect(formatDate('A', morningDate)).toBe('AM')
57
+ expect(formatDate('a', morningDate)).toBe('am')
58
+ })
59
+
60
+ it('应该正确组合多种格式', () => {
61
+ expect(formatDate('YYYY-MM-DD', testDate)).toBe('2023-04-15')
62
+ expect(formatDate('YYYY/MM/DD HH:mm:ss', testDate)).toBe('2023/04/15 14:30:45')
63
+ expect(formatDate('YYYY年MM月DD日 HH时mm分ss秒', testDate)).toBe('2023年04月15日 14时30分45秒')
64
+ expect(formatDate('YY-MM-DD hh:mm:ss A', testDate)).toBe('23-04-15 02:30:45 PM')
65
+ })
66
+
67
+ it('当不传入日期时应该使用当前日期', () => {
68
+ // 由于测试时间不确定,这里只测试格式是否正确,不测试具体的值
69
+ const result = formatDate('YYYY-MM-DD')
70
+ expect(result).toMatch(/^\d{4}-\d{2}-\d{2}$/)
71
+ })
72
+ })
73
+
74
+ describe('Counter', () => {
75
+ it('应该从0开始计数', () => {
76
+ const counter = new Counter()
77
+ expect(counter.count).toBe(0)
78
+ })
79
+
80
+ it('next() 方法应该返回当前计数并递增', () => {
81
+ const counter = new Counter()
82
+ expect(counter.next()).toBe(0)
83
+ expect(counter.count).toBe(1)
84
+ expect(counter.next()).toBe(1)
85
+ expect(counter.count).toBe(2)
86
+ })
87
+
88
+ it('连续调用 next() 应该正确递增', () => {
89
+ const counter = new Counter()
90
+ expect(counter.next()).toBe(0)
91
+ expect(counter.next()).toBe(1)
92
+ expect(counter.next()).toBe(2)
93
+ expect(counter.next()).toBe(3)
94
+ expect(counter.count).toBe(4)
95
+ })
96
+ })
97
+
98
+ /* describe("cx", () => {
99
+ it("应该正确合并字符串类名", () => {
100
+ expect(cx("foo", "bar")).toBe("foo bar");
101
+ expect(cx("foo", "bar", "baz")).toBe("foo bar baz");
102
+ });
103
+
104
+ it("应该过滤掉 falsy 值", () => {
105
+ expect(cx("foo", null, "bar", undefined, false)).toBe("foo bar");
106
+ });
107
+
108
+ it("应该正确处理数组", () => {
109
+ expect(cx(["foo", "bar"])).toBe("foo bar");
110
+ expect(cx("baz", ["foo", "bar"])).toBe("baz foo bar");
111
+ });
112
+
113
+ it("应该正确处理对象", () => {
114
+ expect(cx({ foo: true, bar: false })).toBe("foo");
115
+ expect(cx({ foo: true, bar: true })).toBe("foo bar");
116
+ });
117
+
118
+ it("应该正确处理混合输入", () => {
119
+ expect(cx("foo", ["bar", "baz"], { qux: true, quux: false })).toBe(
120
+ "foo bar baz qux"
121
+ );
122
+ });
123
+
124
+ it("应该去除重复的类名", () => {
125
+ expect(cx("foo", "foo", "bar")).toBe("foo bar");
126
+ expect(cx("foo", ["foo", "bar"])).toBe("foo bar");
127
+ expect(cx("foo", { foo: true })).toBe("foo");
128
+ });
129
+ });
130
+ */
@@ -0,0 +1,130 @@
1
+ /**
2
+ * @param schema
3
+ * @example
4
+ * YY | 18 | Two-digit year
5
+ * YYYY | 2018 | Four-digit year
6
+ * M | 1-12 | The month, beginning at 1
7
+ * MM | 01-12 | The month, 2-digits
8
+ * MMM | Jan-Dec | The abbreviated month name
9
+ * MMMM | January-December | The full month name
10
+ * D | 1-31 | The day of the month
11
+ * DD | 01-31 | The day of the month, 2-digits
12
+ * d | 0-6 | The day of the week, with Sunday as 0
13
+ * dd | Su-Sa | The min name of the day of the week
14
+ * ddd | Sun-Sat | The short name of the day of the week
15
+ * dddd | Sunday-Saturday | The name of the day of the week
16
+ * H | 0-23 | The hour
17
+ * HH | 00-23 | The hour, 2-digits
18
+ * h | 1-12 | The hour, 12-hour clock
19
+ * hh | 01-12 | The hour, 12-hour clock, 2-digits
20
+ * m | 0-59 | The minute
21
+ * mm | 00-59 | The minute, 2-digits
22
+ * s | 0-59 | The second
23
+ * ss | 00-59 | The second, 2-digits
24
+ * SSS | 000-999 | The millisecond, 3-digits
25
+ * Z | +05:00 | The offset from UTC, ±HH:mm
26
+ * ZZ | +0500 | The offset from UTC, ±HHmm
27
+ * A | AM | PM
28
+ * a | am | pm
29
+ */
30
+ export function formatDate(schema: string, date?: Date): string {
31
+ const d = date || new Date()
32
+ const year = d.getFullYear()
33
+ const month = d.getMonth() + 1
34
+ const day = d.getDate()
35
+ const hour = d.getHours()
36
+ const minute = d.getMinutes()
37
+ const second = d.getSeconds()
38
+ const millisecond = d.getMilliseconds()
39
+ const week = d.getDay()
40
+ const weekName = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']
41
+ const weekFullName = [
42
+ 'Sunday',
43
+ 'Monday',
44
+ 'Tuesday',
45
+ 'Wednesday',
46
+ 'Thursday',
47
+ 'Friday',
48
+ 'Saturday',
49
+ ]
50
+ const monthName = [
51
+ 'Jan',
52
+ 'Feb',
53
+ 'Mar',
54
+ 'Apr',
55
+ 'May',
56
+ 'Jun',
57
+ 'Jul',
58
+ 'Aug',
59
+ 'Sep',
60
+ 'Oct',
61
+ 'Nov',
62
+ 'Dec',
63
+ ]
64
+ const monthFullName = [
65
+ 'January',
66
+ 'February',
67
+ 'March',
68
+ 'April',
69
+ 'May',
70
+ 'June',
71
+ 'July',
72
+ 'August',
73
+ 'September',
74
+ 'October',
75
+ 'November',
76
+ 'December',
77
+ ]
78
+ // 直接使用 week 索引,不进行转换,因为 getDay() 已经返回了正确的星期索引 (0-6)
79
+ const weekFull = weekFullName[week]!
80
+ const weekShort = weekName[week]!
81
+ const monthIndex = month - 1
82
+ const monthFull = monthFullName[monthIndex]!
83
+ const monthShort = monthName[monthIndex]!
84
+ const map: Record<string, string> = {
85
+ YY: year.toString().slice(2),
86
+ YYYY: year.toString(),
87
+ M: month.toString(),
88
+ MM: month.toString().padStart(2, '0'),
89
+ MMM: monthShort,
90
+ MMMM: monthFull,
91
+ D: day.toString(),
92
+ DD: day.toString().padStart(2, '0'),
93
+ d: week.toString(),
94
+ dd: weekShort,
95
+ ddd: weekShort,
96
+ dddd: weekFull,
97
+ H: hour.toString(),
98
+ HH: hour.toString().padStart(2, '0'),
99
+ h: (hour % 12).toString(),
100
+ hh: (hour % 12).toString().padStart(2, '0'),
101
+ m: minute.toString(),
102
+ mm: minute.toString().padStart(2, '0'),
103
+ s: second.toString(),
104
+ ss: second.toString().padStart(2, '0'),
105
+ SSS: millisecond.toString().padStart(3, '0'),
106
+ Z: '+08:00',
107
+ ZZ: '+0800',
108
+ A: hour < 12 ? 'AM' : 'PM',
109
+ a: hour < 12 ? 'am' : 'pm',
110
+ }
111
+
112
+ return schema.replace(
113
+ /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,
114
+ (match) => {
115
+ return map[match]!
116
+ },
117
+ )
118
+ }
119
+
120
+ export class Counter {
121
+ count = 0
122
+
123
+ /**
124
+ * @description 获取下一个计数值,不考虑越界。
125
+ * @description_en Get the next count value, without considering overflow.
126
+ */
127
+ next() {
128
+ return this.count++
129
+ }
130
+ }
@@ -1,4 +0,0 @@
1
- export * from "./SizeBox";
2
- export * from "./ArrayRender";
3
- export * from "./Scope";
4
- export * from "./DateRender";
@@ -1,5 +0,0 @@
1
- export * from "./Switch";
2
- export * from "./If";
3
- export * from "./When";
4
- export * from "./Pipe";
5
- export * from "./Toggle";
@@ -1,153 +0,0 @@
1
- import React from "react";
2
- /**
3
- * @description 性能优化,替代 React.Children.forEach, 回调可以返回 false 来中断循环
4
- * @description_en Replace React.Children.forEach, the callback can return false to interrupt the loop
5
- */
6
- export function childrenLoop(
7
- children: React.ReactNode | undefined,
8
- callback: (child: React.ReactNode, index: number) => boolean | void
9
- ): void {
10
- if (children === undefined) return;
11
- let index = 0;
12
- if (Array.isArray(children)) {
13
- for (const child of children) {
14
- const shouldContinue = callback(child, index++);
15
- if (shouldContinue === false) {
16
- break;
17
- }
18
- }
19
- } else {
20
- callback(children, index);
21
- }
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
- }
142
-
143
- export class Counter {
144
- count = 0;
145
-
146
- /**
147
- * @description 获取下一个计数值,不考虑越界。
148
- * @description_en Get the next count value, without considering overflow.
149
- */
150
- next() {
151
- return this.count++;
152
- }
153
- }
File without changes
File without changes