feat: add modal and drawer components and examples (#4229)

* feat: add modal component

* feat: add drawer component

* feat: apply new modal and drawer components to the layout

* chore: typo

* feat: add some unit tests
This commit is contained in:
Vben
2024-08-25 23:40:52 +08:00
committed by GitHub
parent edb55b1fc0
commit 20a3868594
96 changed files with 2700 additions and 743 deletions

View File

@@ -1,6 +1,6 @@
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { StorageManager } from './storage-manager';
import { StorageManager } from '../storage-manager';
describe('storageManager', () => {
let storageManager: StorageManager;

View File

@@ -5,7 +5,7 @@ import {
convertToHslCssVar,
convertToRgb,
isValidColor,
} from './convert';
} from '../convert';
describe('color conversion functions', () => {
it('should correctly convert color to HSL format', () => {

View File

@@ -1,6 +1,6 @@
import { describe, expect, it } from 'vitest';
import { diff } from './diff';
import { diff } from '../diff';
describe('diff function', () => {
it('should return an empty object when comparing identical objects', () => {

View File

@@ -1,6 +1,6 @@
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { getElementVisibleRect } from './dom'; // 假设函数位于 utils.ts 中
import { getElementVisibleRect } from '../dom'; // 假设函数位于 utils.ts 中
describe('getElementVisibleRect', () => {
// 设置浏览器视口尺寸的 mock

View File

@@ -1,12 +1,13 @@
import { describe, expect, it } from 'vitest';
import {
getFirstNonNullOrUndefined,
isEmpty,
isHttpUrl,
isObject,
isUndefined,
isWindow,
} from './inference';
} from '../inference';
describe('isHttpUrl', () => {
it("should return true when given 'http://example.com'", () => {
@@ -103,7 +104,6 @@ describe('isObject', () => {
it('should return false for non-objects', () => {
expect(isObject(null)).toBe(false);
expect(isObject()).toBe(false);
expect(isObject(42)).toBe(false);
expect(isObject('string')).toBe(false);
expect(isObject(true)).toBe(false);
@@ -112,3 +112,56 @@ describe('isObject', () => {
expect(isObject(/regex/)).toBe(true);
});
});
describe('getFirstNonNullOrUndefined', () => {
describe('getFirstNonNullOrUndefined', () => {
it('should return the first non-null and non-undefined value for a number array', () => {
expect(getFirstNonNullOrUndefined<number>(undefined, null, 0, 42)).toBe(
0,
);
expect(getFirstNonNullOrUndefined<number>(null, undefined, 42, 123)).toBe(
42,
);
});
it('should return the first non-null and non-undefined value for a string array', () => {
expect(
getFirstNonNullOrUndefined<string>(undefined, null, '', 'hello'),
).toBe('');
expect(
getFirstNonNullOrUndefined<string>(null, undefined, 'test', 'world'),
).toBe('test');
});
it('should return undefined if all values are null or undefined', () => {
expect(getFirstNonNullOrUndefined(undefined, null)).toBeUndefined();
expect(getFirstNonNullOrUndefined(null)).toBeUndefined();
});
it('should work with a single value', () => {
expect(getFirstNonNullOrUndefined(42)).toBe(42);
expect(getFirstNonNullOrUndefined()).toBeUndefined();
expect(getFirstNonNullOrUndefined(null)).toBeUndefined();
});
it('should handle mixed types correctly', () => {
expect(
getFirstNonNullOrUndefined<number | object | string>(
undefined,
null,
'test',
123,
{ key: 'value' },
),
).toBe('test');
expect(
getFirstNonNullOrUndefined<number | object | string>(
null,
undefined,
[1, 2, 3],
'string',
),
).toEqual([1, 2, 3]);
});
});
});

View File

@@ -2,9 +2,10 @@ import { describe, expect, it } from 'vitest';
import {
capitalizeFirstLetter,
kebabToCamelCase,
toCamelCase,
toLowerCaseFirstLetter,
} from './letter';
} from '../letter';
// 编写测试用例
describe('capitalizeFirstLetter', () => {
@@ -76,3 +77,41 @@ describe('toCamelCase', () => {
expect(toCamelCase('Child', 'Parent')).toBe('ParentChild');
});
});
describe('kebabToCamelCase', () => {
it('should convert kebab-case to camelCase correctly', () => {
expect(kebabToCamelCase('my-component-name')).toBe('myComponentName');
});
it('should handle multiple consecutive hyphens', () => {
expect(kebabToCamelCase('my--component--name')).toBe('myComponentName');
});
it('should trim leading and trailing hyphens', () => {
expect(kebabToCamelCase('-my-component-name-')).toBe('myComponentName');
});
it('should preserve the case of the first word', () => {
expect(kebabToCamelCase('My-component-name')).toBe('MyComponentName');
});
it('should convert a single word correctly', () => {
expect(kebabToCamelCase('component')).toBe('component');
});
it('should return an empty string if input is empty', () => {
expect(kebabToCamelCase('')).toBe('');
});
it('should handle strings with no hyphens', () => {
expect(kebabToCamelCase('mycomponentname')).toBe('mycomponentname');
});
it('should handle strings with only hyphens', () => {
expect(kebabToCamelCase('---')).toBe('');
});
it('should handle mixed case inputs', () => {
expect(kebabToCamelCase('my-Component-Name')).toBe('myComponentName');
});
});

View File

@@ -1,6 +1,6 @@
import { describe, expect, it } from 'vitest';
import { filterTree, mapTree, traverseTreeValues } from './tree';
import { filterTree, mapTree, traverseTreeValues } from '../tree';
describe('traverseTreeValues', () => {
interface Node {

View File

@@ -1,6 +1,6 @@
import { describe, expect, it } from 'vitest';
import { uniqueByField } from './unique';
import { uniqueByField } from '../unique';
describe('uniqueByField', () => {
it('should return an array with unique items based on id field', () => {

View File

@@ -1,6 +1,6 @@
import { expect, it } from 'vitest';
import { updateCSSVariables } from './update-css-variables';
import { updateCSSVariables } from '../update-css-variables';
it('updateCSSVariables should update CSS variables in :root selector', () => {
// 模拟初始的内联样式表内容

View File

@@ -1,6 +1,6 @@
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { openWindow } from './window'; // 假设你的函数在 'openWindow' 文件中
import { openWindow } from '../window'; // 假设你的函数在 'openWindow' 文件中
describe('openWindow', () => {
// 保存原始的 window.open 函数

View File

@@ -24,7 +24,7 @@ function isUndefined(value?: unknown): value is undefined {
* @param {T} value 要检查的值。
* @returns {boolean} 如果值为空返回true否则返回false。
*/
function isEmpty<T = unknown>(value: T): value is T {
function isEmpty<T = unknown>(value?: T): value is T {
if (value === null || value === undefined) {
return true;
}
@@ -105,7 +105,42 @@ function isNumber(value: any): value is number {
return typeof value === 'number' && Number.isFinite(value);
}
/**
* Returns the first value in the provided list that is neither `null` nor `undefined`.
*
* This function iterates over the input values and returns the first one that is
* not strictly equal to `null` or `undefined`. If all values are either `null` or
* `undefined`, it returns `undefined`.
*
* @template T - The type of the input values.
* @param {...(T | null | undefined)[]} values - A list of values to evaluate.
* @returns {T | undefined} - The first value that is not `null` or `undefined`, or `undefined` if none are found.
*
* @example
* // Returns 42 because it is the first non-null, non-undefined value.
* getFirstNonNullOrUndefined(undefined, null, 42, 'hello'); // 42
*
* @example
* // Returns 'hello' because it is the first non-null, non-undefined value.
* getFirstNonNullOrUndefined(null, undefined, 'hello', 123); // 'hello'
*
* @example
* // Returns undefined because all values are either null or undefined.
* getFirstNonNullOrUndefined(undefined, null); // undefined
*/
function getFirstNonNullOrUndefined<T>(
...values: (null | T | undefined)[]
): T | undefined {
for (const value of values) {
if (value !== undefined && value !== null) {
return value;
}
}
return undefined;
}
export {
getFirstNonNullOrUndefined,
isEmpty,
isFunction,
isHttpUrl,

View File

@@ -29,4 +29,19 @@ function toCamelCase(key: string, parentKey: string): string {
return parentKey + key.charAt(0).toUpperCase() + key.slice(1);
}
export { capitalizeFirstLetter, toCamelCase, toLowerCaseFirstLetter };
function kebabToCamelCase(str: string): string {
return str
.split('-')
.filter(Boolean)
.map((word, index) =>
index === 0 ? word : word.charAt(0).toUpperCase() + word.slice(1),
)
.join('');
}
export {
capitalizeFirstLetter,
kebabToCamelCase,
toCamelCase,
toLowerCaseFirstLetter,
};