diff --git a/Sources/Ignite/Modifiers/Border.swift b/Sources/Ignite/Modifiers/Border.swift index 35b90f87..1cfafe8d 100644 --- a/Sources/Ignite/Modifiers/Border.swift +++ b/Sources/Ignite/Modifiers/Border.swift @@ -11,7 +11,7 @@ struct BorderModifier: HTMLModifier { var color: Color /// The width of the border in pixels. - var width: Int + var width: Double /// The style of the border. var style: BorderStyle @@ -75,7 +75,7 @@ public extension HTML { /// - Returns: A modified element with the border applied func border( _ color: Color, - width: Int = 1, + width: Double = 1, style: BorderStyle = .solid, cornerRadii: CornerRadii = CornerRadii(), edges: Edge = .all @@ -101,7 +101,7 @@ public extension InlineHTML { /// - Returns: A modified element with the border applied func border( _ color: Color, - width: Int = 1, + width: Double = 1, style: BorderStyle = .solid, cornerRadii: CornerRadii = CornerRadii(), edges: Edge = .all diff --git a/Sources/Ignite/Publishing/PublishingContext-Copying.swift b/Sources/Ignite/Publishing/PublishingContext-Copying.swift index 025d1554..c6681bbd 100644 --- a/Sources/Ignite/Publishing/PublishingContext-Copying.swift +++ b/Sources/Ignite/Publishing/PublishingContext-Copying.swift @@ -52,23 +52,18 @@ extension PublishingContext { /// Copies custom font files from the project's "Fonts" directory to the build output's "fonts" directory. func copyFonts() throws { do { - // Copy fonts if directory exists - if FileManager.default.fileExists(atPath: fontsDirectory.path()) { - let fonts = try FileManager.default.contentsOfDirectory( - at: fontsDirectory, - includingPropertiesForKeys: nil - ) + let fonts = try FileManager.default.contentsOfDirectory( + at: fontsDirectory, + includingPropertiesForKeys: nil + ) - let fontsDestDir = buildDirectory.appending(path: "fonts") - try FileManager.default.createDirectory(at: fontsDestDir, withIntermediateDirectories: true) + let fontsDestDir = buildDirectory.appending(path: "fonts") + try FileManager.default.createDirectory(at: fontsDestDir, withIntermediateDirectories: true) - for font in fonts { - let destination = fontsDestDir.appending(path: font.lastPathComponent) - try FileManager.default.copyItem(at: font, to: destination) - } + for font in fonts { + let destination = fontsDestDir.appending(path: font.lastPathComponent) + try FileManager.default.copyItem(at: font, to: destination) } - - // Rest of the existing copyResources code... } catch { print("Could not copy assets from \(assetsDirectory) to \(buildDirectory): \(error).") throw error diff --git a/Sources/Ignite/Publishing/PublishingContext-Generators.swift b/Sources/Ignite/Publishing/PublishingContext-Generators.swift index 385b5f82..76a26039 100644 --- a/Sources/Ignite/Publishing/PublishingContext-Generators.swift +++ b/Sources/Ignite/Publishing/PublishingContext-Generators.swift @@ -218,33 +218,144 @@ extension PublishingContext { fatalError("Ignite requires that you provide a light or dark theme.") } + // Set initial root variables cssContent += """ :root { --supports-light-theme: \(supportsLightTheme); --supports-dark-theme: \(supportsDarkTheme); - --bs-root-font-size: 16px; - font-size: var(--bs-root-font-size); + } + + html { + font-size: var(--bs-root-font-size, 16px); + } + + body { + font-family: var(--bs-body-font-family); + font-size: var(--bs-body-font-size, 1rem); } """ - // Container defaults cssContent += containerDefaults - // Generate alternate theme overrides - for theme in themes { - cssContent += """ - [data-bs-theme="\(theme.id)"] { - \(generateThemeVariables(theme)) + if let lightTheme = site.lightTheme { + // Only output light theme variables if it's the only theme + if !supportsDarkTheme { + cssContent += """ + :root { + \(generateThemeVariables(lightTheme)) + } + + [data-bs-theme="\(lightTheme.id)"] { + \(generateThemeVariables(lightTheme)) + } + """ + } else { + // If both themes exist, use the standard structure + cssContent += """ + :root { + \(generateThemeVariables(lightTheme)) + } + + [data-bs-theme="light"] { + \(generateThemeVariables(lightTheme)) + } + + [data-bs-theme="auto"] { + \(generateThemeVariables(lightTheme)) + } + """ } + } + + if let darkTheme = site.darkTheme { + // Only output dark theme variables if it's the only theme + if !supportsLightTheme { + cssContent += """ + :root { + \(generateThemeVariables(darkTheme)) + } + + [data-bs-theme="\(darkTheme.id)"] { + \(generateThemeVariables(darkTheme)) + } + """ + } else { + // If both themes exist, use the standard structure with media query + cssContent += """ + @media (prefers-color-scheme: dark) { + :root { + \(generateThemeVariables(darkTheme)) + } + + [data-bs-theme="dark"] { + \(generateThemeVariables(darkTheme)) + } - """ + [data-bs-theme="auto"] { + \(generateThemeVariables(darkTheme)) + } + } + """ + } } let cssPath = buildDirectory.appending(path: "css/themes.min.css") try cssContent.write(to: cssPath, atomically: true, encoding: .utf8) } + /// Generates CSS rules for the given theme using the specified selector, including color variables and basic styling. + private func generateThemeRules(_ theme: Theme, selector: String) -> String { + var rules = ["\(selector) {", generateThemeVariables(theme), "}"] + + var bodyStyles: [String] = [] + if !theme.primary.isDefault { bodyStyles.append("color: var(--bs-body-color)") } + if !theme.background.isDefault { bodyStyles.append("background-color: var(--bs-body-bg)") } + if !theme.font.isDefault { bodyStyles.append("font-family: var(--bs-body-font-family)") } + if !theme.bodySize.isDefault { bodyStyles.append("font-size: var(--bs-body-font-size)") } + if !theme.regularLineHeight.isDefault { bodyStyles.append("line-height: var(--bs-body-line-height)") } + + if !bodyStyles.isEmpty { + rules.append("\(selector) {") + rules.append(bodyStyles.joined(separator: ";\n")) + rules.append("}") + } + + if !theme.link.isDefault { + rules.append("\(selector) a { color: var(--bs-link-color); }") + } + if !theme.linkHover.isDefault { + rules.append("\(selector) a:hover { color: var(--bs-link-hover-color); }") + } + + var headingStyles: [String] = [] + if !theme.headingBottomMargin.isDefault { headingStyles.append("margin-bottom: var(--bs-headings-margin-bottom)") } + if !theme.headingFontWeight.isDefault { headingStyles.append("font-weight: var(--bs-headings-font-weight)") } + if !theme.headingLineHeight.isDefault { headingStyles.append("line-height: var(--bs-headings-line-height)") } + + if !headingStyles.isEmpty { + rules.append(""" + \(selector) h1, \(selector) h2, \(selector) h3, + \(selector) h4, \(selector) h5, \(selector) h6 { + \(headingStyles.joined(separator: ";\n")) + } + """) + } + + if !theme.xxLargeHeadingSize.isDefault { rules.append("\(selector) h1 { font-size: var(--bs-h1-font-size); }") } + if !theme.xLargeHeadingSize.isDefault { rules.append("\(selector) h2 { font-size: var(--bs-h2-font-size); }") } + if !theme.largeHeadingSize.isDefault { rules.append("\(selector) h3 { font-size: var(--bs-h3-font-size); }") } + if !theme.mediumHeadingSize.isDefault { rules.append("\(selector) h4 { font-size: var(--bs-h4-font-size); }") } + if !theme.smallHeadingSize.isDefault { rules.append("\(selector) h5 { font-size: var(--bs-h5-font-size); }") } + if !theme.xSmallHeadingSize.isDefault { rules.append("\(selector) h6 { font-size: var(--bs-h6-font-size); }") } + + if !theme.monospaceFont.isDefault { + rules.append("\(selector) code, \(selector) pre { font-family: var(--bs-font-monospace); }") + } + + return rules.joined(separator: "\n\n") + } + // swiftlint:disable function_body_length private func generateThemeVariables(_ theme: Theme) -> String { var cssProperties: [String] = []