Browse Source

Initial commit

Add files from pridepack.
master
TheoryOfNekomata 2 months ago
commit
d796ab2844
6 changed files with 367 additions and 0 deletions
  1. +3
    -0
      .gitignore
  2. +68
    -0
      package.json
  3. +3
    -0
      pridepack.json
  4. +143
    -0
      src/index.test.ts
  5. +143
    -0
      src/index.ts
  6. +7
    -0
      tsconfig.json

+ 3
- 0
.gitignore View File

@@ -0,0 +1,3 @@
dist/
node_modules/
.idea/

+ 68
- 0
package.json View File

@@ -0,0 +1,68 @@
{
"name": "@tesseract-design/css-utils",
"version": "0.0.0",
"files": [
"dist",
"src"
],
"engines": {
"node": ">=10"
},
"license": "MIT",
"keywords": [
"pridepack"
],
"devDependencies": {
"@types/node": "^18.0.0",
"eslint": "^8.20.0",
"eslint-config-lxsmnsyc": "^0.4.7",
"pridepack": "2.2.1",
"tslib": "^2.4.0",
"typescript": "^4.7.4",
"vitest": "^0.19.1"
},
"scripts": {
"prepublishOnly": "pridepack clean && pridepack build",
"build": "pridepack build",
"type-check": "pridepack check",
"lint": "pridepack lint",
"clean": "pridepack clean",
"watch": "pridepack watch",
"start": "pridepack start",
"dev": "pridepack dev",
"test": "vitest"
},
"private": true,
"description": "CSS utilities powered by goober.",
"repository": {
"url": "",
"type": "git"
},
"homepage": "",
"bugs": {
"url": ""
},
"author": "TheoryOfNekomata <allan.crisostomo@outlook.com>",
"publishConfig": {
"access": "restricted"
},
"dependencies": {
"csstype": "^3.1.0",
"goober": "^2.1.10"
},
"types": "./dist/types/index.d.ts",
"exports": {
".": {
"development": {
"require": "./dist/cjs/development/index.js",
"import": "./dist/esm/development/index.js"
},
"require": "./dist/cjs/production/index.js",
"import": "./dist/esm/production/index.js",
"types": "./dist/types/index.d.ts"
}
},
"typesVersions": {
"*": {}
}
}

+ 3
- 0
pridepack.json View File

@@ -0,0 +1,3 @@
{
"target": "es2017"
}

+ 143
- 0
src/index.test.ts View File

@@ -0,0 +1,143 @@
import { css, CssIfStringImpl, CssStringImpl } from '.';
import {
vi,
describe,
it,
expect,
} from 'vitest';

vi.mock('goober', () => {
return {
css: () => 'gooberClass',
};
});

describe('css-utils', () => {
describe('css', () => {
it('should return CssString', () => {
const c = css`
background-color: white;
color: black;
`

expect(c).toBeInstanceOf(CssStringImpl);
expect(c.toString()).toBe('background-color:white;color:black;');
})
})

describe('css.if', () => {
it('should return CssString when the condition is true', () => {
const c = css.if(true)(
css`
background-color: white;
`
)

expect(c).toBeInstanceOf(CssIfStringImpl)
expect(c.toString()).toBe('background-color:white;')
})

it('should return empty string when the condition is false', () => {
const c = css.if(false)(
css`
background-color: white;
`
)

expect(c.toString()).toBe('')
})

it('should return CssString with .else when the if condition is false', () => {
const c = css.if(false)(
css`
background-color: white;
`
).else(
css`
background-color: black;
`
)

expect(c.toString()).toBe('background-color:black;')
})

it('should return CssString with .else when the if condition is true', () => {
const c = css.if(true)(
css`
background-color: white;
`
).else(
css`
background-color: black;
`
)

expect(c.toString()).toBe('background-color:white;')
})
})

describe('css.nest', () => {
it('should add selector', () => {
const c = css.nest('> div')(
css`
background-color: white;
`
)

expect(c.toString()).toBe('> div{background-color:white;}')
})
})

describe('css.dynamic', () => {
it('should evaluate dynamic object', () => {
const randomNumber = Math.floor(Math.random() * 1000);

const c = css.dynamic({
'line-height': randomNumber,
})

expect(c.toString()).toBe(`line-height:${randomNumber};`)
})
})

describe('css.media', () => {
it('should accept raw queries', () => {
const c = css.media('only screen and (min-width: 720px)')(
css`
color: black;
background-color: white;
`
)

expect(c.toString()).toBe('@media only screen and (min-width: 720px){color:black;background-color:white;}')
})
})

describe('css.cx', () => {
it('should accept strings as classnames', () => {
expect(css.cx('class1', 'class2')).toBe('class1 class2');
})

it('should accept CSS strings for classname generation', () => {
expect(
css.cx(
css`
color: white;
`
)
).toBe('gooberClass');
})

it('should accept mixed values', () => {
expect(
css.cx(
'class1',
'class2',
css`
color: white;
`
)
).toBe('class1 class2 gooberClass');
})
})
})

