@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
|
@@ -1,36 +1,36 @@
|
|
|
1
|
-
import { useCallback, useMemo, useState
|
|
1
|
+
import {type Dispatch, useCallback, useMemo, useState} from 'react'
|
|
2
2
|
|
|
3
|
-
const DEFAULT_TRIGGER =
|
|
4
|
-
const DEFAULT_VALUE_PROP_NAME =
|
|
3
|
+
const DEFAULT_TRIGGER = 'onChange'
|
|
4
|
+
const DEFAULT_VALUE_PROP_NAME = 'value'
|
|
5
5
|
|
|
6
6
|
export interface UseControlledOptions<T> {
|
|
7
7
|
/**
|
|
8
8
|
* @description - 非受控模式下的默认值,会被受控模式下的值覆盖
|
|
9
9
|
* @description_en - Default value in uncontrolled mode, will be overridden by the value in controlled mode
|
|
10
10
|
*/
|
|
11
|
-
defaultValue: T
|
|
11
|
+
defaultValue: T
|
|
12
12
|
/**
|
|
13
13
|
* @description - 值变更前的回调函数,可用于拦截或修改新值
|
|
14
14
|
* @description_en - Callback function before the value changes, can be used to intercept or modify the new value
|
|
15
15
|
*/
|
|
16
|
-
onBeforeChange?: (newValue: T, currentValue: T) => boolean | void
|
|
16
|
+
onBeforeChange?: (newValue: T, currentValue: T) => boolean | void
|
|
17
17
|
/**
|
|
18
18
|
* @description - 当值发生变化时触发的回调函数名
|
|
19
19
|
* @description_en - Callback function name triggered when the value changes
|
|
20
20
|
* @default - onChange
|
|
21
21
|
*/
|
|
22
|
-
trigger?: string
|
|
22
|
+
trigger?: string
|
|
23
23
|
/**
|
|
24
24
|
* @description - 值的属性名
|
|
25
25
|
* @description_en - Property name of the value
|
|
26
26
|
* @default - value
|
|
27
27
|
*/
|
|
28
|
-
valuePropName?: string
|
|
29
|
-
props: Record<string, any
|
|
28
|
+
valuePropName?: string
|
|
29
|
+
props: Record<string, any>
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
export function useControlled<T>(
|
|
33
|
-
options: UseControlledOptions<T
|
|
33
|
+
options: UseControlledOptions<T>,
|
|
34
34
|
): [T, Dispatch<React.SetStateAction<T>>] {
|
|
35
35
|
const {
|
|
36
36
|
defaultValue,
|
|
@@ -38,36 +38,31 @@ export function useControlled<T>(
|
|
|
38
38
|
trigger = DEFAULT_TRIGGER,
|
|
39
39
|
valuePropName = DEFAULT_VALUE_PROP_NAME,
|
|
40
40
|
props,
|
|
41
|
-
} = options
|
|
42
|
-
const isControlled = Object.prototype.hasOwnProperty.call(
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
)
|
|
46
|
-
const [internalValue, setInternalValue] = useState<T>(defaultValue);
|
|
47
|
-
const value = isControlled ? props[valuePropName] : internalValue;
|
|
48
|
-
const onChange = useMemo(() => props[trigger], [props, trigger]);
|
|
41
|
+
} = options
|
|
42
|
+
const isControlled = Object.prototype.hasOwnProperty.call(props, valuePropName)
|
|
43
|
+
const [internalValue, setInternalValue] = useState<T>(defaultValue)
|
|
44
|
+
const value = isControlled ? props[valuePropName] : internalValue
|
|
45
|
+
const onChange = useMemo(() => props[trigger], [props, trigger])
|
|
49
46
|
|
|
50
47
|
const setValue = useCallback<Dispatch<React.SetStateAction<T>>>(
|
|
51
48
|
(newValue) => {
|
|
52
49
|
const resolvedValue =
|
|
53
|
-
typeof newValue ===
|
|
54
|
-
? (newValue as (prev: T) => T)(value)
|
|
55
|
-
: newValue;
|
|
50
|
+
typeof newValue === 'function' ? (newValue as (prev: T) => T)(value) : newValue
|
|
56
51
|
if (onBeforeChange) {
|
|
57
|
-
const shouldProceed = onBeforeChange(resolvedValue, value)
|
|
52
|
+
const shouldProceed = onBeforeChange(resolvedValue, value)
|
|
58
53
|
if (shouldProceed === false) {
|
|
59
|
-
return
|
|
54
|
+
return
|
|
60
55
|
}
|
|
61
56
|
}
|
|
62
57
|
if (!isControlled) {
|
|
63
|
-
setInternalValue(resolvedValue)
|
|
58
|
+
setInternalValue(resolvedValue)
|
|
64
59
|
}
|
|
65
60
|
if (onChange) {
|
|
66
|
-
onChange(resolvedValue)
|
|
61
|
+
onChange(resolvedValue)
|
|
67
62
|
}
|
|
68
63
|
},
|
|
69
|
-
[isControlled, onBeforeChange,
|
|
70
|
-
)
|
|
64
|
+
[isControlled, onBeforeChange, value, onChange],
|
|
65
|
+
)
|
|
71
66
|
|
|
72
|
-
return [value, setValue]
|
|
67
|
+
return [value, setValue]
|
|
73
68
|
}
|
package/src/index.ts
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
|
-
export * from
|
|
2
|
-
export * from
|
|
3
|
-
export * from
|
|
4
|
-
|
|
5
|
-
export * from
|
|
1
|
+
export * from './components/ProcessControl'
|
|
2
|
+
export * from './components/Sundry'
|
|
3
|
+
export * from './components/Struct'
|
|
4
|
+
|
|
5
|
+
export * from './hooks'
|
|
6
|
+
|
|
7
|
+
export * from './utils/sundry'
|
|
8
|
+
export * from './utils/cx'
|
|
9
|
+
export * from './utils/reactUtils'
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import {describe, expect, it} from 'vitest'
|
|
2
|
+
import {cx} from './cx'
|
|
3
|
+
|
|
4
|
+
describe('cx', () => {
|
|
5
|
+
it('应该正确合并字符串类名', () => {
|
|
6
|
+
expect(cx('foo', 'bar')).toBe('foo bar')
|
|
7
|
+
expect(cx('foo', 'bar', 'baz')).toBe('foo bar baz')
|
|
8
|
+
})
|
|
9
|
+
|
|
10
|
+
it('应该过滤掉 falsy 值', () => {
|
|
11
|
+
expect(cx('foo', null, 'bar', undefined, false)).toBe('foo bar')
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
it('应该正确处理数组', () => {
|
|
15
|
+
expect(cx(['foo', 'bar'])).toBe('foo bar')
|
|
16
|
+
expect(cx('baz', ['foo', 'bar'])).toBe('baz foo bar')
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
it('应该正确处理对象', () => {
|
|
20
|
+
expect(cx({foo: true, bar: false})).toBe('foo')
|
|
21
|
+
expect(cx({foo: true, bar: true})).toBe('foo bar')
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
it('应该正确处理混合输入', () => {
|
|
25
|
+
expect(cx('foo', ['bar', 'baz'], {qux: true, quux: false})).toBe('foo bar baz qux')
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
it('应该去除重复的类名', () => {
|
|
29
|
+
expect(cx('foo', 'foo', 'bar')).toBe('foo bar')
|
|
30
|
+
expect(cx('foo', ['foo', 'bar'])).toBe('foo bar')
|
|
31
|
+
expect(cx('foo', {foo: true})).toBe('foo')
|
|
32
|
+
})
|
|
33
|
+
})
|
package/src/utils/cx.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export type CxInput = string | string[] | Record<string, boolean> | undefined | null | false
|
|
2
|
+
|
|
3
|
+
export function cx(...args: CxInput[]): string {
|
|
4
|
+
const classes = new Set<string>()
|
|
5
|
+
|
|
6
|
+
for (const arg of args) {
|
|
7
|
+
if (!arg) {
|
|
8
|
+
continue
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
if (typeof arg === 'string') {
|
|
12
|
+
classes.add(arg)
|
|
13
|
+
} else if (Array.isArray(arg)) {
|
|
14
|
+
arg.forEach((item) => classes.add(item))
|
|
15
|
+
} else if (typeof arg === 'object') {
|
|
16
|
+
for (const [key, value] of Object.entries(arg)) {
|
|
17
|
+
if (value) {
|
|
18
|
+
classes.add(key)
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return Array.from(classes).join(' ')
|
|
25
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type React from 'react'
|
|
2
|
+
/**
|
|
3
|
+
* @description 性能优化,替代 React.Children.forEach, 回调可以返回 false 来中断循环
|
|
4
|
+
* @description_en Replace React.Children.forEach, the callback can return false to interrupt the loop
|
|
5
|
+
*/
|
|
6
|
+
export function childrenLoop(
|
|
7
|
+
children: React.ReactNode | undefined,
|
|
8
|
+
callback: (child: React.ReactNode, index: number) => boolean | void,
|
|
9
|
+
): void {
|
|
10
|
+
if (children === undefined) return
|
|
11
|
+
let index = 0
|
|
12
|
+
if (Array.isArray(children)) {
|
|
13
|
+
for (const child of children) {
|
|
14
|
+
const shouldContinue = callback(child, index++)
|
|
15
|
+
if (shouldContinue === false) {
|
|
16
|
+
break
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
} else {
|
|
20
|
+
callback(children, index)
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import {describe, expect, it} from 'vitest'
|
|
2
|
+
import {Counter, formatDate} from './sundry'
|
|
3
|
+
|
|
4
|
+
describe('formatDate', () => {
|
|
5
|
+
// 使用一个固定的日期来测试,以避免时间差异导致的测试失败
|
|
6
|
+
// 2023-04-15 14:30:45.678 星期六
|
|
7
|
+
const testDate = new Date(2023, 3, 15, 14, 30, 45, 678)
|
|
8
|
+
|
|
9
|
+
it('应该正确格式化年份', () => {
|
|
10
|
+
expect(formatDate('YY', testDate)).toBe('23')
|
|
11
|
+
expect(formatDate('YYYY', testDate)).toBe('2023')
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
it('应该正确格式化月份', () => {
|
|
15
|
+
expect(formatDate('M', testDate)).toBe('4')
|
|
16
|
+
expect(formatDate('MM', testDate)).toBe('04')
|
|
17
|
+
expect(formatDate('MMM', testDate)).toBe('Apr')
|
|
18
|
+
expect(formatDate('MMMM', testDate)).toBe('April')
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
it('应该正确格式化日期', () => {
|
|
22
|
+
expect(formatDate('D', testDate)).toBe('15')
|
|
23
|
+
expect(formatDate('DD', testDate)).toBe('15')
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
it('应该正确格式化星期', () => {
|
|
27
|
+
expect(formatDate('d', testDate)).toBe('6')
|
|
28
|
+
expect(formatDate('dd', testDate)).toBe('Sat')
|
|
29
|
+
expect(formatDate('ddd', testDate)).toBe('Sat')
|
|
30
|
+
expect(formatDate('dddd', testDate)).toBe('Saturday')
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
it('应该正确格式化小时', () => {
|
|
34
|
+
expect(formatDate('H', testDate)).toBe('14')
|
|
35
|
+
expect(formatDate('HH', testDate)).toBe('14')
|
|
36
|
+
expect(formatDate('h', testDate)).toBe('2')
|
|
37
|
+
expect(formatDate('hh', testDate)).toBe('02')
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
it('应该正确格式化分钟和秒', () => {
|
|
41
|
+
expect(formatDate('m', testDate)).toBe('30')
|
|
42
|
+
expect(formatDate('mm', testDate)).toBe('30')
|
|
43
|
+
expect(formatDate('s', testDate)).toBe('45')
|
|
44
|
+
expect(formatDate('ss', testDate)).toBe('45')
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
it('应该正确格式化毫秒', () => {
|
|
48
|
+
expect(formatDate('SSS', testDate)).toBe('678')
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
it('应该正确格式化上午/下午', () => {
|
|
52
|
+
expect(formatDate('A', testDate)).toBe('PM')
|
|
53
|
+
expect(formatDate('a', testDate)).toBe('pm')
|
|
54
|
+
|
|
55
|
+
const morningDate = new Date(2023, 3, 15, 9, 30, 45, 678)
|
|
56
|
+
expect(formatDate('A', morningDate)).toBe('AM')
|
|
57
|
+
expect(formatDate('a', morningDate)).toBe('am')
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
it('应该正确组合多种格式', () => {
|
|
61
|
+
expect(formatDate('YYYY-MM-DD', testDate)).toBe('2023-04-15')
|
|
62
|
+
expect(formatDate('YYYY/MM/DD HH:mm:ss', testDate)).toBe('2023/04/15 14:30:45')
|
|
63
|
+
expect(formatDate('YYYY年MM月DD日 HH时mm分ss秒', testDate)).toBe('2023年04月15日 14时30分45秒')
|
|
64
|
+
expect(formatDate('YY-MM-DD hh:mm:ss A', testDate)).toBe('23-04-15 02:30:45 PM')
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
it('当不传入日期时应该使用当前日期', () => {
|
|
68
|
+
// 由于测试时间不确定,这里只测试格式是否正确,不测试具体的值
|
|
69
|
+
const result = formatDate('YYYY-MM-DD')
|
|
70
|
+
expect(result).toMatch(/^\d{4}-\d{2}-\d{2}$/)
|
|
71
|
+
})
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
describe('Counter', () => {
|
|
75
|
+
it('应该从0开始计数', () => {
|
|
76
|
+
const counter = new Counter()
|
|
77
|
+
expect(counter.count).toBe(0)
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
it('next() 方法应该返回当前计数并递增', () => {
|
|
81
|
+
const counter = new Counter()
|
|
82
|
+
expect(counter.next()).toBe(0)
|
|
83
|
+
expect(counter.count).toBe(1)
|
|
84
|
+
expect(counter.next()).toBe(1)
|
|
85
|
+
expect(counter.count).toBe(2)
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
it('连续调用 next() 应该正确递增', () => {
|
|
89
|
+
const counter = new Counter()
|
|
90
|
+
expect(counter.next()).toBe(0)
|
|
91
|
+
expect(counter.next()).toBe(1)
|
|
92
|
+
expect(counter.next()).toBe(2)
|
|
93
|
+
expect(counter.next()).toBe(3)
|
|
94
|
+
expect(counter.count).toBe(4)
|
|
95
|
+
})
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
/* describe("cx", () => {
|
|
99
|
+
it("应该正确合并字符串类名", () => {
|
|
100
|
+
expect(cx("foo", "bar")).toBe("foo bar");
|
|
101
|
+
expect(cx("foo", "bar", "baz")).toBe("foo bar baz");
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it("应该过滤掉 falsy 值", () => {
|
|
105
|
+
expect(cx("foo", null, "bar", undefined, false)).toBe("foo bar");
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it("应该正确处理数组", () => {
|
|
109
|
+
expect(cx(["foo", "bar"])).toBe("foo bar");
|
|
110
|
+
expect(cx("baz", ["foo", "bar"])).toBe("baz foo bar");
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it("应该正确处理对象", () => {
|
|
114
|
+
expect(cx({ foo: true, bar: false })).toBe("foo");
|
|
115
|
+
expect(cx({ foo: true, bar: true })).toBe("foo bar");
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it("应该正确处理混合输入", () => {
|
|
119
|
+
expect(cx("foo", ["bar", "baz"], { qux: true, quux: false })).toBe(
|
|
120
|
+
"foo bar baz qux"
|
|
121
|
+
);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it("应该去除重复的类名", () => {
|
|
125
|
+
expect(cx("foo", "foo", "bar")).toBe("foo bar");
|
|
126
|
+
expect(cx("foo", ["foo", "bar"])).toBe("foo bar");
|
|
127
|
+
expect(cx("foo", { foo: true })).toBe("foo");
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
*/
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @param schema
|
|
3
|
+
* @example
|
|
4
|
+
* YY | 18 | Two-digit year
|
|
5
|
+
* YYYY | 2018 | Four-digit year
|
|
6
|
+
* M | 1-12 | The month, beginning at 1
|
|
7
|
+
* MM | 01-12 | The month, 2-digits
|
|
8
|
+
* MMM | Jan-Dec | The abbreviated month name
|
|
9
|
+
* MMMM | January-December | The full month name
|
|
10
|
+
* D | 1-31 | The day of the month
|
|
11
|
+
* DD | 01-31 | The day of the month, 2-digits
|
|
12
|
+
* d | 0-6 | The day of the week, with Sunday as 0
|
|
13
|
+
* dd | Su-Sa | The min name of the day of the week
|
|
14
|
+
* ddd | Sun-Sat | The short name of the day of the week
|
|
15
|
+
* dddd | Sunday-Saturday | The name of the day of the week
|
|
16
|
+
* H | 0-23 | The hour
|
|
17
|
+
* HH | 00-23 | The hour, 2-digits
|
|
18
|
+
* h | 1-12 | The hour, 12-hour clock
|
|
19
|
+
* hh | 01-12 | The hour, 12-hour clock, 2-digits
|
|
20
|
+
* m | 0-59 | The minute
|
|
21
|
+
* mm | 00-59 | The minute, 2-digits
|
|
22
|
+
* s | 0-59 | The second
|
|
23
|
+
* ss | 00-59 | The second, 2-digits
|
|
24
|
+
* SSS | 000-999 | The millisecond, 3-digits
|
|
25
|
+
* Z | +05:00 | The offset from UTC, ±HH:mm
|
|
26
|
+
* ZZ | +0500 | The offset from UTC, ±HHmm
|
|
27
|
+
* A | AM | PM
|
|
28
|
+
* a | am | pm
|
|
29
|
+
*/
|
|
30
|
+
export function formatDate(schema: string, date?: Date): string {
|
|
31
|
+
const d = date || new Date()
|
|
32
|
+
const year = d.getFullYear()
|
|
33
|
+
const month = d.getMonth() + 1
|
|
34
|
+
const day = d.getDate()
|
|
35
|
+
const hour = d.getHours()
|
|
36
|
+
const minute = d.getMinutes()
|
|
37
|
+
const second = d.getSeconds()
|
|
38
|
+
const millisecond = d.getMilliseconds()
|
|
39
|
+
const week = d.getDay()
|
|
40
|
+
const weekName = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']
|
|
41
|
+
const weekFullName = [
|
|
42
|
+
'Sunday',
|
|
43
|
+
'Monday',
|
|
44
|
+
'Tuesday',
|
|
45
|
+
'Wednesday',
|
|
46
|
+
'Thursday',
|
|
47
|
+
'Friday',
|
|
48
|
+
'Saturday',
|
|
49
|
+
]
|
|
50
|
+
const monthName = [
|
|
51
|
+
'Jan',
|
|
52
|
+
'Feb',
|
|
53
|
+
'Mar',
|
|
54
|
+
'Apr',
|
|
55
|
+
'May',
|
|
56
|
+
'Jun',
|
|
57
|
+
'Jul',
|
|
58
|
+
'Aug',
|
|
59
|
+
'Sep',
|
|
60
|
+
'Oct',
|
|
61
|
+
'Nov',
|
|
62
|
+
'Dec',
|
|
63
|
+
]
|
|
64
|
+
const monthFullName = [
|
|
65
|
+
'January',
|
|
66
|
+
'February',
|
|
67
|
+
'March',
|
|
68
|
+
'April',
|
|
69
|
+
'May',
|
|
70
|
+
'June',
|
|
71
|
+
'July',
|
|
72
|
+
'August',
|
|
73
|
+
'September',
|
|
74
|
+
'October',
|
|
75
|
+
'November',
|
|
76
|
+
'December',
|
|
77
|
+
]
|
|
78
|
+
// 直接使用 week 索引,不进行转换,因为 getDay() 已经返回了正确的星期索引 (0-6)
|
|
79
|
+
const weekFull = weekFullName[week]!
|
|
80
|
+
const weekShort = weekName[week]!
|
|
81
|
+
const monthIndex = month - 1
|
|
82
|
+
const monthFull = monthFullName[monthIndex]!
|
|
83
|
+
const monthShort = monthName[monthIndex]!
|
|
84
|
+
const map: Record<string, string> = {
|
|
85
|
+
YY: year.toString().slice(2),
|
|
86
|
+
YYYY: year.toString(),
|
|
87
|
+
M: month.toString(),
|
|
88
|
+
MM: month.toString().padStart(2, '0'),
|
|
89
|
+
MMM: monthShort,
|
|
90
|
+
MMMM: monthFull,
|
|
91
|
+
D: day.toString(),
|
|
92
|
+
DD: day.toString().padStart(2, '0'),
|
|
93
|
+
d: week.toString(),
|
|
94
|
+
dd: weekShort,
|
|
95
|
+
ddd: weekShort,
|
|
96
|
+
dddd: weekFull,
|
|
97
|
+
H: hour.toString(),
|
|
98
|
+
HH: hour.toString().padStart(2, '0'),
|
|
99
|
+
h: (hour % 12).toString(),
|
|
100
|
+
hh: (hour % 12).toString().padStart(2, '0'),
|
|
101
|
+
m: minute.toString(),
|
|
102
|
+
mm: minute.toString().padStart(2, '0'),
|
|
103
|
+
s: second.toString(),
|
|
104
|
+
ss: second.toString().padStart(2, '0'),
|
|
105
|
+
SSS: millisecond.toString().padStart(3, '0'),
|
|
106
|
+
Z: '+08:00',
|
|
107
|
+
ZZ: '+0800',
|
|
108
|
+
A: hour < 12 ? 'AM' : 'PM',
|
|
109
|
+
a: hour < 12 ? 'am' : 'pm',
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return schema.replace(
|
|
113
|
+
/YYYY|YY|M{1,4}|D{1,2}|d{1,4}|H{1,2}|h{1,2}|m{1,2}|s{1,2}|SSS|Z{1,2}|A|a/g,
|
|
114
|
+
(match) => {
|
|
115
|
+
return map[match]!
|
|
116
|
+
},
|
|
117
|
+
)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export class Counter {
|
|
121
|
+
count = 0
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* @description 获取下一个计数值,不考虑越界。
|
|
125
|
+
* @description_en Get the next count value, without considering overflow.
|
|
126
|
+
*/
|
|
127
|
+
next() {
|
|
128
|
+
return this.count++
|
|
129
|
+
}
|
|
130
|
+
}
|
|
@@ -1,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";
|
|
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
|
-
};
|