baseline-foundry 0.1.6 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/README.md +22 -7
  2. package/config/canonical-spacing.resolved.json +71 -0
  3. package/config/experiments/ibm-plex-engine-smoke.json +7 -6
  4. package/config/experiments/ubuntu-engine-smoke.json +7 -6
  5. package/config/foundation-theme.json +7 -6
  6. package/config/tiers/app.json +8 -9
  7. package/config/tiers/documentation.json +7 -8
  8. package/config/tiers/editorial.json +7 -8
  9. package/config/tiers/os.json +7 -8
  10. package/dist/application-layout.js +1 -1
  11. package/dist/build.d.ts +1 -0
  12. package/dist/build.js +98 -36
  13. package/dist/css-app-tier.js +4 -46
  14. package/dist/css-component-contracts.d.ts +7 -0
  15. package/dist/css-component-contracts.js +82 -0
  16. package/dist/css-components/application-geometry.d.ts +5 -0
  17. package/dist/css-components/application-geometry.js +31 -0
  18. package/dist/css-components/article-pagination.js +6 -5
  19. package/dist/css-components/button-actions.js +68 -4
  20. package/dist/css-components/cards-options.js +2 -2
  21. package/dist/css-components/chip-badge-status.d.ts +0 -1
  22. package/dist/css-components/chip-badge-status.js +37 -15
  23. package/dist/css-components/content-card.js +11 -10
  24. package/dist/css-components/control-geometry.d.ts +12 -0
  25. package/dist/css-components/control-geometry.js +22 -0
  26. package/dist/css-components/cta-figure-aspect.js +2 -2
  27. package/dist/css-components/document-navigation.js +28 -18
  28. package/dist/css-components/editorial-content.js +1 -1
  29. package/dist/css-components/icon.js +24 -2
  30. package/dist/css-components/interactive-feedback.js +27 -14
  31. package/dist/css-components/interactive-tables.js +9 -6
  32. package/dist/css-components/legacy-navigation.js +32 -25
  33. package/dist/css-components/list-tree.js +5 -5
  34. package/dist/css-components/list.js +17 -12
  35. package/dist/css-components/logo-media.js +2 -2
  36. package/dist/css-components/navigation-geometry.d.ts +5 -0
  37. package/dist/css-components/navigation-geometry.js +30 -0
  38. package/dist/css-components/nested-controls.d.ts +8 -0
  39. package/dist/css-components/nested-controls.js +37 -0
  40. package/dist/css-components/panel.js +12 -13
  41. package/dist/css-components/search-box-and-filter.d.ts +0 -1
  42. package/dist/css-components/search-box-and-filter.js +3 -3
  43. package/dist/css-components/sites-editorial-ports.js +8 -1
  44. package/dist/css-components/sites-foundation.js +2 -2
  45. package/dist/css-components/table.js +9 -1
  46. package/dist/css-components/tabs-choice-breadcrumbs.js +9 -9
  47. package/dist/css-components/tiered-list-equal-height-row.js +2 -2
  48. package/dist/css-components.js +99 -229
  49. package/dist/css.js +44 -1
  50. package/dist/dtcg-spacing.d.ts +40 -0
  51. package/dist/dtcg-spacing.js +163 -0
  52. package/dist/experiments/ibm-plex-engine-smoke/styles.css +557 -360
  53. package/dist/experiments/ibm-plex-engine-smoke/surfaces.json +182 -14
  54. package/dist/experiments/ibm-plex-engine-smoke/tokens.json +91 -7
  55. package/dist/index.d.ts +1 -0
  56. package/dist/presets/app-tier/styles.css +700 -494
  57. package/dist/presets/app-tier/surfaces.json +709 -29
  58. package/dist/presets/app-tier/tokens.json +178 -8
  59. package/dist/presets/prose/styles.css +699 -486
  60. package/dist/presets/prose/surfaces.json +709 -29
  61. package/dist/presets/prose/tokens.json +177 -7
  62. package/dist/styles.css +699 -486
  63. package/dist/surfaces.json +709 -29
  64. package/dist/tiers/app/styles.css +700 -494
  65. package/dist/tiers/app/surfaces.json +709 -29
  66. package/dist/tiers/app/tokens.json +178 -8
  67. package/dist/tiers/documentation/styles.css +699 -486
  68. package/dist/tiers/documentation/surfaces.json +709 -29
  69. package/dist/tiers/documentation/tokens.json +177 -7
  70. package/dist/tiers/editorial/styles.css +699 -486
  71. package/dist/tiers/editorial/surfaces.json +709 -29
  72. package/dist/tiers/editorial/tokens.json +177 -7
  73. package/dist/tiers/os/styles.css +699 -486
  74. package/dist/tiers/os/surfaces.json +709 -29
  75. package/dist/tiers/os/tokens.json +177 -7
  76. package/dist/tokens.json +177 -7
  77. package/dist/types.d.ts +13 -13
  78. package/dist/vanilla-theme-colors.js +5 -1
  79. package/docs/publishing.md +7 -1
  80. package/package.json +1 -1
