@wwog/react 1.2.5 → 1.2.7
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 +410 -410
- package/dist/index.d.mts +95 -65
- package/dist/index.js +1 -1
- package/package.json +12 -6
- package/src/components/ProcessControl/Switch.tsx +2 -2
- package/src/components/ProcessControl/index.ts +4 -4
- package/src/components/Struct/index.ts +2 -2
- package/src/components/Sundry/Styles.test.tsx +128 -0
- package/src/components/Sundry/Styles.tsx +131 -0
- package/src/components/Sundry/index.ts +4 -4
- package/src/hooks/index.ts +1 -1
- package/src/hooks/useControlled.ts +22 -27
- package/src/index.ts +9 -5
- package/src/utils/cx.test.ts +33 -0
- package/src/utils/cx.ts +25 -0
- package/src/utils/reactUtils.ts +22 -0
- package/src/utils/sundry.test.ts +130 -0
- package/src/utils/sundry.ts +130 -0
- package/src/components/Sundry/ClassName.tsx +0 -86
- package/src/utils/index.ts +0 -185
package/dist/index.d.mts
CHANGED
|
@@ -259,61 +259,49 @@ interface ScopeProps {
|
|
|
259
259
|
*/
|
|
260
260
|
declare const Scope: FC<ScopeProps>;
|
|
261
261
|
|
|
262
|
-
/**
|
|
263
|
-
* @description 性能优化,替代 React.Children.forEach, 回调可以返回 false 来中断循环
|
|
264
|
-
* @description_en Replace React.Children.forEach, the callback can return false to interrupt the loop
|
|
265
|
-
*/
|
|
266
|
-
declare function childrenLoop(children: React$1.ReactNode | undefined, callback: (child: React$1.ReactNode, index: number) => boolean | void): void;
|
|
267
|
-
/**
|
|
268
|
-
* @param schema
|
|
269
|
-
* @example
|
|
270
|
-
* YY | 18 | Two-digit year
|
|
271
|
-
* YYYY | 2018 | Four-digit year
|
|
272
|
-
* M | 1-12 | The month, beginning at 1
|
|
273
|
-
* MM | 01-12 | The month, 2-digits
|
|
274
|
-
* MMM | Jan-Dec | The abbreviated month name
|
|
275
|
-
* MMMM | January-December | The full month name
|
|
276
|
-
* D | 1-31 | The day of the month
|
|
277
|
-
* DD | 01-31 | The day of the month, 2-digits
|
|
278
|
-
* d | 0-6 | The day of the week, with Sunday as 0
|
|
279
|
-
* dd | Su-Sa | The min name of the day of the week
|
|
280
|
-
* ddd | Sun-Sat | The short name of the day of the week
|
|
281
|
-
* dddd | Sunday-Saturday | The name of the day of the week
|
|
282
|
-
* H | 0-23 | The hour
|
|
283
|
-
* HH | 00-23 | The hour, 2-digits
|
|
284
|
-
* h | 1-12 | The hour, 12-hour clock
|
|
285
|
-
* hh | 01-12 | The hour, 12-hour clock, 2-digits
|
|
286
|
-
* m | 0-59 | The minute
|
|
287
|
-
* mm | 00-59 | The minute, 2-digits
|
|
288
|
-
* s | 0-59 | The second
|
|
289
|
-
* ss | 00-59 | The second, 2-digits
|
|
290
|
-
* SSS | 000-999 | The millisecond, 3-digits
|
|
291
|
-
* Z | +05:00 | The offset from UTC, ±HH:mm
|
|
292
|
-
* ZZ | +0500 | The offset from UTC, ±HHmm
|
|
293
|
-
* A | AM | PM
|
|
294
|
-
* a | am | pm
|
|
295
|
-
*/
|
|
296
|
-
declare function formatDate(schema: string, date?: Date): string;
|
|
297
|
-
declare class Counter {
|
|
298
|
-
count: number;
|
|
299
|
-
/**
|
|
300
|
-
* @description 获取下一个计数值,不考虑越界。
|
|
301
|
-
* @description_en Get the next count value, without considering overflow.
|
|
302
|
-
*/
|
|
303
|
-
next(): number;
|
|
304
|
-
}
|
|
305
262
|
type CxInput = string | string[] | Record<string, boolean> | undefined | null | false;
|
|
306
263
|
declare function cx(...args: CxInput[]): string;
|
|
307
264
|
|
|
308
|
-
interface
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
265
|
+
interface StylesDescriptor {
|
|
266
|
+
base?: CxInput;
|
|
267
|
+
hover?: CxInput;
|
|
268
|
+
active?: CxInput;
|
|
269
|
+
focus?: CxInput;
|
|
270
|
+
disabled?: CxInput;
|
|
271
|
+
color?: CxInput;
|
|
272
|
+
size?: CxInput;
|
|
273
|
+
layer?: CxInput;
|
|
274
|
+
wrapper?: CxInput;
|
|
275
|
+
dark?: CxInput;
|
|
276
|
+
light?: CxInput;
|
|
277
|
+
sundry?: CxInput;
|
|
278
|
+
[key: string]: CxInput;
|
|
279
|
+
}
|
|
280
|
+
type StylesType = StylesDescriptor | string;
|
|
281
|
+
interface StylesProps {
|
|
282
|
+
/**
|
|
283
|
+
* @description_en either as a simple string or a categorized object with predefined or custom keys.
|
|
284
|
+
* @description 可以是简单字符串或包含预定义或自定义键的分类对象。
|
|
285
|
+
* @example
|
|
286
|
+
* ```tsx
|
|
287
|
+
* // Simple string
|
|
288
|
+
* <Styles className="p-2 bg-red">
|
|
289
|
+
* <button>Click</button>
|
|
290
|
+
* </Styles>
|
|
291
|
+
*
|
|
292
|
+
* // Categorized object
|
|
293
|
+
* <Styles
|
|
294
|
+
* className={{
|
|
295
|
+
* base: ["p-2", { "bg-red": true }],
|
|
296
|
+
* hover: "hover:bg-blue",
|
|
297
|
+
* color: "text-blue",
|
|
298
|
+
* }}
|
|
299
|
+
* >
|
|
300
|
+
* <button>Click</button>
|
|
301
|
+
* </Styles>
|
|
302
|
+
* ```
|
|
303
|
+
*/
|
|
304
|
+
className?: StylesType;
|
|
317
305
|
/**
|
|
318
306
|
* @description 传入容器标签名.是否生成包含所有 `className` 的 `wrapper`, 默认 false, 传递 `true` 为 `div。`
|
|
319
307
|
* @description_en Whether to generate a `wrapper` containing all `className`, default is false, and pass the container tag name, if `true` will be `div`.
|
|
@@ -322,30 +310,27 @@ interface ClassNameProps {
|
|
|
322
310
|
children?: React$1.ReactNode;
|
|
323
311
|
}
|
|
324
312
|
/**
|
|
325
|
-
* @description
|
|
326
|
-
* @description_en
|
|
313
|
+
* @description 分类编写样式和基本的string样式,内置类似 `clsx` 对类型描述对象的值进行组合,支持去除重复类名,支持嵌套。
|
|
314
|
+
* @description_en Categorized writing styles and basic string styles, built-in similar to `clsx` to combine the values of type description objects, support removing duplicate class names, and support nesting.
|
|
327
315
|
* @component
|
|
328
316
|
* @example
|
|
329
317
|
* ```tsx
|
|
330
|
-
* <
|
|
318
|
+
* <Styles className={{ base: "p-2 bg-red", hover: ["hover:bg-blue", { "hover:text-white": true }] }}>
|
|
331
319
|
* <button>Click me</button>
|
|
332
|
-
* </
|
|
320
|
+
* </Styles>
|
|
333
321
|
* ```
|
|
334
322
|
*
|
|
335
323
|
* @example
|
|
336
324
|
* ```tsx
|
|
337
|
-
* <
|
|
338
|
-
* className=
|
|
339
|
-
* base: ["p-2", { "bg-red": condition }],
|
|
340
|
-
* hover: { "hover:bg-blue": true },
|
|
341
|
-
* }}
|
|
325
|
+
* <Styles
|
|
326
|
+
* className="p-2 bg-red"
|
|
342
327
|
* asWrapper="span"
|
|
343
328
|
* >
|
|
344
329
|
* <button>Click me</button>
|
|
345
|
-
* </
|
|
330
|
+
* </Styles>
|
|
346
331
|
* ```
|
|
347
332
|
*/
|
|
348
|
-
declare const
|
|
333
|
+
declare const Styles: FC<StylesProps>;
|
|
349
334
|
|
|
350
335
|
interface ToggleProps<T = boolean> {
|
|
351
336
|
/**
|
|
@@ -464,5 +449,50 @@ interface UseControlledOptions<T> {
|
|
|
464
449
|
}
|
|
465
450
|
declare function useControlled<T>(options: UseControlledOptions<T>): [T, Dispatch<React.SetStateAction<T>>];
|
|
466
451
|
|
|
467
|
-
|
|
468
|
-
|
|
452
|
+
/**
|
|
453
|
+
* @param schema
|
|
454
|
+
* @example
|
|
455
|
+
* YY | 18 | Two-digit year
|
|
456
|
+
* YYYY | 2018 | Four-digit year
|
|
457
|
+
* M | 1-12 | The month, beginning at 1
|
|
458
|
+
* MM | 01-12 | The month, 2-digits
|
|
459
|
+
* MMM | Jan-Dec | The abbreviated month name
|
|
460
|
+
* MMMM | January-December | The full month name
|
|
461
|
+
* D | 1-31 | The day of the month
|
|
462
|
+
* DD | 01-31 | The day of the month, 2-digits
|
|
463
|
+
* d | 0-6 | The day of the week, with Sunday as 0
|
|
464
|
+
* dd | Su-Sa | The min name of the day of the week
|
|
465
|
+
* ddd | Sun-Sat | The short name of the day of the week
|
|
466
|
+
* dddd | Sunday-Saturday | The name of the day of the week
|
|
467
|
+
* H | 0-23 | The hour
|
|
468
|
+
* HH | 00-23 | The hour, 2-digits
|
|
469
|
+
* h | 1-12 | The hour, 12-hour clock
|
|
470
|
+
* hh | 01-12 | The hour, 12-hour clock, 2-digits
|
|
471
|
+
* m | 0-59 | The minute
|
|
472
|
+
* mm | 00-59 | The minute, 2-digits
|
|
473
|
+
* s | 0-59 | The second
|
|
474
|
+
* ss | 00-59 | The second, 2-digits
|
|
475
|
+
* SSS | 000-999 | The millisecond, 3-digits
|
|
476
|
+
* Z | +05:00 | The offset from UTC, ±HH:mm
|
|
477
|
+
* ZZ | +0500 | The offset from UTC, ±HHmm
|
|
478
|
+
* A | AM | PM
|
|
479
|
+
* a | am | pm
|
|
480
|
+
*/
|
|
481
|
+
declare function formatDate(schema: string, date?: Date): string;
|
|
482
|
+
declare class Counter {
|
|
483
|
+
count: number;
|
|
484
|
+
/**
|
|
485
|
+
* @description 获取下一个计数值,不考虑越界。
|
|
486
|
+
* @description_en Get the next count value, without considering overflow.
|
|
487
|
+
*/
|
|
488
|
+
next(): number;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
/**
|
|
492
|
+
* @description 性能优化,替代 React.Children.forEach, 回调可以返回 false 来中断循环
|
|
493
|
+
* @description_en Replace React.Children.forEach, the callback can return false to interrupt the loop
|
|
494
|
+
*/
|
|
495
|
+
declare function childrenLoop(children: React$1.ReactNode | undefined, callback: (child: React$1.ReactNode, index: number) => boolean | void): void;
|
|
496
|
+
|
|
497
|
+
export { ArrayRender, Counter, DateRender, False, If, Pipe, Scope, SizeBox, Styles, Switch, Toggle, True, When, childrenLoop, cx, formatDate, useControlled };
|
|
498
|
+
export type { ArrayRenderProps, CxInput, DateRenderProps, ElseIfProps, ElseProps, FalseProps, IfProps, PipeProps, ScopeProps, StylesDescriptor, StylesProps, StylesType, SwitchCaseProps, SwitchDefaultProps, SwitchProps, ThenProps, ToggleProps, TrueProps, UseControlledOptions, WhenProps };
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import r,{useMemo as h,
|
|
1
|
+
import r,{useMemo as h,Children as J,Fragment as S,isValidElement as P,cloneElement as R,useEffect as W,useState as I,useCallback as H}from"react";function T(e,n){if(e===void 0)return;let t=0;if(Array.isArray(e)){for(const l of e)if(n(l,t++)===!1)break}else n(e,t)}const _=(e,n)=>e===n,E=e=>r.createElement(r.Fragment,null,e.children);E.displayName="Switch_Case";const w=e=>r.createElement(r.Fragment,null,e.children);w.displayName="Switch_Default";const y=e=>{const{value:n,compare:t=_,children:l,strict:a=!1}=e,o=new Set;let s=null,c=null,u=!1;return T(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===E.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&&t(n,m.value)&&(s=m.children,a===!1))return!1}else if(f.displayName===w.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)};y.displayName="Switch",y.Case=E,y.Default=w,y.createTyped=function(){return{Switch:y,Case:E,Default:w}};const N=e=>r.createElement(r.Fragment,null,e.children),v=({children:e})=>r.createElement(r.Fragment,null,e),F=e=>r.createElement(r.Fragment,null,e.children);N.displayName="If_Then",v.displayName="If_Else",F.displayName="If_ElseIf";const p=({condition:e,children:n})=>{let t=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===N.displayName){if(t)throw new Error("If component can only have one Then child");t=o}else if(s.displayName===F.displayName)a.push(o);else if(s.displayName===v.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)}`)}),e)return t?r.createElement(r.Fragment,null,t.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=N,p.ElseIf=F,p.Else=v,p.createTyped=function(){return{If:p,Then:N,ElseIf:F,Else:v}};const B=({condition:e,children:n})=>e?r.createElement(r.Fragment,null,n):null,V=({condition:e,children:n})=>e===!1?r.createElement(r.Fragment,null,n):null,Z=({all:e,any:n,none:t,children:l,fallback:a})=>h(()=>(e&&(n||t)&&console.warn('When: Multiple condition types (all, any, none) provided; "all" takes precedence.'),!!(e&&e.length>0&&e.every(Boolean)||n&&n.length>0&&n.some(Boolean)||t&&t.length>0&&t.every(o=>!o))),[e,n,t])?r.createElement(r.Fragment,null,l):r.createElement(r.Fragment,null,a||null),z=({data:e,transform:n,render:t,fallback:l})=>{const a=h(()=>n.reduce((o,s)=>s(o),e),[e,n]);return a==null?r.createElement(r.Fragment,null,l||null):r.createElement(r.Fragment,null,t(a))},L=e=>{const{children:n,h:t,w:l,size:a,height:o,width:s,className:c}=e;return r.createElement("div",{style:{width:a||l||s,height:a||t||o,flexShrink:0},className:c},n)},q=({let:e,props:n,children:t,fallback:l})=>{const a=h(()=>typeof e=="function"?e(n):e,[e,n]);return!t||!Object.keys(a).length?r.createElement(r.Fragment,null,l||null):r.createElement(r.Fragment,null,t(a))};function M(...e){const n=new Set;for(const t of e)if(t){if(typeof t=="string")n.add(t);else if(Array.isArray(t))t.forEach(l=>n.add(l));else if(typeof t=="object")for(const[l,a]of Object.entries(t))a&&n.add(l)}return Array.from(n).join(" ")}const G=e=>typeof e=="object"&&!!e,b=({className:e,children:n,asWrapper:t=!1})=>{if(!n)return null;if(J.count(n)>1)return console.error("<Styles>: children has more than one child. Please check your code."),r.createElement(S,null,n);if(!e)return r.createElement(S,null,n);const l=typeof e=="string"?e:M(...Object.values(e));if(t)return r.createElement(t===!0?"div":t,{className:l},n);if(P(n)){const a=n;let o=a?.props?.className;return a?.type?.displayName===b.displayName&&G(o)&&(o=M(...Object.values(o))),R(n,{className:M(l,o)})}return console.error("<Styles>: children is not a valid React element. Please check your code."),r.createElement(S,null,n)};b.displayName="W/Styles";const K=e=>{const{index:n=0,options:t,next:l,render:a}=e;W(()=>{if(t.length<n+1)throw new Error(`Index ${n} is out of bounds for options array of length ${t.length}. Defaulting to first option.`)},[n,t]);const[o,s]=I(n),c=()=>{s(u=>t.length?l?l(u,t):(u+1)%t.length:u)};return a(t[o],c)};function Q(e){const{items:n,renderItem:t,filter:l}=e;return n?r.createElement(S,null,n.map((a,o)=>l&&!l(a)?null:t(a,o))):(console.error("ArrayRender: items is null"),null)}function U({source:e,format:n,children:t}){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?n?n(l):l.toLocaleString():null,[l,n]);return!a||!t?null:r.createElement(r.Fragment,null,t(a))}const X="onChange",$="value";function ee(e){const{defaultValue:n,onBeforeChange:t,trigger:l=X,valuePropName:a=$,props:o}=e,s=Object.prototype.hasOwnProperty.call(o,a),[c,u]=I(n),d=s?o[a]:c,i=h(()=>o[l],[o,l]),f=H(m=>{const g=typeof m=="function"?m(d):m;t&&t(g,d)===!1||(s||u(g),i&&i(g))},[s,t,d,i]);return[d,f]}function te(e,n){const t=n||new Date,l=t.getFullYear(),a=t.getMonth()+1,o=t.getDate(),s=t.getHours(),c=t.getMinutes(),u=t.getSeconds(),d=t.getMilliseconds(),i=t.getDay(),f=["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],m=["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],g=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],A=["January","February","March","April","May","June","July","August","September","October","November","December"],Y=m[i],D=f[i],C=a-1,x=A[C],k=g[C],O={YY:l.toString().slice(2),YYYY:l.toString(),M:a.toString(),MM:a.toString().padStart(2,"0"),MMM:k,MMMM:x,D:o.toString(),DD:o.toString().padStart(2,"0"),d:i.toString(),dd:D,ddd:D,dddd:Y,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 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,j=>O[j])}class ne{count=0;next(){return this.count++}}export{Q as ArrayRender,ne as Counter,U as DateRender,V as False,p as If,z as Pipe,q as Scope,L as SizeBox,b as Styles,y as Switch,K as Toggle,B as True,Z as When,T as childrenLoop,M as cx,te as formatDate,ee as useControlled};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wwog/react",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.7",
|
|
4
4
|
"description": "",
|
|
5
5
|
"keywords": [],
|
|
6
6
|
"author": "wwog",
|
|
@@ -17,18 +17,24 @@
|
|
|
17
17
|
"scripts": {
|
|
18
18
|
"build": "unbuild",
|
|
19
19
|
"format": "biome format --write src",
|
|
20
|
-
"check": "biome check --
|
|
21
|
-
"test": "
|
|
22
|
-
"test:types": "tsc --noEmit --skipLibCheck"
|
|
20
|
+
"check": "biome check --write src",
|
|
21
|
+
"test:unit": "vitest run",
|
|
22
|
+
"test:types": "tsc --noEmit --skipLibCheck",
|
|
23
|
+
"all-suites": "pnpm run format && pnpm run check && pnpm run test:types && pnpm run test:unit",
|
|
24
|
+
"test:watch": "vitest"
|
|
23
25
|
},
|
|
24
26
|
"devDependencies": {
|
|
25
27
|
"@biomejs/biome": "^1.9.4",
|
|
26
28
|
"@types/react": "^19.1.2",
|
|
27
29
|
"@types/react-dom": "^19.1.2",
|
|
28
|
-
"@
|
|
30
|
+
"@vitejs/plugin-react": "^4.4.1",
|
|
31
|
+
"@vitest/browser": "^3.1.3",
|
|
32
|
+
"@vitest/coverage-v8": "^3.1.3",
|
|
33
|
+
"playwright": "^1.52.0",
|
|
29
34
|
"typescript": "^5.8.3",
|
|
30
35
|
"unbuild": "^3.5.0",
|
|
31
|
-
"vitest": "^3.1.
|
|
36
|
+
"vitest": "^3.1.3",
|
|
37
|
+
"vitest-browser-react": "^0.1.1"
|
|
32
38
|
},
|
|
33
39
|
"peerDependencies": {
|
|
34
40
|
"react": ">=16.8.0",
|
|
@@ -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
|
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export * from
|
|
2
|
-
export * from
|
|
3
|
-
export * from
|
|
4
|
-
export * from
|
|
1
|
+
export * from './Switch'
|
|
2
|
+
export * from './If'
|
|
3
|
+
export * from './When'
|
|
4
|
+
export * from './Pipe'
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export * from
|
|
2
|
-
export * from
|
|
1
|
+
export * from './ArrayRender'
|
|
2
|
+
export * from './DateRender'
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { expect, describe, it } from "vitest";
|
|
2
|
+
import { render } from "vitest-browser-react";
|
|
3
|
+
import { Styles } from "./Styles";
|
|
4
|
+
import React from "react";
|
|
5
|
+
|
|
6
|
+
describe("Styles Component", () => {
|
|
7
|
+
it("测试传递styles的className StylesDescriptor", () => {
|
|
8
|
+
const { getByRole } = render(
|
|
9
|
+
<Styles
|
|
10
|
+
className={{
|
|
11
|
+
base: ["p-2"],
|
|
12
|
+
hover: "hover:bg-blue",
|
|
13
|
+
color: "text-blue",
|
|
14
|
+
}}
|
|
15
|
+
>
|
|
16
|
+
<button>Click</button>
|
|
17
|
+
</Styles>
|
|
18
|
+
);
|
|
19
|
+
const renderClassName = getByRole("button").elements()[0].className;
|
|
20
|
+
expect(renderClassName).toBe("p-2 hover:bg-blue text-blue");
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it("测试传递styles的className string", () => {
|
|
24
|
+
const { getByRole } = render(
|
|
25
|
+
<Styles className="button_class">
|
|
26
|
+
<button>Click</button>
|
|
27
|
+
</Styles>
|
|
28
|
+
);
|
|
29
|
+
const renderClassName = getByRole("button").elements()[0].className;
|
|
30
|
+
expect(renderClassName).toBe("button_class");
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("测试嵌套的Styles组件,仅string", () => {
|
|
34
|
+
const { getByRole } = render(
|
|
35
|
+
<Styles className="outer">
|
|
36
|
+
<Styles className="inner">
|
|
37
|
+
<button>Click</button>
|
|
38
|
+
</Styles>
|
|
39
|
+
</Styles>
|
|
40
|
+
);
|
|
41
|
+
const renderClassName = getByRole("button").elements()[0].className;
|
|
42
|
+
expect(renderClassName).toBe("outer inner");
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("测试嵌套的Styles组件,含string和StylesDescriptor", () => {
|
|
46
|
+
const { getByRole } = render(
|
|
47
|
+
<Styles
|
|
48
|
+
className={{
|
|
49
|
+
base: ["p-2"],
|
|
50
|
+
hover: "hover:bg-blue",
|
|
51
|
+
color: "text-blue",
|
|
52
|
+
}}
|
|
53
|
+
>
|
|
54
|
+
<Styles className="button_class">
|
|
55
|
+
<button>Click</button>
|
|
56
|
+
</Styles>
|
|
57
|
+
</Styles>
|
|
58
|
+
);
|
|
59
|
+
const renderClassName = getByRole("button").elements()[0].className;
|
|
60
|
+
expect(renderClassName).toBe("p-2 hover:bg-blue text-blue button_class");
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it("测试children本身含有样式的混入", () => {
|
|
64
|
+
const { getByRole } = render(
|
|
65
|
+
<Styles className={{ base: "wrapper" }}>
|
|
66
|
+
<Styles className="outer">
|
|
67
|
+
<button className="inner">Click</button>
|
|
68
|
+
</Styles>
|
|
69
|
+
</Styles>
|
|
70
|
+
);
|
|
71
|
+
const renderClassName = getByRole("button").elements()[0].className;
|
|
72
|
+
expect(renderClassName).toBe("wrapper outer inner");
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it("测试传递属性空值", () => {
|
|
76
|
+
const { getByRole } = render(
|
|
77
|
+
<Styles className={undefined}>
|
|
78
|
+
<button>Click</button>
|
|
79
|
+
</Styles>
|
|
80
|
+
);
|
|
81
|
+
const renderClassName = getByRole("button").elements()[0].className;
|
|
82
|
+
expect(renderClassName).toBe("");
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("测试空值与有值嵌套", () => {
|
|
86
|
+
const { getByRole } = render(
|
|
87
|
+
<Styles className={undefined}>
|
|
88
|
+
<Styles className="inner">
|
|
89
|
+
<button>Click</button>
|
|
90
|
+
</Styles>
|
|
91
|
+
</Styles>
|
|
92
|
+
);
|
|
93
|
+
const renderClassName = getByRole("button").elements()[0].className;
|
|
94
|
+
expect(renderClassName).toBe("inner");
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it("测试asWrapper为true", () => {
|
|
98
|
+
const { container } = render(
|
|
99
|
+
<Styles className="wrapper" asWrapper={true}>
|
|
100
|
+
<button>Click</button>
|
|
101
|
+
</Styles>
|
|
102
|
+
);
|
|
103
|
+
expect(container.querySelector("div")?.className).toBe("wrapper");
|
|
104
|
+
expect(container.querySelector("button")?.className).toBe("");
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it("测试asWrapper为指定标签", () => {
|
|
108
|
+
const { container } = render(
|
|
109
|
+
<Styles className="wrapper" asWrapper="section">
|
|
110
|
+
<button>Click</button>
|
|
111
|
+
</Styles>
|
|
112
|
+
);
|
|
113
|
+
expect(container.querySelector("section")?.className).toBe("wrapper");
|
|
114
|
+
expect(container.querySelector("button")?.className).toBe("");
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it("测试asWrapper与嵌套组合", () => {
|
|
118
|
+
const { container } = render(
|
|
119
|
+
<Styles className="outer" asWrapper="div">
|
|
120
|
+
<Styles className="inner">
|
|
121
|
+
<button className="btn">Click</button>
|
|
122
|
+
</Styles>
|
|
123
|
+
</Styles>
|
|
124
|
+
);
|
|
125
|
+
expect(container.querySelector("div")?.className).toBe("outer");
|
|
126
|
+
expect(container.querySelector("button")?.className).toBe("inner btn");
|
|
127
|
+
});
|
|
128
|
+
});
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import React, {
|
|
2
|
+
Children,
|
|
3
|
+
cloneElement,
|
|
4
|
+
FC,
|
|
5
|
+
Fragment,
|
|
6
|
+
isValidElement,
|
|
7
|
+
type HTMLElementType,
|
|
8
|
+
} from "react";
|
|
9
|
+
import { cx, type CxInput } from "../../utils/cx";
|
|
10
|
+
|
|
11
|
+
export interface StylesDescriptor {
|
|
12
|
+
base?: CxInput;
|
|
13
|
+
hover?: CxInput;
|
|
14
|
+
active?: CxInput;
|
|
15
|
+
focus?: CxInput;
|
|
16
|
+
disabled?: CxInput;
|
|
17
|
+
color?: CxInput;
|
|
18
|
+
size?: CxInput;
|
|
19
|
+
layer?: CxInput;
|
|
20
|
+
wrapper?: CxInput;
|
|
21
|
+
dark?: CxInput;
|
|
22
|
+
light?: CxInput;
|
|
23
|
+
sundry?: CxInput;
|
|
24
|
+
[key: string]: CxInput;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const isStylesDescriptor = (obj: any): obj is StylesDescriptor => {
|
|
28
|
+
return typeof obj === "object" && !!obj;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export type StylesType = StylesDescriptor | string;
|
|
32
|
+
|
|
33
|
+
export interface StylesProps {
|
|
34
|
+
/**
|
|
35
|
+
* @description_en either as a simple string or a categorized object with predefined or custom keys.
|
|
36
|
+
* @description 可以是简单字符串或包含预定义或自定义键的分类对象。
|
|
37
|
+
* @example
|
|
38
|
+
* ```tsx
|
|
39
|
+
* // Simple string
|
|
40
|
+
* <Styles className="p-2 bg-red">
|
|
41
|
+
* <button>Click</button>
|
|
42
|
+
* </Styles>
|
|
43
|
+
*
|
|
44
|
+
* // Categorized object
|
|
45
|
+
* <Styles
|
|
46
|
+
* className={{
|
|
47
|
+
* base: ["p-2", { "bg-red": true }],
|
|
48
|
+
* hover: "hover:bg-blue",
|
|
49
|
+
* color: "text-blue",
|
|
50
|
+
* }}
|
|
51
|
+
* >
|
|
52
|
+
* <button>Click</button>
|
|
53
|
+
* </Styles>
|
|
54
|
+
* ```
|
|
55
|
+
*/
|
|
56
|
+
className?: StylesType;
|
|
57
|
+
/**
|
|
58
|
+
* @description 传入容器标签名.是否生成包含所有 `className` 的 `wrapper`, 默认 false, 传递 `true` 为 `div。`
|
|
59
|
+
* @description_en Whether to generate a `wrapper` containing all `className`, default is false, and pass the container tag name, if `true` will be `div`.
|
|
60
|
+
*/
|
|
61
|
+
asWrapper?: boolean | HTMLElementType;
|
|
62
|
+
children?: React.ReactNode;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* @description 分类编写样式和基本的string样式,内置类似 `clsx` 对类型描述对象的值进行组合,支持去除重复类名,支持嵌套。
|
|
67
|
+
* @description_en Categorized writing styles and basic string styles, built-in similar to `clsx` to combine the values of type description objects, support removing duplicate class names, and support nesting.
|
|
68
|
+
* @component
|
|
69
|
+
* @example
|
|
70
|
+
* ```tsx
|
|
71
|
+
* <Styles className={{ base: "p-2 bg-red", hover: ["hover:bg-blue", { "hover:text-white": true }] }}>
|
|
72
|
+
* <button>Click me</button>
|
|
73
|
+
* </Styles>
|
|
74
|
+
* ```
|
|
75
|
+
*
|
|
76
|
+
* @example
|
|
77
|
+
* ```tsx
|
|
78
|
+
* <Styles
|
|
79
|
+
* className="p-2 bg-red"
|
|
80
|
+
* asWrapper="span"
|
|
81
|
+
* >
|
|
82
|
+
* <button>Click me</button>
|
|
83
|
+
* </Styles>
|
|
84
|
+
* ```
|
|
85
|
+
*/
|
|
86
|
+
export const Styles: FC<StylesProps> = ({
|
|
87
|
+
className,
|
|
88
|
+
children,
|
|
89
|
+
asWrapper = false,
|
|
90
|
+
}) => {
|
|
91
|
+
if (!children) {
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
if (Children.count(children) > 1) {
|
|
95
|
+
console.error(
|
|
96
|
+
"<Styles>: children has more than one child. Please check your code."
|
|
97
|
+
);
|
|
98
|
+
return <Fragment>{children}</Fragment>;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (!className) {
|
|
102
|
+
return <Fragment>{children}</Fragment>;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const generatedClassName =
|
|
106
|
+
typeof className === "string" ? className : cx(...Object.values(className));
|
|
107
|
+
|
|
108
|
+
if (asWrapper) {
|
|
109
|
+
const Tag = asWrapper === true ? "div" : asWrapper;
|
|
110
|
+
return <Tag className={generatedClassName}>{children}</Tag>;
|
|
111
|
+
}
|
|
112
|
+
if (isValidElement(children)) {
|
|
113
|
+
const typeChildren = children as any;
|
|
114
|
+
let processedChildClassName = typeChildren?.props?.className;
|
|
115
|
+
|
|
116
|
+
if (typeChildren?.type?.displayName === Styles.displayName) {
|
|
117
|
+
if (isStylesDescriptor(processedChildClassName)) {
|
|
118
|
+
processedChildClassName = cx(...Object.values(processedChildClassName));
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return cloneElement(children, {
|
|
123
|
+
className: cx(generatedClassName, processedChildClassName),
|
|
124
|
+
} as any);
|
|
125
|
+
}
|
|
126
|
+
console.error(
|
|
127
|
+
"<Styles>: children is not a valid React element. Please check your code."
|
|
128
|
+
);
|
|
129
|
+
return <Fragment>{children}</Fragment>;
|
|
130
|
+
};
|
|
131
|
+
Styles.displayName = "W/Styles";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export * from
|
|
2
|
-
export * from
|
|
3
|
-
export * from
|
|
4
|
-
export * from
|
|
1
|
+
export * from './SizeBox'
|
|
2
|
+
export * from './Scope'
|
|
3
|
+
export * from './Styles'
|
|
4
|
+
export * from './Toggle'
|
package/src/hooks/index.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export * from
|
|
1
|
+
export * from './useControlled'
|