Skip to content

Commit

Permalink
Merge pull request #1 from roginfarrer/improve-parsing-for-functions
Browse files Browse the repository at this point in the history
  • Loading branch information
roginfarrer committed Nov 13, 2020
2 parents b4a3b7b + eca0e3c commit 783c3fb
Show file tree
Hide file tree
Showing 10 changed files with 232 additions and 58 deletions.
30 changes: 15 additions & 15 deletions docs/components/Box.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,25 @@
import {
createSystem,
space,
color,
typography,
border,
layout,
createSystem,
space,
color,
typography,
border,
layout,
} from '../../dist';
import styled from '@emotion/styled';

const system = createSystem();

const Box = styled('div')(
system({
...space,
...color,
...border,
...typography,
...layout,
textAlign: true,
textDecoration: true,
})
system({
...space,
...color,
...border,
...typography,
...layout,
textAlign: true,
textDecoration: true,
})
);

export default Box;
4 changes: 3 additions & 1 deletion examples/styled-components/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,10 @@ const App = () => {
color="$blue.30"
bg="$gray.30"
padding="$2"
border="1px solid $gray.10"
border="1px solid rgba(0, 0, 0, 0.1)"
mb="$6"
transform="scale(.5)"
opacity=".5"
>
Hello
</Box>
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,7 @@
"docz": "^2.3.1",
"husky": "^4.3.0",
"np": "^6.5.0",
"react": "16",
"react-dom": "16",
"prettier": "^2.1.2",
"tsdx": "^0.14.0",
"tslib": "^2.0.1",
"typescript": "^4.0.2"
Expand All @@ -36,6 +35,7 @@
"test": "tsdx test",
"lint": "tsdx lint src",
"prepare": "tsdx build",
"format": "yarn prettier -w **/*.{ts,tsx,js}",
"start-sc": "yarn --cwd ./examples/styled-components start",
"docs": "docz dev",
"docs:build": "docz build",
Expand Down
5 changes: 3 additions & 2 deletions src/props/border/index.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import { betterGet } from '@/core/get';
import { PropConfigCollection, Transform } from '@/types';
import {tokenizeValue} from '../tokenizeValue'

const borderShorthandTransform: Transform = (value, scale, props) => {
export const borderShorthandTransform: Transform = (value, scale, props) => {
if (typeof value !== 'string') {
return value;
}
let border = betterGet(props?.theme?.borders || scale, value);
if (border) {
return border;
}
const [width, style, color] = value.split(' ');
const [[width, style, color]] = tokenizeValue(value)
const borderWidth = betterGet(props?.theme?.borderWidths, width, width);
const borderStyle = betterGet(props?.theme?.borderStyles, style, style);
const borderColor = betterGet(props?.theme?.colors, color, color);
Expand Down
6 changes: 5 additions & 1 deletion src/props/border/test/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import border from '..';
import border, {borderShorthandTransform} from '..';
import { createSystem } from '@/core/createSystem';

const system = createSystem();
Expand Down Expand Up @@ -91,3 +91,7 @@ test('returns border top and bottom radii', () => {
borderBottomLeftRadius: 5,
});
});

test('transform handles rgba', ()=> {
expect(borderShorthandTransform('1px solid rgba(0, 0, 0, 0.1)', {}, {}, false)).toEqual('1px solid rgba(0, 0, 0, 0.1)')
})
10 changes: 3 additions & 7 deletions src/props/shadow/index.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,14 @@
import { betterGet } from '@/core/get';
import { Transform, PropConfigCollection } from '@/types';
import {tokenizeValue} from '../tokenizeValue'

const getShadow: Transform = (value, _, props) => {
export const getShadow: Transform = (value, _, props) => {
let result = betterGet(props?.theme?.shadows, value);
if (result) {
return result;
}
if (typeof value === 'string') {
const arr = value.split(' ');
const maybeColor = arr.pop();
const foundColor = betterGet(props?.theme?.colors, maybeColor);
if (foundColor) {
return [...arr, foundColor].join(' ');
}
return tokenizeValue(value).map((chain) => chain.map((val) => betterGet(props?.theme?.colors, val, val)).join(' ')).join(', ')
}
return value;
};
Expand Down
26 changes: 25 additions & 1 deletion src/props/shadow/tests/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { shadow } from '..';
import { shadow, getShadow } from '../index';
import { createSystem } from '@/core/createSystem';

const system = createSystem();
Expand Down Expand Up @@ -43,3 +43,27 @@ test('uses theme colors if it can', () => {
boxShadow: '0 -1px #FF0000',
});
});

describe('getShadow', () => {
const props = {
theme: {
colors: {
gray400: '#e3e3e3',
},
},
};

test('handles tokens in box-shadow', () => {
expect(getShadow('10px 5px 1px gray400', {}, props, false)).toEqual(
'10px 5px 1px #e3e3e3'
);

expect(getShadow('1px -16px gray400', {}, props, false)).toEqual('1px -16px #e3e3e3')

expect(getShadow('inset 1px 1em gray400', {}, props, false)).toEqual('inset 1px 1em #e3e3e3')

expect(getShadow('60px -16px gray400', {}, props, false)).toEqual('60px -16px #e3e3e3')

expect(getShadow('60px -16px rgba(0, 242, 42, .24)', {}, props, false)).toEqual('60px -16px rgba(0, 242, 42, .24)')
});
});
57 changes: 57 additions & 0 deletions src/props/tests/tokenizeValue.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { tokenizeValue } from '../tokenizeValue';