+ 143
- 0
src/index.ts View File

@@ -0,0 +1,143 @@
import { css as gooberCss } from 'goober';
import { PropertiesHyphenFallback } from 'csstype';

interface CssString {
// TODO stricter type checking
toString(): string
}

export class CssStringImpl implements CssString {
private css: string

constructor(s: TemplateStringsArray) {
this.css = s.raw[0]
.trim()
.replace(/[ ][ ]+/g, ' ')
.replace(/:[ ]/g, ':')
.replace(/\n/g, '')
.replace(/;[ ]/g, ';');
}

toString() {
return this.css
}
}

interface CssIf {
(b: boolean): (...a: CssString[]) => CssIfString
}

interface CssElse {
(...c: CssString[]): CssString
if: CssIf
}

interface CssIfString extends CssString {
else: CssElse
}

const cssIf: CssIf = (b: boolean) => (...a: CssString[]) => new CssIfStringImpl(b, ...a);

export class CssIfStringImpl implements CssIfString {
readonly else: CssElse
private readonly cssStrings: CssString[]

constructor(private readonly condition: boolean, ...cssStrings: CssString[]) {
this.cssStrings = cssStrings

const elseFn = (...c: CssString[]) => {
if (this.condition) {
return {
toString: () => {
return this.cssStrings.map((c2) => c2.toString()).join('');
}
}
}
return {
toString: () => {
return c.map((cc) => cc.toString()).join('')
}
};
}
elseFn.if = cssIf

this.else = elseFn
}

toString() {
if (this.condition) {
return this.cssStrings.map((c2) => c2.toString()).join('');
}
return '';
}
}

interface CssNest {
(selector: string): (...a: CssString[]) => CssString
}

const cssNest: CssNest = (selector) => (...a) => {
return {
toString: () => `${selector}{${a.map(aa => aa.toString()).join('')}}`
}
}

interface CssDynamic {
(a: PropertiesHyphenFallback): CssString
}

const cssDynamic: CssDynamic = (a: PropertiesHyphenFallback) => {
return {
toString(): string {
return Object
.entries(a)
.map(([key, value]) => `${key}:${value.toString()};`)
.join('')
}
};
};

interface CssMedia {
(raw: string): any
}

const cssMedia: CssMedia = (arg1: string) => {
return (...body: CssString[]) => {
return {
toString(): string {
return `@media ${arg1}{${body.map(b => b.toString()).join('')}}`
}
}
}
}

const cssCompile = (...strings: CssString[]) => {
return strings
.filter((s) => ['string', 'object'].includes(typeof s))
.map((s) => {
if (typeof s === 'object') {
return gooberCss`${s.toString()}`
}

return s
})
.join(' ')
}

interface Css {
(s: TemplateStringsArray): CssString
if: CssIf
nest: CssNest
dynamic: CssDynamic
media: CssMedia
cx(...strings: CssString[]): string
}

const _css: Partial<Css> = (s: TemplateStringsArray) => new CssStringImpl(s);
_css.if = cssIf;
_css.nest = cssNest;
_css.dynamic = cssDynamic;
_css.media = cssMedia;
_css.cx = cssCompile;

export const css = _css as Css;

+ 7
- 0
tsconfig.json View File

@@ -0,0 +1,7 @@
{
"exclude": ["node_modules"],
"extends": "../../../tsconfig.json",
"compilerOptions": {
"rootDir": "src"
}
}

Loading…
Cancel
Save