Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion .changeset/calm-heads-care.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,8 @@
"@mincho-js/css": minor
---

Separate vanilla extract API to `./compat`
**Compatibility**

## Changes

- Separate vanilla extract API to `./compat`
9 changes: 9 additions & 0 deletions .changeset/cold-mirrors-accept.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
"@mincho-js/css": minor
---

**css**

## New

- Add `css.with()` API
4 changes: 3 additions & 1 deletion .changeset/sad-meals-rescue.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,6 @@
"@mincho-js/css": minor
---

css.multiple() API
## New

- Add `css.multiple()` API
2 changes: 2 additions & 0 deletions .changeset/sharp-ears-wear.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
"@mincho-js/css": patch
---

**Types**

## New
- Add `VariantStyle` type for constrained variant styles

Expand Down
2 changes: 1 addition & 1 deletion packages/css/src/compat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export {
export {
globalCss as globalStyle,
css as style,
cssVariants as styleVariants
cssMultiple as styleVariants
} from "./css/index.js";

export type {
Expand Down
181 changes: 175 additions & 6 deletions packages/css/src/css/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { setFileScope } from "@vanilla-extract/css/fileScope";
import { style as vStyle, globalStyle as gStyle } from "@vanilla-extract/css";
import type { GlobalStyleRule } from "@vanilla-extract/css";
import { className, getDebugName } from "../utils.js";
import type { RestrictCSSRule } from "./types.js";

// == Global CSS ===============================================================
export function globalCss(selector: string, rule: GlobalCSSRule) {
Expand Down Expand Up @@ -109,27 +110,80 @@ function cssRaw(style: ComplexCSSRule) {
return style;
}

function cssWith<const T extends CSSRule>(
callback?: (style: RestrictCSSRule<T>) => ComplexCSSRule
) {
type RestrictedCSSRule = RestrictCSSRule<T>;
const cssFunction = callback ?? ((style: RestrictedCSSRule) => style);

function cssWithImpl(style: RestrictedCSSRule, debugId?: string) {
return cssImpl(cssFunction(style), debugId);
}
function cssWithRaw(style: RestrictedCSSRule) {
return cssRaw(cssFunction(style));
}

function cssWithVariants<
StyleMap extends Record<string | number, RestrictedCSSRule>
>(styleMap: StyleMap, debugId?: string): Record<keyof StyleMap, string>;
function cssWithVariants<
Data extends Record<string | number, RestrictedCSSRule>,
Key extends keyof Data,
MapData extends (value: Data[Key], key: Key) => ComplexCSSRule
>(data: Data, mapData: MapData, debugId?: string): Record<keyof Data, string>;
function cssWithVariants<
Data extends Record<string | number, RestrictedCSSRule>,
MapData extends (
value: unknown,
key: string | number | symbol
) => ComplexCSSRule
>(
styleMapOrData: Data,
mapDataOrDebugId?: MapData | string,
debugId?: string
): Record<string | number, string> {
if (isMapDataFunction(mapDataOrDebugId)) {
return cssMultiple(
styleMapOrData,
(value, key) => mapDataOrDebugId(cssFunction(value), key),
debugId
);
} else {
return cssMultiple(styleMapOrData, cssFunction, mapDataOrDebugId);
}
}

return Object.assign(cssWithImpl, {
raw: cssWithRaw,
multiple: cssWithVariants
});
}

export const css = Object.assign(cssImpl, {
raw: cssRaw,
multiple: cssVariants
multiple: cssMultiple,
with: cssWith
});

// == CSS Variants =============================================================
// TODO: Need to optimize
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/get#smart_self-overwriting_lazy_getters
// https://github.com/vanilla-extract-css/vanilla-extract/blob/master/packages/css/src/style.ts
export function cssVariants<
export function cssMultiple<
StyleMap extends Record<string | number, ComplexCSSRule>
>(styleMap: StyleMap, debugId?: string): Record<keyof StyleMap, string>;
export function cssVariants<
export function cssMultiple<
Data extends Record<string | number, unknown>,
Key extends keyof Data,
MapData extends (value: Data[Key], key: Key) => ComplexCSSRule
>(data: Data, mapData: MapData, debugId?: string): Record<keyof Data, string>;
export function cssVariants<
export function cssMultiple<
StyleMap extends Record<string | number, ComplexCSSRule>,
Data extends Record<string | number, unknown>,
MapData extends (value: unknown, key: string | number) => ComplexCSSRule
MapData extends (
value: unknown,
key: string | number | symbol
) => ComplexCSSRule
>(
styleMapOrData: StyleMap | Data,
mapDataOrDebugId?: MapData | string,
Expand Down Expand Up @@ -217,6 +271,23 @@ if (import.meta.vitest) {
});
});

describe.concurrent("css.raw()", () => {
it("handles simple CSS properties", () => {
const style = {
color: "red",
fontSize: 16,
padding: "10px"
};
const result = css.raw(style);

expect(result).toEqual({
color: "red",
fontSize: 16,
padding: "10px"
});
});
});

describe.concurrent("css.multiple()", () => {
it("Variants", () => {
const result = css.multiple(
Expand Down Expand Up @@ -271,7 +342,105 @@ if (import.meta.vitest) {
className(`${debugId}_secondary`, "base")
);
});
});

describe.concurrent("css.with()", () => {
it("css.with() with type restrictions", () => {
const myCss = css.with<{
color: true;
background: "blue" | "grey";
border: false;
}>();

myCss({
color: "red", // Allow all properties
background: "blue", // Only some properties are allowed
// @ts-expect-error: border is not allowed
border: "none"
});
myCss({
// @ts-expect-error: background is allowed only "blue" or "grey"
background: "red"
});
});

it("Basic callback transformation", () => {
const withRedBackground = css.with((style) => ({
...style,
backgroundColor: "red"
}));

const result = withRedBackground({ color: "blue" }, debugId);

assert.isString(result);
expect(result).toMatch(className(debugId));
});

// TODO: Mocking globalCSS() for Variant Reference
it("css.with().raw()", () => {
const withRedBackground = css.with((style) => ({
...style,
backgroundColor: "red"
}));

const result = withRedBackground.raw({ color: "blue" });

expect(result).toEqual({
color: "blue",
backgroundColor: "red"
});
});

it("css.with().multiple()", () => {
const withRedBackground = css.with((style) => ({
...style,
backgroundColor: "red"
}));

const result = withRedBackground.multiple(
{
primary: { color: "blue" },
secondary: { color: "green" }
},
debugId
);

assert.hasAllKeys(result, ["primary", "secondary"]);
expect(result.primary).toMatch(className(`${debugId}_primary`));
expect(result.secondary).toMatch(className(`${debugId}_secondary`));
});

it("css.with() with like mixin", () => {
const myCss = css.with<{ size: number; radius?: number }>(
({ size, radius = 10 }) => {
const styles: CSSRule = {
width: size,
height: size
};

if (radius !== 0) {
styles.borderRadius = radius;
}

return styles;
}
);

expect(myCss.raw({ size: 100 })).toStrictEqual({
width: 100,
height: 100,
borderRadius: 10
});
expect(myCss.raw({ size: 100, radius: 0 })).toStrictEqual({
width: 100,
height: 100
});
expect(myCss.raw({ size: 100, radius: 100 })).toStrictEqual({
width: 100,
height: 100,
borderRadius: 100
});
});
});

// TODO: Mocking globalCSS() for Variant Reference
}
23 changes: 23 additions & 0 deletions packages/css/src/css/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import type { CSSRule } from "@mincho-js/transform-to-vanilla";
import type { Resolve } from "../types.js";

type IsOptional<T, K extends keyof T> =
Record<string, never> extends Pick<T, K> ? true : false;
type IsRequired<T, K extends keyof T> =
IsOptional<T, K> extends true ? false : true;

export type RestrictCSSRule<T extends CSSRule> = {
[K in keyof T as T[K] extends false ? never : K]: T[K] extends true
? K extends keyof CSSRule
? CSSRule[K]
: never
: T[K];
} extends infer U
? Resolve<
{
[K in keyof U as IsRequired<U, K> extends true ? K : never]-?: U[K];
} & {
[K in keyof U as IsOptional<U, K> extends true ? K : never]?: U[K];
}
>
: never;
4 changes: 2 additions & 2 deletions packages/css/src/rules/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import type {
PureCSSVarKey
} from "@mincho-js/transform-to-vanilla";

import { css, cssVariants } from "../css/index.js";
import { css } from "../css/index.js";
import { className, getDebugName, getVarName } from "../utils.js";
import { createRuntimeFn } from "./createRuntimeFn.js";
import type {
Expand Down Expand Up @@ -110,7 +110,7 @@ export function rules<
// @ts-expect-error - Temporarily ignoring the error as the PatternResult type is not fully defined
const variantClassNames: PatternResult<CombinedVariants>["variantClassNames"] =
mapValues(mergedVariants, (variantGroup, variantGroupName) =>
cssVariants(
css.multiple(
variantGroup,
(styleRule) =>
typeof styleRule === "string"
Expand Down
4 changes: 1 addition & 3 deletions packages/css/src/rules/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,8 @@ import type {
ResolvedProperties,
NonNullableString
} from "@mincho-js/transform-to-vanilla";
import type { Resolve } from "../types.js";

type Resolve<T> = {
[Key in keyof T]: T[Key];
} & {};
export type ResolveComplex<T> =
T extends Array<infer U> ? Array<Resolve<U>> : Resolve<T>;
type RemoveUndefined<T> = T extends undefined ? never : T;
Expand Down
3 changes: 3 additions & 0 deletions packages/css/src/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export type Resolve<T> = {
[Key in keyof T]: T[Key];
} & {};
3 changes: 3 additions & 0 deletions packages/css/tsconfig.lib.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
"compilerOptions": {
"rootDir": "./src",
"baseUrl": "./",
"paths": { /* Specify a set of entries that re-map imports to additional lookup locations. */
"@/*": ["src/*"]
},
"tsBuildInfoFile": "./.cache/typescript/tsbuildinfo-esm",
"outDir": "./dist/esm",
"declarationDir": "./dist/esm"
Expand Down