@wwog/react 1.2.6 → 1.2.8
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 -411
- package/dist/index.d.mts +50 -22
- package/dist/index.js +1 -1
- package/package.json +18 -7
- package/src/components/Sundry/Styles.test.tsx +128 -0
- package/src/components/Sundry/Styles.tsx +131 -0
- package/src/components/Sundry/index.ts +1 -1
- package/src/components/Sundry/ClassName.tsx +0 -86
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
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
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
|
|
283
|
-
* @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.
|
|
284
315
|
* @component
|
|
285
316
|
* @example
|
|
286
317
|
* ```tsx
|
|
287
|
-
* <
|
|
318
|
+
* <Styles className={{ base: "p-2 bg-red", hover: ["hover:bg-blue", { "hover:text-white": true }] }}>
|
|
288
319
|
* <button>Click me</button>
|
|
289
|
-
* </
|
|
320
|
+
* </Styles>
|
|
290
321
|
* ```
|
|
291
322
|
*
|
|
292
323
|
* @example
|
|
293
324
|
* ```tsx
|
|
294
|
-
* <
|
|
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
|
-
* </
|
|
330
|
+
* </Styles>
|
|
303
331
|
* ```
|
|
304
332
|
*/
|
|
305
|
-
declare const
|
|
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,
|
|
470
|
-
export type { ArrayRenderProps,
|
|
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,8 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wwog/react",
|
|
3
|
-
"version": "1.2.
|
|
4
|
-
"description": "",
|
|
5
|
-
"keywords": [
|
|
3
|
+
"version": "1.2.8",
|
|
4
|
+
"description": "A practical React component library providing declarative flow control and common UI utility components to make your React code more concise and readable.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"react",
|
|
7
|
+
"components",
|
|
8
|
+
"library",
|
|
9
|
+
"utils",
|
|
10
|
+
"declarative ui"
|
|
11
|
+
],
|
|
6
12
|
"author": "wwog",
|
|
7
13
|
"type": "module",
|
|
8
14
|
"main": "dist/index.js",
|
|
@@ -18,18 +24,23 @@
|
|
|
18
24
|
"build": "unbuild",
|
|
19
25
|
"format": "biome format --write src",
|
|
20
26
|
"check": "biome check --write src",
|
|
21
|
-
"test": "vitest run",
|
|
27
|
+
"test:unit": "vitest run",
|
|
22
28
|
"test:types": "tsc --noEmit --skipLibCheck",
|
|
23
|
-
"all-suites": "pnpm run format && pnpm run check && pnpm run test:types && pnpm run test"
|
|
29
|
+
"all-suites": "pnpm run format && pnpm run check && pnpm run test:types && pnpm run test:unit",
|
|
30
|
+
"test:watch": "vitest"
|
|
24
31
|
},
|
|
25
32
|
"devDependencies": {
|
|
26
33
|
"@biomejs/biome": "^1.9.4",
|
|
27
34
|
"@types/react": "^19.1.2",
|
|
28
35
|
"@types/react-dom": "^19.1.2",
|
|
29
|
-
"@
|
|
36
|
+
"@vitejs/plugin-react": "^4.4.1",
|
|
37
|
+
"@vitest/browser": "^3.1.3",
|
|
38
|
+
"@vitest/coverage-v8": "^3.1.3",
|
|
39
|
+
"playwright": "^1.52.0",
|
|
30
40
|
"typescript": "^5.8.3",
|
|
31
41
|
"unbuild": "^3.5.0",
|
|
32
|
-
"vitest": "^3.1.
|
|
42
|
+
"vitest": "^3.1.3",
|
|
43
|
+
"vitest-browser-react": "^0.1.1"
|
|
33
44
|
},
|
|
34
45
|
"peerDependencies": {
|
|
35
46
|
"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,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
|
-
};
|