@telus-uds/system-theme-tokens 2.7.0 → 2.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +31 -3
- package/appearances.js +20 -4
- package/bin/android/build.js +68 -0
- package/bin/android/templates/component.js +67 -0
- package/bin/android/templates/header.js +21 -0
- package/bin/android/templates/imports.js +28 -0
- package/bin/android/templates/model.js +14 -0
- package/bin/android/tokens.js +55 -0
- package/bin/android/types.js +50 -0
- package/bin/build-android.js +6 -0
- package/bin/build-ios.js +6 -0
- package/bin/ios/build.js +68 -0
- package/bin/ios/templates/component.js +46 -0
- package/bin/ios/templates/header.js +28 -0
- package/bin/ios/templates/model.js +14 -0
- package/bin/ios/tokens.js +107 -0
- package/bin/ios/types.js +49 -0
- package/components.js +61 -2
- package/index.js +2 -1
- package/package.json +15 -3
- package/tokens.js +360 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,18 +1,46 @@
|
|
|
1
1
|
# Change Log - @telus-uds/system-theme-tokens
|
|
2
2
|
|
|
3
|
-
This log was last generated on
|
|
3
|
+
This log was last generated on Wed, 07 Dec 2022 15:09:47 GMT and should not be manually modified.
|
|
4
4
|
|
|
5
5
|
<!-- Start content -->
|
|
6
6
|
|
|
7
|
+
## 2.9.0
|
|
8
|
+
|
|
9
|
+
Wed, 07 Dec 2022 15:09:47 GMT
|
|
10
|
+
|
|
11
|
+
### Minor changes
|
|
12
|
+
|
|
13
|
+
- Add generated Swift structs (alan.slater@nearform.com)
|
|
14
|
+
- QuickLinksFeature component implementation (tiagohldb@gmail.com)
|
|
15
|
+
- Adding iconPosition for ButtonGroup (tiagohldb@gmail.com)
|
|
16
|
+
- Add generated Kotlin type classes (alan.slater@nearform.com)
|
|
17
|
+
|
|
18
|
+
### Patches
|
|
19
|
+
|
|
20
|
+
- enable icon padding top for icons themes (mauricio.batresmontejo@telus.com)
|
|
21
|
+
|
|
22
|
+
## 2.8.0
|
|
23
|
+
|
|
24
|
+
Tue, 08 Nov 2022 01:35:30 GMT
|
|
25
|
+
|
|
26
|
+
### Minor changes
|
|
27
|
+
|
|
28
|
+
- feat: add ButtonDropdown component. (ugursysl@gmail.com)
|
|
29
|
+
- Background feature for Carousel (tiagohldb@gmail.com)
|
|
30
|
+
- Add platform specificity to some appearances (alan.slater@nearform.com)
|
|
31
|
+
- Box gradient (tiagohldb@gmail.com)
|
|
32
|
+
- feat: add clear button tokens to TextInput (ruslan.bredikhin@nearform.com)
|
|
33
|
+
- Bump @telus-uds/system-constants to v1.2.0
|
|
34
|
+
|
|
7
35
|
## 2.7.0
|
|
8
36
|
|
|
9
|
-
Fri, 14 Oct 2022 19:
|
|
37
|
+
Fri, 14 Oct 2022 19:30:03 GMT
|
|
10
38
|
|
|
11
39
|
### Minor changes
|
|
12
40
|
|
|
13
41
|
- Implementing hide label for pagination on larger screens (tiagohldb@gmail.com)
|
|
14
42
|
- feat: add tokens for TextInput buttons (ruslan.bredikhin@nearform.com)
|
|
15
|
-
- Update `Button` theme with `textLine` and `textLineStyle`
|
|
43
|
+
- Update `Button` theme with `textLine` and `textLineStyle` (shahzaibkhalidmalik@outlook.com)
|
|
16
44
|
- Bump @telus-uds/system-constants to v1.1.0
|
|
17
45
|
|
|
18
46
|
## 2.6.0
|
package/appearances.js
CHANGED
|
@@ -7,6 +7,14 @@ const focus = {
|
|
|
7
7
|
description:
|
|
8
8
|
"Currently only web has good support for this. Applies when an interactive component's focus handler is triggered, such as keyboard tabbing or selection.",
|
|
9
9
|
values: [true],
|
|
10
|
+
type: 'state',
|
|
11
|
+
platforms: ['rn'] // In most cases, Native Apps team want to skip focus state
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const inputFocus = {
|
|
15
|
+
description:
|
|
16
|
+
'Focus states for some input elements are well supported across web, RN, ios and android',
|
|
17
|
+
values: [true],
|
|
10
18
|
type: 'state'
|
|
11
19
|
}
|
|
12
20
|
|
|
@@ -14,7 +22,8 @@ const hover = {
|
|
|
14
22
|
description:
|
|
15
23
|
"Currently web only. Applies when an interactive component's hover handler is triggered, such as on mouseover.",
|
|
16
24
|
values: [true],
|
|
17
|
-
type: 'state'
|
|
25
|
+
type: 'state',
|
|
26
|
+
platforms: ['rn'] // Native Apps team currently want to skip ios / android hover
|
|
18
27
|
}
|
|
19
28
|
|
|
20
29
|
const pressed = {
|
|
@@ -50,6 +59,12 @@ const validation = {
|
|
|
50
59
|
type: 'state'
|
|
51
60
|
}
|
|
52
61
|
|
|
62
|
+
const open = {
|
|
63
|
+
description: 'Applies when a ButtonDropdown is open.',
|
|
64
|
+
values: [true],
|
|
65
|
+
type: 'state'
|
|
66
|
+
}
|
|
67
|
+
|
|
53
68
|
// TODO this is used in radio and checkbox, should re-use validation state above?
|
|
54
69
|
const error = { values: [true], type: 'state' }
|
|
55
70
|
|
|
@@ -70,6 +85,7 @@ module.exports = {
|
|
|
70
85
|
}
|
|
71
86
|
},
|
|
72
87
|
Button: { focus, hover, pressed, inactive, selected },
|
|
88
|
+
ButtonDropdown: { focus, hover, pressed, inactive, selected, open },
|
|
73
89
|
ButtonGroupItem: { focus, hover, pressed, inactive, selected },
|
|
74
90
|
CarouselTabsPanelItem: { focus, hover, pressed, inactive, selected },
|
|
75
91
|
Checkbox: { checked, error, focus, hover, inactive },
|
|
@@ -138,9 +154,9 @@ module.exports = {
|
|
|
138
154
|
QuickLinksList: { hover, focus, pressed },
|
|
139
155
|
Radio: { checked, error, focus, hover, inactive },
|
|
140
156
|
RadioCard: { pressed, checked, error, focus, hover, inactive },
|
|
141
|
-
Search: { hover, focus, inactive },
|
|
157
|
+
Search: { hover, focus: inputFocus, inactive },
|
|
142
158
|
SearchButton: { hover, focus, pressed, inactive },
|
|
143
|
-
Select: { validation, hover, focus, inactive },
|
|
159
|
+
Select: { validation, hover, focus: inputFocus, inactive },
|
|
144
160
|
SideNavItem: {
|
|
145
161
|
active: {
|
|
146
162
|
description:
|
|
@@ -167,7 +183,7 @@ module.exports = {
|
|
|
167
183
|
},
|
|
168
184
|
TabsItem: { focus, hover, pressed, selected },
|
|
169
185
|
TagsItem: { focus, hover, pressed, inactive, selected },
|
|
170
|
-
TextInput: { validation, hover,
|
|
186
|
+
TextInput: { validation, hover, inactive, focus: inputFocus },
|
|
171
187
|
ToggleSwitch: { focus, hover, pressed, inactive, selected },
|
|
172
188
|
TooltipButton: { focus, hover, pressed },
|
|
173
189
|
SkipLink: { focus }
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
const { writeFile, rm, mkdir } = require('fs/promises')
|
|
2
|
+
const { resolve } = require('path')
|
|
3
|
+
const chalk = require('chalk')
|
|
4
|
+
|
|
5
|
+
const components = require('../../components')
|
|
6
|
+
const tokensTemplate = require('./templates/component')
|
|
7
|
+
const modelsTemplate = require('./templates/model')
|
|
8
|
+
const packageJson = require('../../package.json')
|
|
9
|
+
const tokenTypes = require('./tokens')
|
|
10
|
+
const { customEnums } = require('./types')
|
|
11
|
+
|
|
12
|
+
// eslint-disable-next-line no-console
|
|
13
|
+
const log = (message, colour = chalk.grey) => console.log(colour(message))
|
|
14
|
+
|
|
15
|
+
async function build({ author = 'Telus' } = {}) {
|
|
16
|
+
log(chalk.grey('Beginning system-theme-tokens/android build:'))
|
|
17
|
+
|
|
18
|
+
const { version } = packageJson
|
|
19
|
+
const credit = `Created by ${author} from @telus-uds/system-theme-tokens ${version}`
|
|
20
|
+
const outputDir = resolve(__dirname, '../../generated/android')
|
|
21
|
+
|
|
22
|
+
const componentsDir = resolve(outputDir, 'Components')
|
|
23
|
+
const getComponentDirectory = (componentName) => resolve(componentsDir, componentName)
|
|
24
|
+
const componentEntries = Object.entries(components)
|
|
25
|
+
|
|
26
|
+
const modelsDir = resolve(outputDir, 'Models')
|
|
27
|
+
const models = Object.values(tokenTypes).filter(({ type }) =>
|
|
28
|
+
Object.values(customEnums).includes(type)
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
// Preparation
|
|
32
|
+
log(chalk.grey(' • Flushing android/output folders...'))
|
|
33
|
+
await rm(outputDir, { recursive: true, force: true })
|
|
34
|
+
await mkdir(outputDir)
|
|
35
|
+
|
|
36
|
+
// Models for UDS enum types
|
|
37
|
+
log(chalk.grey(` • Creating Kotlin enum definitions to model enum token types...`))
|
|
38
|
+
await mkdir(modelsDir)
|
|
39
|
+
await Promise.all(
|
|
40
|
+
models.map(async ({ type, values }) => {
|
|
41
|
+
const fileName = `${type}.kt`
|
|
42
|
+
const fileContents = modelsTemplate({ credit, type, values, fileName })
|
|
43
|
+
const filePath = resolve(modelsDir, fileName)
|
|
44
|
+
await writeFile(filePath, fileContents)
|
|
45
|
+
})
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
// Component directories
|
|
49
|
+
log(chalk.grey(` • Creating component folders in android/output/Components/...`))
|
|
50
|
+
await mkdir(componentsDir)
|
|
51
|
+
await Promise.all(
|
|
52
|
+
componentEntries.map(([componentName]) => mkdir(getComponentDirectory(componentName)))
|
|
53
|
+
)
|
|
54
|
+
// Component tokens definitions
|
|
55
|
+
log(chalk.grey(` • Creating Kotlin token type classes for every component...`))
|
|
56
|
+
await Promise.all(
|
|
57
|
+
componentEntries.map(async ([componentName, tokens]) => {
|
|
58
|
+
const fileName = `${componentName}Tokens.kt`
|
|
59
|
+
const fileContents = tokensTemplate({ credit, componentName, fileName, tokens })
|
|
60
|
+
const filePath = resolve(getComponentDirectory(componentName), fileName)
|
|
61
|
+
await writeFile(filePath, fileContents)
|
|
62
|
+
})
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
log(chalk.grey('✔️ Completed system-theme-tokens/android build.'))
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
module.exports = build
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
const tokenTypes = require('../tokens')
|
|
2
|
+
const getImports = require('./imports')
|
|
3
|
+
const { customEnums } = require('../types')
|
|
4
|
+
|
|
5
|
+
const headerTemplate = require('./header')
|
|
6
|
+
|
|
7
|
+
function getKotlinType(tokenType) {
|
|
8
|
+
const { type, nullable } = tokenTypes[tokenType]
|
|
9
|
+
return nullable ? `${type}?` : type
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const customEnumValues = Object.values(customEnums)
|
|
13
|
+
|
|
14
|
+
function formatDefaultValue(defaultValue, type) {
|
|
15
|
+
if (typeof defaultValue === 'string') {
|
|
16
|
+
if (defaultValue.match(/^UDSColor/)) {
|
|
17
|
+
return defaultValue
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return customEnumValues.includes(type) && defaultValue[0] === '.'
|
|
21
|
+
? // Specify enum's default property
|
|
22
|
+
`${type}${defaultValue}`
|
|
23
|
+
: // Wrap string value in quotes
|
|
24
|
+
`"${defaultValue}"`
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (typeof defaultValue === 'number') {
|
|
28
|
+
switch (type) {
|
|
29
|
+
case 'Dp':
|
|
30
|
+
return `${defaultValue.toFixed(1)}dp`
|
|
31
|
+
case 'Double':
|
|
32
|
+
return defaultValue.toFixed(1)
|
|
33
|
+
default:
|
|
34
|
+
return defaultValue
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return defaultValue
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function getKotlinDefault(tokenType) {
|
|
42
|
+
const { default: defaultValue, type, nullable } = tokenTypes[tokenType]
|
|
43
|
+
if (defaultValue === undefined) return ''
|
|
44
|
+
if (nullable && defaultValue === null) return '' // don't need explicit nullable nulls
|
|
45
|
+
|
|
46
|
+
return ` = ${formatDefaultValue(defaultValue, type)}`
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function componentTemplate({ componentName, credit, tokens }) {
|
|
50
|
+
const header = headerTemplate({
|
|
51
|
+
packageName: `com.telus.udsnative.components.${componentName.toLowerCase()}`,
|
|
52
|
+
credit,
|
|
53
|
+
imports: getImports(Object.values(tokens), ['com.telus.udsnative.models.Tokens'])
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
return `${header}data class ${componentName}Tokens(
|
|
57
|
+
${Object.entries(tokens)
|
|
58
|
+
.map(
|
|
59
|
+
([tokenName, tokenType]) => ` // UDS '${tokenType}' token
|
|
60
|
+
val ${tokenName}: ${getKotlinType(tokenType)}${getKotlinDefault(tokenType)}`
|
|
61
|
+
)
|
|
62
|
+
.join(',\n')}
|
|
63
|
+
): Tokens
|
|
64
|
+
`
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
module.exports = componentTemplate
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
function formatPackage(packageName) {
|
|
2
|
+
return packageName
|
|
3
|
+
? `package ${packageName}
|
|
4
|
+
|
|
5
|
+
`
|
|
6
|
+
: ''
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function formatImports(imports = []) {
|
|
10
|
+
return imports.length
|
|
11
|
+
? `${imports.map((importName) => `import ${importName}`).join('\n')}
|
|
12
|
+
|
|
13
|
+
`
|
|
14
|
+
: ''
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function headerTemplate({ packageName, imports }) {
|
|
18
|
+
return `${formatPackage(packageName)}${formatImports(imports)}`
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
module.exports = headerTemplate
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
const { composeTypes, customTypes, customEnums } = require('../types')
|
|
2
|
+
const tokenTypes = require('../tokens')
|
|
3
|
+
|
|
4
|
+
function mapImports(source, path) {
|
|
5
|
+
// This pattern holds for everything so far; if there are exceptions, could add an object of them here
|
|
6
|
+
return Object.fromEntries(Object.values(source).map((value) => [value, `${path}${value}`]))
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const composeImports = mapImports(composeTypes, 'androidx.compose.ui.unit.')
|
|
10
|
+
const udsImports = mapImports({ ...customTypes, ...customEnums }, 'com.telus.udsnative.models.')
|
|
11
|
+
const allImports = { ...composeImports, ...udsImports }
|
|
12
|
+
|
|
13
|
+
function getImports(tokensValues, imports = []) {
|
|
14
|
+
tokensValues.forEach((token) => {
|
|
15
|
+
const { type: tokenType } = tokenTypes[token]
|
|
16
|
+
const importPath = allImports[tokenType]
|
|
17
|
+
if (importPath && !imports.includes(importPath)) {
|
|
18
|
+
imports.push(importPath)
|
|
19
|
+
}
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
// Follow convention of sorting imports alphabetically
|
|
23
|
+
imports.sort()
|
|
24
|
+
|
|
25
|
+
return imports
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
module.exports = getImports
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
const headerTemplate = require('./header')
|
|
2
|
+
|
|
3
|
+
function modelTemplate({ type, values }) {
|
|
4
|
+
const valuesKeys = Object.keys(values)
|
|
5
|
+
|
|
6
|
+
const header = headerTemplate({ packageName: 'com.telus.udsnative.models' })
|
|
7
|
+
|
|
8
|
+
return `${header}enum class ${type} {
|
|
9
|
+
${valuesKeys.map((valueKey) => ` ${valueKey}`).join(',\n')}
|
|
10
|
+
}
|
|
11
|
+
`
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
module.exports = modelTemplate
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
const deepMerge = require('lodash.merge')
|
|
2
|
+
const iosTokens = require('../ios/tokens')
|
|
3
|
+
const { types } = require('./types')
|
|
4
|
+
|
|
5
|
+
const nilToNull = (tokens) =>
|
|
6
|
+
Object.fromEntries(
|
|
7
|
+
Object.entries(tokens).map(([key, { default: defaultValue, ...values }]) => [
|
|
8
|
+
key,
|
|
9
|
+
{ ...values, default: defaultValue === 'nil' ? null : defaultValue }
|
|
10
|
+
])
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
const androidTokens = deepMerge({}, nilToNull(iosTokens), {
|
|
14
|
+
size: {
|
|
15
|
+
// Gson sometimes has trouble deserializing Dp Optional; might need to use Int or Double?
|
|
16
|
+
type: types.Dp
|
|
17
|
+
},
|
|
18
|
+
border: {
|
|
19
|
+
type: types.Dp
|
|
20
|
+
},
|
|
21
|
+
color: {
|
|
22
|
+
type: types.Color,
|
|
23
|
+
default: 'UDSColor(0, 0, 0, 0)'
|
|
24
|
+
},
|
|
25
|
+
radius: {
|
|
26
|
+
type: types.Dp
|
|
27
|
+
},
|
|
28
|
+
shadow: {
|
|
29
|
+
type: types.Shadow
|
|
30
|
+
},
|
|
31
|
+
fontSize: {
|
|
32
|
+
type: types.TextUnit
|
|
33
|
+
},
|
|
34
|
+
lineHeight: {
|
|
35
|
+
type: types.Double
|
|
36
|
+
},
|
|
37
|
+
fontWeight: {
|
|
38
|
+
type: types.Int,
|
|
39
|
+
default: 400
|
|
40
|
+
},
|
|
41
|
+
opacity: {
|
|
42
|
+
type: types.Double
|
|
43
|
+
},
|
|
44
|
+
letterSpacing: {
|
|
45
|
+
type: types.Dp
|
|
46
|
+
},
|
|
47
|
+
duration: {
|
|
48
|
+
type: types.Double
|
|
49
|
+
},
|
|
50
|
+
iconScale: {
|
|
51
|
+
type: types.Double
|
|
52
|
+
}
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
module.exports = androidTokens
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// Built-in Kotlin primitive types:
|
|
2
|
+
const kotlinTypes = {
|
|
3
|
+
Double: 'Double', // Numbers to use in calculations
|
|
4
|
+
Int: 'Int', // Integers to use as indexes etc
|
|
5
|
+
String: 'String', // Arbitrary strings without an existing enum
|
|
6
|
+
Bool: 'Bool' // Boolean (true | false)
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
// These types are available as imports from androidx.compose
|
|
10
|
+
const composeTypes = {
|
|
11
|
+
Dp: 'Dp',
|
|
12
|
+
TextUnit: 'TextUnit'
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// These types are pre-defined in universal-design-system-android
|
|
16
|
+
const customTypes = {
|
|
17
|
+
Color: 'UDSColor', // Custom class for objects with red, green, blue, alpha
|
|
18
|
+
PaletteGradient: 'PaletteGradient', // Custom class
|
|
19
|
+
Shadow: 'Shadow'
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// These are enum types generated here
|
|
23
|
+
const customEnums = {
|
|
24
|
+
TextAlignment: 'TextAlignmentToken',
|
|
25
|
+
TextLine: 'TextLineToken',
|
|
26
|
+
TextLineStyle: 'TextLineStyleToken',
|
|
27
|
+
BorderStyle: 'BorderStyleToken',
|
|
28
|
+
FlexAlign: 'FlexAlignToken',
|
|
29
|
+
FlexJustifyContent: 'FlexJustifyContentToken',
|
|
30
|
+
FontStyle: 'FontStyleToken',
|
|
31
|
+
Position: 'PositionToken',
|
|
32
|
+
Direction: 'DirectionToken',
|
|
33
|
+
VerticalAlign: 'VerticalAlignToken',
|
|
34
|
+
TextTransform: 'TextTransformToken'
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const types = {
|
|
38
|
+
...kotlinTypes,
|
|
39
|
+
...composeTypes,
|
|
40
|
+
...customEnums,
|
|
41
|
+
...customTypes
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
module.exports = {
|
|
45
|
+
types,
|
|
46
|
+
kotlinTypes,
|
|
47
|
+
composeTypes,
|
|
48
|
+
customEnums,
|
|
49
|
+
customTypes
|
|
50
|
+
}
|
package/bin/build-ios.js
ADDED
package/bin/ios/build.js
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
const { writeFile, rm, mkdir } = require('fs/promises')
|
|
2
|
+
const { resolve } = require('path')
|
|
3
|
+
const chalk = require('chalk')
|
|
4
|
+
|
|
5
|
+
const components = require('../../components')
|
|
6
|
+
const tokensTemplate = require('./templates/component')
|
|
7
|
+
const modelsTemplate = require('./templates/model')
|
|
8
|
+
const packageJson = require('../../package.json')
|
|
9
|
+
const tokenTypes = require('./tokens')
|
|
10
|
+
const { customEnums } = require('./types')
|
|
11
|
+
|
|
12
|
+
// eslint-disable-next-line no-console
|
|
13
|
+
const log = (message, colour = chalk.grey) => console.log(colour(message))
|
|
14
|
+
|
|
15
|
+
async function build({ author = 'Telus' } = {}) {
|
|
16
|
+
log(chalk.grey('Beginning system-theme-tokens/ios build:'))
|
|
17
|
+
|
|
18
|
+
const { version } = packageJson
|
|
19
|
+
const credit = `Created by ${author} from @telus-uds/system-theme-tokens ${version}`
|
|
20
|
+
const outputDir = resolve(__dirname, '../../generated/ios')
|
|
21
|
+
|
|
22
|
+
const componentsDir = resolve(outputDir, 'Components')
|
|
23
|
+
const getComponentDirectory = (componentName) => resolve(componentsDir, componentName)
|
|
24
|
+
const componentEntries = Object.entries(components)
|
|
25
|
+
|
|
26
|
+
const modelsDir = resolve(outputDir, 'Models')
|
|
27
|
+
const models = Object.values(tokenTypes).filter(({ type }) =>
|
|
28
|
+
Object.values(customEnums).includes(type)
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
// Preparation
|
|
32
|
+
log(chalk.grey(' • Flushing ios/output folders...'))
|
|
33
|
+
await rm(outputDir, { recursive: true, force: true })
|
|
34
|
+
await mkdir(outputDir)
|
|
35
|
+
|
|
36
|
+
// Models for UDS enum types
|
|
37
|
+
log(chalk.grey(` • Creating Swift enum definitions to model enum token types...`))
|
|
38
|
+
await mkdir(modelsDir)
|
|
39
|
+
await Promise.all(
|
|
40
|
+
models.map(async ({ type, values }) => {
|
|
41
|
+
const fileName = `${type}.swift`
|
|
42
|
+
const fileContents = modelsTemplate({ credit, type, values, fileName })
|
|
43
|
+
const filePath = resolve(modelsDir, fileName)
|
|
44
|
+
await writeFile(filePath, fileContents)
|
|
45
|
+
})
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
// Component directories
|
|
49
|
+
log(chalk.grey(` • Creating component folders in ios/output/Components/...`))
|
|
50
|
+
await mkdir(componentsDir)
|
|
51
|
+
await Promise.all(
|
|
52
|
+
componentEntries.map(([componentName]) => mkdir(getComponentDirectory(componentName)))
|
|
53
|
+
)
|
|
54
|
+
// Component tokens definitions
|
|
55
|
+
log(chalk.grey(` • Creating Swift token type structs for every component...`))
|
|
56
|
+
await Promise.all(
|
|
57
|
+
componentEntries.map(async ([componentName, tokens]) => {
|
|
58
|
+
const fileName = `${componentName}Tokens.swift`
|
|
59
|
+
const fileContents = tokensTemplate({ credit, componentName, fileName, tokens })
|
|
60
|
+
const filePath = resolve(getComponentDirectory(componentName), fileName)
|
|
61
|
+
await writeFile(filePath, fileContents)
|
|
62
|
+
})
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
log(chalk.grey('✔️ Completed system-theme-tokens/ios build.'))
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
module.exports = build
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
const tokenTypes = require('../tokens')
|
|
2
|
+
|
|
3
|
+
const headerTemplate = require('./header')
|
|
4
|
+
|
|
5
|
+
function getSwiftType(tokenType) {
|
|
6
|
+
const { type, nullable } = tokenTypes[tokenType]
|
|
7
|
+
return nullable ? `${type}?` : type
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function getSwiftDefault(tokenType) {
|
|
11
|
+
return ` = ${tokenTypes[tokenType].default}`
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function componentTemplate({ componentName, fileName, credit, tokens }) {
|
|
15
|
+
const tokensEntries = Object.entries(tokens)
|
|
16
|
+
const header = headerTemplate({ fileName, credit, imports: ['SwiftUI'] })
|
|
17
|
+
|
|
18
|
+
return `${header}public struct ${componentName}Tokens: Tokens {
|
|
19
|
+
${tokensEntries
|
|
20
|
+
.map(
|
|
21
|
+
([tokenName, tokenType]) => ` // UDS '${tokenType}' token
|
|
22
|
+
public let ${tokenName}: ${getSwiftType(tokenType)}`
|
|
23
|
+
)
|
|
24
|
+
.join('\n')}
|
|
25
|
+
|
|
26
|
+
public init(
|
|
27
|
+
${tokensEntries
|
|
28
|
+
.map(
|
|
29
|
+
([tokenName, tokenType]) =>
|
|
30
|
+
` ${tokenName}: ${getSwiftType(tokenType)}${getSwiftDefault(tokenType)}`
|
|
31
|
+
)
|
|
32
|
+
.join(',\n')}
|
|
33
|
+
) {
|
|
34
|
+
${tokensEntries
|
|
35
|
+
.map(
|
|
36
|
+
([tokenName]) =>
|
|
37
|
+
// If we need fancy special-case mapping could add it as a function property to ios types
|
|
38
|
+
` self.${tokenName} = ${tokenName}`
|
|
39
|
+
)
|
|
40
|
+
.join('\n')}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
`
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
module.exports = componentTemplate
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
function formatName(fileName) {
|
|
2
|
+
return `//
|
|
3
|
+
// ${fileName}
|
|
4
|
+
//
|
|
5
|
+
`
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function formatCredit(credit) {
|
|
9
|
+
if (!credit) return ''
|
|
10
|
+
return `// ${credit}
|
|
11
|
+
//
|
|
12
|
+
`
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function formatImports(imports = []) {
|
|
16
|
+
return imports.length
|
|
17
|
+
? `
|
|
18
|
+
${imports.map((importName) => `import ${importName}`).join('\n')}
|
|
19
|
+
|
|
20
|
+
`
|
|
21
|
+
: ''
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function headerTemplate({ fileName, credit, imports }) {
|
|
25
|
+
return `${formatName(fileName)}${formatCredit(credit)}${formatImports(imports)}`
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
module.exports = headerTemplate
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
const headerTemplate = require('./header')
|
|
2
|
+
|
|
3
|
+
function modelTemplate({ type, values, fileName, credit }) {
|
|
4
|
+
const valuesKeys = Object.keys(values)
|
|
5
|
+
|
|
6
|
+
const header = headerTemplate({ fileName, credit, imports: ['SwiftUI'] })
|
|
7
|
+
|
|
8
|
+
return `${header}public enum ${type}: String, Codable {
|
|
9
|
+
${valuesKeys.map((valueKey) => ` case ${valueKey}`).join('\n')}
|
|
10
|
+
}
|
|
11
|
+
`
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
module.exports = modelTemplate
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
const deepMerge = require('lodash.merge')
|
|
2
|
+
const rnTokens = require('../../tokens')
|
|
3
|
+
const { types } = require('./types')
|
|
4
|
+
|
|
5
|
+
const iosTokens = deepMerge({}, rnTokens, {
|
|
6
|
+
size: {
|
|
7
|
+
type: types.Double
|
|
8
|
+
},
|
|
9
|
+
border: {
|
|
10
|
+
type: types.Double
|
|
11
|
+
},
|
|
12
|
+
color: {
|
|
13
|
+
type: types.Color,
|
|
14
|
+
default: '.clear',
|
|
15
|
+
nullable: false
|
|
16
|
+
},
|
|
17
|
+
radius: {
|
|
18
|
+
type: types.Double
|
|
19
|
+
},
|
|
20
|
+
shadow: {
|
|
21
|
+
type: types.Shadow,
|
|
22
|
+
default: 'nil'
|
|
23
|
+
},
|
|
24
|
+
fontSize: {
|
|
25
|
+
type: types.CGFloat
|
|
26
|
+
},
|
|
27
|
+
lineHeight: {
|
|
28
|
+
type: types.CGFloat
|
|
29
|
+
},
|
|
30
|
+
flexJustifyContent: {
|
|
31
|
+
type: types.FlexJustifyContent,
|
|
32
|
+
default: '.flexStart'
|
|
33
|
+
},
|
|
34
|
+
flexAlign: {
|
|
35
|
+
type: types.FlexAlign,
|
|
36
|
+
default: '.flexStart'
|
|
37
|
+
},
|
|
38
|
+
fontName: {
|
|
39
|
+
type: types.String,
|
|
40
|
+
default: 'nil'
|
|
41
|
+
},
|
|
42
|
+
fontWeight: {
|
|
43
|
+
type: types.CGFloat
|
|
44
|
+
},
|
|
45
|
+
fontStyle: {
|
|
46
|
+
type: types.FontStyle,
|
|
47
|
+
default: '.normal'
|
|
48
|
+
},
|
|
49
|
+
opacity: {
|
|
50
|
+
type: types.Double
|
|
51
|
+
},
|
|
52
|
+
integer: {
|
|
53
|
+
type: types.Int
|
|
54
|
+
},
|
|
55
|
+
icon: {
|
|
56
|
+
type: types.String,
|
|
57
|
+
default: 'nil'
|
|
58
|
+
},
|
|
59
|
+
textLine: {
|
|
60
|
+
type: types.TextLine,
|
|
61
|
+
default: '.none'
|
|
62
|
+
},
|
|
63
|
+
textLineStyle: {
|
|
64
|
+
type: types.TextLineStyle,
|
|
65
|
+
default: '.none'
|
|
66
|
+
},
|
|
67
|
+
position: {
|
|
68
|
+
type: types.Position
|
|
69
|
+
},
|
|
70
|
+
direction: {
|
|
71
|
+
type: types.Direction,
|
|
72
|
+
default: '.column'
|
|
73
|
+
},
|
|
74
|
+
show: {
|
|
75
|
+
type: types.Bool
|
|
76
|
+
},
|
|
77
|
+
borderStyle: {
|
|
78
|
+
type: types.BorderStyle,
|
|
79
|
+
default: '.solid'
|
|
80
|
+
},
|
|
81
|
+
letterSpacing: {
|
|
82
|
+
type: types.CGFloat
|
|
83
|
+
},
|
|
84
|
+
verticalAlign: {
|
|
85
|
+
type: types.VerticalAlign,
|
|
86
|
+
default: '.baseline'
|
|
87
|
+
},
|
|
88
|
+
duration: {
|
|
89
|
+
type: types.Double // animate(withDuration) expects TimeInterval, which is a Double
|
|
90
|
+
},
|
|
91
|
+
iconScale: {
|
|
92
|
+
type: types.CGFloat // CGAffineTransform expects CGFloats
|
|
93
|
+
},
|
|
94
|
+
textAlign: {
|
|
95
|
+
type: types.TextAlignment
|
|
96
|
+
},
|
|
97
|
+
gradient: {
|
|
98
|
+
type: types.PaletteGradient,
|
|
99
|
+
default: 'nil'
|
|
100
|
+
},
|
|
101
|
+
textTransform: {
|
|
102
|
+
type: types.TextTransform,
|
|
103
|
+
default: '.none'
|
|
104
|
+
}
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
module.exports = iosTokens
|
package/bin/ios/types.js
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// Built-in Swift primitive types:
|
|
2
|
+
const swiftTypes = {
|
|
3
|
+
CGFloat: 'CGFloat', // Numbers to pass to APIs that expect 32-bit or 64-bit floats depending on system
|
|
4
|
+
Double: 'Double', // Numbers to pass to APIs that always expect 64-bit floats
|
|
5
|
+
Int: 'Int', // Signed integer that is 32 bit or 64 bit depending on platform
|
|
6
|
+
String: 'String', // Arbitrary strings without an existing enum
|
|
7
|
+
Bool: 'Bool' // Boolean (true | false)
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
// These types are available as imports from SwiftUi
|
|
11
|
+
const swiftUiTypes = {
|
|
12
|
+
TextAlignment: 'TextAlignment',
|
|
13
|
+
Color: 'Color'
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// These types are pre-defined in universal-design-system-ios:
|
|
17
|
+
// See https://github.com/telus/universal-design-system-ios/tree/main/Sources/UDSNative/Models
|
|
18
|
+
const customStructs = {
|
|
19
|
+
Shadow: 'Shadow', // Custom UDS struct
|
|
20
|
+
PaletteGradient: 'PaletteGradient' // Custom UDS struct
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const customEnums = {
|
|
24
|
+
TextLine: 'TextLineToken',
|
|
25
|
+
TextLineStyle: 'TextLineStyleToken',
|
|
26
|
+
BorderStyle: 'BorderStyleToken',
|
|
27
|
+
FlexAlign: 'FlexAlignToken',
|
|
28
|
+
FlexJustifyContent: 'FlexJustifyContentToken',
|
|
29
|
+
FontStyle: 'FontStyleToken',
|
|
30
|
+
Position: 'PositionToken',
|
|
31
|
+
Direction: 'DirectionToken',
|
|
32
|
+
VerticalAlign: 'VerticalAlignToken',
|
|
33
|
+
TextTransform: 'TextTransformToken'
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const types = {
|
|
37
|
+
...swiftTypes,
|
|
38
|
+
...swiftUiTypes,
|
|
39
|
+
...customEnums,
|
|
40
|
+
...customStructs
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
module.exports = {
|
|
44
|
+
types,
|
|
45
|
+
swiftTypes,
|
|
46
|
+
swiftUiTypes,
|
|
47
|
+
customEnums,
|
|
48
|
+
customStructs
|
|
49
|
+
}
|
package/components.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Define the required components and their supported tokens
|
|
2
2
|
module.exports = {
|
|
3
3
|
ActivityIndicator: { size: 'size', thickness: 'border', color: 'color' },
|
|
4
|
-
Box: { backgroundColor: 'color' },
|
|
4
|
+
Box: { backgroundColor: 'color', gradient: 'gradient' },
|
|
5
5
|
Button: {
|
|
6
6
|
borderColor: 'color',
|
|
7
7
|
borderWidth: 'border',
|
|
@@ -35,6 +35,44 @@ module.exports = {
|
|
|
35
35
|
textLine: 'textLine',
|
|
36
36
|
textLineStyle: 'textLineStyle'
|
|
37
37
|
},
|
|
38
|
+
ButtonDropdown: {
|
|
39
|
+
icon: 'icon',
|
|
40
|
+
iconPosition: 'position',
|
|
41
|
+
iconSpace: 'integer',
|
|
42
|
+
iconSize: 'size',
|
|
43
|
+
iconColor: 'color',
|
|
44
|
+
iconPadding: 'size',
|
|
45
|
+
iconAlignSelf: 'flexAlign',
|
|
46
|
+
iconTranslateX: 'size',
|
|
47
|
+
iconTranslateY: 'size',
|
|
48
|
+
iconBackground: 'color',
|
|
49
|
+
iconBorderRadius: 'radius',
|
|
50
|
+
// These are duplicated Button tokens. TODO: consider token sets to reduce duplication.
|
|
51
|
+
// https://github.com/telus/universal-design-system/issues/782
|
|
52
|
+
borderColor: 'color',
|
|
53
|
+
borderWidth: 'border',
|
|
54
|
+
borderRadius: 'radius',
|
|
55
|
+
shadow: 'shadow',
|
|
56
|
+
fontSize: 'fontSize',
|
|
57
|
+
color: 'color',
|
|
58
|
+
lineHeight: 'lineHeight',
|
|
59
|
+
textAlign: 'flexJustifyContent',
|
|
60
|
+
alignSelf: 'flexAlign',
|
|
61
|
+
fontName: 'fontName',
|
|
62
|
+
fontWeight: 'fontWeight',
|
|
63
|
+
backgroundColor: 'color',
|
|
64
|
+
opacity: 'opacity',
|
|
65
|
+
paddingLeft: 'size',
|
|
66
|
+
paddingRight: 'size',
|
|
67
|
+
paddingTop: 'size',
|
|
68
|
+
paddingBottom: 'size',
|
|
69
|
+
width: 'size',
|
|
70
|
+
minWidth: 'size',
|
|
71
|
+
outerBorderColor: 'color',
|
|
72
|
+
outerBorderWidth: 'border',
|
|
73
|
+
outerBorderGap: 'size',
|
|
74
|
+
outerBackgroundColor: 'color'
|
|
75
|
+
},
|
|
38
76
|
ButtonGroup: {
|
|
39
77
|
space: 'integer',
|
|
40
78
|
fieldSpace: 'integer',
|
|
@@ -69,7 +107,9 @@ module.exports = {
|
|
|
69
107
|
outerBorderColor: 'color',
|
|
70
108
|
outerBorderWidth: 'border',
|
|
71
109
|
outerBorderGap: 'size',
|
|
72
|
-
outerBackgroundColor: 'color'
|
|
110
|
+
outerBackgroundColor: 'color',
|
|
111
|
+
iconSpace: 'integer',
|
|
112
|
+
iconSize: 'size'
|
|
73
113
|
},
|
|
74
114
|
Card: {
|
|
75
115
|
flex: 'integer',
|
|
@@ -85,6 +125,7 @@ module.exports = {
|
|
|
85
125
|
shadow: 'shadow'
|
|
86
126
|
},
|
|
87
127
|
Carousel: {
|
|
128
|
+
backgroundColor: 'color',
|
|
88
129
|
nextIcon: 'icon',
|
|
89
130
|
previousIcon: 'icon',
|
|
90
131
|
showPreviousNextNavigation: 'show',
|
|
@@ -162,6 +203,7 @@ module.exports = {
|
|
|
162
203
|
iconColor: 'color',
|
|
163
204
|
iconSize: 'size',
|
|
164
205
|
iconGap: 'size',
|
|
206
|
+
iconPaddingTop: 'size',
|
|
165
207
|
iconPosition: 'position',
|
|
166
208
|
verticalAlign: 'verticalAlign',
|
|
167
209
|
justifyContent: 'flexJustifyContent',
|
|
@@ -484,6 +526,22 @@ module.exports = {
|
|
|
484
526
|
iconSize: 'size',
|
|
485
527
|
iconSpace: 'integer'
|
|
486
528
|
},
|
|
529
|
+
QuickLinksFeature: {
|
|
530
|
+
stackSpace: 'integer',
|
|
531
|
+
stackGap: 'integer',
|
|
532
|
+
stackJustify: 'flexJustifyContent'
|
|
533
|
+
},
|
|
534
|
+
QuickLinksFeatureItem: {
|
|
535
|
+
color: 'color',
|
|
536
|
+
textLine: 'textLine',
|
|
537
|
+
imageDimension: 'size',
|
|
538
|
+
contentMaxDimension: 'size',
|
|
539
|
+
contentDirection: 'direction',
|
|
540
|
+
contentSpace: 'integer',
|
|
541
|
+
contentAlignItems: 'flexAlign',
|
|
542
|
+
textAlign: 'textAlign',
|
|
543
|
+
outerBorderColor: 'color'
|
|
544
|
+
},
|
|
487
545
|
Radio: {
|
|
488
546
|
checkedBackgroundColor: 'color',
|
|
489
547
|
checkedSize: 'size',
|
|
@@ -820,6 +878,7 @@ module.exports = {
|
|
|
820
878
|
buttonSize: 'size',
|
|
821
879
|
buttonsGap: 'size',
|
|
822
880
|
buttonsPaddingRight: 'size',
|
|
881
|
+
clearButtonIcon: 'icon',
|
|
823
882
|
paddingTop: 'size',
|
|
824
883
|
paddingBottom: 'size',
|
|
825
884
|
paddingLeft: 'size',
|
package/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@telus-uds/system-theme-tokens",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.9.0",
|
|
4
4
|
"description": "Theme token schema for UDS",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"system"
|
|
@@ -15,7 +15,10 @@
|
|
|
15
15
|
"scripts": {
|
|
16
16
|
"lint": "npm run --prefix ../.. lint:path -- --color packages/system-theme-tokens",
|
|
17
17
|
"lint:fix": "npm run --prefix ../.. lint:path -- --fix packages/system-theme-tokens",
|
|
18
|
-
"format": "prettier --write ."
|
|
18
|
+
"format": "prettier --write .",
|
|
19
|
+
"build:android": "system-theme-tokens-build-android",
|
|
20
|
+
"build:ios": "system-theme-tokens-build-ios",
|
|
21
|
+
"build": "npm run build:android && npm run build:ios"
|
|
19
22
|
},
|
|
20
23
|
"bugs": {
|
|
21
24
|
"url": "https://github.com/telus/universal-design-system/issues"
|
|
@@ -25,6 +28,15 @@
|
|
|
25
28
|
"skip": true
|
|
26
29
|
},
|
|
27
30
|
"dependencies": {
|
|
28
|
-
"@telus-uds/system-constants": "^1.
|
|
31
|
+
"@telus-uds/system-constants": "^1.2.0"
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"chalk": "^4.1.2",
|
|
35
|
+
"dotenv": "^10.0.0",
|
|
36
|
+
"lodash.merge": "^4.6.2"
|
|
37
|
+
},
|
|
38
|
+
"bin": {
|
|
39
|
+
"system-theme-tokens-build-android": "./bin/build-android.js",
|
|
40
|
+
"system-theme-tokens-build-ios": "./bin/build-ios.js"
|
|
29
41
|
}
|
|
30
42
|
}
|
package/tokens.js
ADDED
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
const { fontBasePixels, viewports } = require('@telus-uds/system-constants')
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
* Simulates an object that has every positive integer as a property
|
|
5
|
+
* e.g. integer[12345] --> 12345, integer.10px --> undefined
|
|
6
|
+
*/
|
|
7
|
+
const integer = new Proxy(
|
|
8
|
+
{},
|
|
9
|
+
{
|
|
10
|
+
get(self, key) {
|
|
11
|
+
const n = typeof key === 'string' ? Number(key) : key
|
|
12
|
+
if (!Number.isInteger(n) || n < 0) {
|
|
13
|
+
// React Native does some build-time type sniffing on `.prototype` and `.$$typeof`.
|
|
14
|
+
// Behave like a normal object, return `undefined` or the real default/prototype values.
|
|
15
|
+
return self[key]
|
|
16
|
+
}
|
|
17
|
+
return n
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
// Add a system size for each viewport
|
|
23
|
+
// This adds 'viewportXs', 'viewportSm', 'viewportMd', etc..
|
|
24
|
+
const viewportSizes = Object.fromEntries(
|
|
25
|
+
[...viewports.map].map(([viewportName, viewportValue]) => [
|
|
26
|
+
`viewport${viewportName.charAt(0).toUpperCase()}${viewportName.slice(1)}`,
|
|
27
|
+
viewportValue
|
|
28
|
+
])
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
// Typescript types; may be used to generate .d.ts
|
|
32
|
+
const types = {
|
|
33
|
+
integer: 'Number',
|
|
34
|
+
float: 'Number',
|
|
35
|
+
color: 'String',
|
|
36
|
+
string: 'String',
|
|
37
|
+
enum: ({ values }) =>
|
|
38
|
+
`'${Object.entries(values)
|
|
39
|
+
.map(([, value]) => value)
|
|
40
|
+
.join("'|'")}'`,
|
|
41
|
+
size: 'Number|String',
|
|
42
|
+
shadow:
|
|
43
|
+
'{"blur": Number, "color": String, "inset": Boolean, "offsetX": Number, "offsetY": Number, "spread": Number}',
|
|
44
|
+
gradient:
|
|
45
|
+
'{"type": ["linear"|"radial"], "angle": Number, "stops": Array<{"stop": Number, "color": String }>}'
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const applyDynamicTypes = (tokens) =>
|
|
49
|
+
Object.fromEntries(
|
|
50
|
+
Object.entries(tokens).map(([key, properties]) => {
|
|
51
|
+
const { type, nullable } = properties
|
|
52
|
+
const typeDef = typeof type === 'function' ? type(properties) : type
|
|
53
|
+
return [
|
|
54
|
+
key,
|
|
55
|
+
{
|
|
56
|
+
...properties,
|
|
57
|
+
type: nullable ? `${typeDef}|null` : typeDef
|
|
58
|
+
}
|
|
59
|
+
]
|
|
60
|
+
})
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Every token type currently used in themes; i.e. every key on `palette` or `system` as
|
|
65
|
+
* used in theme source JSON files. Each token type has these properties:
|
|
66
|
+
* - `default`: The expected behaviour where a component doesn't apply this token
|
|
67
|
+
* - `type`: A platform-specific string which can be used in generated type files (e.g. Typescript type)
|
|
68
|
+
* - `nullable`: If true, `null` (or platform equivalent) is a meaningful, valid value for this token
|
|
69
|
+
* - `values`: An object of values that can be accessed via `system` like `{system.someType.someValue}`
|
|
70
|
+
*/
|
|
71
|
+
const tokenTypes = applyDynamicTypes({
|
|
72
|
+
size: {
|
|
73
|
+
// Size in device pixels that may be used for widths, heights, padding and spacing.
|
|
74
|
+
default: 0,
|
|
75
|
+
type: types.size,
|
|
76
|
+
nullable: true,
|
|
77
|
+
values: {
|
|
78
|
+
none: null,
|
|
79
|
+
full: '100%',
|
|
80
|
+
twoThirds: '66.67%',
|
|
81
|
+
zero: 0,
|
|
82
|
+
...viewportSizes
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
border: {
|
|
86
|
+
// Width of a border line. To avoid layout shifts where border tokens change, implementations
|
|
87
|
+
// should consider if they need to also subtract this from an internal padding size.
|
|
88
|
+
default: 0,
|
|
89
|
+
nullable: false,
|
|
90
|
+
type: types.number,
|
|
91
|
+
values: {
|
|
92
|
+
zero: 0
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
color: {
|
|
96
|
+
// Accepts as input any colour value understood by TinyColor2 library, which accepts
|
|
97
|
+
// all common web formats and supports alpha transparency.
|
|
98
|
+
default: null,
|
|
99
|
+
nullable: true,
|
|
100
|
+
type: types.color,
|
|
101
|
+
values: {
|
|
102
|
+
transparent: 'transparent',
|
|
103
|
+
none: null
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
radius: {
|
|
107
|
+
default: 0,
|
|
108
|
+
nullable: true,
|
|
109
|
+
type: types.number,
|
|
110
|
+
values: {
|
|
111
|
+
zero: 0,
|
|
112
|
+
// On both React Native and Web, a border radius greater than width/height
|
|
113
|
+
// creates a circle or pill effect. Use this to create circles or near-circles;
|
|
114
|
+
// for pill effects around text, where possible use a more reasonable value
|
|
115
|
+
// to allow for text that is larger than expected due to, for example, user font scaling
|
|
116
|
+
round: 99999999999999
|
|
117
|
+
}
|
|
118
|
+
},
|
|
119
|
+
shadow: {
|
|
120
|
+
// An object describing an intended shadow affect. Different platforms will need different
|
|
121
|
+
// interpretations of these properties to give as close an approximation of the designer's
|
|
122
|
+
// intent as is reasonably possible on each platform.
|
|
123
|
+
default: null,
|
|
124
|
+
nullable: true,
|
|
125
|
+
type: 'null|{"blur": Number, "color": String, "inset": Boolean, "offsetX": Number, "offsetY": Number, "spread": Number}',
|
|
126
|
+
values: {
|
|
127
|
+
none: null
|
|
128
|
+
}
|
|
129
|
+
},
|
|
130
|
+
fontSize: {
|
|
131
|
+
// An intended font size, in device pixels, before applying user accessibility settings.
|
|
132
|
+
default: fontBasePixels,
|
|
133
|
+
nullable: false,
|
|
134
|
+
type: 'Number'
|
|
135
|
+
},
|
|
136
|
+
lineHeight: {
|
|
137
|
+
// Line heights are applied as relative multipliers on font size
|
|
138
|
+
default: 1, // check this matches RN / RNW behaviour,
|
|
139
|
+
nullable: false,
|
|
140
|
+
type: 'Number'
|
|
141
|
+
},
|
|
142
|
+
flexJustifyContent: {
|
|
143
|
+
// Denotes that an element should have similar layout behaviour to an element in the
|
|
144
|
+
// Yoga layout engine (https://yogalayout.com/) with this `justify-content` value.
|
|
145
|
+
default: 'flex-start',
|
|
146
|
+
type: types.enum,
|
|
147
|
+
nullable: false,
|
|
148
|
+
values: {
|
|
149
|
+
center: 'center',
|
|
150
|
+
flexStart: 'flex-start',
|
|
151
|
+
spaceBetween: 'space-between'
|
|
152
|
+
}
|
|
153
|
+
},
|
|
154
|
+
flexAlign: {
|
|
155
|
+
// Denotes alignment behaviour similar to the Yoga layout engine (https://yogalayout.com/).
|
|
156
|
+
// Depending on the token, this may apply to the element itself (like `align-self`), its
|
|
157
|
+
// children in a flex container (like `align-items`), or between lines in a multi-line
|
|
158
|
+
// wrapping element (like `align-content`). May need special handling on some platforms.
|
|
159
|
+
default: 'flex-start',
|
|
160
|
+
type: types.enum,
|
|
161
|
+
nullable: false,
|
|
162
|
+
values: {
|
|
163
|
+
center: 'center',
|
|
164
|
+
flexStart: 'flex-start',
|
|
165
|
+
stretch: 'stretch'
|
|
166
|
+
}
|
|
167
|
+
},
|
|
168
|
+
fontName: {
|
|
169
|
+
// Font family name of a custom palette font. This should be only the typeface name and will be
|
|
170
|
+
// concatenated with font weight and font style to reference a specific palette font file.
|
|
171
|
+
default: null,
|
|
172
|
+
nullable: true,
|
|
173
|
+
type: 'null|String'
|
|
174
|
+
},
|
|
175
|
+
fontWeight: {
|
|
176
|
+
// A numeric string representation of a font weight. When used with a custom font via `fontName` tokens,
|
|
177
|
+
// this is appended to the fontName when choosing which palette font file to use, before `fontStyle`.
|
|
178
|
+
default: '400',
|
|
179
|
+
nullable: false,
|
|
180
|
+
type: 'String'
|
|
181
|
+
},
|
|
182
|
+
fontStyle: {
|
|
183
|
+
// A string denoting a font style (e.g. italic). When used with a custom font via `fontName` tokens,
|
|
184
|
+
// this is appended to the fontName and fontWeight to complete a palette font file selection.
|
|
185
|
+
default: 'normal',
|
|
186
|
+
nullable: false,
|
|
187
|
+
type: types.enum,
|
|
188
|
+
values: {
|
|
189
|
+
normal: 'normal',
|
|
190
|
+
italic: 'italic'
|
|
191
|
+
}
|
|
192
|
+
},
|
|
193
|
+
opacity: {
|
|
194
|
+
// A floating point number between 0 and 1, where 1 is fully opaque and 0 is fully transparent.
|
|
195
|
+
default: 1,
|
|
196
|
+
nullable: false,
|
|
197
|
+
type: 'Number',
|
|
198
|
+
values: {
|
|
199
|
+
opaque: 1
|
|
200
|
+
}
|
|
201
|
+
},
|
|
202
|
+
integer: {
|
|
203
|
+
// A fallback token type for integers that have context-specific meaning. Use sparingly.
|
|
204
|
+
default: null,
|
|
205
|
+
nullable: true,
|
|
206
|
+
type: 'Number',
|
|
207
|
+
// Magic proxy enabling any integer to be specified in a theme, e.g. "{system.integer.42}" --> `42`
|
|
208
|
+
values: integer
|
|
209
|
+
},
|
|
210
|
+
icon: {
|
|
211
|
+
// In theme.json, state the name of an icon that exists in a theme's target palette.
|
|
212
|
+
// React Native theme builds resolve this to a ready-to-use ReactNativeSVG component.
|
|
213
|
+
// e.g. `"{palette.icon.CheckMark}"` will resolve as a CheckMark SVG function component
|
|
214
|
+
// in the theme's React Native build, or the string "PaletteIconCheckMark" in ios/android.
|
|
215
|
+
default: null,
|
|
216
|
+
nullable: true,
|
|
217
|
+
type: 'String|ReactNativeSVGComponent',
|
|
218
|
+
values: {
|
|
219
|
+
none: null
|
|
220
|
+
}
|
|
221
|
+
},
|
|
222
|
+
textLine: {
|
|
223
|
+
// String describing typographic text line effects e.g. underline, strikethrough
|
|
224
|
+
default: 'none',
|
|
225
|
+
nullable: false,
|
|
226
|
+
type: types.enum,
|
|
227
|
+
values: {
|
|
228
|
+
none: 'none',
|
|
229
|
+
underline: 'underline',
|
|
230
|
+
strikethrough: 'strikethrough'
|
|
231
|
+
}
|
|
232
|
+
},
|
|
233
|
+
textLineStyle: {
|
|
234
|
+
// String describing the style of a particular textLine e.g. dotted, dashed, solid
|
|
235
|
+
default: 'solid',
|
|
236
|
+
nullable: false,
|
|
237
|
+
type: types.enum,
|
|
238
|
+
values: {
|
|
239
|
+
solid: 'solid'
|
|
240
|
+
}
|
|
241
|
+
},
|
|
242
|
+
position: {
|
|
243
|
+
// String describing the placement of a sub-element relative to a container or main element
|
|
244
|
+
default: null,
|
|
245
|
+
nullable: true,
|
|
246
|
+
type: types.enum,
|
|
247
|
+
values: {
|
|
248
|
+
bottom: 'bottom',
|
|
249
|
+
left: 'left',
|
|
250
|
+
right: 'right',
|
|
251
|
+
top: 'top'
|
|
252
|
+
}
|
|
253
|
+
},
|
|
254
|
+
direction: {
|
|
255
|
+
// String describing how a stacking container should behave relative to its direct children
|
|
256
|
+
default: 'column',
|
|
257
|
+
nullable: false,
|
|
258
|
+
type: types.enum,
|
|
259
|
+
values: {
|
|
260
|
+
column: 'column', // Stacks children vertically
|
|
261
|
+
row: 'row' // Stacks children horizontally
|
|
262
|
+
}
|
|
263
|
+
},
|
|
264
|
+
show: {
|
|
265
|
+
// Boolean toggle stating whether a sub-element should be shown or hidden.
|
|
266
|
+
// Take care in component implementations around timing of appearing/disappearing animations
|
|
267
|
+
// and focusability of children; implementation will rarely be just a simple on/off.
|
|
268
|
+
default: true,
|
|
269
|
+
nullable: false,
|
|
270
|
+
type: 'Boolean',
|
|
271
|
+
values: {
|
|
272
|
+
true: true,
|
|
273
|
+
false: false
|
|
274
|
+
}
|
|
275
|
+
},
|
|
276
|
+
borderStyle: {
|
|
277
|
+
// How a border line (if there is one) should be drawn e.g. solid, dotted, dashed etc.
|
|
278
|
+
default: 'solid',
|
|
279
|
+
nullable: false,
|
|
280
|
+
type: types.enum,
|
|
281
|
+
values: {
|
|
282
|
+
none: 'none',
|
|
283
|
+
solid: 'solid'
|
|
284
|
+
}
|
|
285
|
+
},
|
|
286
|
+
letterSpacing: {
|
|
287
|
+
// A relative adjustment, positive or negative, used to control typography spacing between characters.
|
|
288
|
+
default: 0,
|
|
289
|
+
nullable: false,
|
|
290
|
+
type: 'Number',
|
|
291
|
+
values: {
|
|
292
|
+
none: 0
|
|
293
|
+
}
|
|
294
|
+
},
|
|
295
|
+
verticalAlign: {
|
|
296
|
+
// The intended alignment of an element within a flow of content, e.g. an image in inline text.
|
|
297
|
+
// Implementations may need to map this to different properties depending on context.
|
|
298
|
+
default: 'baseline',
|
|
299
|
+
nullable: false,
|
|
300
|
+
type: types.enum,
|
|
301
|
+
values: {
|
|
302
|
+
top: 'top',
|
|
303
|
+
middle: 'middle',
|
|
304
|
+
baseline: 'baseline'
|
|
305
|
+
}
|
|
306
|
+
},
|
|
307
|
+
duration: {
|
|
308
|
+
// A number of milliseconds that something like a transition animation should persist for.
|
|
309
|
+
// Palettes may define common transition times used for common animation types.
|
|
310
|
+
default: 0,
|
|
311
|
+
nullable: false,
|
|
312
|
+
type: 'Number',
|
|
313
|
+
values: { zero: 0 }
|
|
314
|
+
},
|
|
315
|
+
iconScale: {
|
|
316
|
+
// A multiplier to apply to an element or sub-element's size, e.g. icon "bulge" effects.
|
|
317
|
+
// Such scaling should be implemented in a way that does not affect layout.
|
|
318
|
+
default: 1,
|
|
319
|
+
nullable: false,
|
|
320
|
+
type: 'Number',
|
|
321
|
+
values: {
|
|
322
|
+
scale1: 1,
|
|
323
|
+
scale1_10: 1.1,
|
|
324
|
+
scale1_25: 1.25
|
|
325
|
+
}
|
|
326
|
+
},
|
|
327
|
+
textAlign: {
|
|
328
|
+
// The horizontal alignment of flowing text within its immediate container.
|
|
329
|
+
default: 'left',
|
|
330
|
+
nullable: false,
|
|
331
|
+
type: types.enum,
|
|
332
|
+
values: {
|
|
333
|
+
left: 'left',
|
|
334
|
+
center: 'center',
|
|
335
|
+
right: 'right'
|
|
336
|
+
}
|
|
337
|
+
},
|
|
338
|
+
gradient: {
|
|
339
|
+
// An object describing an intended gradient affect. Different platforms will need different
|
|
340
|
+
// interpretations of these properties to give as close an approximation of the designer's
|
|
341
|
+
// intent as is reasonably possible on each platform.
|
|
342
|
+
default: null,
|
|
343
|
+
nullable: true,
|
|
344
|
+
type: '{"type": ["linear"|"radial"], "angle": Number, "stops": Array<{"stop": Number, "color": String }>}',
|
|
345
|
+
values: {
|
|
346
|
+
none: null
|
|
347
|
+
}
|
|
348
|
+
},
|
|
349
|
+
textTransform: {
|
|
350
|
+
default: 'none',
|
|
351
|
+
type: types.enum,
|
|
352
|
+
nullable: false,
|
|
353
|
+
values: {
|
|
354
|
+
none: 'none',
|
|
355
|
+
uppercase: 'uppercase'
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
})
|
|
359
|
+
|
|
360
|
+
module.exports = tokenTypes
|