@wwog/react 1.2.4 → 1.2.5

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,47 @@ 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
+ }}
331
+ >
332
+ <button>点击我</button>
333
+ </ClassName>
334
+ );
335
+ }
336
+ ```
337
+
338
+ 还可以使用容器包装元素:
339
+
340
+ ```tsx
341
+ <ClassName
342
+ className={{
343
+ base: ["p-2", { "bg-red-500": isError }],
344
+ hover: { "hover:bg-blue-500": true },
345
+ }}
346
+ asWrapper="span"
347
+ >
348
+ 内容
349
+ </ClassName>
350
+ ```
351
+
352
+ - `className`:分类的类名对象,支持各种状态的类名(base, hover, active, focus, disabled 等)
353
+ - `asWrapper`:是否生成包含所有 className 的 wrapper,默认 false,传递标签名如'div'或'span'
354
+ - `children`:子元素,通常是一个 React 元素
355
+
315
356
  ### hooks
316
357
 
317
358
  - 一些常用的 hooks 的封装
@@ -324,11 +365,45 @@ function Layout() {
324
365
 
325
366
  - 用于部分组件的内部函数,如需要也可使用
326
367
 
327
- formatDate 比较标准的格式化时间函数
368
+ #### `formatDate`
369
+
370
+ 比较标准的格式化时间函数
371
+
372
+ #### `childrenLoop`
373
+
374
+ 可以中断的子节点遍历,让一些分支流程拥有极致性能
375
+
376
+ #### `Counter`
377
+
378
+ 计数器
379
+
380
+ #### `cn` (v1.2.5+)
381
+
382
+ 一个高效的 CSS 类名合并工具函数,类似于`clsx`或`classnames`,但能自动去除重复的类名。
383
+
384
+ ```tsx
385
+ import { cn } from "@wwog/react";
386
+
387
+ function Example({ isActive, isDisabled }) {
388
+ return (
389
+ <div
390
+ className={cn("base-class", ["array-class-1", "array-class-2"], {
391
+ "active-class": isActive,
392
+ "disabled-class": isDisabled,
393
+ })}
394
+ >
395
+ 内容
396
+ </div>
397
+ );
398
+ }
399
+ ```
328
400
 
329
- childrenLoop 可以中断的子节点遍历,让一些分支流程拥有极致性能
401
+ 支持多种参数类型:
330
402
 
331
- Counter 计数器
403
+ - 字符串: `"class1 class2"`
404
+ - 字符串数组: `["class1", "class2"]`
405
+ - 对象: `{ "class1": true, "class2": false }`
406
+ - 以上类型的任意组合
332
407
 
333
408
  ## License
334
409
 
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,49 +259,6 @@ interface ScopeProps {
306
259
  */
307
260
  declare const Scope: FC<ScopeProps>;
308
261
 
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
-
352
262
  /**
353
263
  * @description 性能优化,替代 React.Children.forEach, 回调可以返回 false 来中断循环
354
264
  * @description_en Replace React.Children.forEach, the callback can return false to interrupt the loop
@@ -392,6 +302,140 @@ declare class Counter {
392
302
  */
393
303
  next(): number;
394
304
  }
305
+ type CxInput = string | string[] | Record<string, boolean> | undefined | null | false;
306
+ declare function cx(...args: CxInput[]): string;
307
+
308
+ interface ClassNameProps {
309
+ className?: {
310
+ base?: CxInput;
311
+ hover?: CxInput;
312
+ active?: CxInput;
313
+ focus?: CxInput;
314
+ disabled?: CxInput;
315
+ [key: string]: CxInput;
316
+ };
317
+ /**
318
+ * @description 传入容器标签名.是否生成包含所有 `className` 的 `wrapper`, 默认 false, 传递 `true` 为 `div。`
319
+ * @description_en Whether to generate a `wrapper` containing all `className`, default is false, and pass the container tag name, if `true` will be `div`.
320
+ */
321
+ asWrapper?: boolean | HTMLElementType;
322
+ children?: React$1.ReactNode;
323
+ }
324
+ /**
325
+ * @description 用于将 `className` 分类编写的组件,内置了类似`clsx`的功能,并且去除重复的 className。
326
+ * @description_en A component for `className` classification, built-in similar to `clsx` functionality, and removes duplicate className.
327
+ * @component
328
+ * @example
329
+ * ```tsx
330
+ * <ClassName className={{ base: "p-2 bg-red", hover: ["hover:bg-blue", { "hover:text-white": true }] }}>
331
+ * <button>Click me</button>
332
+ * </ClassName>
333
+ * ```
334
+ *
335
+ * @example
336
+ * ```tsx
337
+ * <ClassName
338
+ * className={{
339
+ * base: ["p-2", { "bg-red": condition }],
340
+ * hover: { "hover:bg-blue": true },
341
+ * }}
342
+ * asWrapper="span"
343
+ * >
344
+ * <button>Click me</button>
345
+ * </ClassName>
346
+ * ```
347
+ */
348
+ declare const ClassName: FC<ClassNameProps>;
349
+
350
+ interface ToggleProps<T = boolean> {
351
+ /**
352
+ * @description_en The initial value to toggle.
353
+ * @description_zh 初始切换值。
354
+ * @default 0
355
+ */
356
+ index?: number;
357
+ /**
358
+ * @description_en Array of values to toggle between.
359
+ * @description_zh 可切换的值数组。
360
+ */
361
+ options: T[];
362
+ /**
363
+ * @description_en Function to determine the next value index in the toggle sequence.
364
+ * @description_zh 确定切换序列中下一个值索引的函数。
365
+ * @optional
366
+ */
367
+ next?: (curIndex: number, options: T[]) => number;
368
+ /**
369
+ * @description_en Render function, receiving the toggled value and toggle function.
370
+ * @description_zh 渲染函数,接收切换后的值和切换函数。
371
+ */
372
+ render: (value: T, toggle: () => void) => ReactNode;
373
+ }
374
+ /**
375
+ * @description_zh 一个声明式组件,用于在预定义选项中切换值并通过 render 函数传递给子组件,支持自定义切换逻辑。
376
+ * @description_en A declarative component for toggling between predefined values and passing them to children via a render function, supporting custom toggle logic.
377
+ * @component
378
+ * @example
379
+ * ```tsx
380
+ * <Toggle
381
+ * options={["light", "dark"]}
382
+ * render={(theme, toggleTheme) => (
383
+ * <div onClick={toggleTheme}>当前主题: {theme}</div>
384
+ * )}
385
+ * />
386
+ * ```
387
+ */
388
+ declare const Toggle: <T>(props: ToggleProps<T>) => React$1.ReactNode;
389
+
390
+ interface ArrayRenderProps<T> {
391
+ items: T[];
392
+ renderItem: (item: T, index: number) => React$1.ReactNode;
393
+ filter?: (item: T) => boolean;
394
+ }
395
+ declare function ArrayRender<T>(props: ArrayRenderProps<T>): ReactNode;
396
+
397
+ interface DateRenderProps<T = string> {
398
+ /**
399
+ * @description_en The input date to render (Date object, ISO string, or timestamp).
400
+ * @description_zh 要渲染的输入日期(Date 对象、ISO 字符串或时间戳)。
401
+ */
402
+ source: Date | string | number;
403
+ /**
404
+ * @description_en Function to format the date.
405
+ * @description_zh 格式化日期的函数。
406
+ * @optional
407
+ * @default toLocaleString
408
+ */
409
+ format?: (date: Date) => T;
410
+ /**
411
+ * @description_en Function to render the formatted date.
412
+ * @description_zh 渲染格式化后日期的函数。
413
+ */
414
+ children: (formatted: T) => React$1.ReactNode;
415
+ }
416
+ /**
417
+ * @description_zh 一个声明式组件,用于格式化并渲染日期,简单易用且支持自定义格式化。
418
+ * @description_en A declarative component for formatting and rendering dates, simple to use with support for custom formatting.
419
+ * @component
420
+ * @template T - The type of the formatted date value
421
+ * @example
422
+ * ```tsx
423
+ * <DateRender source="2025-05-06">
424
+ * {(formatted) => <div>日期: {formatted}</div>}
425
+ * </DateRender>
426
+ * ```
427
+ *
428
+ * @example
429
+ * ```tsx
430
+ * <DateRender<string>
431
+ * source={new Date()}
432
+ * format={(date) => date.toLocaleDateString("zh-CN")}
433
+ * >
434
+ * {(formatted) => <div>日期: {formatted}</div>}
435
+ * </DateRender>
436
+ * ```
437
+ */
438
+ declare function DateRender<T = string>({ source, format, children, }: DateRenderProps<T>): React$1.JSX.Element | null;
395
439
 
396
440
  interface UseControlledOptions<T> {
397
441
  /**
@@ -420,5 +464,5 @@ interface UseControlledOptions<T> {
420
464
  }
421
465
  declare function useControlled<T>(options: UseControlledOptions<T>): [T, Dispatch<React.SetStateAction<T>>];
422
466
 
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 };
467
+ export { ArrayRender, ClassName, Counter, DateRender, False, If, Pipe, Scope, SizeBox, Switch, Toggle, True, When, childrenLoop, cx, formatDate, useControlled };
468
+ 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 I,isValidElement as T,Fragment as M,cloneElement as H,useState as A,useCallback as P}from"react";function Y(t,n){if(t===void 0)return;let e=0;if(Array.isArray(t)){for(const l of t)if(n(l,e++)===!1)break}else n(t,e)}function W(t,n){const e=n||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"],x=["January","February","March","April","May","June","July","August","September","October","November","December"],D=i===0?6:i-1,k=m[D],b=f[D],C=a-1,O=x[C],J=y[C],j={YY:l.toString().slice(2),YYYY:l.toString(),M:a.toString(),MM:a.toString().padStart(2,"0"),MMM:J,MMMM:O,D:o.toString(),DD:o.toString().padStart(2,"0"),d:i.toString(),dd:b,ddd:b,dddd:k,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 t.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,R=>j[R])}class _{count=0;next(){return this.count++}}function v(...t){const n=new Set;for(const e of t)if(e){if(typeof e=="string")n.add(e);else if(Array.isArray(e))e.forEach(l=>n.add(l));else if(typeof e=="object")for(const[l,a]of Object.entries(e))a&&n.add(l)}return Array.from(n).join(" ")}const B=(t,n)=>t===n,S=t=>r.createElement(r.Fragment,null,t.children);S.displayName="Switch_Case";const E=t=>r.createElement(r.Fragment,null,t.children);E.displayName="Switch_Default";const g=t=>{const{value:n,compare:e=B,children:l,strict:a=!1}=t,o=new Set;let s=null,c=null,u=!1;return Y(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(n,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=t=>r.createElement(r.Fragment,null,t.children),N=({children:t})=>r.createElement(r.Fragment,null,t),F=t=>r.createElement(r.Fragment,null,t.children);w.displayName="If_Then",N.displayName="If_Else",F.displayName="If_ElseIf";const p=({condition:t,children:n})=>{let e=null,l=null;const a=[];if(r.Children.forEach(n,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)}`)}),t)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 V=({condition:t,children:n})=>t?r.createElement(r.Fragment,null,n):null,Z=({condition:t,children:n})=>t===!1?r.createElement(r.Fragment,null,n):null,$=({all:t,any:n,none:e,children:l,fallback:a})=>h(()=>(t&&(n||e)&&console.warn('When: Multiple condition types (all, any, none) provided; "all" takes precedence.'),!!(t&&t.length>0&&t.every(Boolean)||n&&n.length>0&&n.some(Boolean)||e&&e.length>0&&e.every(o=>!o))),[t,n,e])?r.createElement(r.Fragment,null,l):r.createElement(r.Fragment,null,a||null),z=({data:t,transform:n,render:e,fallback:l})=>{const a=h(()=>n.reduce((o,s)=>s(o),t),[t,n]);return a==null?r.createElement(r.Fragment,null,l||null):r.createElement(r.Fragment,null,e(a))},L=t=>{const{children:n,h:e,w:l,size:a,height:o,width:s,className:c}=t;return r.createElement("div",{style:{width:a||l||s,height:a||e||o,flexShrink:0},className:c},n)},q=({let:t,props:n,children:e,fallback:l})=>{const a=h(()=>typeof t=="function"?t(n):t,[t,n]);return!e||!Object.keys(a).length?r.createElement(r.Fragment,null,l||null):r.createElement(r.Fragment,null,e(a))},G=t=>{const{className:n,children:e,asWrapper:l}=t;if(I(()=>{T(e)===!1&&console.warn("<ClassName>: children is not a valid React element. Please check your code.")},[e]),!e)return null;if(!n)return r.createElement(M,null,e);const a=v(...Object.values(n));return l?r.createElement(typeof l=="string"?l:"div",{className:a},e):T(e)?H(e,{className:v(e.props.className,a)}):r.createElement(M,null,e)},K=t=>{const{index:n=0,options:e,next:l,render:a}=t;I(()=>{if(e.length<n+1)throw new Error(`Index ${n} is out of bounds for options array of length ${e.length}. Defaulting to first option.`)},[n,e]);const[o,s]=A(n),c=()=>{s(u=>e.length?l?l(u,e):(u+1)%e.length:u)};return a(e[o],c)};function Q(t){const{items:n,renderItem:e,filter:l}=t;return n?r.createElement(M,null,n.map((a,o)=>l&&!l(a)?null:e(a,o))):(console.error("ArrayRender: items is null"),null)}function U({source:t,format:n,children:e}){const l=h(()=>{if(t instanceof Date)return t;if(typeof t=="string"||typeof t=="number"){const o=new Date(t);return isNaN(o.getTime())?null:o}return null},[t]),a=h(()=>l?n?n(l):l.toLocaleString():null,[l,n]);return!a||!e?null:r.createElement(r.Fragment,null,e(a))}const X="onChange",ee="value";function te(t){const{defaultValue:n,onBeforeChange:e,trigger:l=X,valuePropName:a=ee,props:o}=t,s=Object.prototype.hasOwnProperty.call(o,a),[c,u]=A(n),d=s?o[a]:c,i=h(()=>o[l],[o,l]),f=P(m=>{const y=typeof m=="function"?m(d):m;e&&e(y,d)===!1||(s||u(y),i&&i(y))},[s,e,l,d,i]);return[d,f]}export{Q as ArrayRender,G as ClassName,_ as Counter,U as DateRender,Z as False,p as If,z as Pipe,q as Scope,L as SizeBox,g as Switch,K as Toggle,V as True,$ as When,Y as childrenLoop,v as cx,W as formatDate,te 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.5",
4
4
  "description": "",
5
5
  "keywords": [],
6
6
  "author": "wwog",
@@ -1,5 +1,5 @@
1
1
  import React from "react";
2
- import { childrenLoop } from "../utils";
2
+ import { childrenLoop } from "../../utils";
3
3
 
4
4
  export interface SwitchProps<T> {
5
5
  value: T;
@@ -2,4 +2,3 @@ export * from "./Switch";
2
2
  export * from "./If";
3
3
  export * from "./When";
4
4
  export * from "./Pipe";
5
- export * from "./Toggle";
@@ -1,4 +1,2 @@
1
- export * from "./SizeBox";
2
1
  export * from "./ArrayRender";
3
- export * from "./Scope";
4
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";
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";
package/src/index.ts CHANGED
@@ -1,4 +1,5 @@
1
- export * from "./ProcessControl";
2
- export * from "./Common";
1
+ export * from "./components/ProcessControl";
2
+ export * from "./components/Sundry";
3
+ export * from "./components/Struct";
3
4
  export * from "./utils";
4
5
  export * from "./hooks";
@@ -151,3 +151,35 @@ export class Counter {
151
151
  return this.count++;
152
152
  }
153
153
  }
154
+
155
+ export type CxInput =
156
+ | string
157
+ | string[]
158
+ | Record<string, boolean>
159
+ | undefined
160
+ | null
161
+ | false;
162
+
163
+ export function cx(...args: CxInput[]): string {
164
+ const classes = new Set<string>();
165
+
166
+ for (const arg of args) {
167
+ if (!arg) {
168
+ continue;
169
+ }
170
+
171
+ if (typeof arg === "string") {
172
+ classes.add(arg);
173
+ } else if (Array.isArray(arg)) {
174
+ arg.forEach((item) => classes.add(item));
175
+ } else if (typeof arg === "object") {
176
+ for (const [key, value] of Object.entries(arg)) {
177
+ if (value) {
178
+ classes.add(key);
179
+ }
180
+ }
181
+ }
182
+ }
183
+
184
+ return Array.from(classes).join(" ");
185
+ }
File without changes
File without changes