package/dist/css.js CHANGED
@@ -2,6 +2,7 @@ import { gridCss } from "./css-grid.js";
2
2
  import { componentsCss } from "./css-components.js";
3
3
  import { appTierPresetCss } from "./css-app-tier.js";
4
4
  import { generateBaselineGridOverlayCss, generateBaselineGridThemeOverrideCss } from "./baseline-grid-overlay.js";
5
+ import { bfSpacingCompatibilityAliases, dtcgSpacingCssProperty, dtcgSpacingTokenIds, dtcgSpacingValue } from "./dtcg-spacing.js";
5
6
  import { foundryThemeRootColorVars, vanillaThemeColorVars } from "./vanilla-theme-colors.js";
6
7
  function parseRemValue(rem) {
7
8
  return Number.parseFloat(rem.replace("rem", ""));
@@ -81,10 +82,52 @@ function roleVarDeclarations(roleName, token, baselineUnit) {
81
82
  const compensation = token.marginBottom ?? baselineCompensation(token.nudgeTop, baselineUnit);
82
83
  return ` --bf-${roleName}-font-family: ${token.fontStack};\n --bf-${roleName}-font-size: ${token.fontSize};\n --bf-${roleName}-font-style: ${token.fontStyle ?? "normal"};\n --bf-${roleName}-font-weight: ${token.fontWeight ?? 400};\n --bf-${roleName}-font-variant-caps: ${token.fontVariantCaps ?? styleDefaults.fontVariantCaps ?? "normal"};\n --bf-${roleName}-letter-spacing: ${token.letterSpacing ?? styleDefaults.letterSpacing ?? "normal"};\n --bf-${roleName}-text-transform: ${token.textTransform ?? styleDefaults.textTransform ?? "none"};\n --bf-${roleName}-line-height: ${token.lineHeight};\n --bf-${roleName}-space-after: ${token.spaceAfter};\n --bf-${roleName}-baseline-compensation: ${compensation};\n --bf-${roleName}-margin-bottom: ${compensation};\n --bf-${roleName}-nudge-start: ${token.nudgeTop};\n --bf-${roleName}-nudge-end: ${compensation};\n`;
83
84
  }
85
+ function spacingVarDeclarations(tokens) {
86
+ if (!tokens.canonicalSpacing) {
87
+ const bfValues = dtcgSpacingTokenIds
88
+ .map(id => ` ${bfSpacingCompatibilityAliases[id]}: ${dtcgSpacingValue(tokens.spacing[id])};`)
89
+ .join("\n");
90
+ return ` /* Theme-config spacing remains in BF's namespace; it does not claim Canonical provenance. */
91
+ ${bfValues}
92
+ `;
93
+ }
94
+ const canonical = dtcgSpacingTokenIds
95
+ .map(id => ` ${dtcgSpacingCssProperty(id)}: ${dtcgSpacingValue(tokens.canonicalSpacing?.[id] ?? tokens.spacing[id])};`)
96
+ .join("\n");
97
+ const compatibility = dtcgSpacingTokenIds
98
+ .map(id => ` ${bfSpacingCompatibilityAliases[id]}: var(${dtcgSpacingCssProperty(id)});`)
99
+ .join("\n");
100
+ return ` /* Resolved Canonical DTCG spacing; these names always retain the pinned final matrix. */
101
+ ${canonical}
102
+ /* Temporary BF compatibility properties alias Canonical directly during
103
+ the bounded public-name deprecation window. */
104
+ ${compatibility}
105
+ `;
106
+ }
84
107
  function themeSurfaceRule(selector, tokens) {
85
108
  const body = tokens.roles.body;
86
109
  const smallFontFallback = body?.fontSize ?? tokens.roles.h6?.fontSize ?? "1rem";
87
- return `${selector} {\n --bf-baseline: ${tokens.baselineUnit};\n --bf-space-0: 0rem;\n --bf-space-half: calc(var(--bf-baseline) / 2);\n --bf-space-1: var(--bf-baseline);\n --bf-space-2: calc(var(--bf-baseline) * 2);\n --bf-space-3: calc(var(--bf-baseline) * 3);\n --bf-space-4: calc(var(--bf-baseline) * 4);\n --bf-space-6: calc(var(--bf-baseline) * 6);\n --bf-space-8: calc(var(--bf-baseline) * 8);\n --bf-space-12: calc(var(--bf-baseline) * 12);\n --bf-space-16: calc(var(--bf-baseline) * 16);\n --bf-content-max-width: ${tokens.layout.contentMaxWidth};\n --bf-content-padding-inline: ${tokens.layout.contentPaddingInline};\n --bf-measure: ${tokens.layout.measure};\n --bf-section-space: ${tokens.layout.sectionSpace};\n --bf-section-space-shallow: ${tokens.layout.sectionSpaceShallow};\n --bf-section-space-deep: ${tokens.layout.sectionSpaceDeep};\n --bf-strip-space: ${tokens.layout.stripSpace};\n --bf-grid-gap-inline: ${tokens.layout.gridGapInline};\n --bf-grid-gap-block: ${tokens.layout.gridGapBlock};\n --bf-page-margin: ${tokens.layout.pageMargin};\n${Object.entries(tokens.roles).map(([roleName, token]) => roleVarDeclarations(roleName, token, tokens.baselineUnit)).join("")} --bf-font-size-small: var(--bf-body-font-size, ${smallFontFallback});\n }\n`;
110
+ return `${selector} {
111
+ ${spacingVarDeclarations(tokens)} --bf-space-0: 0rem;
112
+ --bf-inline-unit: ${tokens.inlineUnit};
113
+ --bf-space-half: calc(var(--bf-baseline) / 2);
114
+ --bf-space-1: var(--bf-baseline);
115
+ --bf-space-2: calc(var(--bf-baseline) * 2);
116
+ --bf-space-3: calc(var(--bf-baseline) * 3);
117
+ --bf-space-4: calc(var(--bf-baseline) * 4);
118
+ --bf-space-6: calc(var(--bf-baseline) * 6);
119
+ --bf-space-8: calc(var(--bf-baseline) * 8);
120
+ --bf-space-12: calc(var(--bf-baseline) * 12);
121
+ --bf-space-16: calc(var(--bf-baseline) * 16);
122
+ --bf-content-max-width: ${tokens.layout.contentMaxWidth};
123
+ --bf-content-padding-inline: ${tokens.layout.contentPaddingInline};
124
+ --bf-measure: ${tokens.layout.measure};
125
+ --bf-grid-gap-inline: ${tokens.layout.gridGapInline};
126
+ --bf-grid-gap-block: ${tokens.layout.gridGapBlock};
127
+ --bf-page-margin: ${tokens.layout.pageMargin};
128
+ ${Object.entries(tokens.roles).map(([roleName, token]) => roleVarDeclarations(roleName, token, tokens.baselineUnit)).join("")} --bf-font-size-small: var(--bf-body-font-size, ${smallFontFallback});
129
+ }
130
+ `;
88
131
  }
89
132
  function collectFontFiles(tokens, themeSurfaces) {
90
133
  const seen = new Set();
@@ -0,0 +1,40 @@
1
+ import type { BuiltInThemeName } from "./tier-registry.js";
2
+ import type { ThemeConfig } from "./types.js";
3
+ export declare const dtcgSpacingTokenIds: readonly ["spacing.baseline", "spacing.gap.field.block", "spacing.gap.mark.inline", "spacing.gap.group.block", "spacing.gap.pattern.block", "spacing.gap.region.block", "spacing.inset.field.inline", "spacing.inset.action.inline", "spacing.inset.continuation.inline", "spacing.inset.surface.inline", "spacing.inset.surface.block", "spacing.inset.strip.block"];
4
+ export type DtcgSpacingTokenId = (typeof dtcgSpacingTokenIds)[number];
5
+ export type CanonicalProduct = "site" | "docs" | "app" | "os";
6
+ export interface ResolvedDtcgDimensionToken {
7
+ $type: "dimension";
8
+ $value: {
9
+ value: number;
10
+ unit: "rem";
11
+ };
12
+ }
13
+ export type ResolvedDtcgSpacing = Record<DtcgSpacingTokenId, ResolvedDtcgDimensionToken>;
14
+ interface ResolvedDtcgSpacingArtifact {
15
+ format: "canonical-resolver-apply-spacing-v1";
16
+ integrity: {
17
+ algorithm: "sha256";
18
+ canonicalProducts: string;
19
+ };
20
+ source: {
21
+ package: "@canonical/design-tokens";
22
+ repository: string;
23
+ commit: string;
24
+ resolver: string;
25
+ };
26
+ products: Record<CanonicalProduct, ResolvedDtcgSpacing>;
27
+ }
28
+ export declare const canonicalSpacingSourceCommit = "18f57b95b1aa1dfe85a45746016b055c807d6628";
29
+ export declare const canonicalSpacingProductsSha256 = "97cffe22691cebbe29d786d2fbe10d04d014d412ed35ccaca386ca41e73bd571";
30
+ export declare const bfSpacingCompatibilityAliases: Record<DtcgSpacingTokenId, string>;
31
+ export declare function validateCanonicalSpacingArtifact(value: unknown): asserts value is ResolvedDtcgSpacingArtifact;
32
+ export declare function canonicalProductForBuiltInTheme(name: BuiltInThemeName): CanonicalProduct;
33
+ export declare function dtcgSpacingValue(token: ResolvedDtcgDimensionToken): string;
34
+ export declare function dtcgSpacingCssProperty(id: DtcgSpacingTokenId): string;
35
+ export declare function legacyThemeConfigSpacing(config: ThemeConfig): ResolvedDtcgSpacing;
36
+ export declare function readCanonicalSpacingProduct(product: CanonicalProduct, options?: {
37
+ artifactPath?: string;
38
+ }): Promise<ResolvedDtcgSpacing>;
39
+ export declare function assertSpacingSetsEqual(actual: ResolvedDtcgSpacing, expected: ResolvedDtcgSpacing, label: string): void;
40
+ export {};
@@ -0,0 +1,163 @@
1
+ import { createHash } from "node:crypto";
2
+ import fs from "node:fs/promises";
3
+ import path from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ export const dtcgSpacingTokenIds = [
6
+ "spacing.baseline",
7
+ "spacing.gap.field.block",
8
+ "spacing.gap.mark.inline",
9
+ "spacing.gap.group.block",
10
+ "spacing.gap.pattern.block",
11
+ "spacing.gap.region.block",
12
+ "spacing.inset.field.inline",
13
+ "spacing.inset.action.inline",
14
+ "spacing.inset.continuation.inline",
15
+ "spacing.inset.surface.inline",
16
+ "spacing.inset.surface.block",
17
+ "spacing.inset.strip.block"
18
+ ];
19
+ export const canonicalSpacingSourceCommit = "18f57b95b1aa1dfe85a45746016b055c807d6628";
20
+ export const canonicalSpacingProductsSha256 = "97cffe22691cebbe29d786d2fbe10d04d014d412ed35ccaca386ca41e73bd571";
21
+ const canonicalSpacingSourceRepository = "https://github.com/canonical/design-tokens";
22
+ const canonicalSpacingResolver = "tokens/canonical/canonical.resolver.json";
23
+ const canonicalProductOrder = ["site", "docs", "app", "os"];
24
+ export const bfSpacingCompatibilityAliases = {
25
+ "spacing.baseline": "--bf-baseline",
26
+ "spacing.gap.field.block": "--bf-field-gap",
27
+ "spacing.gap.mark.inline": "--bf-leading-mark-gap",
28
+ "spacing.gap.group.block": "--bf-section-space-shallow",
29
+ "spacing.gap.pattern.block": "--bf-section-space",
30
+ "spacing.gap.region.block": "--bf-section-space-deep",
31
+ "spacing.inset.field.inline": "--bf-component-inline-inset-field",
32
+ "spacing.inset.action.inline": "--bf-component-inline-inset-action",
33
+ "spacing.inset.continuation.inline": "--bf-component-inline-inset-continuation",
34
+ "spacing.inset.surface.inline": "--bf-panel-padding-inline",
35
+ "spacing.inset.surface.block": "--bf-panel-padding-block",
36
+ "spacing.inset.strip.block": "--bf-strip-space"
37
+ };
38
+ const productByTier = {
39
+ editorial: "site",
40
+ documentation: "docs",
41
+ app: "app",
42
+ os: "os"
43
+ };
44
+ const moduleDir = path.dirname(fileURLToPath(import.meta.url));
45
+ const defaultArtifactPath = path.resolve(moduleDir, "..", "config", "canonical-spacing.resolved.json");
46
+ function isRecord(value) {
47
+ return typeof value === "object" && value !== null && !Array.isArray(value);
48
+ }
49
+ function validateDimensionToken(value, location) {
50
+ if (!isRecord(value) || value.$type !== "dimension" || !isRecord(value.$value)) {
51
+ throw new Error(`Resolved spacing token "${location}" must use the DTCG dimension token shape.`);
52
+ }
53
+ const magnitude = value.$value.value;
54
+ if (typeof magnitude !== "number" || !Number.isFinite(magnitude) || magnitude < 0 || value.$value.unit !== "rem") {
55
+ throw new Error(`Resolved spacing token "${location}" must contain a finite non-negative rem value.`);
56
+ }
57
+ }
58
+ function validateTokenSet(value, location) {
59
+ if (!isRecord(value)) {
60
+ throw new Error(`Resolved spacing product "${location}" must be an object.`);
61
+ }
62
+ const actualIds = Object.keys(value).sort();
63
+ const expectedIds = [...dtcgSpacingTokenIds].sort();
64
+ if (JSON.stringify(actualIds) !== JSON.stringify(expectedIds)) {
65
+ throw new Error(`Resolved spacing product "${location}" must contain exactly the twelve approved v1 token IDs.`);
66
+ }
67
+ for (const id of dtcgSpacingTokenIds) {
68
+ validateDimensionToken(value[id], `${location}:${id}`);
69
+ }
70
+ }
71
+ function canonicalProductsDigest(products) {
72
+ const payload = JSON.stringify(canonicalProductOrder.map(product => [
73
+ product,
74
+ dtcgSpacingTokenIds.map(id => [
75
+ id,
76
+ products[product][id].$type,
77
+ products[product][id].$value.value,
78
+ products[product][id].$value.unit
79
+ ])
80
+ ]));
81
+ return createHash("sha256").update(payload).digest("hex");
82
+ }
83
+ export function validateCanonicalSpacingArtifact(value) {
84
+ if (!isRecord(value) ||
85
+ value.format !== "canonical-resolver-apply-spacing-v1" ||
86
+ !isRecord(value.integrity) ||
87
+ !isRecord(value.source) ||
88
+ !isRecord(value.products)) {
89
+ throw new Error("Canonical spacing artifact has an unsupported shape.");
90
+ }
91
+ if (value.integrity.algorithm !== "sha256" ||
92
+ value.integrity.canonicalProducts !== canonicalSpacingProductsSha256) {
93
+ throw new Error(`Canonical spacing artifact must declare product digest ${canonicalSpacingProductsSha256}.`);
94
+ }
95
+ if (value.source.package !== "@canonical/design-tokens" ||
96
+ value.source.repository !== canonicalSpacingSourceRepository ||
97
+ value.source.commit !== canonicalSpacingSourceCommit ||
98
+ value.source.resolver !== canonicalSpacingResolver) {
99
+ throw new Error(`Canonical spacing artifact must pin design-tokens ${canonicalSpacingSourceCommit}.`);
100
+ }
101
+ const actualProducts = Object.keys(value.products).sort();
102
+ const expectedProducts = ["app", "docs", "os", "site"];
103
+ if (JSON.stringify(actualProducts) !== JSON.stringify(expectedProducts)) {
104
+ throw new Error("Canonical spacing artifact must contain exactly site, docs, app, and os products.");
105
+ }
106
+ for (const product of expectedProducts) {
107
+ validateTokenSet(value.products[product], product);
108
+ }
109
+ const actualDigest = canonicalProductsDigest(value.products);
110
+ if (actualDigest !== canonicalSpacingProductsSha256) {
111
+ throw new Error(`Canonical spacing artifact product digest changed: expected ${canonicalSpacingProductsSha256}, received ${actualDigest}.`);
112
+ }
113
+ }
114
+ async function readJson(filePath) {
115
+ return JSON.parse(await fs.readFile(filePath, "utf8"));
116
+ }
117
+ export function canonicalProductForBuiltInTheme(name) {
118
+ if (name === "prose")
119
+ return "site";
120
+ if (name === "app-tier")
121
+ return "app";
122
+ return productByTier[name];
123
+ }
124
+ export function dtcgSpacingValue(token) {
125
+ return `${token.$value.value}rem`;
126
+ }
127
+ export function dtcgSpacingCssProperty(id) {
128
+ return `--${id.replaceAll(".", "-")}`;
129
+ }
130
+ export function legacyThemeConfigSpacing(config) {
131
+ const value = (magnitude) => ({
132
+ $type: "dimension",
133
+ $value: { value: magnitude, unit: "rem" }
134
+ });
135
+ return {
136
+ "spacing.baseline": value(config.baselineUnit),
137
+ "spacing.gap.field.block": value(config.components.fieldGapBaselineUnits * config.baselineUnit),
138
+ "spacing.gap.mark.inline": value(config.components.markGapInlineUnits * config.inlineUnitRem),
139
+ "spacing.gap.group.block": value(config.layout.sectionSpaceShallowBaselineUnits * config.baselineUnit),
140
+ "spacing.gap.pattern.block": value(config.layout.sectionSpaceBaselineUnits * config.baselineUnit),
141
+ "spacing.gap.region.block": value(config.layout.sectionSpaceDeepBaselineUnits * config.baselineUnit),
142
+ "spacing.inset.field.inline": value(config.components.inlineInsetFieldUnits * config.inlineUnitRem),
143
+ "spacing.inset.action.inline": value(config.components.inlineInsetActionUnits * config.inlineUnitRem),
144
+ "spacing.inset.continuation.inline": value(config.components.inlineInsetContinuationUnits * config.inlineUnitRem),
145
+ "spacing.inset.surface.inline": value(config.components.panelPaddingInlineUnits * config.inlineUnitRem),
146
+ "spacing.inset.surface.block": value(config.components.panelPaddingBlockBaselineUnits * config.baselineUnit),
147
+ "spacing.inset.strip.block": value(config.layout.stripSpaceBaselineUnits * config.baselineUnit)
148
+ };
149
+ }
150
+ export async function readCanonicalSpacingProduct(product, options = {}) {
151
+ const artifact = await readJson(options.artifactPath ?? defaultArtifactPath);
152
+ validateCanonicalSpacingArtifact(artifact);
153
+ return structuredClone(artifact.products[product]);
154
+ }
155
+ export function assertSpacingSetsEqual(actual, expected, label) {
156
+ for (const id of dtcgSpacingTokenIds) {
157
+ const actualValue = dtcgSpacingValue(actual[id]);
158
+ const expectedValue = dtcgSpacingValue(expected[id]);
159
+ if (actualValue !== expectedValue) {
160
+ throw new Error(`${label} changed ${id}: expected ${expectedValue}, received ${actualValue}.`);
161
+ }
162
+ }
163
+ }