@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 CHANGED
@@ -1,18 +1,46 @@
1
1
  # Change Log - @telus-uds/system-theme-tokens
2
2
 
3
- This log was last generated on Fri, 14 Oct 2022 19:26:37 GMT and should not be manually modified.
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:26:37 GMT
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` (shahzaibkhalidmalik@outlook.com)
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, focus, inactive },
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
+ }
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env node
2
+
3
+ require('dotenv').config()
4
+ const buildAndroid = require('./android/build')
5
+
6
+ buildAndroid()
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env node
2
+
3
+ require('dotenv').config()
4
+ const buildIos = require('./ios/build')
5
+
6
+ buildIos()
@@ -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
@@ -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
@@ -4,5 +4,6 @@
4
4
  */
5
5
  module.exports = {
6
6
  components: require('./components'),
7
- appearances: require('./appearances')
7
+ appearances: require('./appearances'),
8
+ tokens: require('./tokens')
8
9
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@telus-uds/system-theme-tokens",
3
- "version": "2.7.0",
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.1.0"
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