describe('Parse css values into token groups', () => {
test('Will bail out early if giving a falsy value', () => {
expect(tokenizeValue(undefined as any)).toEqual([[]]);
});
test('Parse space separated values', () => {
expect(tokenizeValue('solid red')).toEqual([['solid', 'red']]);
});
test('Parse space separated values that contain units', () => {
expect(tokenizeValue('1px 1px')).toEqual([['1px', '1px']]);
});
test('Ignore useless whitespace', () => {
expect(tokenizeValue(' 1px 1px ')).toEqual([['1px', '1px']]);
});

test('Handles quoted strings', () => {
expect(tokenizeValue('1.2em "Fira Sans"')).toEqual([['1.2em', '"Fira Sans"']]);
});

test('Handles css functions', () => {
expect(tokenizeValue('3s cubic-bezier(0.1, -0.6, 0.2, 0) 1s')).toEqual([
['3s', 'cubic-bezier(0.1, -0.6, 0.2, 0)', '1s'],
]);
});

test('Handles nested css functions', () => {
expect(tokenizeValue('linear-gradient(red, hsla(120,100%,50%,0.3))')).toEqual([
['linear-gradient(red, hsla(120,100%,50%,0.3))'],
]);
});

test('Handles multiple groups', () => {
expect(tokenizeValue('ease-in 1s slidein, ease-in 2s')).toEqual([
['ease-in', '1s', 'slidein'],
['ease-in', '2s'],
]);
});

test('Handles multiple groups with css functions', () => {
expect(tokenizeValue('linear-gradient(red, blue), red')).toEqual([['linear-gradient(red, blue)'], ['red']]);
});

test('handles slashes', () => {
expect(tokenizeValue('small-caps bold 24px/1 sans-serif')).toEqual([
['small-caps', 'bold', '24px/1', 'sans-serif'],
]);
expect(tokenizeValue('2em / 5em')).toEqual([['2em', '/', '5em']]);
});

test('Handles nested css functions in multiple groups', () => {
expect(tokenizeValue('linear-gradient(red, hsla(120,100%,50%,0.3)), red')).toEqual([
['linear-gradient(red, hsla(120,100%,50%,0.3))'],
['red'],
]);
});
});
117 changes: 117 additions & 0 deletions src/props/tokenizeValue.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
// Full credit goes to modulz/stitches
// Handles complex CSS values and functions like rgba, linear-gradient,
// and other things with parenthesis
// https://github.com/modulz/stitches/blob/1ea8656e849d48b08a9222b89cb919936b0f5f93/packages/core/src/shorthand-parser/value-tokenizer.ts

const TOKEN_STRING = 1;
const TOKEN_QUOTED_STRING = 2;
const TOKEN_FUNCTION = 3;
const TOKEN_BRACKET = 4;

type TokenType = typeof TOKEN_STRING | typeof TOKEN_QUOTED_STRING | typeof TOKEN_FUNCTION | typeof TOKEN_BRACKET | 0;

let currentType: TokenType;
let currentToken = '';
let currentDepth = 0;
let tokenGroups: string[][] = [[]];

export const tokenizeValue = (str: string) => {
resetCurrentToken();
tokenGroups = [[]];
if (!str) {
return tokenGroups;
}
const strLength = str.length;
for (let i = 0; i < strLength; i++) {
const char = str[i];
switch (char) {
// whitespace
case ' ':
if (currentType === TOKEN_STRING) {
addCurrentTokenToGroup();
} else if (currentType) {
currentToken += char;
}
break;
// new token group
case ',':
if (!currentDepth) {
addCurrentTokenToGroup();
addNewTokenGroup();
} else {
currentToken += char;
}
break;

// Quoted string:
case '"':
currentToken += char;
if (!currentDepth && !currentType) {
currentType = TOKEN_QUOTED_STRING;
currentDepth = 1;
} else if (currentDepth === 1 && currentType === TOKEN_QUOTED_STRING) {
currentDepth = 0;
addCurrentTokenToGroup();
}
break;

// Css function:
case '(':
if (!currentDepth) currentType = TOKEN_FUNCTION;
currentDepth++;
currentToken += char;
break;

case ')':
currentToken += char;
currentDepth--;
if (currentType === TOKEN_FUNCTION && !currentDepth) addCurrentTokenToGroup();
break;

// Bracket values:
case '[':
if (!currentDepth) currentType = TOKEN_BRACKET;
currentToken += char;
currentDepth++;
break;
case ']':
currentToken += char;
currentDepth--;
if (!currentDepth) addCurrentTokenToGroup();
break;

default:
if (!currentType) currentType = TOKEN_STRING;
currentToken += char;
}
}
if (currentToken) addCurrentTokenToGroup();
return tokenGroups;
};
/**
* UTILS:
*/

/**
* Resets the current token info
*/
function resetCurrentToken() {
currentDepth = currentType = 0;
currentToken = '';
}
/**
* Adds current token to the stack then starts a new one
*/
function addCurrentTokenToGroup() {
if (currentType) tokenGroups[tokenGroups.length - 1].push(currentToken);
resetCurrentToken();
}
/**
* Adds a new token group and requests a new one
* For things like animations or box shadow where there might be multiple rules
* applied to the same value
*/
function addNewTokenGroup() {
tokenGroups[tokenGroups.length] = [];
resetCurrentToken();
}
Loading

0 comments on commit 783c3fb

Please sign in to comment.