Skip to content

Commit

Permalink
feat(themes): add token meta info for site and v11 theme (#8959)
Browse files Browse the repository at this point in the history
* feat(themes): add token meta info for site and v11 theme

* fix(themes): update import for tokens to Tokens

Co-authored-by: Scott Strubberg <[email protected]>
  • Loading branch information
joshblack and sstrubberg authored Jun 22, 2021
1 parent 0aea094 commit 4e62c63
Show file tree
Hide file tree
Showing 13 changed files with 1,386 additions and 0 deletions.
34 changes: 34 additions & 0 deletions packages/themes/src/next/tokens/Token.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/**
* Copyright IBM Corp. 2018, 2018
*
* This source code is licensed under the Apache-2.0 license found in the
* LICENSE file in the root directory of this source tree.
*/

/**
* A Token is the simplest unit in our theme. It can have a name, properties
* that it applies to like border or background, along with a state if the
* token should only be used for specific states like hover or focus.
*/
export class Token {
static create(token) {
if (typeof token === 'string') {
return new Token(token);
}

return new Token(token.name, token.properties, token.state);
}

constructor(name, properties, state) {
this.kind = 'Token';
this.name = name;

if (properties) {
this.properties = properties;
}

if (state) {
this.state = state;
}
}
}
37 changes: 37 additions & 0 deletions packages/themes/src/next/tokens/TokenFormat.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/**
* Copyright IBM Corp. 2018, 2018
*
* This source code is licensed under the Apache-2.0 license found in the
* LICENSE file in the root directory of this source tree.
*/

const formats = {
js: 'javascript',
};

export const TokenFormat = {
formats,

convert({ name, format }) {
if (format === formats.js) {
const keywords = new Set(['ui']);

return name
.split('-')
.map((part, index) => {
if (index === 0) {
return part;
}

if (keywords.has(part)) {
return part.toUpperCase();
}

return part[0].toUpperCase() + part.slice(1);
})
.join('');
}

return name;
},
};
161 changes: 161 additions & 0 deletions packages/themes/src/next/tokens/TokenGroup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
/**
* Copyright IBM Corp. 2018, 2018
*
* This source code is licensed under the Apache-2.0 license found in the
* LICENSE file in the root directory of this source tree.
*/

import { Token } from './Token';

/**
* A TokenGroup allows us to group up a collection of tokens and nested token
* groups. A group allows us to colocate related tokens and write information
* once that applies to the entire collection of tokens. For example, if all the
* tokens apply to the `border` color property then we can specify this property
* at the group level
*
* A TokenGroup allows us to colocate all this information while also providing
* ways to get information about the entire group, including properties and
* states
*/
export class TokenGroup {
static create({ name, properties, tokens = [] }) {
return new TokenGroup(name, tokens, properties);
}

constructor(name, tokens, properties) {
this.kind = 'TokenGroup';
this.name = name;

if (properties) {
this.properties = properties;
}

this.children = tokens.map((child) => {
if (child.kind === 'TokenGroup') {
return child;
}

return Token.create(child);
});
}

*[Symbol.iterator]() {
yield this;

for (const child of this.children) {
yield child;

if (child.kind === 'TokenGroup') {
yield* child;
}
}
}

/**
* Get all the tokens available in every Token Group in this TokenGroup,
* including itself.
* @returns {Array<Token>}
*/
getTokens(parentContext = {}) {
const context = {
...parentContext,
groups: parentContext.groups ? parentContext.groups.concat(this) : [this],
properties: this.properties || parentContext.properties,
};

return this.children.flatMap((child) => {
if (child.kind === 'TokenGroup') {
return child.getTokens(context);
}

const token = {
...context,
name: child.name,
properties: child.properties || context.properties,
};

if (child.state) {
token.state = child.state;
}

return token;
});
}

/**
* Get a specific token from the TokenGroup, or form one of its nested
* TokenGroups
* @returns {Token}
*/
getToken(tokenOrName) {
const name =
typeof tokenOrName === 'string' ? tokenOrName : tokenOrName.name;
for (const child of this) {
if (child.kind === 'TokenGroup') {
continue;
}

if (child.name === name) {
return child;
}
}
return null;
}

/**
* Get all the unique groups in the token group, including this group
* @returns {Array<TokenGroup>}
*/
getTokenGroups() {
const set = new Set();

for (const child of this) {
if (child.kind !== 'TokenGroup') {
continue;
}
set.add(child);
}

return Array.from(set);
}

/**
* Get all the unique properties in the token group, including this group
* @returns {Array<string>}
*/
getTokenProperties() {
const set = new Set();

for (const child of this) {
if (!Array.isArray(child.properties)) {
continue;
}

for (const property of child.properties) {
set.add(property);
}
}

return Array.from(set);
}

/**
* Get all the unique states in the token group, including this group
* @returns {Array<string>}
*/
getTokenStates() {
const set = new Set();

for (const child of this) {
if (child.kind !== 'Token') {
continue;
}
if (child.state) {
set.add(child.state);
}
}

return Array.from(set);
}
}
77 changes: 77 additions & 0 deletions packages/themes/src/next/tokens/TokenSet.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/**
* Copyright IBM Corp. 2018, 2018
*
* This source code is licensed under the Apache-2.0 license found in the
* LICENSE file in the root directory of this source tree.
*/

/**
* A token set is a collection of tokens which should be along with each other.
* For example, we have tokens that correspond to a layer level in a UI. A
* token set allows us to group these tokens together in a way that's different
* than their token group.
*/
export class TokenSet {
static create({ name, tokens }) {
return new TokenSet(name, tokens);
}

constructor(name, tokens = []) {
this.kind = 'TokenSet';
this.name = name;
this.children = tokens;
}

*[Symbol.iterator]() {
for (const child of this.children) {
yield child;

if (child.kind === 'TokenSet') {
yield* child;
}
}
}

getTokenSets() {
const children = this.children
.filter((child) => {
return child.kind === 'TokenSet';
})
.flatMap((child) => {
return child.getTokenSets();
});

return [this, ...children];
}

getTokenSet(name) {
for (const child of this) {
if (!child.kind === 'TokenSet') {
continue;
}

if (child.name === name) {
return child;
}
}

return null;
}

hasToken(tokenOrName) {
const name =
typeof tokenOrName === 'string' ? tokenOrName : tokenOrName.name;

for (const child of this) {
if (child.kind === 'TokenSet') {
continue;
}

if (child.name === name) {
return true;
}
}

return false;
}
}
47 changes: 47 additions & 0 deletions packages/themes/src/next/tokens/__tests__/Token-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/**
* Copyright IBM Corp. 2018, 2018
*
* This source code is licensed under the Apache-2.0 license found in the
* LICENSE file in the root directory of this source tree.
*/

import { Token } from '../';

describe('Token', () => {
it('should build a token with a string or object', () => {
const test1 = Token.create('test1');
expect(test1).toBeInstanceOf(Token);

const test2 = Token.create({
name: 'test2',
});
expect(test2).toBeInstanceOf(Token);
});

it('should have a name property', () => {
const token = Token.create('test');
expect(token.name).toBe('test');
});

it('should have a properties property if one is provided', () => {
const none = Token.create('none');
expect(none.properties).not.toBeDefined();

const some = Token.create({
name: 'some',
properties: ['background'],
});
expect(some.properties).toBeDefined();
});

it('should have a state property if one is provided', () => {
const none = Token.create('none');
expect(none.state).not.toBeDefined();

const some = Token.create({
name: 'some',
state: 'hover',
});
expect(some.state).toBeDefined();
});
});
29 changes: 29 additions & 0 deletions packages/themes/src/next/tokens/__tests__/TokenFormat-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/**
* Copyright IBM Corp. 2018, 2018
*
* This source code is licensed under the Apache-2.0 license found in the
* LICENSE file in the root directory of this source tree.
*/

import { TokenFormat } from '../';

describe('TokenFormat', () => {
it('should return the given name if no formatting is required', () => {
expect(TokenFormat.convert({ name: 'token-name' })).toBe('token-name');
});

describe('formats.js', () => {
test.each([
['token-name', 'tokenName'],
['test-ui', 'testUI'],
['token-01', 'token01'],
])('%s should be formatted to %s', (input, formatted) => {
expect(
TokenFormat.convert({
name: input,
format: TokenFormat.formats.js,
})
).toBe(formatted);
});
});
});
Loading

0 comments on commit 4e62c63

Please sign in to comment.