Skip to content

Commit

Permalink
chore: split code.
Browse files Browse the repository at this point in the history
  • Loading branch information
chizukicn committed May 30, 2023
1 parent c42c3cc commit 57e16d3
Show file tree
Hide file tree
Showing 11 changed files with 196 additions and 159 deletions.
30 changes: 30 additions & 0 deletions src/cls.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { isArray, isObject, isString, unique } from "./shared";

/**
*
*/
export function classnames(...literals: ClassName[]) {
return unique(literals
.reduce<string[]>((prev, cur) => {
if (isArray(cur)) {
prev.push(...classnames(...cur));
} else if (isString(cur)) {
prev.push(...cur.trim().split(/\s+/));
} else if (isObject(cur)) {
prev.push(...Object.keys(cur).filter((key) => !!cur[key]));
}
return prev;
}, [])).filter((item) => !!item);
}

/**
* @alias classnames
*/
export const cls = classnames;

export type ClassName =
| Record<string, boolean | undefined | null>
| string
| undefined
| null
| ClassName[];
31 changes: 31 additions & 0 deletions src/each.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { range } from "./range";
import { isArray, isIterable, isNumber, isObject } from "./shared";

export function renderList<U>(source: string, render: (item: string, index: number) => U): U[];

export function renderList<T, U>(source: T[], render: (item: T, index: number) => U): U[];

export function renderList<T, U>(source: Iterable<T>, render: (item: T, index: number) => U): U[];

export function renderList<U>(source: number, render: (item: number, index: number) => U): U[];

export function renderList<T, U>(source: T, render: <K extends keyof T>(item: T[K], key: T[K], index: number) => U): U[];

export function renderList(source: any, render: (...args: any[]) => any) {
if (isArray(source)) {
return source.map(render);
} else if (isNumber(source)) {
return range(1, source + 1).map(render);
} else if (isIterable(source)) {
return Array.from(source).map(render);
}
if (isObject(source)) {
return Object.keys(source).map((key, index) => render(source[key], key, index));
}
return [];
}

/**
* @alias renderList
*/
export const each = renderList;
107 changes: 4 additions & 103 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,106 +1,7 @@
import type * as CSS from "csstype";
export * from "./each";

function trimClassName(className: string) {
return className.trim().split(/\s+/);
}
export * from "./range";

function unique<S>(arr: Iterable<S>) {
return Array.from(new Set(arr));
}
export * from "./cls";

function toSnakeCase(str: string, separator = "_") {
return str.replace(/[A-Z]/g, (match) => `${separator}${match.toLowerCase()}`);
}

export function classnames(...literals: ClassName[]) {
return unique(literals
.reduce<string[]>((prev, cur) => {
if (Array.isArray(cur)) {
prev.push(...classnames(...cur));
} else if (typeof cur === "string") {
prev.push(...trimClassName(cur));
} else if (typeof cur === "object" && cur !== null) {
prev.push(...Object.keys(cur).filter((key) => !!cur[key]));
}
return prev;
}, [])).filter((item) => !!item);
}

export function unit_f<U extends string = StyleUnit>(value: string | number, unit: U = "px" as U) {
if (/^[0-9]+(\.[0-9]+)?$/.test(String(value))) {
return `${value}${unit}`;
}
return value;
}

export const cls = classnames;

export function style(stylesheet: CSSProperties) {
return Object.keys(stylesheet).reduce<string[]>((prev, cur) => {
const value = stylesheet[cur as keyof CSSProperties];
if (typeof value === "string" || typeof value === "number") {
prev.push(`${toSnakeCase(cur, "-")}: ${value};`);
}
return prev;
}, []).join(" ");
}

export interface CSSProperties extends CSS.Properties<string | number>, CSS.PropertiesHyphen<string | number> {
}

export type ClassName =
| Record<string, boolean | undefined | null>
| string
| undefined
| null
| ClassName[];

export type StyleUnit = "px" | "rem" | "em" | "vw" | "vh" | "vmin" | "vmax" | "%" | "cm" | "mm" | "in" | "pt" | "pc";

function isIterable<T>(obj: any): obj is Iterable<T> {
return obj && typeof obj[Symbol.iterator] === "function";
}

export function range(source: string): string[];

export function range<T>(iterable: Iterable<T>): T[];

export function range(min: number, max: number): number[];

export function range(max: number): number[];

export function range<T>(min: number | Iterable<T>, max?: number): T[] | number[] | string[] {
if (typeof min === "number") {
if (typeof max === "number") {
return Array.from({ length: max - min }, (_, index) => index + min);
}
return Array.from({ length: min }, (_, index) => index);
}
return Array.from(min);
}

export function renderList<U>(source: string, render: (item: string, index: number) => U): U[];

export function renderList<T, U>(source: T[], render: (item: T, index: number) => U): U[];

export function renderList<T, U>(source: Iterable<T>, render: (item: T, index: number) => U): U[];

export function renderList<U>(source: number, render: (item: number, index: number) => U): U[];

export function renderList<T, U>(source: T, render: <K extends keyof T>(item: T[K], key: T[K], index: number) => U): U[];

export function renderList(source: any, render: (...args: any[]) => any) {
if (Array.isArray(source)) {
return source.map(render);
} else if (typeof source === "number") {
return range(1, source + 1).map(render);
} else if (isIterable(source)) {
return Array.from(source).map(render);
}
if (typeof source === "object" && source !== null) {
return Object.keys(source).map((key, index) => render(source[key], key, index));
}
return [];
}

