@wwog/react 1.2.6 → 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/dist/index.d.mts CHANGED
@@ -262,15 +262,46 @@ declare const Scope: FC<ScopeProps>;
262
262
  type CxInput = string | string[] | Record<string, boolean> | undefined | null | false;
263
263
  declare function cx(...args: CxInput[]): string;
264
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
- };
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;
274
305
  /**
275
306
  * @description 传入容器标签名.是否生成包含所有 `className` 的 `wrapper`, 默认 false, 传递 `true` 为 `div。`
276
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`.
@@ -279,30 +310,27 @@ interface ClassNameProps {
279
310
  children?: React$1.ReactNode;
280
311
  }
281
312
  /**
282
- * @description 用于将 `className` 分类编写的组件,内置了类似`clsx`的功能,并且去除重复的 className。
283
- * @description_en A component for `className` classification, built-in similar to `clsx` functionality, and removes duplicate className.
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.
284
315
  * @component
285
316
  * @example
286
317
  * ```tsx
287
- * <ClassName className={{ base: "p-2 bg-red", hover: ["hover:bg-blue", { "hover:text-white": true }] }}>
318
+ * <Styles className={{ base: "p-2 bg-red", hover: ["hover:bg-blue", { "hover:text-white": true }] }}>
288
319
  * <button>Click me</button>
289
- * </ClassName>
320
+ * </Styles>
290
321
  * ```
291
322
  *
292
323
  * @example
293
324
  * ```tsx
294
- * <ClassName
295
- * className={{
296
- * base: ["p-2", { "bg-red": condition }],
297
- * hover: { "hover:bg-blue": true },
298
- * }}
325
+ * <Styles
326
+ * className="p-2 bg-red"
299
327
  * asWrapper="span"
300
328
  * >
301
329
  * <button>Click me</button>
302
- * </ClassName>
330
+ * </Styles>
303
331
  * ```
304
332
  */
305
- declare const ClassName: FC<ClassNameProps>;
333
+ declare const Styles: FC<StylesProps>;
306
334
 
307
335
  interface ToggleProps<T = boolean> {
308
336
  /**
@@ -466,5 +494,5 @@ declare class Counter {
466
494
  */
467
495
  declare function childrenLoop(children: React$1.ReactNode | undefined, callback: (child: React$1.ReactNode, index: number) => boolean | void): void;
468
496
 
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 };
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,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};
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.6",
3
+ "version": "1.2.7",
4
4
  "description": "",
5
5
  "keywords": [],
6
6
  "author": "wwog",
@@ -18,18 +18,23 @@
18
18
  "build": "unbuild",
19
19
  "format": "biome format --write src",
20
20
  "check": "biome check --write src",
21
- "test": "vitest run",
21
+ "test:unit": "vitest run",
22
22
  "test:types": "tsc --noEmit --skipLibCheck",
23
- "all-suites": "pnpm run format && pnpm run check && pnpm run test:types && pnpm run test"
23
+ "all-suites": "pnpm run format && pnpm run check && pnpm run test:types && pnpm run test:unit",
24
+ "test:watch": "vitest"
24
25
  },
25
26
  "devDependencies": {
26
27
  "@biomejs/biome": "^1.9.4",
27
28
  "@types/react": "^19.1.2",
28
29
  "@types/react-dom": "^19.1.2",
29
- "@vitest/coverage-v8": "^3.1.1",
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",
30
34
  "typescript": "^5.8.3",
31
35
  "unbuild": "^3.5.0",
32
- "vitest": "^3.1.1"
36
+ "vitest": "^3.1.3",
37
+ "vitest-browser-react": "^0.1.1"
33
38
  },
34
39
  "peerDependencies": {
35
40
  "react": ">=16.8.0",
@@ -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
1
  export * from './SizeBox'
2
2
  export * from './Scope'
3
- export * from './ClassName'
3
+ export * from './Styles'
4
4
  export * from './Toggle'
@@ -1,86 +0,0 @@
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
- };