From f93bdaae6d95fe24b54be3fce10a1dd9a0daff5b Mon Sep 17 00:00:00 2001 From: Dennis Post Date: Wed, 9 Oct 2024 13:04:24 +0200 Subject: [PATCH 1/5] add output scripts for swiftui --- src/utils/outputs/download.ts | 58 +++++ .../swiftui/AdaptiveColors+Descripitve.swift | 232 +++++++++++++++++ src/utils/outputs/swiftui/colors.ts | 233 +++++++++++++++++ src/utils/outputs/swiftui/density.ts | 12 + src/utils/outputs/swiftui/dimensions.ts | 127 ++++++++++ src/utils/outputs/swiftui/elevation.ts | 63 +++++ src/utils/outputs/swiftui/readme.ts | 36 +++ src/utils/outputs/swiftui/shared.ts | 239 ++++++++++++++++++ src/utils/outputs/swiftui/theme.ts | 91 +++++++ src/utils/outputs/swiftui/typography.ts | 218 ++++++++++++++++ 10 files changed, 1309 insertions(+) create mode 100644 src/utils/outputs/swiftui/AdaptiveColors+Descripitve.swift create mode 100644 src/utils/outputs/swiftui/colors.ts create mode 100644 src/utils/outputs/swiftui/density.ts create mode 100644 src/utils/outputs/swiftui/dimensions.ts create mode 100644 src/utils/outputs/swiftui/elevation.ts create mode 100644 src/utils/outputs/swiftui/readme.ts create mode 100644 src/utils/outputs/swiftui/shared.ts create mode 100644 src/utils/outputs/swiftui/theme.ts create mode 100644 src/utils/outputs/swiftui/typography.ts diff --git a/src/utils/outputs/download.ts b/src/utils/outputs/download.ts index baaeec62..efdc5416 100644 --- a/src/utils/outputs/download.ts +++ b/src/utils/outputs/download.ts @@ -29,6 +29,14 @@ import { } from "./index.ts"; import { generateCustomColorClass } from "./web/custom-color-class.ts"; import { generateAndroidReadmeFile } from "./compose/readme.ts"; +import { generateSwiftUIColorFile, generateSwiftUIColorScheme } from "./swiftui/colors.ts"; +import { generateSwiftUIReadmeFile } from "./swiftui/readme.ts"; +import { generateSwiftUIDimensionsFile, generateSwiftUIDimensionsSchemeFile } from "./swiftui/dimensions.ts"; +import { generateSwiftUIDensityEnumFile } from "./swiftui/density.ts"; +import { generateSwiftUIThemeFile } from "./swiftui/theme.ts"; +import { generateStaticSwiftUIFiles } from "./swiftui/shared.ts"; +import { generateSwiftUIElevationsFile } from "./swiftui/elevation.ts"; +import { generateSwiftUIFontFamilyFile, generateSwiftUITypographyFile, generateSwiftUITypographyScheme, generateSwiftUITypographySchemeFile } from "./swiftui/typography.ts"; const download = (fileName: string, file: Blob) => { const element = document.createElement("a"); @@ -107,6 +115,56 @@ export const downloadTheme = async ( ); zip.file(`${androidDataFolder}/Density.kt`, generateDensityEnumFile()); + // SwiftUI (iOS) + const iOSFileName = kebabCase(fileName); + + const iOSFolder: string = "swiftui"; + const iOSThemeFolder: string = `${iOSFolder}/theme`; + const iOSDataFolder: string = `${iOSThemeFolder}/data`; + zip.file( + `${iOSFolder}/README.md`, + generateSwiftUIReadmeFile(iOSFileName), + ); + zip.file( + `${iOSFolder}/AdaptiveColors+Descriptive.swift`, + generateStaticSwiftUIFiles() + ); + zip.file( + `${iOSThemeFolder}/${iOSFileName}.swift`, + generateSwiftUIThemeFile(iOSFileName), + ); + zip.file( + `${iOSThemeFolder}/${iOSFileName}ColorScheme.swift`, + generateSwiftUIColorScheme(iOSFileName, speakingNames, allColors), + ); + zip.file( + `${iOSThemeFolder}/${iOSFileName}Dimensions.swift`, + generateSwiftUIDimensionsSchemeFile(iOSFileName), + ); + zip.file( + `${iOSThemeFolder}/${iOSFileName}Typography.swift`, + generateSwiftUITypographySchemeFile(iOSFileName), + ); + zip.file(`${iOSDataFolder}/Fonts.swift`, generateSwiftUIFontFamilyFile(Object.entries(theme.font.head), Object.entries(theme.font.sans))); + zip.file( + `${iOSDataFolder}/Dimensions.swift`, + generateSwiftUIDimensionsFile(theme), + ); + + zip.file( + `${iOSDataFolder}/Elevations.swift`, + generateSwiftUIElevationsFile(theme.elevation), + ); + zip.file( + `${iOSDataFolder}/Typography.swift`, + generateSwiftUITypographyFile(theme), + ); + zip.file( + `${iOSDataFolder}/Colors.swift`, + generateSwiftUIColorFile(allColors, luminanceSteps), + ); + zip.file(`${iOSDataFolder}/Density.swift`, generateSwiftUIDensityEnumFile()); + // Utils const utilsFolder: string = "Utils"; zip.file( diff --git a/src/utils/outputs/swiftui/AdaptiveColors+Descripitve.swift b/src/utils/outputs/swiftui/AdaptiveColors+Descripitve.swift new file mode 100644 index 00000000..2596a34e --- /dev/null +++ b/src/utils/outputs/swiftui/AdaptiveColors+Descripitve.swift @@ -0,0 +1,232 @@ +// +// Copyright 2024 by DB Systel GmbH +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import SwiftUI + +struct BasicColor { + var text: TextColor + var icon: IconColor + var border: BorderColor + var background: BackgroundColor +} + +struct InvertedBackgroundColor { + var contrastMax: StateColor + var contrastHigh: StateColor + var contrastLow: StateColor +} + +struct InvertedColor { + var background: InvertedBackgroundColor + var onBackground: StateColor +} + +struct OriginColor { + var origin: StateColor + var onOrigin: StateColor +} + +struct TextColor { + var `default`: StateColor + var emphasis100: StateColor + var emphasis90: StateColor + var emphasis80: StateColor +} + +struct IconColor { + var `default`: StateColor + var emphasis100: StateColor + var emphasis90: StateColor + var emphasis80: StateColor + var emphasis70: StateColor +} + +struct BorderColor { + var `default`: StateColor + var emphasis100: StateColor + var emphasis70: StateColor + var emphasis60: StateColor + var emphasis50: StateColor +} + +struct BackgroundColor { + var transparent: TransparentColor + var level1: StateColor + var level2: StateColor + var level3: StateColor +} + +struct TransparentColor { + var full: Color + var semi: Color + var hovered: Color + var pressed: Color +} + +struct StateColor { + var `default`: Color + var hovered: Color + var pressed: Color +} + +extension AdaptiveColors { + + public var basic: BasicColor { + .init( + text: .init( + default: .init( + default: onBgBasicEmphasis100Default, + hovered: onBgBasicEmphasis100Hovered, + pressed: onBgBasicEmphasis100Pressed + ), + emphasis100: .init( + default: onBgBasicEmphasis100Default, + hovered: onBgBasicEmphasis100Hovered, + pressed: onBgBasicEmphasis100Pressed + ), + emphasis90: .init( + default: onBgBasicEmphasis80Default, + hovered: onBgBasicEmphasis80Hovered, + pressed: onBgBasicEmphasis80Pressed + ), + emphasis80: .init( + default: onBgBasicEmphasis80Default, + hovered: onBgBasicEmphasis80Hovered, + pressed: onBgBasicEmphasis80Pressed + ) + ), + icon: .init( + default: .init( + default: onBgBasicEmphasis70Default, + hovered: onBgBasicEmphasis70Hovered, + pressed: onBgBasicEmphasis70Pressed + ), + emphasis100: .init( + default: onBgBasicEmphasis100Default, + hovered: onBgBasicEmphasis100Hovered, + pressed: onBgBasicEmphasis100Pressed + ), + emphasis90: .init( + default: onBgBasicEmphasis90Default, + hovered: onBgBasicEmphasis90Hovered, + pressed: onBgBasicEmphasis90Pressed + ), + emphasis80: .init( + default: onBgBasicEmphasis80Default, + hovered: onBgBasicEmphasis80Hovered, + pressed: onBgBasicEmphasis80Pressed + ), + emphasis70: .init( + default: onBgBasicEmphasis70Default, + hovered: onBgBasicEmphasis70Hovered, + pressed: onBgBasicEmphasis70Pressed + ) + ), + border: .init( + default: .init( + default: onBgBasicEmphasis60Default, + hovered: onBgBasicEmphasis60Hovered, + pressed: onBgBasicEmphasis60Pressed + ), + emphasis100: .init( + default: onBgBasicEmphasis100Default, + hovered: onBgBasicEmphasis100Hovered, + pressed: onBgBasicEmphasis100Pressed + ), + emphasis70: .init( + default: onBgBasicEmphasis70Default, + hovered: onBgBasicEmphasis70Hovered, + pressed: onBgBasicEmphasis70Pressed + ), + emphasis60: .init( + default: onBgBasicEmphasis60Default, + hovered: onBgBasicEmphasis60Hovered, + pressed: onBgBasicEmphasis60Pressed + ), + emphasis50: .init( + default: onBgBasicEmphasis50Default, + hovered: onBgBasicEmphasis50Hovered, + pressed: onBgBasicEmphasis50Pressed + ) + ), + background: .init( + transparent: .init( + full: bgBasicTransparentFullDefault, + semi: bgBasicTransparentSemiDefault, + hovered: bgBasicTransparentHovered, + pressed: bgBasicTransparentPressed + ), + level1: .init( + default: bgBasicLevel1Default, + hovered: bgBasicLevel1Hovered, + pressed: bgBasicLevel1Pressed + ), + level2: .init( + default: bgBasicLevel2Default, + hovered: bgBasicLevel2Hovered, + pressed: bgBasicLevel2Pressed + ), + level3: .init( + default: bgBasicLevel3Default, + hovered: bgBasicLevel3Hovered, + pressed: bgBasicLevel3Pressed + ) + ) + ) + } + + public var inverted: InvertedColor { + .init( + background: .init( + contrastMax: .init( + default: bgInvertedContrastMaxDefault, + hovered: bgInvertedContrastMaxHovered, + pressed: bgInvertedContrastMaxPressed + ), + contrastHigh: .init( + default: bgInvertedContrastHighDefault, + hovered: bgInvertedContrastHighHovered, + pressed: bgInvertedContrastHighPressed + ), + contrastLow: .init( + default: bgInvertedContrastLowDefault, + hovered: bgInvertedContrastLowHovered, + pressed: bgInvertedContrastLowPressed + ) + ), + onBackground: .init( + default: onBgInvertedDefault, + hovered: onBgInvertedHovered, + pressed: onBgInvertedPressed + ) + ) + } + + public var origin: OriginColor { + .init( + origin: .init( + default: originDefault, + hovered: originHovered, + pressed: originPressed + ), + onOrigin: .init( + default: onOriginDefault, + hovered: onOriginHovered, + pressed: onOriginPressed + ) + ) + } +} diff --git a/src/utils/outputs/swiftui/colors.ts b/src/utils/outputs/swiftui/colors.ts new file mode 100644 index 00000000..511932f8 --- /dev/null +++ b/src/utils/outputs/swiftui/colors.ts @@ -0,0 +1,233 @@ +import { DefaultColorType, HeisslufType, SpeakingName } from "../../data.ts"; +import { kebabCase } from "../../index.ts"; +import { getPalette } from "../index.ts"; +import { FALLBACK_COLOR } from "../../../constants.ts"; + +const originAdditionalColors = [ + { name: "onOriginDefault", light: 0, dark: 0 }, + { name: "onOriginHovered", light: 0, dark: 0 }, + { name: "onOriginPressed", light: 0, dark: 0 }, + { name: "originDefault", light: 0, dark: 0 }, + { name: "originHovered", light: 0, dark: 0 }, + { name: "originPressed", light: 0, dark: 0 }, +]; + +const getSwiftUIColorFromHex = (hex: string = FALLBACK_COLOR): string => { + return `Color(hex: 0x${hex.replace("#", "")})`; +}; + +export const generateSwiftUIColorFile = ( + allColors: Record, + luminanceSteps: number[], +): string => { + let resolvedTokenFile: string = `import SwiftUI + +enum ColorScheme { + case light + case dark +} + +let dbColors: [String: Color] = [ +`; + + const palette: Record = getPalette( + allColors, + luminanceSteps, + ); + Object.entries(allColors).forEach(([name, color]) => { + const hslType = palette[name]; + hslType.forEach((hsl) => { + const key = `${name}${hsl.index}`; + resolvedTokenFile += ` "${key}": ${getSwiftUIColorFromHex(hsl.hex)},\n`; + }); + + resolvedTokenFile += ` "${name}Origin": ${getSwiftUIColorFromHex(color.origin)},\n`; + + resolvedTokenFile += ` "${name}OnOriginDefaultLight": ${getSwiftUIColorFromHex(color.onOriginLight)},\n`; + resolvedTokenFile += ` "${name}OnOriginHoveredLight": ${getSwiftUIColorFromHex(color.onOriginLightHovered)},\n`; + resolvedTokenFile += ` "${name}OnOriginPressedLight": ${getSwiftUIColorFromHex(color.onOriginLightPressed)},\n`; + resolvedTokenFile += ` "${name}OriginDefaultLight": ${getSwiftUIColorFromHex(color.originLight)},\n`; + resolvedTokenFile += ` "${name}OriginHoveredLight": ${getSwiftUIColorFromHex(color.originLightHovered)},\n`; + resolvedTokenFile += ` "${name}OriginPressedLight": ${getSwiftUIColorFromHex(color.originLightPressed)},\n`; + + resolvedTokenFile += ` "${name}OnOriginDefaultDark": ${getSwiftUIColorFromHex(color.onOriginDark)},\n`; + resolvedTokenFile += ` "${name}OnOriginHoveredDark": ${getSwiftUIColorFromHex(color.onOriginDarkHovered)},\n`; + resolvedTokenFile += ` "${name}OnOriginPressedDark": ${getSwiftUIColorFromHex(color.onOriginDarkPressed)},\n`; + resolvedTokenFile += ` "${name}OriginDefaultDark": ${getSwiftUIColorFromHex(color.originDark)},\n`; + resolvedTokenFile += ` "${name}OriginHoveredDark": ${getSwiftUIColorFromHex(color.originDarkHovered)},\n`; + resolvedTokenFile += ` "${name}OriginPressedDark": ${getSwiftUIColorFromHex(color.originDarkPressed)},\n`; + }); + + resolvedTokenFile = resolvedTokenFile.substring(0, resolvedTokenFile.lastIndexOf(',')); + resolvedTokenFile += `\n] + +extension Color { + init(hex: Int, opacity: Double = 1) { + self.init( + .sRGB, + red: Double((hex >> 16) & 0xff) / 255, + green: Double((hex >> 08) & 0xff) / 255, + blue: Double((hex >> 00) & 0xff) / 255, + opacity: opacity + ) + } +} + + `; + + return resolvedTokenFile; +}; + +const generateSwiftUIAdaptiveColorsExtension = ( + speakingNames: SpeakingName[], + resolvedScheme: string, + darkMode: boolean +): string => { + const colorScheme = kebabCase(darkMode ? "dark" : "light"); + + for (const speakingName of speakingNames) { + const color = `${name}${ + darkMode ? speakingName.dark : speakingName.light + }`; + const resolvedName = `${kebabCase(speakingName.name, true)}`; + if ( + speakingName.transparencyDark !== undefined || + speakingName.transparencyLight !== undefined + ) { + const transparency = + (speakingName.transparencyDark !== undefined + ? speakingName.transparencyDark + : speakingName.transparencyLight || 0) / 100; + resolvedScheme += ` self.${resolvedName} = dbColors["\\(colorName)${color}", default: .clear].opacity(${transparency})\n`; // DBColors.${color}.opacity(${transparency})\n`; + } else { + resolvedScheme += ` self.${resolvedName} = dbColors["\\(colorName)${color}", default: .clear]\n`; + } + } + + resolvedScheme += ` self.onOriginDefault = dbColors["\\(colorName)${name}${colorScheme}", default: .clear]\n`; + resolvedScheme += ` self.onOriginHovered = dbColors["\\(colorName)${name}OnOriginHovered${colorScheme}", default: .clear]\n`; + resolvedScheme += ` self.onOriginPressed = dbColors["\\(colorName)${name}OnOriginPressed${colorScheme}", default: .clear]\n`; + resolvedScheme += ` self.originDefault = dbColors["\\(colorName)${name}OriginDefault${colorScheme}", default: .clear]\n`; + resolvedScheme += ` self.originHovered = dbColors["\\(colorName)${name}OriginHovered${colorScheme}", default: .clear]\n`; + resolvedScheme += ` self.originPressed = dbColors["\\(colorName)${name}OriginPressed${colorScheme}", default: .clear]\n`; + return resolvedScheme; +}; + +const generateSwiftUIColorSchemeDarkLight = ( + fileName: string, + colorKeys: string[], + resolvedScheme: string, + darkMode?: boolean, +): string => { + const colorScheme = kebabCase(darkMode ? "dark" : "light"); + + for (const name of colorKeys) { + resolvedScheme += ` +let ${name.toLowerCase()}Colors${colorScheme}: AdaptiveColors = .init(.${colorScheme.toLowerCase()}, colorName: "${name.toLowerCase()}")\n`; + } + + resolvedScheme += `\nfunc getColorScheme${colorScheme}(\n`; + + colorKeys.forEach((name, index) => { + resolvedScheme += ` ${name}: AdaptiveColors = ${name.toLowerCase()}Colors${colorScheme}`; + // if not last element: + if (index < colorKeys.length - 1) { + resolvedScheme += `,\n` + } + }); + resolvedScheme += `\n) -> ${fileName}ColorScheme { + .init(\n`; + colorKeys.forEach((name, index) => { + resolvedScheme += ` ${name}: ${name}`; + // if not last element: + if (index < colorKeys.length - 1) { + resolvedScheme += `,\n` + } + }); + resolvedScheme += `\n) + \n}\n`; + + return resolvedScheme; +}; + +export const generateSwiftUIColorScheme = ( + fileName: string, + speakingNames: SpeakingName[], + allColors: Record, +): string => { + const resolvedNames: Record = {}; + const colorKeys = Object.keys(allColors); + let resolvedScheme: string = `import SwiftUI + +`; + + // 1. Generate generic AdaptiveColors protocol' + const name = colorKeys[0] + const allSpeakingNames = [...speakingNames, ...originAdditionalColors]; + resolvedScheme += `struct AdaptiveColors {\n`; + for (const speakingName of allSpeakingNames) { + const resolvedName = `${kebabCase(speakingName.name, true)}`; + resolvedNames[`${name}${speakingName.name}`] = resolvedName; + resolvedScheme += ` let ${resolvedName}: Color\n`; + } + + resolvedScheme += `}\n`; + + // 2. Generate ColorSchemes for semantic colors + resolvedScheme += `struct ${fileName}ColorScheme {\n`; + for (const name of colorKeys) { + resolvedScheme += ` let ${name}: AdaptiveColors\n`; + } + + resolvedScheme += `}\n`; + + resolvedScheme = generateSwiftUIColorSchemeDarkLight( + fileName, + colorKeys, + resolvedScheme, + true, + ); + + resolvedScheme = generateSwiftUIColorSchemeDarkLight( + fileName, + colorKeys, + resolvedScheme, + false, + ); + + resolvedScheme += ` +enum DBColorScheme { + case light + case dark +} + +extension AdaptiveColors { + init(_ scheme: DBColorScheme, colorName: String) { + switch scheme { + case .dark: +`; + + resolvedScheme = generateSwiftUIAdaptiveColorsExtension( + speakingNames, + resolvedScheme, + true + ); + + resolvedScheme += ` + case .light: +`; + + resolvedScheme = generateSwiftUIAdaptiveColorsExtension( + speakingNames, + resolvedScheme, + false + ); + + resolvedScheme += ` + } + } +} +`; + + return resolvedScheme; +}; diff --git a/src/utils/outputs/swiftui/density.ts b/src/utils/outputs/swiftui/density.ts new file mode 100644 index 00000000..a69fa4c7 --- /dev/null +++ b/src/utils/outputs/swiftui/density.ts @@ -0,0 +1,12 @@ +import { densities } from "./shared.ts"; + +export const generateSwiftUIDensityEnumFile = (): string => { + let resolvedString: string = "enum Density: String {\n"; + + densities.forEach( density => { + resolvedString += ` case ${density.toLowerCase()} = "${density}"\n`; + }) + + resolvedString += "}\n"; + return resolvedString; +}; diff --git a/src/utils/outputs/swiftui/dimensions.ts b/src/utils/outputs/swiftui/dimensions.ts new file mode 100644 index 00000000..16a6f852 --- /dev/null +++ b/src/utils/outputs/swiftui/dimensions.ts @@ -0,0 +1,127 @@ +import { ThemeType } from "../../data.ts"; +import traverse from "traverse"; +import { kebabCase } from "../../index.ts"; +import { + densities, + devices, + shirtSizes, +} from "./shared.ts"; + +export const generateSwiftUIDimensionsFile = (theme: ThemeType): string => { + let resolvedTokenFile: string = ` +import SwiftUI + +struct Dimensions { +`; + + traverse(theme).forEach(function (value) { + if ( + this.isLeaf && + this.path.length > 0 && + this.path[0] !== "branding" && + this.path[0] !== "colors" && + this.path[0] !== "additionalColors" && + this.path[0] !== "font" && + this.path[0] !== "transition" && + this.path[0] !== "elevation" && + this.path[0] !== "typography" && + !this.path.includes("desktop") && + !this.path.includes("_scale") + ) { + const key = `${kebabCase(this.path.join("-"), true)}`; + console.log(key) + console.log(value) + const finalValue = + typeof value === "string" || value instanceof String + ? `${Number(value) * 16 || `.nan`}` + : value; + + resolvedTokenFile += ` static let ${key}: CGFloat = ${finalValue}\n`; + } + }); + + resolvedTokenFile += "}\n"; + + return resolvedTokenFile; +}; + +const dimensionTypes: Record = { + spacing: ["responsive", "fixed"], + sizing: ["base"], + border: ["height", "radius"], +}; + +export const generateSwiftUIDimensionsScheme = ( + fileName: string, + resolvedTokenFile: string, + density: string, + device: string, +): string => { + for (const [type, values] of Object.entries(dimensionTypes)) { + resolvedTokenFile += `var ${type}Dimensions${density}${device} = ${kebabCase(type)}Dimensions(`; + values.forEach((value, index) => { + const resolvedValue = value === "base" ? "" : `-${value}`; + const resolvedDevice = value === "responsive" ? `-${device}` : ""; + const resolvedDensity = type === "border" ? "" : `-${density}`; + + shirtSizes.forEach((size, shirtIndex) => { + resolvedTokenFile += `${kebabCase(`${value}-${size}`, true)}: Dimensions.${kebabCase(`${type}${resolvedValue}${resolvedDensity}${resolvedDevice}-${size}`, true)}`; + resolvedTokenFile += `,\n` + }); + }); + resolvedTokenFile = resolvedTokenFile.substring(0, resolvedTokenFile.lastIndexOf(',')); + resolvedTokenFile += `\n)\n`; + } + + resolvedTokenFile += `func getDimensions${density}${device}(`; + + for (const type of Object.keys(dimensionTypes)) { + resolvedTokenFile += `${type}: ${kebabCase(type)}Dimensions = ${type}Dimensions${density}${device},\n`; + } + resolvedTokenFile = resolvedTokenFile.substring(0, resolvedTokenFile.lastIndexOf(',')); + resolvedTokenFile += `\n) -> ${fileName}Dimensions { ${fileName}Dimensions(\n`; + for (const type of Object.keys(dimensionTypes)) { + resolvedTokenFile += `${type}: ${type},\n`; + } + resolvedTokenFile = resolvedTokenFile.substring(0, resolvedTokenFile.lastIndexOf(',')); + resolvedTokenFile += `\n)\n}\n`; + + return resolvedTokenFile; +}; + +export const generateSwiftUIDimensionsSchemeFile = (fileName: string): string => { + let resolvedTokenFile: string = ` +import SwiftUI + +`; + + for (const [type, values] of Object.entries(dimensionTypes)) { + resolvedTokenFile += `struct ${kebabCase(type)}Dimensions {\n`; + for (const value of values) { + for (const size of shirtSizes) { + // val fixedXl: Dp = Dimensions.spacingFixedXl, + resolvedTokenFile += ` var ${kebabCase(`${value}-${size}`, true)}: CGFloat\n`; + } + } + resolvedTokenFile += "}\n"; + } + + resolvedTokenFile += `struct ${fileName}Dimensions {\n`; + for (const type of Object.keys(dimensionTypes)) { + resolvedTokenFile += ` var ${type}: ${kebabCase(type)}Dimensions\n`; + } + resolvedTokenFile += "}\n"; + + for (const density of densities) { + for (const device of devices) { + resolvedTokenFile = generateSwiftUIDimensionsScheme( + fileName, + resolvedTokenFile, + density, + device, + ); + } + } + + return resolvedTokenFile; +}; diff --git a/src/utils/outputs/swiftui/elevation.ts b/src/utils/outputs/swiftui/elevation.ts new file mode 100644 index 00000000..ad5da8d3 --- /dev/null +++ b/src/utils/outputs/swiftui/elevation.ts @@ -0,0 +1,63 @@ +import { ThemeSizing } from "../../data.ts"; + +export const generateSwiftUIElevationsFile = ( + allElevations: ThemeSizing, +): string => { + let resolvedString: string = `import SwiftUI + +struct DBSubElevation { + let first: DBElevationShadow + let second: DBElevationShadow + let third: DBElevationShadow +} + +struct DBElevationShadow { + let x: CGFloat + let y: CGFloat + let blur: CGFloat + let spread: CGFloat + let color: Color +} + +struct DBElevation {\n`; + + Object.entries(allElevations).forEach(([name, elevation]) => { + if (!name.includes("_scale")) { + resolvedString += ` static let ${name.toLowerCase()} = DBSubElevation(`; + let shadows = elevation.toString().replaceAll(" ", " ").replaceAll("rgba(", "").replaceAll ("), ", "#").replaceAll(")", "").replaceAll(",", "").split("#") + resolvedString += ` + first: .init( + x: ${shadows[0].split(' ')[0]}, + y: ${shadows[0].split(' ')[1]}, + blur: ${shadows[0].split(' ')[2].replace("px", "")}, + spread: ${shadows[0].split(' ')[3].replace("px", "")}, + color: Color(red: ${shadows[0].split(' ')[4]}, green: ${shadows[0].split(' ')[5]}, blue: ${shadows[0].split(' ')[6]}, opacity: ${shadows[0].split(' ')[7]}) + ), + second: .init( + x: ${shadows[1].split(' ')[0]}, + y: ${shadows[1].split(' ')[1]}, + blur: ${shadows[1].split(' ')[2].replace("px", "")}, + spread: ${shadows[1].split(' ')[3].replace("px", "")}, + color: Color(red: ${shadows[1].split(' ')[4]}, green: ${shadows[1].split(' ')[5]}, blue: ${shadows[1].split(' ')[6]}, opacity: ${shadows[1].split(' ')[7]}) + ), + third: .init( + x: ${shadows[2].split(' ')[0]}, + y: ${shadows[2].split(' ')[1]}, + blur: ${shadows[2].split(' ')[2].replace("px", "")}, + spread: ${shadows[2].split(' ')[3].replace("px", "")}, + color: Color(red: ${shadows[2].split(' ')[4]}, green: ${shadows[2].split(' ')[5]}, blue: ${shadows[2].split(' ')[6]}, opacity: ${shadows[2].split(' ')[7]}) + ) + ) +`; + } + }) + + resolvedString += "}\n"; + + resolvedString += ` + + ` + return resolvedString; +}; + + diff --git a/src/utils/outputs/swiftui/readme.ts b/src/utils/outputs/swiftui/readme.ts new file mode 100644 index 00000000..25c539e7 --- /dev/null +++ b/src/utils/outputs/swiftui/readme.ts @@ -0,0 +1,36 @@ +export const generateSwiftUIReadmeFile = (fileName: string): string => { + return `# How to use the theme + +1. Move the \`theme\` directory into your project +2. Replace the string \`replace.\` inside the \`theme\` directory with your package name for example: \`com.example.myapplication.\` +3. Add your theme to the MainActivity: + +\`\`\`\` kotlin +import com.example.myapplication.theme.${fileName} +... + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContent { + ${fileName} { + //... your content + } + } + } +\`\`\`\` + +Use the tokens like this: +\`\`\`\` kotlin + Text( + text = "Headline", + style = DBTheme.typography.h1, + color = DBTheme.colors.neutral.onBgBasicEmphasis100, + modifier = Modifier.padding(DBTheme.dimensions.spacing.fixedMd) + ) +\`\`\`\` + +## Fonts + +[Download](https://marketingportal.extranet.deutschebahn.com/marketingportal/Design-Anwendungen/db-ux-design-system/version-2/foundation/Typografie) fonts and use the \`.ttf\` files. +You might rename it based on the names in \`~/theme/data/Fonts.kt\` and move the \`.ttf\` files into \`~/res/font\` folder. +`; +}; diff --git a/src/utils/outputs/swiftui/shared.ts b/src/utils/outputs/swiftui/shared.ts new file mode 100644 index 00000000..b00b2568 --- /dev/null +++ b/src/utils/outputs/swiftui/shared.ts @@ -0,0 +1,239 @@ +import fs from 'fs'; + +export const densities = ["Functional", "Regular", "Expressive"]; + +export const devices = ["Mobile", "Tablet"]; + +export const shirtSizes = [ + "3xs", + "2xs", + "xs", + "sm", + "md", + "lg", + "xl", + "2xl", + "3xl", +]; + +export const generateStaticSwiftUIFiles = (): string => { + return ` +import SwiftUI + +struct BasicColor { + var text: TextColor + var icon: IconColor + var border: BorderColor + var background: BackgroundColor +} + +struct InvertedBackgroundColor { + var contrastMax: StateColor + var contrastHigh: StateColor + var contrastLow: StateColor +} + +struct InvertedColor { + var background: InvertedBackgroundColor + var onBackground: StateColor +} + +struct OriginColor { + var origin: StateColor + var onOrigin: StateColor +} + +struct TextColor { + var \`default\`: StateColor + var emphasis100: StateColor + var emphasis90: StateColor + var emphasis80: StateColor +} + +struct IconColor { + var \`default\`: StateColor + var emphasis100: StateColor + var emphasis90: StateColor + var emphasis80: StateColor + var emphasis70: StateColor +} + +struct BorderColor { + var \`default\`: StateColor + var emphasis100: StateColor + var emphasis70: StateColor + var emphasis60: StateColor + var emphasis50: StateColor +} + +struct BackgroundColor { + var transparent: TransparentColor + var level1: StateColor + var level2: StateColor + var level3: StateColor +} + +struct TransparentColor { + var full: Color + var semi: Color + var hovered: Color + var pressed: Color +} + +struct StateColor { + var \`default\`: Color + var hovered: Color + var pressed: Color +} + +extension AdaptiveColors { + + public var basic: BasicColor { + .init( + text: .init( + default: .init( + default: onBgBasicEmphasis100Default, + hovered: onBgBasicEmphasis100Hovered, + pressed: onBgBasicEmphasis100Pressed + ), + emphasis100: .init( + default: onBgBasicEmphasis100Default, + hovered: onBgBasicEmphasis100Hovered, + pressed: onBgBasicEmphasis100Pressed + ), + emphasis90: .init( + default: onBgBasicEmphasis80Default, + hovered: onBgBasicEmphasis80Hovered, + pressed: onBgBasicEmphasis80Pressed + ), + emphasis80: .init( + default: onBgBasicEmphasis80Default, + hovered: onBgBasicEmphasis80Hovered, + pressed: onBgBasicEmphasis80Pressed + ) + ), + icon: .init( + default: .init( + default: onBgBasicEmphasis70Default, + hovered: onBgBasicEmphasis70Hovered, + pressed: onBgBasicEmphasis70Pressed + ), + emphasis100: .init( + default: onBgBasicEmphasis100Default, + hovered: onBgBasicEmphasis100Hovered, + pressed: onBgBasicEmphasis100Pressed + ), + emphasis90: .init( + default: onBgBasicEmphasis90Default, + hovered: onBgBasicEmphasis90Hovered, + pressed: onBgBasicEmphasis90Pressed + ), + emphasis80: .init( + default: onBgBasicEmphasis80Default, + hovered: onBgBasicEmphasis80Hovered, + pressed: onBgBasicEmphasis80Pressed + ), + emphasis70: .init( + default: onBgBasicEmphasis70Default, + hovered: onBgBasicEmphasis70Hovered, + pressed: onBgBasicEmphasis70Pressed + ) + ), + border: .init( + default: .init( + default: onBgBasicEmphasis60Default, + hovered: onBgBasicEmphasis60Hovered, + pressed: onBgBasicEmphasis60Pressed + ), + emphasis100: .init( + default: onBgBasicEmphasis100Default, + hovered: onBgBasicEmphasis100Hovered, + pressed: onBgBasicEmphasis100Pressed + ), + emphasis70: .init( + default: onBgBasicEmphasis70Default, + hovered: onBgBasicEmphasis70Hovered, + pressed: onBgBasicEmphasis70Pressed + ), + emphasis60: .init( + default: onBgBasicEmphasis60Default, + hovered: onBgBasicEmphasis60Hovered, + pressed: onBgBasicEmphasis60Pressed + ), + emphasis50: .init( + default: onBgBasicEmphasis50Default, + hovered: onBgBasicEmphasis50Hovered, + pressed: onBgBasicEmphasis50Pressed + ) + ), + background: .init( + transparent: .init( + full: bgBasicTransparentFullDefault, + semi: bgBasicTransparentSemiDefault, + hovered: bgBasicTransparentHovered, + pressed: bgBasicTransparentPressed + ), + level1: .init( + default: bgBasicLevel1Default, + hovered: bgBasicLevel1Hovered, + pressed: bgBasicLevel1Pressed + ), + level2: .init( + default: bgBasicLevel2Default, + hovered: bgBasicLevel2Hovered, + pressed: bgBasicLevel2Pressed + ), + level3: .init( + default: bgBasicLevel3Default, + hovered: bgBasicLevel3Hovered, + pressed: bgBasicLevel3Pressed + ) + ) + ) + } + + public var inverted: InvertedColor { + .init( + background: .init( + contrastMax: .init( + default: bgInvertedContrastMaxDefault, + hovered: bgInvertedContrastMaxHovered, + pressed: bgInvertedContrastMaxPressed + ), + contrastHigh: .init( + default: bgInvertedContrastHighDefault, + hovered: bgInvertedContrastHighHovered, + pressed: bgInvertedContrastHighPressed + ), + contrastLow: .init( + default: bgInvertedContrastLowDefault, + hovered: bgInvertedContrastLowHovered, + pressed: bgInvertedContrastLowPressed + ) + ), + onBackground: .init( + default: onBgInvertedDefault, + hovered: onBgInvertedHovered, + pressed: onBgInvertedPressed + ) + ) + } + + public var origin: OriginColor { + .init( + origin: .init( + default: originDefault, + hovered: originHovered, + pressed: originPressed + ), + onOrigin: .init( + default: onOriginDefault, + hovered: onOriginHovered, + pressed: onOriginPressed + ) + ) + } +} + + ` +} \ No newline at end of file diff --git a/src/utils/outputs/swiftui/theme.ts b/src/utils/outputs/swiftui/theme.ts new file mode 100644 index 00000000..17ddbf92 --- /dev/null +++ b/src/utils/outputs/swiftui/theme.ts @@ -0,0 +1,91 @@ +import { replacePackageName } from "./shared.ts"; + +export const generateSwiftUIThemeFile = (fileName: string): string => { + return ` +import SwiftUI + +private var DarkColorScheme = getColorSchemeDark() + +private var LightColorScheme = getColorSchemeLight() + + +struct ${fileName} { + val colors: ${fileName}ColorScheme + @Composable + @ReadOnlyComposable + get() = LocalColors.current + + val dimensions: ${fileName}Dimensions + @Composable + @ReadOnlyComposable + get() = LocalDimensions.current + + val typography: ${fileName}TextStyles + @Composable + @ReadOnlyComposable + get() = LocalTypography.current +} + +func ${fileName}( + density: Density = .regular, + darkTheme: Boolean = isSystemInDarkTheme(), + content: @Composable () -> Unit +) { + val configuration = LocalConfiguration.current + // typography + val typography: ${fileName}TextStyles = when { + configuration.screenWidthDp > 768 -> + when (density) { + Density.FUNCTIONAL -> getTextStyles(getTypographyFunctionalTablet()) + Density.EXPRESSIVE -> getTextStyles(getTypographyExpressiveTablet()) + else -> getTextStyles(getTypographyRegularTablet()) + } + + else -> when (density) { + Density.FUNCTIONAL -> getTextStyles(getTypographyFunctionalMobile()) + Density.EXPRESSIVE -> getTextStyles(getTypographyExpressiveMobile()) + else -> getTextStyles(getTypographyRegularMobile()) + } + } + + // screen + val dimensions: ${fileName}Dimensions = when { + configuration.screenWidthDp > 768 -> + when (density) { + Density.FUNCTIONAL -> getDimensionsFunctionalTablet() + Density.EXPRESSIVE -> getDimensionsExpressiveTablet() + else -> getDimensionsRegularTablet() + } + + else -> when (density) { + Density.FUNCTIONAL -> getDimensionsFunctionalMobile() + Density.EXPRESSIVE -> getDimensionsExpressiveMobile() + else -> getDimensionsRegularMobile() + } + } + + // colors + val colorScheme: ${fileName}ColorScheme = when { + darkTheme -> DarkColorScheme + else -> LightColorScheme + } + val view = LocalView.current + if (!view.isInEditMode) { + SideEffect { + val window = (view.context as Activity).window + window.statusBarColor = colorScheme.neutral.bgBasicLevel1Default.toArgb() + window.navigationBarColor = colorScheme.neutral.bgBasicLevel1Default.toArgb() + WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = darkTheme + } + } + + CompositionLocalProvider( + LocalColors provides colorScheme, + LocalDimensions provides dimensions, + LocalTypography provides typography + ) { + content() + } +} +`; +}; diff --git a/src/utils/outputs/swiftui/typography.ts b/src/utils/outputs/swiftui/typography.ts new file mode 100644 index 00000000..ce6f3886 --- /dev/null +++ b/src/utils/outputs/swiftui/typography.ts @@ -0,0 +1,218 @@ +import { FontType, ThemeType } from "../../data.ts"; +import traverse from "traverse"; +import { kebabCase } from "../../index.ts"; +import { + densities, + devices, + shirtSizes, +} from "./shared.ts"; + +export const generateSwiftUIFontFamilyFile = (headlineFont: [string, FontType][], sansFont: [string, FontType][]): string => { + let resolvedTokenFile = `import SwiftUI +struct DBFont { + let name: String + + private init(named name: String) { + self.name = name + do { + try registerFont(fontName: name) + print("Registered Font \\(name)") + } catch { + let reason = error.localizedDescription + fatalError("Failed to register font: \(reason)") + } + } + + static let dbFlex = DBFont(named: "DBNeoScreenFlex") + +} + +public enum FontError: Swift.Error { + case failedToRegisterFont +} + +func registerFont(fontName: String) throws { + guard let asset = NSDataAsset(name: "Fonts/\(fontName)", bundle: Bundle.main), + let provider = CGDataProvider(data: asset.data as NSData), + let font = CGFont(provider), + CTFontManagerRegisterGraphicsFont(font, nil) else { + throw FontError.failedToRegisterFont + } +} +` + return resolvedTokenFile +} + +export const generateSwiftUITypographyFile = (theme: ThemeType): string => { + let resolvedTokenFile: string = `import SwiftUI + +let dbTypography: [String: CGFloat] = [ +`; + + traverse(theme).forEach(function (value) { + if ( + this.isLeaf && + this.path.length === 6 && + this.path[0] === "typography" && + !this.path.includes("desktop") && + !this.path.includes("_scale") + ) { + const resolvedNameArray = [ + this.path[3], + this.path[5], + this.path[1], + this.path[2], + this.path[4], + ]; + const key = `${kebabCase(resolvedNameArray.join("-"), true)}`; + + let finalValue = `${Number(value) * 16}`; + if (this.path.at(-1) === "lineHeight") { + const fontSizePath = [...this.path]; + fontSizePath[fontSizePath.length - 1] = "fontSize"; + finalValue = `${Number(traverse(theme).get(fontSizePath)) * value * 16}`; + } + + resolvedTokenFile += ` "${key}": ${finalValue},\n`; + } + });; + + resolvedTokenFile += `]\n` + + return resolvedTokenFile; +}; + +const typoVariants: string[] = ["body", "headline"]; +const typoTypes: string[] = ["lineHeight", "fontSize"]; + +const fontsTypes: Record = { + h1: "Xl", + h2: "Lg", + h3: "Md", + h4: "Sm", + h5: "Xs", + h6: "2xs", + body: "Md", + body3xl: "3xl", + body2xl: "2xl", + bodyXl: "Xl", + bodyLg: "Lg", + bodyMd: "Md", + bodySm: "Sm", + bodyXs: "Xs", + body2xs: "2xs", + body3xs: "3xs", +}; + +export const generateSwiftUITypographyScheme = ( + fileName: string, + resolvedTokenFile: string, + density: string, + device: string, +): string => { + for (const variant of typoVariants) { + resolvedTokenFile += `let ${variant}Typography${density}${device}: Typography = .init(\n`; + resolvedTokenFile += ` variant: TypographyVariant.${variant.toLowerCase()},\n` + resolvedTokenFile += ` density: Density.${density.toLowerCase()},\n` + resolvedTokenFile += ` device: DeviceType.${device.toLowerCase()}\n` + resolvedTokenFile += `\n)\n\n` + } + + resolvedTokenFile += `func getTypography${density}${device}(`; + + for (const variant of typoVariants) { + resolvedTokenFile += `${variant}: Typography = ${variant}Typography${density}${device},\n`; + } + resolvedTokenFile = resolvedTokenFile.substring(0, resolvedTokenFile.lastIndexOf(',')); + resolvedTokenFile += `) -> ${fileName}Typography { + .init(\n`; + for (const variant of typoVariants) { + resolvedTokenFile += ` ${variant}: ${variant},\n`; + } + resolvedTokenFile = resolvedTokenFile.substring(0, resolvedTokenFile.lastIndexOf(',')); + resolvedTokenFile += `\n )\n}\n\n`; + + return resolvedTokenFile; +}; + +export const generateSwiftUITypographySchemeFile = (fileName: string): string => { + let resolvedTokenFile: string = `import SwiftUI +`; + + resolvedTokenFile += `struct Typography {\n`; + for (const type of typoTypes) { + for (const size of shirtSizes) { + resolvedTokenFile += ` let ${kebabCase(`${type}-${size}`, true)}: CGFloat\n`; + } + } + resolvedTokenFile += "}\n\n"; + + resolvedTokenFile += `struct ${fileName}Typography {\n`; + for (const variant of typoVariants) { + resolvedTokenFile += ` let ${variant}: Typography\n`; + } + resolvedTokenFile += "}\n\n"; + + for (const density of densities) { + for (const device of devices) { + resolvedTokenFile = generateSwiftUITypographyScheme( + fileName, + resolvedTokenFile, + density, + device, + ); + } + } + + resolvedTokenFile += ` +enum DeviceType: String { + case mobile = "Mobile" + case tablet = "Tablet" +} + +enum TypographyVariant: String { + case body + case headline +} + +extension Typography { + init(variant: TypographyVariant, density: Density, device: DeviceType) { + lineHeight3xs = dbTypography["\\(variant)LineHeight\\(density.rawValue)\\(device.rawValue)3xs", default: 12] + lineHeight2xs = dbTypography["\\(variant)LineHeight\\(density.rawValue)\\(device.rawValue)2xs", default: 12] + lineHeightXs = dbTypography["\\(variant)LineHeight\\(density.rawValue)\\(device.rawValue)Xs", default: 12] + lineHeightSm = dbTypography["\\(variant)LineHeight\\(density.rawValue)\\(device.rawValue)Sm", default: 12] + lineHeightMd = dbTypography["\\(variant)LineHeight\\(density.rawValue)\\(device.rawValue)Md", default: 12] + lineHeightLg = dbTypography["\\(variant)LineHeight\\(density.rawValue)\\(device.rawValue)Lg", default: 12] + lineHeightXl = dbTypography["\\(variant)LineHeight\\(density.rawValue)\\(device.rawValue)Xl", default: 12] + lineHeight2xl = dbTypography["\\(variant)LineHeight\\(density.rawValue)\\(device.rawValue)2xl", default: 12] + lineHeight3xl = dbTypography["\\(variant)LineHeight\\(density.rawValue)\\(device.rawValue)3xl", default: 12] + + fontSize3xs = dbTypography["\\(variant)FontSize\\(density.rawValue)\\(device.rawValue)3xs", default: 12] + fontSize2xs = dbTypography["\\(variant)FontSize\\(density.rawValue)\\(device.rawValue)2xs", default: 12] + fontSizeXs = dbTypography["\\(variant)FontSize\\(density.rawValue)\\(device.rawValue)2xs", default: 12] + fontSizeSm = dbTypography["\\(variant)FontSize\\(density.rawValue)\\(device.rawValue)Sm", default: 12] + fontSizeMd = dbTypography["\\(variant)FontSize\\(density.rawValue)\\(device.rawValue)Md", default: 12] + fontSizeLg = dbTypography["\\(variant)FontSize\\(density.rawValue)\\(device.rawValue)Lg", default: 12] + fontSizeXl = dbTypography["\\(variant)FontSize\\(density.rawValue)\\(device.rawValue)Xl", default: 12] + fontSize2xl = dbTypography["\\(variant)FontSize\\(density.rawValue)\\(device.rawValue)2xl", default: 12] + fontSize3xl = dbTypography["\\(variant)FontSize\\(density.rawValue)\\(device.rawValue)3xl", default: 12] + } +} + ` + + resolvedTokenFile += `struct ${fileName}Fonts{\n`; + for (const [font] of Object.entries(fontsTypes)) { + resolvedTokenFile += `let ${font}: Font\n`; + } + resolvedTokenFile += "}\n\n"; + + resolvedTokenFile += `func getFonts(typo: DeutscheBahnThemeTypography) -> ${fileName}Fonts { + .init(\n`; + for (const [font, size] of Object.entries(fontsTypes)) { + resolvedTokenFile += ` ${font}: DBFont.flex.font(size: typo.${font.includes("body") ? "body" : "headline"}.fontSize${size}),\n`; + } + resolvedTokenFile = resolvedTokenFile.substring(0, resolvedTokenFile.lastIndexOf(',')); + resolvedTokenFile += `\n )\n}\n\n`; + + return resolvedTokenFile; +}; From d11d79ed575a822125001f40041f88e5a68cdad8 Mon Sep 17 00:00:00 2001 From: Dennis Post Date: Wed, 9 Oct 2024 13:38:28 +0200 Subject: [PATCH 2/5] fix linting errors & update typography --- src/utils/outputs/download.ts | 4 +-- src/utils/outputs/swiftui/colors.ts | 5 --- src/utils/outputs/swiftui/dimensions.ts | 12 +++---- src/utils/outputs/swiftui/elevation.ts | 2 +- src/utils/outputs/swiftui/shared.ts | 2 -- src/utils/outputs/swiftui/theme.ts | 2 -- src/utils/outputs/swiftui/typography.ts | 42 ++++++++++++++++--------- 7 files changed, 36 insertions(+), 33 deletions(-) diff --git a/src/utils/outputs/download.ts b/src/utils/outputs/download.ts index efdc5416..50869a2c 100644 --- a/src/utils/outputs/download.ts +++ b/src/utils/outputs/download.ts @@ -36,7 +36,7 @@ import { generateSwiftUIDensityEnumFile } from "./swiftui/density.ts"; import { generateSwiftUIThemeFile } from "./swiftui/theme.ts"; import { generateStaticSwiftUIFiles } from "./swiftui/shared.ts"; import { generateSwiftUIElevationsFile } from "./swiftui/elevation.ts"; -import { generateSwiftUIFontFamilyFile, generateSwiftUITypographyFile, generateSwiftUITypographyScheme, generateSwiftUITypographySchemeFile } from "./swiftui/typography.ts"; +import { generateSwiftUIFontFamilyFile, generateSwiftUITypographyFile, generateSwiftUITypographySchemeFile } from "./swiftui/typography.ts"; const download = (fileName: string, file: Blob) => { const element = document.createElement("a"); @@ -145,7 +145,7 @@ export const downloadTheme = async ( `${iOSThemeFolder}/${iOSFileName}Typography.swift`, generateSwiftUITypographySchemeFile(iOSFileName), ); - zip.file(`${iOSDataFolder}/Fonts.swift`, generateSwiftUIFontFamilyFile(Object.entries(theme.font.head), Object.entries(theme.font.sans))); + zip.file(`${iOSDataFolder}/Fonts.swift`, generateSwiftUIFontFamilyFile()); zip.file( `${iOSDataFolder}/Dimensions.swift`, generateSwiftUIDimensionsFile(theme), diff --git a/src/utils/outputs/swiftui/colors.ts b/src/utils/outputs/swiftui/colors.ts index 511932f8..c8078b53 100644 --- a/src/utils/outputs/swiftui/colors.ts +++ b/src/utils/outputs/swiftui/colors.ts @@ -22,11 +22,6 @@ export const generateSwiftUIColorFile = ( ): string => { let resolvedTokenFile: string = `import SwiftUI -enum ColorScheme { - case light - case dark -} - let dbColors: [String: Color] = [ `; diff --git a/src/utils/outputs/swiftui/dimensions.ts b/src/utils/outputs/swiftui/dimensions.ts index 16a6f852..8279a154 100644 --- a/src/utils/outputs/swiftui/dimensions.ts +++ b/src/utils/outputs/swiftui/dimensions.ts @@ -58,17 +58,17 @@ export const generateSwiftUIDimensionsScheme = ( device: string, ): string => { for (const [type, values] of Object.entries(dimensionTypes)) { - resolvedTokenFile += `var ${type}Dimensions${density}${device} = ${kebabCase(type)}Dimensions(`; - values.forEach((value, index) => { + resolvedTokenFile += `let ${type}Dimensions${density}${device} = ${kebabCase(type)}Dimensions(\n`; + for (const value of values) { const resolvedValue = value === "base" ? "" : `-${value}`; const resolvedDevice = value === "responsive" ? `-${device}` : ""; const resolvedDensity = type === "border" ? "" : `-${density}`; - shirtSizes.forEach((size, shirtIndex) => { - resolvedTokenFile += `${kebabCase(`${value}-${size}`, true)}: Dimensions.${kebabCase(`${type}${resolvedValue}${resolvedDensity}${resolvedDevice}-${size}`, true)}`; + for (const size of shirtSizes) { + resolvedTokenFile += ` ${kebabCase(`${value}-${size}`, true)}: Dimensions.${kebabCase(`${type}${resolvedValue}${resolvedDensity}${resolvedDevice}-${size}`, true)}`; resolvedTokenFile += `,\n` - }); - }); + } + } resolvedTokenFile = resolvedTokenFile.substring(0, resolvedTokenFile.lastIndexOf(',')); resolvedTokenFile += `\n)\n`; } diff --git a/src/utils/outputs/swiftui/elevation.ts b/src/utils/outputs/swiftui/elevation.ts index ad5da8d3..4fd3d5f0 100644 --- a/src/utils/outputs/swiftui/elevation.ts +++ b/src/utils/outputs/swiftui/elevation.ts @@ -24,7 +24,7 @@ struct DBElevation {\n`; Object.entries(allElevations).forEach(([name, elevation]) => { if (!name.includes("_scale")) { resolvedString += ` static let ${name.toLowerCase()} = DBSubElevation(`; - let shadows = elevation.toString().replaceAll(" ", " ").replaceAll("rgba(", "").replaceAll ("), ", "#").replaceAll(")", "").replaceAll(",", "").split("#") + const shadows = elevation.toString().replaceAll(" ", " ").replaceAll("rgba(", "").replaceAll ("), ", "#").replaceAll(")", "").replaceAll(",", "").split("#") resolvedString += ` first: .init( x: ${shadows[0].split(' ')[0]}, diff --git a/src/utils/outputs/swiftui/shared.ts b/src/utils/outputs/swiftui/shared.ts index b00b2568..ca81bc77 100644 --- a/src/utils/outputs/swiftui/shared.ts +++ b/src/utils/outputs/swiftui/shared.ts @@ -1,5 +1,3 @@ -import fs from 'fs'; - export const densities = ["Functional", "Regular", "Expressive"]; export const devices = ["Mobile", "Tablet"]; diff --git a/src/utils/outputs/swiftui/theme.ts b/src/utils/outputs/swiftui/theme.ts index 17ddbf92..a32e2f4b 100644 --- a/src/utils/outputs/swiftui/theme.ts +++ b/src/utils/outputs/swiftui/theme.ts @@ -1,5 +1,3 @@ -import { replacePackageName } from "./shared.ts"; - export const generateSwiftUIThemeFile = (fileName: string): string => { return ` import SwiftUI diff --git a/src/utils/outputs/swiftui/typography.ts b/src/utils/outputs/swiftui/typography.ts index ce6f3886..40a781af 100644 --- a/src/utils/outputs/swiftui/typography.ts +++ b/src/utils/outputs/swiftui/typography.ts @@ -1,4 +1,4 @@ -import { FontType, ThemeType } from "../../data.ts"; +import { ThemeType } from "../../data.ts"; import traverse from "traverse"; import { kebabCase } from "../../index.ts"; import { @@ -7,40 +7,52 @@ import { shirtSizes, } from "./shared.ts"; -export const generateSwiftUIFontFamilyFile = (headlineFont: [string, FontType][], sansFont: [string, FontType][]): string => { - let resolvedTokenFile = `import SwiftUI +export const generateSwiftUIFontFamilyFile = (): string => { + return `import SwiftUI struct DBFont { let name: String + private let publicName: String - private init(named name: String) { + private init(named name: String, publicName: String) { self.name = name + self.publicName = publicName do { try registerFont(fontName: name) print("Registered Font \\(name)") } catch { let reason = error.localizedDescription - fatalError("Failed to register font: \(reason)") + fatalError("Failed to register font: \\(reason)") } } - static let dbFlex = DBFont(named: "DBNeoScreenFlex") + public func font(size: CGFloat) -> Font { + Font.custom(publicName, size: size) + } + + static let dbFlex = DBFont(named: "DBNeoScreenFlex", publicName: "DB Neo Screen Flex") } public enum FontError: Swift.Error { - case failedToRegisterFont + case failedToRegisterFont } func registerFont(fontName: String) throws { - guard let asset = NSDataAsset(name: "Fonts/\(fontName)", bundle: Bundle.main), - let provider = CGDataProvider(data: asset.data as NSData), - let font = CGFont(provider), - CTFontManagerRegisterGraphicsFont(font, nil) else { + guard let fontURL = Bundle.module.url(forResource: "\\(fontName)", withExtension: "ttf") else { throw FontError.failedToRegisterFont } + + let fontURLs = [fontURL] as CFArray + + CTFontManagerRegisterFontURLs(fontURLs, .process, true) { errors, done in + let errors = errors as! [CFError] + guard errors.isEmpty else { + preconditionFailure(errors.map(\\.localizedDescription).joined()) + } + return true + } } -` - return resolvedTokenFile +`; } export const generateSwiftUITypographyFile = (theme: ThemeType): string => { @@ -75,7 +87,7 @@ let dbTypography: [String: CGFloat] = [ resolvedTokenFile += ` "${key}": ${finalValue},\n`; } - });; + }); resolvedTokenFile += `]\n` @@ -209,7 +221,7 @@ extension Typography { resolvedTokenFile += `func getFonts(typo: DeutscheBahnThemeTypography) -> ${fileName}Fonts { .init(\n`; for (const [font, size] of Object.entries(fontsTypes)) { - resolvedTokenFile += ` ${font}: DBFont.flex.font(size: typo.${font.includes("body") ? "body" : "headline"}.fontSize${size}),\n`; + resolvedTokenFile += ` ${font}: DBFont.dbFlex.font(size: typo.${font.includes("body") ? "body" : "headline"}.fontSize${size}),\n`; } resolvedTokenFile = resolvedTokenFile.substring(0, resolvedTokenFile.lastIndexOf(',')); resolvedTokenFile += `\n )\n}\n\n`; From 34e1240a53d5696c4137164873ddcec846cff02c Mon Sep 17 00:00:00 2001 From: Dennis Post Date: Mon, 14 Oct 2024 16:08:41 +0200 Subject: [PATCH 3/5] wip new output structure --- src/utils/outputs/download.ts | 56 ++++---- src/utils/outputs/swiftui/colors.ts | 133 +++++++++--------- src/utils/outputs/swiftui/dimensions.ts | 47 ++++--- src/utils/outputs/swiftui/shared.ts | 2 + src/utils/outputs/swiftui/theme.ts | 175 ++++++++++++++---------- src/utils/outputs/swiftui/typography.ts | 89 ++++++------ 6 files changed, 272 insertions(+), 230 deletions(-) diff --git a/src/utils/outputs/download.ts b/src/utils/outputs/download.ts index 50869a2c..4801f464 100644 --- a/src/utils/outputs/download.ts +++ b/src/utils/outputs/download.ts @@ -33,8 +33,8 @@ import { generateSwiftUIColorFile, generateSwiftUIColorScheme } from "./swiftui/ import { generateSwiftUIReadmeFile } from "./swiftui/readme.ts"; import { generateSwiftUIDimensionsFile, generateSwiftUIDimensionsSchemeFile } from "./swiftui/dimensions.ts"; import { generateSwiftUIDensityEnumFile } from "./swiftui/density.ts"; -import { generateSwiftUIThemeFile } from "./swiftui/theme.ts"; -import { generateStaticSwiftUIFiles } from "./swiftui/shared.ts"; +import { generateSwiftUIDesignSystemThemeFile, generateSwiftUIThemeFile } from "./swiftui/theme.ts"; +import { designSystemName, generateStaticSwiftUIFiles } from "./swiftui/shared.ts"; import { generateSwiftUIElevationsFile } from "./swiftui/elevation.ts"; import { generateSwiftUIFontFamilyFile, generateSwiftUITypographyFile, generateSwiftUITypographySchemeFile } from "./swiftui/typography.ts"; @@ -119,51 +119,59 @@ export const downloadTheme = async ( const iOSFileName = kebabCase(fileName); const iOSFolder: string = "swiftui"; - const iOSThemeFolder: string = `${iOSFolder}/theme`; + const iOSThemeBrandingName: string = kebabCase(theme.branding.name) + const iOSCoreFolder: string = `${iOSFolder}/core`; + const iOSThemeFolder: string = `${iOSFolder}/${iOSThemeBrandingName}`; const iOSDataFolder: string = `${iOSThemeFolder}/data`; zip.file( `${iOSFolder}/README.md`, generateSwiftUIReadmeFile(iOSFileName), ); zip.file( - `${iOSFolder}/AdaptiveColors+Descriptive.swift`, - generateStaticSwiftUIFiles() + `${iOSFolder}/${designSystemName}ColorScheme.swift`, + generateSwiftUIColorScheme(iOSFileName, speakingNames, allColors), ); zip.file( - `${iOSThemeFolder}/${iOSFileName}.swift`, - generateSwiftUIThemeFile(iOSFileName), + `${iOSFolder}/${designSystemName}Dimensions.swift`, + generateSwiftUIDimensionsSchemeFile(iOSFileName), ); zip.file( - `${iOSThemeFolder}/${iOSFileName}ColorScheme.swift`, - generateSwiftUIColorScheme(iOSFileName, speakingNames, allColors), + `${iOSFolder}/${designSystemName}Typography.swift`, + generateSwiftUITypographySchemeFile(iOSFileName), ); zip.file( - `${iOSThemeFolder}/${iOSFileName}Dimensions.swift`, - generateSwiftUIDimensionsSchemeFile(iOSFileName), + `${iOSFolder}/${designSystemName}Theme.swift`, + generateSwiftUIDesignSystemThemeFile(iOSThemeBrandingName), ); + // iOS - Theme zip.file( - `${iOSThemeFolder}/${iOSFileName}Typography.swift`, - generateSwiftUITypographySchemeFile(iOSFileName), + `${iOSThemeFolder}/${iOSFileName}.swift`, + generateSwiftUIThemeFile(iOSThemeBrandingName), ); - zip.file(`${iOSDataFolder}/Fonts.swift`, generateSwiftUIFontFamilyFile()); + // iOS - Theme - Date zip.file( - `${iOSDataFolder}/Dimensions.swift`, - generateSwiftUIDimensionsFile(theme), + `${iOSDataFolder}/${iOSThemeBrandingName}Dimensions.swift`, + generateSwiftUIDimensionsFile(iOSThemeBrandingName, theme), ); - zip.file( - `${iOSDataFolder}/Elevations.swift`, - generateSwiftUIElevationsFile(theme.elevation), + `${iOSDataFolder}/${iOSThemeBrandingName}Typography.swift`, + generateSwiftUITypographyFile(iOSThemeBrandingName, theme), ); zip.file( - `${iOSDataFolder}/Typography.swift`, - generateSwiftUITypographyFile(theme), + `${iOSDataFolder}/${iOSThemeBrandingName}Colors.swift`, + generateSwiftUIColorFile(iOSThemeBrandingName, allColors, luminanceSteps), ); + // iOS - Core zip.file( - `${iOSDataFolder}/Colors.swift`, - generateSwiftUIColorFile(allColors, luminanceSteps), + `${iOSCoreFolder}/AdaptiveColors+Descriptive.swift`, + generateStaticSwiftUIFiles() + ); + zip.file(`${iOSCoreFolder}/Fonts.swift`, generateSwiftUIFontFamilyFile()); + zip.file( + `${iOSCoreFolder}/Elevations.swift`, + generateSwiftUIElevationsFile(theme.elevation), ); - zip.file(`${iOSDataFolder}/Density.swift`, generateSwiftUIDensityEnumFile()); + zip.file(`${iOSCoreFolder}/Density.swift`, generateSwiftUIDensityEnumFile()); // Utils const utilsFolder: string = "Utils"; diff --git a/src/utils/outputs/swiftui/colors.ts b/src/utils/outputs/swiftui/colors.ts index c8078b53..b82beab9 100644 --- a/src/utils/outputs/swiftui/colors.ts +++ b/src/utils/outputs/swiftui/colors.ts @@ -2,6 +2,7 @@ import { DefaultColorType, HeisslufType, SpeakingName } from "../../data.ts"; import { kebabCase } from "../../index.ts"; import { getPalette } from "../index.ts"; import { FALLBACK_COLOR } from "../../../constants.ts"; +import { designSystemName } from "./shared.ts"; const originAdditionalColors = [ { name: "onOriginDefault", light: 0, dark: 0 }, @@ -17,12 +18,13 @@ const getSwiftUIColorFromHex = (hex: string = FALLBACK_COLOR): string => { }; export const generateSwiftUIColorFile = ( + fileName: string, allColors: Record, luminanceSteps: number[], ): string => { let resolvedTokenFile: string = `import SwiftUI -let dbColors: [String: Color] = [ +let ${fileName}Colors: [String: Color] = [ `; const palette: Record = getPalette( @@ -54,21 +56,7 @@ let dbColors: [String: Color] = [ }); resolvedTokenFile = resolvedTokenFile.substring(0, resolvedTokenFile.lastIndexOf(',')); - resolvedTokenFile += `\n] - -extension Color { - init(hex: Int, opacity: Double = 1) { - self.init( - .sRGB, - red: Double((hex >> 16) & 0xff) / 255, - green: Double((hex >> 08) & 0xff) / 255, - blue: Double((hex >> 00) & 0xff) / 255, - opacity: opacity - ) - } -} - - `; + resolvedTokenFile += `\n]\n`; return resolvedTokenFile; }; @@ -93,18 +81,18 @@ const generateSwiftUIAdaptiveColorsExtension = ( (speakingName.transparencyDark !== undefined ? speakingName.transparencyDark : speakingName.transparencyLight || 0) / 100; - resolvedScheme += ` self.${resolvedName} = dbColors["\\(colorName)${color}", default: .clear].opacity(${transparency})\n`; // DBColors.${color}.opacity(${transparency})\n`; + resolvedScheme += ` self.${resolvedName} = colors["\\(colorName)${color}", default: .clear].opacity(${transparency})\n`; // DBColors.${color}.opacity(${transparency})\n`; } else { - resolvedScheme += ` self.${resolvedName} = dbColors["\\(colorName)${color}", default: .clear]\n`; + resolvedScheme += ` self.${resolvedName} = colors["\\(colorName)${color}", default: .clear]\n`; } } - resolvedScheme += ` self.onOriginDefault = dbColors["\\(colorName)${name}${colorScheme}", default: .clear]\n`; - resolvedScheme += ` self.onOriginHovered = dbColors["\\(colorName)${name}OnOriginHovered${colorScheme}", default: .clear]\n`; - resolvedScheme += ` self.onOriginPressed = dbColors["\\(colorName)${name}OnOriginPressed${colorScheme}", default: .clear]\n`; - resolvedScheme += ` self.originDefault = dbColors["\\(colorName)${name}OriginDefault${colorScheme}", default: .clear]\n`; - resolvedScheme += ` self.originHovered = dbColors["\\(colorName)${name}OriginHovered${colorScheme}", default: .clear]\n`; - resolvedScheme += ` self.originPressed = dbColors["\\(colorName)${name}OriginPressed${colorScheme}", default: .clear]\n`; + resolvedScheme += ` self.onOriginDefault = colors["\\(colorName)${name}${colorScheme}", default: .clear]\n`; + resolvedScheme += ` self.onOriginHovered = colors["\\(colorName)${name}OnOriginHovered${colorScheme}", default: .clear]\n`; + resolvedScheme += ` self.onOriginPressed = colors["\\(colorName)${name}OnOriginPressed${colorScheme}", default: .clear]\n`; + resolvedScheme += ` self.originDefault = colors["\\(colorName)${name}OriginDefault${colorScheme}", default: .clear]\n`; + resolvedScheme += ` self.originHovered = colors["\\(colorName)${name}OriginHovered${colorScheme}", default: .clear]\n`; + resolvedScheme += ` self.originPressed = colors["\\(colorName)${name}OriginPressed${colorScheme}", default: .clear]\n`; return resolvedScheme; }; @@ -115,32 +103,25 @@ const generateSwiftUIColorSchemeDarkLight = ( darkMode?: boolean, ): string => { const colorScheme = kebabCase(darkMode ? "dark" : "light"); + console.log(fileName) - for (const name of colorKeys) { + resolvedScheme += `\nfunc getColorScheme${colorScheme}(colors: [String: Color]) -> ${designSystemName}ColorScheme {\n` + for (const name of colorKeys) { resolvedScheme += ` -let ${name.toLowerCase()}Colors${colorScheme}: AdaptiveColors = .init(.${colorScheme.toLowerCase()}, colorName: "${name.toLowerCase()}")\n`; + var ${name.toLowerCase()}Colors${colorScheme}: AdaptiveColors { + .init(.${colorScheme.toLowerCase()}, colorName: "${name.toLowerCase()}", colors: colors) + }\n\n`; } - - resolvedScheme += `\nfunc getColorScheme${colorScheme}(\n`; - - colorKeys.forEach((name, index) => { - resolvedScheme += ` ${name}: AdaptiveColors = ${name.toLowerCase()}Colors${colorScheme}`; - // if not last element: - if (index < colorKeys.length - 1) { - resolvedScheme += `,\n` - } - }); - resolvedScheme += `\n) -> ${fileName}ColorScheme { - .init(\n`; + resolvedScheme += ` + return .init(\n`; colorKeys.forEach((name, index) => { - resolvedScheme += ` ${name}: ${name}`; + resolvedScheme += ` ${name}: ${name}Colors${colorScheme}`; // if not last element: if (index < colorKeys.length - 1) { resolvedScheme += `,\n` } }); - resolvedScheme += `\n) - \n}\n`; + resolvedScheme += `\n )\n}\n`; return resolvedScheme; }; @@ -154,6 +135,18 @@ export const generateSwiftUIColorScheme = ( const colorKeys = Object.keys(allColors); let resolvedScheme: string = `import SwiftUI +extension Color { + init(hex: Int, opacity: Double = 1) { + self.init( + .sRGB, + red: Double((hex >> 16) & 0xff) / 255, + green: Double((hex >> 08) & 0xff) / 255, + blue: Double((hex >> 00) & 0xff) / 255, + opacity: opacity + ) + } +} + `; // 1. Generate generic AdaptiveColors protocol' @@ -163,13 +156,39 @@ export const generateSwiftUIColorScheme = ( for (const speakingName of allSpeakingNames) { const resolvedName = `${kebabCase(speakingName.name, true)}`; resolvedNames[`${name}${speakingName.name}`] = resolvedName; - resolvedScheme += ` let ${resolvedName}: Color\n`; + resolvedScheme += ` let ${resolvedName}: Color\n\n`; } - resolvedScheme += `}\n`; + resolvedScheme += ` init(_ scheme: DBColorScheme, colorName: String, colors: [String: Color]) { + switch scheme { + case .dark: +`; + + resolvedScheme = generateSwiftUIAdaptiveColorsExtension( + speakingNames, + resolvedScheme, + true + ); + + resolvedScheme += ` + case .light: +`; + + resolvedScheme = generateSwiftUIAdaptiveColorsExtension( + speakingNames, + resolvedScheme, + false + ); + + resolvedScheme += ` + } + } +` + + resolvedScheme += `}\n\n`; // 2. Generate ColorSchemes for semantic colors - resolvedScheme += `struct ${fileName}ColorScheme {\n`; + resolvedScheme += `struct ${designSystemName}ColorScheme {\n`; for (const name of colorKeys) { resolvedScheme += ` let ${name}: AdaptiveColors\n`; } @@ -196,32 +215,6 @@ enum DBColorScheme { case dark } -extension AdaptiveColors { - init(_ scheme: DBColorScheme, colorName: String) { - switch scheme { - case .dark: -`; - - resolvedScheme = generateSwiftUIAdaptiveColorsExtension( - speakingNames, - resolvedScheme, - true - ); - - resolvedScheme += ` - case .light: -`; - - resolvedScheme = generateSwiftUIAdaptiveColorsExtension( - speakingNames, - resolvedScheme, - false - ); - - resolvedScheme += ` - } - } -} `; return resolvedScheme; diff --git a/src/utils/outputs/swiftui/dimensions.ts b/src/utils/outputs/swiftui/dimensions.ts index 8279a154..d66449a5 100644 --- a/src/utils/outputs/swiftui/dimensions.ts +++ b/src/utils/outputs/swiftui/dimensions.ts @@ -3,15 +3,16 @@ import traverse from "traverse"; import { kebabCase } from "../../index.ts"; import { densities, + designSystemName, devices, shirtSizes, } from "./shared.ts"; -export const generateSwiftUIDimensionsFile = (theme: ThemeType): string => { +export const generateSwiftUIDimensionsFile = (fileName: string, theme: ThemeType): string => { let resolvedTokenFile: string = ` import SwiftUI -struct Dimensions { +struct ${fileName}Dimensions: Dimensions { `; traverse(theme).forEach(function (value) { @@ -36,7 +37,7 @@ struct Dimensions { ? `${Number(value) * 16 || `.nan`}` : value; - resolvedTokenFile += ` static let ${key}: CGFloat = ${finalValue}\n`; + resolvedTokenFile += ` let ${key}: CGFloat = ${finalValue}\n`; } }); @@ -57,34 +58,36 @@ export const generateSwiftUIDimensionsScheme = ( density: string, device: string, ): string => { + console.log(fileName) + for (const [type, values] of Object.entries(dimensionTypes)) { - resolvedTokenFile += `let ${type}Dimensions${density}${device} = ${kebabCase(type)}Dimensions(\n`; + resolvedTokenFile += ` private static func get${kebabCase(type)}Dimensions${density}${device}(dimensions: Dimensions) -> ${kebabCase(type)}Dimensions {\n .init(\n`; for (const value of values) { const resolvedValue = value === "base" ? "" : `-${value}`; const resolvedDevice = value === "responsive" ? `-${device}` : ""; const resolvedDensity = type === "border" ? "" : `-${density}`; for (const size of shirtSizes) { - resolvedTokenFile += ` ${kebabCase(`${value}-${size}`, true)}: Dimensions.${kebabCase(`${type}${resolvedValue}${resolvedDensity}${resolvedDevice}-${size}`, true)}`; + resolvedTokenFile += ` ${kebabCase(`${value}-${size}`, true)}: dimensions.${kebabCase(`${type}${resolvedValue}${resolvedDensity}${resolvedDevice}-${size}`, true)}`; resolvedTokenFile += `,\n` } } resolvedTokenFile = resolvedTokenFile.substring(0, resolvedTokenFile.lastIndexOf(',')); - resolvedTokenFile += `\n)\n`; + resolvedTokenFile += `\n )\n }\n\n`; } - resolvedTokenFile += `func getDimensions${density}${device}(`; + resolvedTokenFile += ` static func getDimensions${density}${device}(`; + // for (const type of Object.keys(dimensionTypes)) { + // resolvedTokenFile += ` ${type}: ${kebabCase(type)}Dimensions,\n`; + // } + resolvedTokenFile += `dimensions: Dimensions` + resolvedTokenFile += `) -> ${designSystemName}Dimensions {\n .init(\n`; for (const type of Object.keys(dimensionTypes)) { - resolvedTokenFile += `${type}: ${kebabCase(type)}Dimensions = ${type}Dimensions${density}${device},\n`; + resolvedTokenFile += ` ${type}: get${kebabCase(type)}Dimensions${density}${device}(dimensions: dimensions),\n`; } resolvedTokenFile = resolvedTokenFile.substring(0, resolvedTokenFile.lastIndexOf(',')); - resolvedTokenFile += `\n) -> ${fileName}Dimensions { ${fileName}Dimensions(\n`; - for (const type of Object.keys(dimensionTypes)) { - resolvedTokenFile += `${type}: ${type},\n`; - } - resolvedTokenFile = resolvedTokenFile.substring(0, resolvedTokenFile.lastIndexOf(',')); - resolvedTokenFile += `\n)\n}\n`; + resolvedTokenFile += `\n )\n }\n\n`; return resolvedTokenFile; }; @@ -106,11 +109,19 @@ import SwiftUI resolvedTokenFile += "}\n"; } - resolvedTokenFile += `struct ${fileName}Dimensions {\n`; + resolvedTokenFile += `struct ${designSystemName}Dimensions {\n`; for (const type of Object.keys(dimensionTypes)) { - resolvedTokenFile += ` var ${type}: ${kebabCase(type)}Dimensions\n`; + resolvedTokenFile += ` var ${type}: ${kebabCase(type)}Dimensions\n\n`; } - resolvedTokenFile += "}\n"; + + resolvedTokenFile += ` + init(spacing: SpacingDimensions, sizing: SizingDimensions, border: BorderDimensions) { + self.spacing = spacing + self.sizing = sizing + self.border = border + } + +` for (const density of densities) { for (const device of devices) { @@ -123,5 +134,7 @@ import SwiftUI } } + resolvedTokenFile += "\n}\n"; + return resolvedTokenFile; }; diff --git a/src/utils/outputs/swiftui/shared.ts b/src/utils/outputs/swiftui/shared.ts index ca81bc77..d538a9df 100644 --- a/src/utils/outputs/swiftui/shared.ts +++ b/src/utils/outputs/swiftui/shared.ts @@ -1,3 +1,5 @@ +export const designSystemName = "DesignSystem" + export const densities = ["Functional", "Regular", "Expressive"]; export const devices = ["Mobile", "Tablet"]; diff --git a/src/utils/outputs/swiftui/theme.ts b/src/utils/outputs/swiftui/theme.ts index a32e2f4b..201feff9 100644 --- a/src/utils/outputs/swiftui/theme.ts +++ b/src/utils/outputs/swiftui/theme.ts @@ -1,89 +1,114 @@ -export const generateSwiftUIThemeFile = (fileName: string): string => { - return ` +import { designSystemName } from "./shared"; + +export const generateSwiftUIDesignSystemThemeFile = (fileName: string): string => { + return ` import SwiftUI -private var DarkColorScheme = getColorSchemeDark() +// Design System Theme File +// import SwiftUI -private var LightColorScheme = getColorSchemeLight() +// private var DarkColorScheme = getColorSchemeDark() +// private var LightColorScheme = getColorSchemeLight() -struct ${fileName} { - val colors: ${fileName}ColorScheme - @Composable - @ReadOnlyComposable - get() = LocalColors.current - val dimensions: ${fileName}Dimensions - @Composable - @ReadOnlyComposable - get() = LocalDimensions.current +// struct ${fileName} { +// val colors: ${fileName}ColorScheme +// @Composable +// @ReadOnlyComposable +// get() = LocalColors.current - val typography: ${fileName}TextStyles - @Composable - @ReadOnlyComposable - get() = LocalTypography.current -} +// val dimensions: ${fileName}Dimensions +// @Composable +// @ReadOnlyComposable +// get() = LocalDimensions.current -func ${fileName}( - density: Density = .regular, - darkTheme: Boolean = isSystemInDarkTheme(), - content: @Composable () -> Unit -) { - val configuration = LocalConfiguration.current - // typography - val typography: ${fileName}TextStyles = when { - configuration.screenWidthDp > 768 -> - when (density) { - Density.FUNCTIONAL -> getTextStyles(getTypographyFunctionalTablet()) - Density.EXPRESSIVE -> getTextStyles(getTypographyExpressiveTablet()) - else -> getTextStyles(getTypographyRegularTablet()) - } - - else -> when (density) { - Density.FUNCTIONAL -> getTextStyles(getTypographyFunctionalMobile()) - Density.EXPRESSIVE -> getTextStyles(getTypographyExpressiveMobile()) - else -> getTextStyles(getTypographyRegularMobile()) - } - } +// val typography: ${fileName}TextStyles +// @Composable +// @ReadOnlyComposable +// get() = LocalTypography.current +// } - // screen - val dimensions: ${fileName}Dimensions = when { - configuration.screenWidthDp > 768 -> - when (density) { - Density.FUNCTIONAL -> getDimensionsFunctionalTablet() - Density.EXPRESSIVE -> getDimensionsExpressiveTablet() - else -> getDimensionsRegularTablet() - } - - else -> when (density) { - Density.FUNCTIONAL -> getDimensionsFunctionalMobile() - Density.EXPRESSIVE -> getDimensionsExpressiveMobile() - else -> getDimensionsRegularMobile() - } - } +// func ${fileName}( +// density: Density = .regular, +// darkTheme: Boolean = isSystemInDarkTheme(), +// content: @Composable () -> Unit +// ) { +// val configuration = LocalConfiguration.current +// // typography +// val typography: ${fileName}TextStyles = when { +// configuration.screenWidthDp > 768 -> +// when (density) { +// Density.FUNCTIONAL -> getTextStyles(getTypographyFunctionalTablet()) +// Density.EXPRESSIVE -> getTextStyles(getTypographyExpressiveTablet()) +// else -> getTextStyles(getTypographyRegularTablet()) +// } - // colors - val colorScheme: ${fileName}ColorScheme = when { - darkTheme -> DarkColorScheme - else -> LightColorScheme - } - val view = LocalView.current - if (!view.isInEditMode) { - SideEffect { - val window = (view.context as Activity).window - window.statusBarColor = colorScheme.neutral.bgBasicLevel1Default.toArgb() - window.navigationBarColor = colorScheme.neutral.bgBasicLevel1Default.toArgb() - WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = darkTheme - } - } +// else -> when (density) { +// Density.FUNCTIONAL -> getTextStyles(getTypographyFunctionalMobile()) +// Density.EXPRESSIVE -> getTextStyles(getTypographyExpressiveMobile()) +// else -> getTextStyles(getTypographyRegularMobile()) +// } +// } + +// // screen +// val dimensions: ${fileName}Dimensions = when { +// configuration.screenWidthDp > 768 -> +// when (density) { +// Density.FUNCTIONAL -> getDimensionsFunctionalTablet() +// Density.EXPRESSIVE -> getDimensionsExpressiveTablet() +// else -> getDimensionsRegularTablet() +// } + +// else -> when (density) { +// Density.FUNCTIONAL -> getDimensionsFunctionalMobile() +// Density.EXPRESSIVE -> getDimensionsExpressiveMobile() +// else -> getDimensionsRegularMobile() +// } +// } - CompositionLocalProvider( - LocalColors provides colorScheme, - LocalDimensions provides dimensions, - LocalTypography provides typography - ) { - content() +// // colors +// val colorScheme: ${fileName}ColorScheme = when { +// darkTheme -> DarkColorScheme +// else -> LightColorScheme +// } +// val view = LocalView.current +// if (!view.isInEditMode) { +// SideEffect { +// val window = (view.context as Activity).window +// window.statusBarColor = colorScheme.neutral.bgBasicLevel1Default.toArgb() +// window.navigationBarColor = colorScheme.neutral.bgBasicLevel1Default.toArgb() +// WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = darkTheme +// } +// } + +// CompositionLocalProvider( +// LocalColors provides colorScheme, +// LocalDimensions provides dimensions, +// LocalTypography provides typography +// ) { +// content() +// } +// } +`; +} + +export const generateSwiftUIThemeFile = (themeName: string): string => { + return ` +import SwiftUI + +struct ${themeName}Theme: Theme { + var colorScheme: ${designSystemName}ColorScheme + var activeColor: AdaptiveColors + var dimensions: ${designSystemName}Dimensions + var fonts: ${designSystemName}Fonts + + init(_ colorScheme: ColorScheme = .light) { + self.colorScheme = colorScheme == .light ? getColorSchemeLight(colors: ${themeName}Colors) : getColorSchemeDark(colors: ${themeName}Colors) + self.activeColor = self.colorScheme.brand + self.dimensions = ${designSystemName}Dimensions.getDimensionsFunctionalMobile(dimensions: ${themeName}Dimensions()) + self.fonts = ${designSystemName}Fonts.getTypographyFunctionalMobile(sizes: ${themeName}Typography) } } -`; +` }; diff --git a/src/utils/outputs/swiftui/typography.ts b/src/utils/outputs/swiftui/typography.ts index 40a781af..1a3c83f6 100644 --- a/src/utils/outputs/swiftui/typography.ts +++ b/src/utils/outputs/swiftui/typography.ts @@ -3,6 +3,7 @@ import traverse from "traverse"; import { kebabCase } from "../../index.ts"; import { densities, + designSystemName, devices, shirtSizes, } from "./shared.ts"; @@ -55,10 +56,10 @@ func registerFont(fontName: String) throws { `; } -export const generateSwiftUITypographyFile = (theme: ThemeType): string => { +export const generateSwiftUITypographyFile = (fileName: string, theme: ThemeType): string => { let resolvedTokenFile: string = `import SwiftUI -let dbTypography: [String: CGFloat] = [ +let ${fileName}Typography: [String: CGFloat] = [ `; traverse(theme).forEach(function (value) { @@ -122,27 +123,25 @@ export const generateSwiftUITypographyScheme = ( density: string, device: string, ): string => { + console.log(fileName) + + resolvedTokenFile += `extension ${designSystemName}Typography {\n` for (const variant of typoVariants) { - resolvedTokenFile += `let ${variant}Typography${density}${device}: Typography = .init(\n`; - resolvedTokenFile += ` variant: TypographyVariant.${variant.toLowerCase()},\n` - resolvedTokenFile += ` density: Density.${density.toLowerCase()},\n` - resolvedTokenFile += ` device: DeviceType.${device.toLowerCase()}\n` - resolvedTokenFile += `\n)\n\n` + resolvedTokenFile += ` private static func ${variant}Typography${density}${device}(sizes: [String: CGFloat]) -> Typography { .init(\n`; + resolvedTokenFile += ` variant: TypographyVariant.${variant.toLowerCase()},\n` + resolvedTokenFile += ` density: Density.${density.toLowerCase()},\n` + resolvedTokenFile += ` device: DeviceType.${device.toLowerCase()},\n` + resolvedTokenFile += ` sizes: sizes\n` + resolvedTokenFile += `\n )\n }\n\n` } - resolvedTokenFile += `func getTypography${density}${device}(`; - - for (const variant of typoVariants) { - resolvedTokenFile += `${variant}: Typography = ${variant}Typography${density}${device},\n`; - } - resolvedTokenFile = resolvedTokenFile.substring(0, resolvedTokenFile.lastIndexOf(',')); - resolvedTokenFile += `) -> ${fileName}Typography { + resolvedTokenFile += ` static func getTypography${density}${device}(sizes: [String: CGFloat]) -> ${designSystemName}Typography { .init(\n`; for (const variant of typoVariants) { - resolvedTokenFile += ` ${variant}: ${variant},\n`; + resolvedTokenFile += ` ${variant}: ${variant}Typography${density}${device}(sizes: sizes),\n`; } resolvedTokenFile = resolvedTokenFile.substring(0, resolvedTokenFile.lastIndexOf(',')); - resolvedTokenFile += `\n )\n}\n\n`; + resolvedTokenFile += `\n )\n }\n}\n\n`; return resolvedTokenFile; }; @@ -159,7 +158,7 @@ export const generateSwiftUITypographySchemeFile = (fileName: string): string => } resolvedTokenFile += "}\n\n"; - resolvedTokenFile += `struct ${fileName}Typography {\n`; + resolvedTokenFile += `struct ${designSystemName}Typography {\n`; for (const variant of typoVariants) { resolvedTokenFile += ` let ${variant}: Typography\n`; } @@ -188,43 +187,45 @@ enum TypographyVariant: String { } extension Typography { - init(variant: TypographyVariant, density: Density, device: DeviceType) { - lineHeight3xs = dbTypography["\\(variant)LineHeight\\(density.rawValue)\\(device.rawValue)3xs", default: 12] - lineHeight2xs = dbTypography["\\(variant)LineHeight\\(density.rawValue)\\(device.rawValue)2xs", default: 12] - lineHeightXs = dbTypography["\\(variant)LineHeight\\(density.rawValue)\\(device.rawValue)Xs", default: 12] - lineHeightSm = dbTypography["\\(variant)LineHeight\\(density.rawValue)\\(device.rawValue)Sm", default: 12] - lineHeightMd = dbTypography["\\(variant)LineHeight\\(density.rawValue)\\(device.rawValue)Md", default: 12] - lineHeightLg = dbTypography["\\(variant)LineHeight\\(density.rawValue)\\(device.rawValue)Lg", default: 12] - lineHeightXl = dbTypography["\\(variant)LineHeight\\(density.rawValue)\\(device.rawValue)Xl", default: 12] - lineHeight2xl = dbTypography["\\(variant)LineHeight\\(density.rawValue)\\(device.rawValue)2xl", default: 12] - lineHeight3xl = dbTypography["\\(variant)LineHeight\\(density.rawValue)\\(device.rawValue)3xl", default: 12] + init(variant: TypographyVariant, density: Density, device: DeviceType, sizes: [String: CGFloat]) { + lineHeight3xs = sizes["\\(variant)LineHeight\\(density.rawValue)\\(device.rawValue)3xs", default: 12] + lineHeight2xs = sizes["\\(variant)LineHeight\\(density.rawValue)\\(device.rawValue)2xs", default: 12] + lineHeightXs = sizes["\\(variant)LineHeight\\(density.rawValue)\\(device.rawValue)Xs", default: 12] + lineHeightSm = sizes["\\(variant)LineHeight\\(density.rawValue)\\(device.rawValue)Sm", default: 12] + lineHeightMd = sizes["\\(variant)LineHeight\\(density.rawValue)\\(device.rawValue)Md", default: 12] + lineHeightLg = sizes["\\(variant)LineHeight\\(density.rawValue)\\(device.rawValue)Lg", default: 12] + lineHeightXl = sizes["\\(variant)LineHeight\\(density.rawValue)\\(device.rawValue)Xl", default: 12] + lineHeight2xl = sizes["\\(variant)LineHeight\\(density.rawValue)\\(device.rawValue)2xl", default: 12] + lineHeight3xl = sizes["\\(variant)LineHeight\\(density.rawValue)\\(device.rawValue)3xl", default: 12] - fontSize3xs = dbTypography["\\(variant)FontSize\\(density.rawValue)\\(device.rawValue)3xs", default: 12] - fontSize2xs = dbTypography["\\(variant)FontSize\\(density.rawValue)\\(device.rawValue)2xs", default: 12] - fontSizeXs = dbTypography["\\(variant)FontSize\\(density.rawValue)\\(device.rawValue)2xs", default: 12] - fontSizeSm = dbTypography["\\(variant)FontSize\\(density.rawValue)\\(device.rawValue)Sm", default: 12] - fontSizeMd = dbTypography["\\(variant)FontSize\\(density.rawValue)\\(device.rawValue)Md", default: 12] - fontSizeLg = dbTypography["\\(variant)FontSize\\(density.rawValue)\\(device.rawValue)Lg", default: 12] - fontSizeXl = dbTypography["\\(variant)FontSize\\(density.rawValue)\\(device.rawValue)Xl", default: 12] - fontSize2xl = dbTypography["\\(variant)FontSize\\(density.rawValue)\\(device.rawValue)2xl", default: 12] - fontSize3xl = dbTypography["\\(variant)FontSize\\(density.rawValue)\\(device.rawValue)3xl", default: 12] + fontSize3xs = sizes["\\(variant)FontSize\\(density.rawValue)\\(device.rawValue)3xs", default: 12] + fontSize2xs = sizes["\\(variant)FontSize\\(density.rawValue)\\(device.rawValue)2xs", default: 12] + fontSizeXs = sizes["\\(variant)FontSize\\(density.rawValue)\\(device.rawValue)2xs", default: 12] + fontSizeSm = sizes["\\(variant)FontSize\\(density.rawValue)\\(device.rawValue)Sm", default: 12] + fontSizeMd = sizes["\\(variant)FontSize\\(density.rawValue)\\(device.rawValue)Md", default: 12] + fontSizeLg = sizes["\\(variant)FontSize\\(density.rawValue)\\(device.rawValue)Lg", default: 12] + fontSizeXl = sizes["\\(variant)FontSize\\(density.rawValue)\\(device.rawValue)Xl", default: 12] + fontSize2xl = sizes["\\(variant)FontSize\\(density.rawValue)\\(device.rawValue)2xl", default: 12] + fontSize3xl = sizes["\\(variant)FontSize\\(density.rawValue)\\(device.rawValue)3xl", default: 12] } } - ` - resolvedTokenFile += `struct ${fileName}Fonts{\n`; +` + + resolvedTokenFile += `struct ${designSystemName}Fonts{\n`; for (const [font] of Object.entries(fontsTypes)) { - resolvedTokenFile += `let ${font}: Font\n`; + resolvedTokenFile += ` let ${font}: Font\n`; } - resolvedTokenFile += "}\n\n"; + resolvedTokenFile += `\n\n`; - resolvedTokenFile += `func getFonts(typo: DeutscheBahnThemeTypography) -> ${fileName}Fonts { - .init(\n`; + resolvedTokenFile += ` static func getFonts(typo: ${designSystemName}Typography) -> ${designSystemName}Fonts { + .init(\n`; for (const [font, size] of Object.entries(fontsTypes)) { - resolvedTokenFile += ` ${font}: DBFont.dbFlex.font(size: typo.${font.includes("body") ? "body" : "headline"}.fontSize${size}),\n`; + resolvedTokenFile += ` ${font}: .init(font: DBFont.dbFlex.font(size: typo.${font.includes("body") ? "body" : "headline"}.fontSize${size}), uiFont: DBFont.dbFlex.uiFont(size: typo.${font.includes("body") ? "body" : "headline"}.fontSize${size}), lineHeight: typo.${font.includes("body") ? "body" : "headline"}.lineHeight${size}, fontWeight: ${font.includes("body") ? ".regular" : ".black"}),\n` + // resolvedTokenFile += ` ${font}: DBFont.dbFlex.font(size: typo.${font.includes("body") ? "body" : "headline"}.fontSize${size}).weight(${font.includes("body") ? ".regular" : ".black"}),\n`; } resolvedTokenFile = resolvedTokenFile.substring(0, resolvedTokenFile.lastIndexOf(',')); - resolvedTokenFile += `\n )\n}\n\n`; + resolvedTokenFile += `\n )\n }\n}\n`; return resolvedTokenFile; }; From e1c42a016c2521d6568a57522e571be6fe3fb5dc Mon Sep 17 00:00:00 2001 From: Dennis Post Date: Tue, 15 Oct 2024 18:57:40 +0200 Subject: [PATCH 4/5] renaming & add accessor visibility --- .../swiftui/AdaptiveColors+Descripitve.swift | 95 +++++----- src/utils/outputs/swiftui/colors.ts | 54 +++--- src/utils/outputs/swiftui/density.ts | 2 +- src/utils/outputs/swiftui/dimensions.ts | 155 +++++++++++++-- src/utils/outputs/swiftui/elevation.ts | 35 +++- src/utils/outputs/swiftui/shared.ts | 99 +++++----- src/utils/outputs/swiftui/theme.ts | 179 +++++++++--------- src/utils/outputs/swiftui/typography.ts | 136 +++++++------ 8 files changed, 464 insertions(+), 291 deletions(-) diff --git a/src/utils/outputs/swiftui/AdaptiveColors+Descripitve.swift b/src/utils/outputs/swiftui/AdaptiveColors+Descripitve.swift index 2596a34e..d3e23941 100644 --- a/src/utils/outputs/swiftui/AdaptiveColors+Descripitve.swift +++ b/src/utils/outputs/swiftui/AdaptiveColors+Descripitve.swift @@ -14,75 +14,76 @@ // limitations under the License. // + import SwiftUI -struct BasicColor { - var text: TextColor - var icon: IconColor - var border: BorderColor - var background: BackgroundColor +public struct BasicColor { + public var text: TextColor + public var icon: IconColor + public var border: BorderColor + public var background: BackgroundColor } -struct InvertedBackgroundColor { - var contrastMax: StateColor - var contrastHigh: StateColor - var contrastLow: StateColor +public struct InvertedBackgroundColor { + public var contrastMax: StateColor + public var contrastHigh: StateColor + public var contrastLow: StateColor } -struct InvertedColor { - var background: InvertedBackgroundColor - var onBackground: StateColor +public struct InvertedColor { + public var background: InvertedBackgroundColor + public var onBackground: StateColor } -struct OriginColor { - var origin: StateColor - var onOrigin: StateColor +public struct OriginColor { + public var origin: StateColor + public var onOrigin: StateColor } -struct TextColor { - var `default`: StateColor - var emphasis100: StateColor - var emphasis90: StateColor - var emphasis80: StateColor +public struct TextColor { + public var `default`: StateColor + public var emphasis100: StateColor + public var emphasis90: StateColor + public var emphasis80: StateColor } -struct IconColor { - var `default`: StateColor - var emphasis100: StateColor - var emphasis90: StateColor - var emphasis80: StateColor - var emphasis70: StateColor +public struct IconColor { + public var `default`: StateColor + public var emphasis100: StateColor + public var emphasis90: StateColor + public var emphasis80: StateColor + public var emphasis70: StateColor } -struct BorderColor { - var `default`: StateColor - var emphasis100: StateColor - var emphasis70: StateColor - var emphasis60: StateColor - var emphasis50: StateColor +public struct BorderColor { + public var `default`: StateColor + public var emphasis100: StateColor + public var emphasis70: StateColor + public var emphasis60: StateColor + public var emphasis50: StateColor } -struct BackgroundColor { - var transparent: TransparentColor - var level1: StateColor - var level2: StateColor - var level3: StateColor +public struct BackgroundColor { + public var transparent: TransparentColor + public var level1: StateColor + public var level2: StateColor + public var level3: StateColor } -struct TransparentColor { - var full: Color - var semi: Color - var hovered: Color - var pressed: Color +public struct TransparentColor { + public var full: Color + public var semi: Color + public var hovered: Color + public var pressed: Color } -struct StateColor { - var `default`: Color - var hovered: Color - var pressed: Color +public struct StateColor { + public var `default`: Color + public var hovered: Color + public var pressed: Color } -extension AdaptiveColors { +extension DSColorVariant { public var basic: BasicColor { .init( diff --git a/src/utils/outputs/swiftui/colors.ts b/src/utils/outputs/swiftui/colors.ts index b82beab9..59f1638d 100644 --- a/src/utils/outputs/swiftui/colors.ts +++ b/src/utils/outputs/swiftui/colors.ts @@ -61,7 +61,7 @@ let ${fileName}Colors: [String: Color] = [ return resolvedTokenFile; }; -const generateSwiftUIAdaptiveColorsExtension = ( +const generateSwiftUIColorVariantExtension = ( speakingNames: SpeakingName[], resolvedScheme: string, darkMode: boolean @@ -102,26 +102,27 @@ const generateSwiftUIColorSchemeDarkLight = ( resolvedScheme: string, darkMode?: boolean, ): string => { - const colorScheme = kebabCase(darkMode ? "dark" : "light"); console.log(fileName) + const colorScheme = kebabCase(darkMode ? "dark" : "light"); - resolvedScheme += `\nfunc getColorScheme${colorScheme}(colors: [String: Color]) -> ${designSystemName}ColorScheme {\n` + resolvedScheme += `\n static func getColorScheme${colorScheme}(colors: [String: Color]) -> ${designSystemName}ColorScheme {\n` for (const name of colorKeys) { resolvedScheme += ` - var ${name.toLowerCase()}Colors${colorScheme}: AdaptiveColors { - .init(.${colorScheme.toLowerCase()}, colorName: "${name.toLowerCase()}", colors: colors) - }\n\n`; - } + var ${name.toLowerCase()}Colors${colorScheme}: DSColorVariant { + .init(.${colorScheme.toLowerCase()}, colorName: "${name.toLowerCase()}", colors: colors) + }\n`; + } resolvedScheme += ` - return .init(\n`; + + return .init(\n`; colorKeys.forEach((name, index) => { - resolvedScheme += ` ${name}: ${name}Colors${colorScheme}`; + resolvedScheme += ` ${name}: ${name}Colors${colorScheme}`; // if not last element: if (index < colorKeys.length - 1) { resolvedScheme += `,\n` } }); - resolvedScheme += `\n )\n}\n`; + resolvedScheme += `\n )\n }\n`; return resolvedScheme; }; @@ -149,52 +150,43 @@ extension Color { `; - // 1. Generate generic AdaptiveColors protocol' + // 1. Generate generic DSColorVariant protocol' const name = colorKeys[0] const allSpeakingNames = [...speakingNames, ...originAdditionalColors]; - resolvedScheme += `struct AdaptiveColors {\n`; + resolvedScheme += `public struct DSColorVariant {\n`; for (const speakingName of allSpeakingNames) { const resolvedName = `${kebabCase(speakingName.name, true)}`; resolvedNames[`${name}${speakingName.name}`] = resolvedName; - resolvedScheme += ` let ${resolvedName}: Color\n\n`; + resolvedScheme += ` public let ${resolvedName}: Color\n`; } - resolvedScheme += ` init(_ scheme: DBColorScheme, colorName: String, colors: [String: Color]) { + resolvedScheme += `\n init(_ scheme: DSColorScheme, colorName: String, colors: [String: Color]) { switch scheme { case .dark: `; - resolvedScheme = generateSwiftUIAdaptiveColorsExtension( + resolvedScheme = generateSwiftUIColorVariantExtension( speakingNames, resolvedScheme, true ); - resolvedScheme += ` - case .light: -`; + resolvedScheme += `\n case .light:\n`; - resolvedScheme = generateSwiftUIAdaptiveColorsExtension( + resolvedScheme = generateSwiftUIColorVariantExtension( speakingNames, resolvedScheme, false ); - resolvedScheme += ` - } - } -` - - resolvedScheme += `}\n\n`; + resolvedScheme += `\n }\n }\n}\n\n`; // 2. Generate ColorSchemes for semantic colors - resolvedScheme += `struct ${designSystemName}ColorScheme {\n`; + resolvedScheme += `public struct ${designSystemName}ColorScheme {\n`; for (const name of colorKeys) { - resolvedScheme += ` let ${name}: AdaptiveColors\n`; + resolvedScheme += ` public let ${name}: DSColorVariant\n`; } - resolvedScheme += `}\n`; - resolvedScheme = generateSwiftUIColorSchemeDarkLight( fileName, colorKeys, @@ -209,8 +201,10 @@ extension Color { false, ); + resolvedScheme += `\n}\n\n`; + resolvedScheme += ` -enum DBColorScheme { +enum DSColorScheme { case light case dark } diff --git a/src/utils/outputs/swiftui/density.ts b/src/utils/outputs/swiftui/density.ts index a69fa4c7..b7346961 100644 --- a/src/utils/outputs/swiftui/density.ts +++ b/src/utils/outputs/swiftui/density.ts @@ -1,7 +1,7 @@ import { densities } from "./shared.ts"; export const generateSwiftUIDensityEnumFile = (): string => { - let resolvedString: string = "enum Density: String {\n"; + let resolvedString: string = "enum DSDensity: String {\n"; densities.forEach( density => { resolvedString += ` case ${density.toLowerCase()} = "${density}"\n`; diff --git a/src/utils/outputs/swiftui/dimensions.ts b/src/utils/outputs/swiftui/dimensions.ts index d66449a5..94e5397c 100644 --- a/src/utils/outputs/swiftui/dimensions.ts +++ b/src/utils/outputs/swiftui/dimensions.ts @@ -12,7 +12,7 @@ export const generateSwiftUIDimensionsFile = (fileName: string, theme: ThemeType let resolvedTokenFile: string = ` import SwiftUI -struct ${fileName}Dimensions: Dimensions { +struct ${fileName}Dimensions: DSDimensions { `; traverse(theme).forEach(function (value) { @@ -61,7 +61,7 @@ export const generateSwiftUIDimensionsScheme = ( console.log(fileName) for (const [type, values] of Object.entries(dimensionTypes)) { - resolvedTokenFile += ` private static func get${kebabCase(type)}Dimensions${density}${device}(dimensions: Dimensions) -> ${kebabCase(type)}Dimensions {\n .init(\n`; + resolvedTokenFile += ` private static func get${kebabCase(type)}Dimensions${density}${device}(dimensions: DSDimensions) -> DS${kebabCase(type)}Dimensions {\n .init(\n`; for (const value of values) { const resolvedValue = value === "base" ? "" : `-${value}`; const resolvedDevice = value === "responsive" ? `-${device}` : ""; @@ -76,13 +76,7 @@ export const generateSwiftUIDimensionsScheme = ( resolvedTokenFile += `\n )\n }\n\n`; } - resolvedTokenFile += ` static func getDimensions${density}${device}(`; - - // for (const type of Object.keys(dimensionTypes)) { - // resolvedTokenFile += ` ${type}: ${kebabCase(type)}Dimensions,\n`; - // } - resolvedTokenFile += `dimensions: Dimensions` - resolvedTokenFile += `) -> ${designSystemName}Dimensions {\n .init(\n`; + resolvedTokenFile += ` static func getDimensions${density}${device}(dimensions: DSDimensions) -> ${designSystemName}Dimensions {\n .init(\n`; for (const type of Object.keys(dimensionTypes)) { resolvedTokenFile += ` ${type}: get${kebabCase(type)}Dimensions${density}${device}(dimensions: dimensions),\n`; } @@ -96,26 +90,157 @@ export const generateSwiftUIDimensionsSchemeFile = (fileName: string): string => let resolvedTokenFile: string = ` import SwiftUI +protocol DSDimensions { + var spacingResponsiveRegularTablet3xs: CGFloat { get } + var spacingResponsiveRegularTablet2xs: CGFloat { get } + var spacingResponsiveRegularTabletXs: CGFloat { get } + var spacingResponsiveRegularTabletSm: CGFloat { get } + var spacingResponsiveRegularTabletMd: CGFloat { get } + var spacingResponsiveRegularTabletLg: CGFloat { get } + var spacingResponsiveRegularTabletXl: CGFloat { get } + var spacingResponsiveRegularTablet2xl: CGFloat { get } + var spacingResponsiveRegularTablet3xl: CGFloat { get } + var spacingResponsiveRegularMobile3xs: CGFloat { get } + var spacingResponsiveRegularMobile2xs: CGFloat { get } + var spacingResponsiveRegularMobileXs: CGFloat { get } + var spacingResponsiveRegularMobileSm: CGFloat { get } + var spacingResponsiveRegularMobileMd: CGFloat { get } + var spacingResponsiveRegularMobileLg: CGFloat { get } + var spacingResponsiveRegularMobileXl: CGFloat { get } + var spacingResponsiveRegularMobile2xl: CGFloat { get } + var spacingResponsiveRegularMobile3xl: CGFloat { get } + var spacingResponsiveFunctionalTablet3xs: CGFloat { get } + var spacingResponsiveFunctionalTablet2xs: CGFloat { get } + var spacingResponsiveFunctionalTabletXs: CGFloat { get } + var spacingResponsiveFunctionalTabletSm: CGFloat { get } + var spacingResponsiveFunctionalTabletMd: CGFloat { get } + var spacingResponsiveFunctionalTabletLg: CGFloat { get } + var spacingResponsiveFunctionalTabletXl: CGFloat { get } + var spacingResponsiveFunctionalTablet2xl: CGFloat { get } + var spacingResponsiveFunctionalTablet3xl: CGFloat { get } + var spacingResponsiveFunctionalMobile3xs: CGFloat { get } + var spacingResponsiveFunctionalMobile2xs: CGFloat { get } + var spacingResponsiveFunctionalMobileXs: CGFloat { get } + var spacingResponsiveFunctionalMobileSm: CGFloat { get } + var spacingResponsiveFunctionalMobileMd: CGFloat { get } + var spacingResponsiveFunctionalMobileLg: CGFloat { get } + var spacingResponsiveFunctionalMobileXl: CGFloat { get } + var spacingResponsiveFunctionalMobile2xl: CGFloat { get } + var spacingResponsiveFunctionalMobile3xl: CGFloat { get } + var spacingResponsiveExpressiveTablet3xs: CGFloat { get } + var spacingResponsiveExpressiveTablet2xs: CGFloat { get } + var spacingResponsiveExpressiveTabletXs: CGFloat { get } + var spacingResponsiveExpressiveTabletSm: CGFloat { get } + var spacingResponsiveExpressiveTabletMd: CGFloat { get } + var spacingResponsiveExpressiveTabletLg: CGFloat { get } + var spacingResponsiveExpressiveTabletXl: CGFloat { get } + var spacingResponsiveExpressiveTablet2xl: CGFloat { get } + var spacingResponsiveExpressiveTablet3xl: CGFloat { get } + var spacingResponsiveExpressiveMobile3xs: CGFloat { get } + var spacingResponsiveExpressiveMobile2xs: CGFloat { get } + var spacingResponsiveExpressiveMobileXs: CGFloat { get } + var spacingResponsiveExpressiveMobileSm: CGFloat { get } + var spacingResponsiveExpressiveMobileMd: CGFloat { get } + var spacingResponsiveExpressiveMobileLg: CGFloat { get } + var spacingResponsiveExpressiveMobileXl: CGFloat { get } + var spacingResponsiveExpressiveMobile2xl: CGFloat { get } + var spacingResponsiveExpressiveMobile3xl: CGFloat { get } + var spacingFixedRegular3xs: CGFloat { get } + var spacingFixedRegular2xs: CGFloat { get } + var spacingFixedRegularXs: CGFloat { get } + var spacingFixedRegularSm: CGFloat { get } + var spacingFixedRegularMd: CGFloat { get } + var spacingFixedRegularLg: CGFloat { get } + var spacingFixedRegularXl: CGFloat { get } + var spacingFixedRegular2xl: CGFloat { get } + var spacingFixedRegular3xl: CGFloat { get } + var spacingFixedFunctional3xs: CGFloat { get } + var spacingFixedFunctional2xs: CGFloat { get } + var spacingFixedFunctionalXs: CGFloat { get } + var spacingFixedFunctionalSm: CGFloat { get } + var spacingFixedFunctionalMd: CGFloat { get } + var spacingFixedFunctionalLg: CGFloat { get } + var spacingFixedFunctionalXl: CGFloat { get } + var spacingFixedFunctional2xl: CGFloat { get } + var spacingFixedFunctional3xl: CGFloat { get } + var spacingFixedExpressive3xs: CGFloat { get } + var spacingFixedExpressive2xs: CGFloat { get } + var spacingFixedExpressiveXs: CGFloat { get } + var spacingFixedExpressiveSm: CGFloat { get } + var spacingFixedExpressiveMd: CGFloat { get } + var spacingFixedExpressiveLg: CGFloat { get } + var spacingFixedExpressiveXl: CGFloat { get } + var spacingFixedExpressive2xl: CGFloat { get } + var spacingFixedExpressive3xl: CGFloat { get } + var sizingFixedMobileHeader: CGFloat { get } + var sizingRegular3xl: CGFloat { get } + var sizingRegular2xl: CGFloat { get } + var sizingRegularXl: CGFloat { get } + var sizingRegularLg: CGFloat { get } + var sizingRegularMd: CGFloat { get } + var sizingRegularSm: CGFloat { get } + var sizingRegularXs: CGFloat { get } + var sizingRegular2xs: CGFloat { get } + var sizingRegular3xs: CGFloat { get } + var sizingFunctional3xs: CGFloat { get } + var sizingFunctional2xs: CGFloat { get } + var sizingFunctionalXs: CGFloat { get } + var sizingFunctionalSm: CGFloat { get } + var sizingFunctionalMd: CGFloat { get } + var sizingFunctionalLg: CGFloat { get } + var sizingFunctionalXl: CGFloat { get } + var sizingFunctional2xl: CGFloat { get } + var sizingFunctional3xl: CGFloat { get } + var sizingExpressive3xs: CGFloat { get } + var sizingExpressive2xs: CGFloat { get } + var sizingExpressiveXs: CGFloat { get } + var sizingExpressiveSm: CGFloat { get } + var sizingExpressiveMd: CGFloat { get } + var sizingExpressiveLg: CGFloat { get } + var sizingExpressiveXl: CGFloat { get } + var sizingExpressive2xl: CGFloat { get } + var sizingExpressive3xl: CGFloat { get } + var borderHeight3xs: CGFloat { get } + var borderHeight2xs: CGFloat { get } + var borderHeightXs: CGFloat { get } + var borderHeightSm: CGFloat { get } + var borderHeightMd: CGFloat { get } + var borderHeightLg: CGFloat { get } + var borderHeightXl: CGFloat { get } + var borderHeight2xl: CGFloat { get } + var borderHeight3xl: CGFloat { get } + var borderRadius3xs: CGFloat { get } + var borderRadius2xs: CGFloat { get } + var borderRadiusXs: CGFloat { get } + var borderRadiusSm: CGFloat { get } + var borderRadiusMd: CGFloat { get } + var borderRadiusLg: CGFloat { get } + var borderRadiusXl: CGFloat { get } + var borderRadius2xl: CGFloat { get } + var borderRadius3xl: CGFloat { get } + var borderRadiusFull: CGFloat { get } +} + `; for (const [type, values] of Object.entries(dimensionTypes)) { - resolvedTokenFile += `struct ${kebabCase(type)}Dimensions {\n`; + resolvedTokenFile += `public struct DS${kebabCase(type)}Dimensions {\n`; for (const value of values) { for (const size of shirtSizes) { // val fixedXl: Dp = Dimensions.spacingFixedXl, - resolvedTokenFile += ` var ${kebabCase(`${value}-${size}`, true)}: CGFloat\n`; + resolvedTokenFile += ` public var ${kebabCase(`${value}-${size}`, true)}: CGFloat\n`; } } - resolvedTokenFile += "}\n"; + resolvedTokenFile += "}\n\n"; } - resolvedTokenFile += `struct ${designSystemName}Dimensions {\n`; + resolvedTokenFile += `public struct ${designSystemName}Dimensions {\n`; for (const type of Object.keys(dimensionTypes)) { - resolvedTokenFile += ` var ${type}: ${kebabCase(type)}Dimensions\n\n`; + resolvedTokenFile += ` public var ${type}: DS${kebabCase(type)}Dimensions\n\n`; } resolvedTokenFile += ` - init(spacing: SpacingDimensions, sizing: SizingDimensions, border: BorderDimensions) { + init(spacing: DSSpacingDimensions, sizing: DSSizingDimensions, border: DSBorderDimensions) { self.spacing = spacing self.sizing = sizing self.border = border diff --git a/src/utils/outputs/swiftui/elevation.ts b/src/utils/outputs/swiftui/elevation.ts index 4fd3d5f0..d8ee2db2 100644 --- a/src/utils/outputs/swiftui/elevation.ts +++ b/src/utils/outputs/swiftui/elevation.ts @@ -5,13 +5,34 @@ export const generateSwiftUIElevationsFile = ( ): string => { let resolvedString: string = `import SwiftUI -struct DBSubElevation { - let first: DBElevationShadow - let second: DBElevationShadow - let third: DBElevationShadow +struct DSShadowViewModifier: ViewModifier { + + let elevation: DSSubElevation + + func body(content: Content) -> some View { + content + .background( + content + .shadow(color: elevation.first.color, radius: elevation.first.spread, x: elevation.first.x, y: elevation.first.y) + .shadow(color: elevation.second.color, radius: elevation.second.spread, x: elevation.second.x, y: elevation.second.y) + .shadow(color: elevation.third.color, radius: elevation.third.spread, x: elevation.third.x, y: elevation.third.y) + ) + } +} + +extension View { + func dsShadow(elevation: DSSubElevation = DSElevation.sm) -> some View { + self.modifier(DSShadowViewModifier(elevation: elevation)) + } +} + +struct DSSubElevation { + let first: DSElevationShadowConfig + let second: DSElevationShadowConfig + let third: DSElevationShadowConfig } -struct DBElevationShadow { +struct DSElevationShadowConfig { let x: CGFloat let y: CGFloat let blur: CGFloat @@ -19,11 +40,11 @@ struct DBElevationShadow { let color: Color } -struct DBElevation {\n`; +public struct DSElevation {\n`; Object.entries(allElevations).forEach(([name, elevation]) => { if (!name.includes("_scale")) { - resolvedString += ` static let ${name.toLowerCase()} = DBSubElevation(`; + resolvedString += ` static let ${name.toLowerCase()} = DSSubElevation(`; const shadows = elevation.toString().replaceAll(" ", " ").replaceAll("rgba(", "").replaceAll ("), ", "#").replaceAll(")", "").replaceAll(",", "").split("#") resolvedString += ` first: .init( diff --git a/src/utils/outputs/swiftui/shared.ts b/src/utils/outputs/swiftui/shared.ts index d538a9df..d7fc705e 100644 --- a/src/utils/outputs/swiftui/shared.ts +++ b/src/utils/outputs/swiftui/shared.ts @@ -17,76 +17,75 @@ export const shirtSizes = [ ]; export const generateStaticSwiftUIFiles = (): string => { - return ` -import SwiftUI - -struct BasicColor { - var text: TextColor - var icon: IconColor - var border: BorderColor - var background: BackgroundColor + return `import SwiftUI + +public struct BasicColor { + public var text: TextColor + public var icon: IconColor + public var border: BorderColor + public var background: BackgroundColor } -struct InvertedBackgroundColor { - var contrastMax: StateColor - var contrastHigh: StateColor - var contrastLow: StateColor +public struct InvertedBackgroundColor { + public var contrastMax: StateColor + public var contrastHigh: StateColor + public var contrastLow: StateColor } -struct InvertedColor { - var background: InvertedBackgroundColor - var onBackground: StateColor +public struct InvertedColor { + public var background: InvertedBackgroundColor + public var onBackground: StateColor } -struct OriginColor { - var origin: StateColor - var onOrigin: StateColor +public struct OriginColor { + public var origin: StateColor + public var onOrigin: StateColor } -struct TextColor { - var \`default\`: StateColor - var emphasis100: StateColor - var emphasis90: StateColor - var emphasis80: StateColor +public struct TextColor { + public var \`default\`: StateColor + public var emphasis100: StateColor + public var emphasis90: StateColor + public var emphasis80: StateColor } -struct IconColor { - var \`default\`: StateColor - var emphasis100: StateColor - var emphasis90: StateColor - var emphasis80: StateColor - var emphasis70: StateColor +public struct IconColor { + public var \`default\`: StateColor + public var emphasis100: StateColor + public var emphasis90: StateColor + public var emphasis80: StateColor + public var emphasis70: StateColor } -struct BorderColor { - var \`default\`: StateColor - var emphasis100: StateColor - var emphasis70: StateColor - var emphasis60: StateColor - var emphasis50: StateColor +public struct BorderColor { + public var \`default\`: StateColor + public var emphasis100: StateColor + public var emphasis70: StateColor + public var emphasis60: StateColor + public var emphasis50: StateColor } -struct BackgroundColor { - var transparent: TransparentColor - var level1: StateColor - var level2: StateColor - var level3: StateColor +public struct BackgroundColor { + public var transparent: TransparentColor + public var level1: StateColor + public var level2: StateColor + public var level3: StateColor } -struct TransparentColor { - var full: Color - var semi: Color - var hovered: Color - var pressed: Color +public struct TransparentColor { + public var full: Color + public var semi: Color + public var hovered: Color + public var pressed: Color } -struct StateColor { - var \`default\`: Color - var hovered: Color - var pressed: Color +public struct StateColor { + public var \`default\`: Color + public var hovered: Color + public var pressed: Color } -extension AdaptiveColors { +extension DSColorVariant { public var basic: BasicColor { .init( diff --git a/src/utils/outputs/swiftui/theme.ts b/src/utils/outputs/swiftui/theme.ts index 201feff9..bb250935 100644 --- a/src/utils/outputs/swiftui/theme.ts +++ b/src/utils/outputs/swiftui/theme.ts @@ -1,95 +1,98 @@ import { designSystemName } from "./shared"; export const generateSwiftUIDesignSystemThemeFile = (fileName: string): string => { + console.log(fileName) return ` import SwiftUI -// Design System Theme File -// import SwiftUI - -// private var DarkColorScheme = getColorSchemeDark() - -// private var LightColorScheme = getColorSchemeLight() - - -// struct ${fileName} { -// val colors: ${fileName}ColorScheme -// @Composable -// @ReadOnlyComposable -// get() = LocalColors.current - -// val dimensions: ${fileName}Dimensions -// @Composable -// @ReadOnlyComposable -// get() = LocalDimensions.current - -// val typography: ${fileName}TextStyles -// @Composable -// @ReadOnlyComposable -// get() = LocalTypography.current -// } +struct ThemeModifier: ViewModifier { + @Environment(\\.colorScheme) var systemColorScheme + + var theme: DSTheme + + func body(content: Content) -> some View { + var changedTheme = theme + changedTheme.fonts = adaptiveFonts + changedTheme.dimensions = adaptiveDimensions + + return content + .theme(changedTheme) + } + + var adaptiveFonts: DesignSystemTextStyles { + // TODO: Use dimensions environment variable + let typography = UIDevice.current.userInterfaceIdiom == .pad ? DesignSystemTypography.getTypographyRegularTablet(sizes: DeutscheBahnTypography) : DesignSystemTypography.getTypographyRegularMobile(sizes: DeutscheBahnTypography) + return DesignSystemTextStyles.getFonts(typo: typography) + } + + var adaptiveDimensions: DesignSystemDimensions { + UIDevice.current.userInterfaceIdiom == .pad ? DesignSystemDimensions.getDimensionsRegularTablet(dimensions: DeutscheBahnDimensions()) : DesignSystemDimensions.getDimensionsRegularMobile(dimensions: DeutscheBahnDimensions()) + } +} -// func ${fileName}( -// density: Density = .regular, -// darkTheme: Boolean = isSystemInDarkTheme(), -// content: @Composable () -> Unit -// ) { -// val configuration = LocalConfiguration.current -// // typography -// val typography: ${fileName}TextStyles = when { -// configuration.screenWidthDp > 768 -> -// when (density) { -// Density.FUNCTIONAL -> getTextStyles(getTypographyFunctionalTablet()) -// Density.EXPRESSIVE -> getTextStyles(getTypographyExpressiveTablet()) -// else -> getTextStyles(getTypographyRegularTablet()) -// } +extension EnvironmentValues { + @Entry public var theme: DSTheme = DeutscheBahnTheme() +} -// else -> when (density) { -// Density.FUNCTIONAL -> getTextStyles(getTypographyFunctionalMobile()) -// Density.EXPRESSIVE -> getTextStyles(getTypographyExpressiveMobile()) -// else -> getTextStyles(getTypographyRegularMobile()) -// } -// } +extension View { + public func dsTheme(_ theme: DSTheme = DeutscheBahnTheme()) -> some View { + modifier(ThemeModifier(theme: theme)) + } + + public func theme(_ theme: DSTheme) -> some View { + environment(\\.theme, theme) + } + + public func activeColorScheme(_ colorScheme: DSColorVariant) -> some View { + modifier(ActiveColorViewModifier(color: colorScheme)) + } + + public func dsExpressive() -> some View { + modifier(DimensionsViewModifier(dimensions: DesignSystemDimensions.getDimensionsExpressiveMobile(dimensions: DeutscheBahnDimensions()))) + } + + public func dsFunctional() -> some View { + modifier(DimensionsViewModifier(dimensions: + UIDevice.current.userInterfaceIdiom == .pad ? DesignSystemDimensions.getDimensionsFunctionalTablet(dimensions: DeutscheBahnDimensions()) : + DesignSystemDimensions.getDimensionsFunctionalMobile(dimensions: DeutscheBahnDimensions()))) + } +} -// // screen -// val dimensions: ${fileName}Dimensions = when { -// configuration.screenWidthDp > 768 -> -// when (density) { -// Density.FUNCTIONAL -> getDimensionsFunctionalTablet() -// Density.EXPRESSIVE -> getDimensionsExpressiveTablet() -// else -> getDimensionsRegularTablet() -// } +struct ActiveColorViewModifier: ViewModifier { + @Environment(\\.theme) var theme + + var color: DSColorVariant + + func body(content: Content) -> some View { + var changedTheme = theme + changedTheme.activeColor = color + + return content + .theme(changedTheme) + } +} -// else -> when (density) { -// Density.FUNCTIONAL -> getDimensionsFunctionalMobile() -// Density.EXPRESSIVE -> getDimensionsExpressiveMobile() -// else -> getDimensionsRegularMobile() -// } -// } +struct DimensionsViewModifier: ViewModifier { + @Environment(\\.theme) var theme + + var dimensions: DesignSystemDimensions + + func body(content: Content) -> some View { + var changedTheme = theme + changedTheme.dimensions = dimensions + + return content + .theme(changedTheme) + } +} -// // colors -// val colorScheme: ${fileName}ColorScheme = when { -// darkTheme -> DarkColorScheme -// else -> LightColorScheme -// } -// val view = LocalView.current -// if (!view.isInEditMode) { -// SideEffect { -// val window = (view.context as Activity).window -// window.statusBarColor = colorScheme.neutral.bgBasicLevel1Default.toArgb() -// window.navigationBarColor = colorScheme.neutral.bgBasicLevel1Default.toArgb() -// WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = darkTheme -// } -// } +public protocol DSTheme { + var colorScheme: DesignSystemColorScheme { get set } + var activeColor: DSColorVariant { get set } + var fonts: DesignSystemTextStyles { get set } + var dimensions: DesignSystemDimensions { get set } +} -// CompositionLocalProvider( -// LocalColors provides colorScheme, -// LocalDimensions provides dimensions, -// LocalTypography provides typography -// ) { -// content() -// } -// } `; } @@ -97,17 +100,17 @@ export const generateSwiftUIThemeFile = (themeName: string): string => { return ` import SwiftUI -struct ${themeName}Theme: Theme { - var colorScheme: ${designSystemName}ColorScheme - var activeColor: AdaptiveColors - var dimensions: ${designSystemName}Dimensions - var fonts: ${designSystemName}Fonts +public struct ${themeName}Theme: DSTheme { + public var colorScheme: ${designSystemName}ColorScheme + public var activeColor: DSColorVariant + public var dimensions: ${designSystemName}Dimensions + public var fonts: ${designSystemName}TextStyles - init(_ colorScheme: ColorScheme = .light) { - self.colorScheme = colorScheme == .light ? getColorSchemeLight(colors: ${themeName}Colors) : getColorSchemeDark(colors: ${themeName}Colors) + public init(_ colorScheme: ColorScheme = .light) { + self.colorScheme = colorScheme == .light ? DesignSystemColorScheme.getColorSchemeLight(colors: ${themeName}Colors) : DesignSystemColorScheme.getColorSchemeDark(colors: ${themeName}Colors) self.activeColor = self.colorScheme.brand self.dimensions = ${designSystemName}Dimensions.getDimensionsFunctionalMobile(dimensions: ${themeName}Dimensions()) - self.fonts = ${designSystemName}Fonts.getTypographyFunctionalMobile(sizes: ${themeName}Typography) + self.fonts = DesignSystemTextStyles.getFonts(typo: ${designSystemName}Typography.getTypographyFunctionalMobile(sizes: ${themeName}Typography)) } } ` diff --git a/src/utils/outputs/swiftui/typography.ts b/src/utils/outputs/swiftui/typography.ts index 1a3c83f6..08913988 100644 --- a/src/utils/outputs/swiftui/typography.ts +++ b/src/utils/outputs/swiftui/typography.ts @@ -10,48 +10,53 @@ import { export const generateSwiftUIFontFamilyFile = (): string => { return `import SwiftUI -struct DBFont { - let name: String - private let publicName: String - - private init(named name: String, publicName: String) { - self.name = name - self.publicName = publicName - do { - try registerFont(fontName: name) - print("Registered Font \\(name)") - } catch { - let reason = error.localizedDescription - fatalError("Failed to register font: \\(reason)") - } - } - - public func font(size: CGFloat) -> Font { - Font.custom(publicName, size: size) - } +import UIKit - static let dbFlex = DBFont(named: "DBNeoScreenFlex", publicName: "DB Neo Screen Flex") - -} - -public enum FontError: Swift.Error { - case failedToRegisterFont -} +struct DSFont { + let name: String + private let publicName: String + + private init(named name: String, publicName: String) { + self.name = name + self.publicName = publicName + do { + try registerFont(fontName: name) + print("Registered Font \\(name)") + } catch { + let reason = error.localizedDescription + fatalError("Failed to register font: \\(reason)") + } + } + + private enum FontError: Swift.Error { + case failedToRegisterFont + } -func registerFont(fontName: String) throws { - guard let fontURL = Bundle.module.url(forResource: "\\(fontName)", withExtension: "ttf") else { - throw FontError.failedToRegisterFont + private func registerFont(fontName: String) throws { + guard let fontURL = Bundle.module.url(forResource: "\\(fontName)", withExtension: "ttf") else { + throw FontError.failedToRegisterFont + } + + let fontURLs = [fontURL] as CFArray + + CTFontManagerRegisterFontURLs(fontURLs, .process, true) { errors, done in + let errors = errors as! [CFError] + guard errors.isEmpty else { + preconditionFailure(errors.map(\\.localizedDescription).joined()) + } + return true + } } - let fontURLs = [fontURL] as CFArray + func font(size: CGFloat) -> Font { + Font.custom(publicName, size: size) + } - CTFontManagerRegisterFontURLs(fontURLs, .process, true) { errors, done in - let errors = errors as! [CFError] - guard errors.isEmpty else { - preconditionFailure(errors.map(\\.localizedDescription).joined()) - } - return true + func uiFont(size: CGFloat) -> UIFont { + UIFont(name: publicName, size: size)! } + + static let dbNeoScreenFlex = DSFont(named: "DBNeoScreenFlex", publicName: "DB Neo Screen Flex") } `; } @@ -127,10 +132,10 @@ export const generateSwiftUITypographyScheme = ( resolvedTokenFile += `extension ${designSystemName}Typography {\n` for (const variant of typoVariants) { - resolvedTokenFile += ` private static func ${variant}Typography${density}${device}(sizes: [String: CGFloat]) -> Typography { .init(\n`; - resolvedTokenFile += ` variant: TypographyVariant.${variant.toLowerCase()},\n` - resolvedTokenFile += ` density: Density.${density.toLowerCase()},\n` - resolvedTokenFile += ` device: DeviceType.${device.toLowerCase()},\n` + resolvedTokenFile += ` private static func ${variant}Typography${density}${device}(sizes: [String: CGFloat]) -> DSTypography { .init(\n`; + resolvedTokenFile += ` variant: DSTypographyVariant.${variant.toLowerCase()},\n` + resolvedTokenFile += ` density: DSDensity.${density.toLowerCase()},\n` + resolvedTokenFile += ` device: DSDeviceType.${device.toLowerCase()},\n` resolvedTokenFile += ` sizes: sizes\n` resolvedTokenFile += `\n )\n }\n\n` } @@ -147,10 +152,9 @@ export const generateSwiftUITypographyScheme = ( }; export const generateSwiftUITypographySchemeFile = (fileName: string): string => { - let resolvedTokenFile: string = `import SwiftUI -`; + let resolvedTokenFile: string = `import SwiftUI\n\n`; - resolvedTokenFile += `struct Typography {\n`; + resolvedTokenFile += `struct DSTypography {\n`; for (const type of typoTypes) { for (const size of shirtSizes) { resolvedTokenFile += ` let ${kebabCase(`${type}-${size}`, true)}: CGFloat\n`; @@ -160,7 +164,7 @@ export const generateSwiftUITypographySchemeFile = (fileName: string): string => resolvedTokenFile += `struct ${designSystemName}Typography {\n`; for (const variant of typoVariants) { - resolvedTokenFile += ` let ${variant}: Typography\n`; + resolvedTokenFile += ` let ${variant}: DSTypography\n`; } resolvedTokenFile += "}\n\n"; @@ -176,18 +180,18 @@ export const generateSwiftUITypographySchemeFile = (fileName: string): string => } resolvedTokenFile += ` -enum DeviceType: String { +enum DSDeviceType: String { case mobile = "Mobile" case tablet = "Tablet" } -enum TypographyVariant: String { +enum DSTypographyVariant: String { case body case headline } -extension Typography { - init(variant: TypographyVariant, density: Density, device: DeviceType, sizes: [String: CGFloat]) { +extension DSTypography { + init(variant: DSTypographyVariant, density: DSDensity, device: DSDeviceType, sizes: [String: CGFloat]) { lineHeight3xs = sizes["\\(variant)LineHeight\\(density.rawValue)\\(device.rawValue)3xs", default: 12] lineHeight2xs = sizes["\\(variant)LineHeight\\(density.rawValue)\\(device.rawValue)2xs", default: 12] lineHeightXs = sizes["\\(variant)LineHeight\\(density.rawValue)\\(device.rawValue)Xs", default: 12] @@ -210,22 +214,48 @@ extension Typography { } } +public struct DSTextStyle { + public let font: Font + let uiFont: UIFont + public let lineHeight: CGFloat + public let fontWeight: Font.Weight +} + ` - resolvedTokenFile += `struct ${designSystemName}Fonts{\n`; + resolvedTokenFile += `public struct ${designSystemName}TextStyles{\n`; for (const [font] of Object.entries(fontsTypes)) { - resolvedTokenFile += ` let ${font}: Font\n`; + resolvedTokenFile += ` public let ${font}: DSTextStyle\n`; } resolvedTokenFile += `\n\n`; - resolvedTokenFile += ` static func getFonts(typo: ${designSystemName}Typography) -> ${designSystemName}Fonts { + resolvedTokenFile += ` static func getFonts(typo: ${designSystemName}Typography) -> ${designSystemName}TextStyles { .init(\n`; for (const [font, size] of Object.entries(fontsTypes)) { - resolvedTokenFile += ` ${font}: .init(font: DBFont.dbFlex.font(size: typo.${font.includes("body") ? "body" : "headline"}.fontSize${size}), uiFont: DBFont.dbFlex.uiFont(size: typo.${font.includes("body") ? "body" : "headline"}.fontSize${size}), lineHeight: typo.${font.includes("body") ? "body" : "headline"}.lineHeight${size}, fontWeight: ${font.includes("body") ? ".regular" : ".black"}),\n` - // resolvedTokenFile += ` ${font}: DBFont.dbFlex.font(size: typo.${font.includes("body") ? "body" : "headline"}.fontSize${size}).weight(${font.includes("body") ? ".regular" : ".black"}),\n`; + resolvedTokenFile += ` ${font}: .init(font: DSFont.dbNeoScreenFlex.font(size: typo.${font.includes("body") ? "body" : "headline"}.fontSize${size}), uiFont: DSFont.dbNeoScreenFlex.uiFont(size: typo.${font.includes("body") ? "body" : "headline"}.fontSize${size}), lineHeight: typo.${font.includes("body") ? "body" : "headline"}.lineHeight${size}, fontWeight: ${font.includes("body") ? ".regular" : ".black"}),\n` } resolvedTokenFile = resolvedTokenFile.substring(0, resolvedTokenFile.lastIndexOf(',')); resolvedTokenFile += `\n )\n }\n}\n`; + resolvedTokenFile += ` +struct TextStyleViewModifier: ViewModifier { + let font: DSTextStyle + + func body(content: Content) -> some View { + content + .font(font.font) + .fontWeight(font.fontWeight) + .lineSpacing(font.lineHeight - font.uiFont.lineHeight) + .padding(.vertical, (font.lineHeight - font.uiFont.lineHeight) / 2) + } +} + +extension View { + public func dsTextStyle(_ font: DSTextStyle) -> some View { + modifier(TextStyleViewModifier(font: font)) + } +} +`; + return resolvedTokenFile; }; From 523f8ce90b9e377ea5f02732626995208be4ee84 Mon Sep 17 00:00:00 2001 From: Dennis Post Date: Wed, 23 Oct 2024 10:48:09 +0200 Subject: [PATCH 5/5] update readme --- src/utils/outputs/download.ts | 2 +- src/utils/outputs/swiftui/readme.ts | 59 ++++++++++++++++--------- src/utils/outputs/swiftui/typography.ts | 1 - 3 files changed, 39 insertions(+), 23 deletions(-) diff --git a/src/utils/outputs/download.ts b/src/utils/outputs/download.ts index 4801f464..f84d8428 100644 --- a/src/utils/outputs/download.ts +++ b/src/utils/outputs/download.ts @@ -125,7 +125,7 @@ export const downloadTheme = async ( const iOSDataFolder: string = `${iOSThemeFolder}/data`; zip.file( `${iOSFolder}/README.md`, - generateSwiftUIReadmeFile(iOSFileName), + generateSwiftUIReadmeFile(), ); zip.file( `${iOSFolder}/${designSystemName}ColorScheme.swift`, diff --git a/src/utils/outputs/swiftui/readme.ts b/src/utils/outputs/swiftui/readme.ts index 25c539e7..fad4d00c 100644 --- a/src/utils/outputs/swiftui/readme.ts +++ b/src/utils/outputs/swiftui/readme.ts @@ -1,36 +1,53 @@ -export const generateSwiftUIReadmeFile = (fileName: string): string => { +export const generateSwiftUIReadmeFile = (): string => { return `# How to use the theme 1. Move the \`theme\` directory into your project -2. Replace the string \`replace.\` inside the \`theme\` directory with your package name for example: \`com.example.myapplication.\` -3. Add your theme to the MainActivity: +3. Add your theme to your \`ContentView\`: \`\`\`\` kotlin -import com.example.myapplication.theme.${fileName} -... - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setContent { - ${fileName} { - //... your content - } +import SwiftUI + +@main +struct YourApp: App { + var body: some Scene { + WindowGroup { + ContentView() + .dsTheme() } } +} \`\`\`\` Use the tokens like this: -\`\`\`\` kotlin - Text( - text = "Headline", - style = DBTheme.typography.h1, - color = DBTheme.colors.neutral.onBgBasicEmphasis100, - modifier = Modifier.padding(DBTheme.dimensions.spacing.fixedMd) - ) +\`\`\`\` swift + +struct ContentView: View { + @Environment(\\.theme) var theme + + @State var scheme: DSColorVariant? + + var body: some View { + Text("Headline") + .font(theme.fonts.h1.font) + .foregroundColor(theme.activeColor.onBgBasicEmphasis80Default) + .padding(theme.dimensions.spacing.responsiveXs) + } +} \`\`\`\` -## Fonts +To use another theme, export it and copy the \`theme/\` directory to your project. Set it like this: -[Download](https://marketingportal.extranet.deutschebahn.com/marketingportal/Design-Anwendungen/db-ux-design-system/version-2/foundation/Typografie) fonts and use the \`.ttf\` files. -You might rename it based on the names in \`~/theme/data/Fonts.kt\` and move the \`.ttf\` files into \`~/res/font\` folder. +\`\`\`swift +// Code from step 3 +@main +struct YourApp: App { + var body: some Scene { + WindowGroup { + ContentView() + .dsTheme(Theme()) + } + } +} +\`\`\` `; }; diff --git a/src/utils/outputs/swiftui/typography.ts b/src/utils/outputs/swiftui/typography.ts index 08913988..3413464c 100644 --- a/src/utils/outputs/swiftui/typography.ts +++ b/src/utils/outputs/swiftui/typography.ts @@ -21,7 +21,6 @@ struct DSFont { self.publicName = publicName do { try registerFont(fontName: name) - print("Registered Font \\(name)") } catch { let reason = error.localizedDescription fatalError("Failed to register font: \\(reason)")