export const each = renderList;
export * from "./style";
19 changes: 19 additions & 0 deletions src/range.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { isNumber } from "./shared";

export function range(source: string): string[];

export function range<T>(iterable: Iterable<T>): T[];

export function range(min: number, max: number): number[];

export function range(max: number): number[];

export function range<T>(min: number | Iterable<T>, max?: number): T[] | number[] | string[] {
if (isNumber(min)) {
if (isNumber(max)) {
return Array.from({ length: max - min }, (_, index) => index + min);
}
return Array.from({ length: min }, (_, index) => index);
}
return Array.from(min);
}
27 changes: 27 additions & 0 deletions src/shared.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
export function isIterable<T>(obj: any): obj is Iterable<T> {
return obj && typeof obj[Symbol.iterator] === "function";
}

export function isObject(obj: any): obj is Record<string, any> {
return typeof obj === "object" && obj !== null;
}

export function isString(obj: any): obj is string {
return typeof obj === "string";
}

export function isNumber(obj: any): obj is number {
return typeof obj === "number";
}

export function isArray<T>(obj: any): obj is T[] {
return Array.isArray(obj);
}

export function unique<S>(arr: Iterable<S>) {
return Array.from(new Set(arr));
}

export function camelToSnakeCase(str: string, separator = "_") {
return str.replace(/[A-Z]/g, (match) => `${separator}${match.toLowerCase()}`);
}
24 changes: 24 additions & 0 deletions src/style.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import type * as CSS from "csstype";
import { camelToSnakeCase, isNumber, isString } from "./shared";

export function unit_f<U extends string = StyleUnit>(value: string | number, unit: U = "px" as U) {
if (/^[0-9]+(\.[0-9]+)?$/.test(String(value))) {
return `${value}${unit}`;
}
return value;
}

export function style(stylesheet: CSSProperties) {
return Object.keys(stylesheet).reduce<string[]>((prev, cur) => {
const value = stylesheet[cur as keyof CSSProperties];
if (isNumber(value) || isString(value)) {
prev.push(`${camelToSnakeCase(cur, "-")}: ${value};`);
}
return prev;
}, []).join(" ");
}

export interface CSSProperties extends CSS.Properties<string | number>, CSS.PropertiesHyphen<string | number> {
}

export type StyleUnit = "px" | "rem" | "em" | "vw" | "vh" | "vmin" | "vmax" | "%" | "cm" | "mm" | "in" | "pt" | "pc";
12 changes: 12 additions & 0 deletions test/cls.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { expect, test } from "vitest";
import { cls } from "../src/cls";

test("test classnames", () => {
expect(cls({
foo: true,
bar: false
}, "baz", ["p-4 foo m-3", {
absolute: true,
relative: false
}]).join(" ")).toBe("foo baz p-4 m-3 absolute");
});
21 changes: 21 additions & 0 deletions test/each.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { expect, test } from "vitest";
import { each } from "../src/each";

test("test each", () => {
expect(each(5, (item) => item)).toEqual([1, 2, 3, 4, 5]);
expect(each([0, 1, 2, 3], (_, index) => index)).toEqual([0, 1, 2, 3]);
expect(each("abc", (s) => s)).toEqual(["a", "b", "c"]);
expect(each({ a: 1, b: 2, c: 3 }, (item, key) => `${item}-${key}`)).toEqual(["1-a", "2-b", "3-c"]);

// invalid value
expect(each(null, (item) => item)).toEqual([]);
expect(each(undefined, (item) => item)).toEqual([]);
expect(each(0, (item) => item)).toEqual([]);
expect(each("", (item) => item)).toEqual([]);
expect(each(false, (item) => item)).toEqual([]);
expect(each(true, (item) => item)).toEqual([]);
expect(each(NaN, (item) => item)).toEqual([]);
expect(each(String, (item) => item)).toEqual([]);
expect(each(Symbol, (item) => item)).toEqual([]);
expect(each(() => [1, 2, 3, 4], (item) => item)).toEqual([]);
});
56 changes: 0 additions & 56 deletions test/index.test.ts

This file was deleted.

9 changes: 9 additions & 0 deletions test/range.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { expect, test } from "vitest";
import { range } from "../src/range";

test("test range", () => {
expect(range(5)).toEqual([0, 1, 2, 3, 4]);
expect(range(1, 5)).toEqual([1, 2, 3, 4]);
expect(range("abc")).toEqual(["a", "b", "c"]);
});

19 changes: 19 additions & 0 deletions test/style.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { expect, test } from "vitest";
import { style, unit_f } from "../src/style";

test("test unit_f", () => {
expect(unit_f(1)).toBe("1px");
expect(unit_f("1")).toBe("1px");
expect(unit_f("1rem")).toBe("1rem");
expect(unit_f("1px", "rem")).toBe("1px");
expect(unit_f("1rem", "%")).toBe("1rem");
});

test("test style", () => {
expect(style({
color: "red",
fontSize: "1rem",
margin: 0,
padding: "1rem"
})).toBe("color: red; font-size: 1rem; margin: 0; padding: 1rem;");
});

0 comments on commit 57e16d3

Please sign in to comment.