rafters 0.0.41 → 0.0.42

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 (2) hide show
  1. package/dist/index.js +631 -8
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -45078,6 +45078,228 @@ var DEFAULT_FONT_WEIGHTS = {
45078
45078
  extrabold: { value: 800, meaning: "Extra bold weight", contexts: ["display", "hero"] },
45079
45079
  black: { value: 900, meaning: "Black weight", contexts: ["display", "impact"] }
45080
45080
  };
45081
+ var TYPOGRAPHY_ROLE_CONSUMERS = {
45082
+ "display-large": ["hero"],
45083
+ "display-medium": ["h1"],
45084
+ "title-large": ["h2"],
45085
+ "title-medium": [
45086
+ "h3",
45087
+ "card-title",
45088
+ "dialog-title",
45089
+ "sheet-title",
45090
+ "drawer-title",
45091
+ "empty-title",
45092
+ "alert-dialog-title"
45093
+ ],
45094
+ "title-small": ["h4", "alert-title", "accordion-trigger"],
45095
+ "body-large": ["lead"],
45096
+ "body-medium": ["p", "list-item", "blockquote", "accordion-content"],
45097
+ "body-small": [
45098
+ "card-description",
45099
+ "dialog-description",
45100
+ "sheet-description",
45101
+ "alert-description",
45102
+ "field-description",
45103
+ "tooltip",
45104
+ "menu-item",
45105
+ "table-cell",
45106
+ "input",
45107
+ "select",
45108
+ "textarea"
45109
+ ],
45110
+ "label-large": ["button", "tab-trigger", "nav-trigger", "toggle", "pagination-link"],
45111
+ "label-medium": ["label", "breadcrumb", "button-sm", "sidebar-item"],
45112
+ "label-small": ["badge", "sidebar-label", "caption", "command-group-heading"],
45113
+ "code-large": ["code-block"],
45114
+ "code-small": ["code-inline", "kbd"],
45115
+ shortcut: ["keyboard-shortcut"]
45116
+ };
45117
+ var DEFAULT_TYPOGRAPHY_COMPOSITE_MAPPINGS = {
45118
+ "display-large": {
45119
+ fontFamily: "heading",
45120
+ fontSize: "5xl",
45121
+ fontWeight: "bold",
45122
+ lineHeight: "5xl",
45123
+ letterSpacing: "5xl",
45124
+ responsive: { lg: { fontSize: "6xl" } },
45125
+ meaning: "Largest display text for hero sections and landing pages",
45126
+ contexts: ["hero-heading", "landing-page"],
45127
+ do: ["Use sparingly for maximum visual impact", "Pair with ample whitespace"],
45128
+ never: ["Use more than once per page", "Use in constrained containers like cards"],
45129
+ trustLevel: "high",
45130
+ consequence: "reversible"
45131
+ },
45132
+ "display-medium": {
45133
+ fontFamily: "heading",
45134
+ fontSize: "4xl",
45135
+ fontWeight: "bold",
45136
+ lineHeight: "4xl",
45137
+ letterSpacing: "4xl",
45138
+ responsive: { lg: { fontSize: "5xl" } },
45139
+ meaning: "Primary page heading -- one per page",
45140
+ contexts: ["page-title", "h1"],
45141
+ do: ["Use once per page for the main title", "Place at the top of the content area"],
45142
+ never: ["Use multiple times on a single page", "Use inside cards or dialogs"],
45143
+ trustLevel: "high",
45144
+ consequence: "reversible"
45145
+ },
45146
+ "title-large": {
45147
+ fontFamily: "heading",
45148
+ fontSize: "3xl",
45149
+ fontWeight: "semibold",
45150
+ lineHeight: "3xl",
45151
+ letterSpacing: "3xl",
45152
+ meaning: "Major section heading",
45153
+ contexts: ["section-title", "h2"],
45154
+ do: ["Use to divide major content sections", "Maintain heading hierarchy (h1 > h2 > h3)"],
45155
+ never: ["Skip heading levels (h1 then h3)", "Use for decorative emphasis"],
45156
+ trustLevel: "medium",
45157
+ consequence: "reversible"
45158
+ },
45159
+ "title-medium": {
45160
+ fontFamily: "heading",
45161
+ fontSize: "lg",
45162
+ fontWeight: "semibold",
45163
+ lineHeight: "lg",
45164
+ letterSpacing: "lg",
45165
+ meaning: "Component and subsection title -- shared by card, dialog, sheet, drawer, empty state",
45166
+ contexts: ["h3", "card-title", "dialog-title", "sheet-title", "drawer-title", "empty-title"],
45167
+ do: ["Use for component-level headings", "Use consistently across all overlay and card titles"],
45168
+ never: ["Mix with other title sizes in the same component", "Override without why-gate"],
45169
+ trustLevel: "medium",
45170
+ consequence: "reversible"
45171
+ },
45172
+ "title-small": {
45173
+ fontFamily: "heading",
45174
+ fontSize: "base",
45175
+ fontWeight: "semibold",
45176
+ lineHeight: "base",
45177
+ letterSpacing: "base",
45178
+ meaning: "Minor heading and alert title",
45179
+ contexts: ["h4", "alert-title", "accordion-trigger"],
45180
+ do: ["Use for subsections within a card or panel", "Use for alert and accordion headings"],
45181
+ never: ["Use as the primary heading on a page"],
45182
+ trustLevel: "medium",
45183
+ consequence: "reversible"
45184
+ },
45185
+ "body-large": {
45186
+ fontFamily: "body",
45187
+ fontSize: "xl",
45188
+ fontWeight: "normal",
45189
+ lineHeight: "xl",
45190
+ letterSpacing: "xl",
45191
+ meaning: "Lead paragraph and introductory text",
45192
+ contexts: ["lead", "introduction", "hero-body"],
45193
+ do: ["Use for the first paragraph of a section", "Use for hero body copy"],
45194
+ never: ["Use for all body text", "Use in compact UI like sidebars"],
45195
+ trustLevel: "low",
45196
+ consequence: "reversible"
45197
+ },
45198
+ "body-medium": {
45199
+ fontFamily: "body",
45200
+ fontSize: "base",
45201
+ fontWeight: "normal",
45202
+ lineHeight: "base",
45203
+ letterSpacing: "base",
45204
+ meaning: "Default body text for paragraphs and content",
45205
+ contexts: ["paragraph", "body-text", "list-item", "blockquote", "accordion-content"],
45206
+ do: ["Use for all standard body content", "Ensure sufficient contrast against background"],
45207
+ never: ["Use for labels or UI chrome", "Set below 1rem"],
45208
+ trustLevel: "low",
45209
+ consequence: "reversible"
45210
+ },
45211
+ "body-small": {
45212
+ fontFamily: "body",
45213
+ fontSize: "sm",
45214
+ fontWeight: "normal",
45215
+ lineHeight: "sm",
45216
+ letterSpacing: "sm",
45217
+ meaning: "Secondary text -- descriptions, tooltips, menu items, table cells",
45218
+ contexts: ["description", "tooltip", "menu-item", "table-cell", "input-text", "helper-text"],
45219
+ do: ["Use for supplementary information", "Use for compact UI elements like menus and tables"],
45220
+ never: ["Use for primary content that users must read", "Set below 0.875rem for body text"],
45221
+ trustLevel: "low",
45222
+ consequence: "reversible"
45223
+ },
45224
+ "label-large": {
45225
+ fontFamily: "body",
45226
+ fontSize: "base",
45227
+ fontWeight: "medium",
45228
+ lineHeight: "base",
45229
+ letterSpacing: "base",
45230
+ meaning: "Interactive element text -- buttons, tabs, nav triggers, toggles",
45231
+ contexts: ["button", "tab-trigger", "nav-trigger", "toggle", "pagination"],
45232
+ do: ["Use for primary interactive controls", "Ensure touch target meets 24px minimum"],
45233
+ never: ["Use for passive content", "Mix with body text styling"],
45234
+ trustLevel: "medium",
45235
+ consequence: "reversible"
45236
+ },
45237
+ "label-medium": {
45238
+ fontFamily: "body",
45239
+ fontSize: "sm",
45240
+ fontWeight: "medium",
45241
+ lineHeight: "sm",
45242
+ letterSpacing: "sm",
45243
+ meaning: "Form labels, breadcrumbs, small buttons, sidebar items",
45244
+ contexts: ["label", "breadcrumb", "small-button", "sidebar-item"],
45245
+ do: ["Use for form field labels", "Use for secondary navigation"],
45246
+ never: ["Use for headings", "Use for primary call-to-action buttons"],
45247
+ trustLevel: "low",
45248
+ consequence: "reversible"
45249
+ },
45250
+ "label-small": {
45251
+ fontFamily: "body",
45252
+ fontSize: "xs",
45253
+ fontWeight: "medium",
45254
+ lineHeight: "xs",
45255
+ letterSpacing: "xs",
45256
+ meaning: "Smallest label text -- badges, sidebar labels, captions",
45257
+ contexts: ["badge", "sidebar-label", "caption", "command-group-heading"],
45258
+ do: ["Use only for tertiary UI information", "Ensure adequate contrast at small size"],
45259
+ never: ["Use for content users must read to complete a task", "Set below 0.75rem"],
45260
+ trustLevel: "low",
45261
+ consequence: "reversible"
45262
+ },
45263
+ "code-large": {
45264
+ fontFamily: "code",
45265
+ fontSize: "base",
45266
+ fontWeight: "normal",
45267
+ lineHeight: "base",
45268
+ letterSpacing: "base",
45269
+ meaning: "Code blocks and pre-formatted text",
45270
+ contexts: ["code-block", "pre", "terminal-output"],
45271
+ do: ["Use for multi-line code content", "Pair with syntax highlighting"],
45272
+ never: ["Use for inline code snippets", "Use for non-code content"],
45273
+ trustLevel: "low",
45274
+ consequence: "reversible"
45275
+ },
45276
+ "code-small": {
45277
+ fontFamily: "code",
45278
+ fontSize: "sm",
45279
+ fontWeight: "normal",
45280
+ lineHeight: "sm",
45281
+ letterSpacing: "sm",
45282
+ meaning: "Inline code and keyboard key indicators",
45283
+ contexts: ["code-inline", "kbd", "technical-term"],
45284
+ do: ["Use for code references within prose", "Use for keyboard shortcut labels"],
45285
+ never: ["Use for multi-line code blocks", "Use for body text"],
45286
+ trustLevel: "low",
45287
+ consequence: "reversible"
45288
+ },
45289
+ shortcut: {
45290
+ fontFamily: "code",
45291
+ fontSize: "xs",
45292
+ fontWeight: "normal",
45293
+ lineHeight: "xs",
45294
+ letterSpacing: "widest",
45295
+ meaning: "Keyboard shortcut indicators in menus",
45296
+ contexts: ["keyboard-shortcut", "command-shortcut", "menu-shortcut"],
45297
+ do: ["Use for keyboard shortcut text in menus and command palettes"],
45298
+ never: ["Use for regular text content", "Use outside of menu/command contexts"],
45299
+ trustLevel: "low",
45300
+ consequence: "reversible"
45301
+ }
45302
+ };
45081
45303
  var DEFAULT_SEMANTIC_COLOR_MAPPINGS = {
45082
45304
  // ============================================================================
45083
45305
  // CORE SURFACE TOKENS (shadcn compatible)
@@ -46839,6 +47061,7 @@ function groupTokens(tokens) {
46839
47061
  breakpoint: [],
46840
47062
  elevation: [],
46841
47063
  focus: [],
47064
+ "typography-composite": [],
46842
47065
  other: []
46843
47066
  };
46844
47067
  for (const token of tokens) {
@@ -46876,6 +47099,9 @@ function groupTokens(tokens) {
46876
47099
  case "focus":
46877
47100
  groups.focus.push(token);
46878
47101
  break;
47102
+ case "typography-composite":
47103
+ groups["typography-composite"].push(token);
47104
+ break;
46879
47105
  default:
46880
47106
  groups.other.push(token);
46881
47107
  }
@@ -47157,7 +47383,95 @@ function generateAnimationTokens(motionTokens) {
47157
47383
  }
47158
47384
  return lines.join("\n");
47159
47385
  }
47160
- function tokensToTailwind(tokens, options = {}) {
47386
+ function generateTypographyCompositeUtilities(compositeTokens) {
47387
+ if (compositeTokens.length === 0) {
47388
+ return "";
47389
+ }
47390
+ const lines = [];
47391
+ const mappings = compositeTokens.map((t2) => {
47392
+ try {
47393
+ const parsed = JSON.parse(t2.value);
47394
+ return { name: t2.name, ...parsed };
47395
+ } catch {
47396
+ return null;
47397
+ }
47398
+ }).filter((m3) => m3 !== null);
47399
+ for (const mapping of mappings) {
47400
+ lines.push(`@utility text-${mapping.name} {`);
47401
+ lines.push(` font-family: var(--font-${mapping.fontFamily});`);
47402
+ lines.push(` font-size: var(--font-size-${mapping.fontSize});`);
47403
+ lines.push(` font-weight: var(--font-weight-${mapping.fontWeight});`);
47404
+ lines.push(` line-height: var(--line-height-${mapping.lineHeight});`);
47405
+ const namedTrackingValues = {
47406
+ tighter: "-0.05em",
47407
+ tight: "-0.025em",
47408
+ normal: "0em",
47409
+ wide: "0.025em",
47410
+ wider: "0.05em",
47411
+ widest: "0.1em"
47412
+ };
47413
+ if (mapping.letterSpacing in namedTrackingValues) {
47414
+ lines.push(` letter-spacing: ${namedTrackingValues[mapping.letterSpacing]};`);
47415
+ } else {
47416
+ lines.push(` letter-spacing: var(--letter-spacing-${mapping.letterSpacing});`);
47417
+ }
47418
+ if (mapping.responsive) {
47419
+ for (const [breakpoint, overrides] of Object.entries(mapping.responsive)) {
47420
+ if (overrides.fontSize) {
47421
+ const bpWidth = breakpoint === "sm" ? "480px" : breakpoint === "md" ? "640px" : "1024px";
47422
+ lines.push(` @container (min-width: ${bpWidth}) {`);
47423
+ lines.push(` font-size: var(--font-size-${overrides.fontSize});`);
47424
+ lines.push(` line-height: var(--line-height-${overrides.fontSize});`);
47425
+ lines.push(" }");
47426
+ }
47427
+ }
47428
+ }
47429
+ lines.push("}");
47430
+ }
47431
+ return lines.join("\n");
47432
+ }
47433
+ function overridePropertyToUtility(property, value2) {
47434
+ switch (property) {
47435
+ case "fontFamily":
47436
+ return `font-${value2}`;
47437
+ case "fontWeight":
47438
+ return `font-${value2}`;
47439
+ case "fontSize":
47440
+ return `text-${value2}`;
47441
+ case "lineHeight":
47442
+ return `leading-${value2}`;
47443
+ case "letterSpacing":
47444
+ return `tracking-${value2}`;
47445
+ default:
47446
+ return "";
47447
+ }
47448
+ }
47449
+ function generateTypographyOverrideCSS(overrides) {
47450
+ if (overrides.length === 0) {
47451
+ return "";
47452
+ }
47453
+ const lines = [];
47454
+ lines.push("/* -- Typography Element Overrides -- */");
47455
+ for (const override of overrides) {
47456
+ const overrideUtilities = [];
47457
+ for (const [prop, val] of Object.entries(override.overrides)) {
47458
+ if (val) {
47459
+ const utility = overridePropertyToUtility(prop, val);
47460
+ if (utility) {
47461
+ overrideUtilities.push(utility);
47462
+ }
47463
+ }
47464
+ }
47465
+ if (overrideUtilities.length > 0) {
47466
+ lines.push(`/* ${override.element}: diverges from ${override.role} (${override.why}) */`);
47467
+ lines.push(`${override.element} {`);
47468
+ lines.push(` @apply text-${override.role} ${overrideUtilities.join(" ")};`);
47469
+ lines.push("}");
47470
+ }
47471
+ }
47472
+ return lines.join("\n");
47473
+ }
47474
+ function tokensToTailwind(tokens, options = {}, typographyOverrides = []) {
47161
47475
  const { includeImport = true } = options;
47162
47476
  if (tokens.length === 0) {
47163
47477
  throw new Error("Registry is empty");
@@ -47181,13 +47495,24 @@ function tokensToTailwind(tokens, options = {}) {
47181
47495
  if (keyframes) {
47182
47496
  sections.push(keyframes);
47183
47497
  }
47498
+ const typographyUtilities = generateTypographyCompositeUtilities(groups["typography-composite"]);
47499
+ if (typographyUtilities) {
47500
+ sections.push("");
47501
+ sections.push(typographyUtilities);
47502
+ }
47503
+ const overrideCSS = generateTypographyOverrideCSS(typographyOverrides);
47504
+ if (overrideCSS) {
47505
+ sections.push("");
47506
+ sections.push(overrideCSS);
47507
+ }
47184
47508
  sections.push("");
47185
47509
  sections.push(generateArticleBaseLayer());
47186
47510
  return sections.join("\n");
47187
47511
  }
47188
47512
  function registryToTailwind(registry2, options) {
47189
47513
  const tokens = registry2.list();
47190
- return tokensToTailwind(tokens, options);
47514
+ const typographyOverrides = registry2.getTypographyOverrides();
47515
+ return tokensToTailwind(tokens, options, typographyOverrides);
47191
47516
  }
47192
47517
  function generateVarsRootBlock(groups) {
47193
47518
  const lines = [];
@@ -47204,7 +47529,7 @@ function generateVarsRootBlock(groups) {
47204
47529
  for (const token of groups.spacing) {
47205
47530
  const value2 = tokenValueToCSS(token);
47206
47531
  if (value2 === null) continue;
47207
- lines.push(` --rafters-spacing-${token.name}: ${value2};`);
47532
+ lines.push(` --rafters-${token.name}: ${value2};`);
47208
47533
  }
47209
47534
  lines.push("");
47210
47535
  }
@@ -47223,7 +47548,7 @@ function generateVarsRootBlock(groups) {
47223
47548
  for (const token of groups.radius) {
47224
47549
  const value2 = tokenValueToCSS(token);
47225
47550
  if (value2 === null) continue;
47226
- lines.push(` --rafters-radius-${token.name}: ${value2};`);
47551
+ lines.push(` --rafters-${token.name}: ${value2};`);
47227
47552
  }
47228
47553
  lines.push("");
47229
47554
  }
@@ -48448,6 +48773,26 @@ var TokenSchema = external_exports.object({
48448
48773
  requiresConfirmation: external_exports.boolean().optional()
48449
48774
  // UI pattern requirement for destructive actions
48450
48775
  });
48776
+ var TypographyElementOverrideSchema = external_exports.object({
48777
+ /** CSS element selector, e.g. 'h3' */
48778
+ element: external_exports.string().min(1),
48779
+ /** Base role this element uses, e.g. 'title-medium' */
48780
+ role: external_exports.string().min(1),
48781
+ /** Only the properties that differ from the role */
48782
+ overrides: external_exports.object({
48783
+ fontFamily: external_exports.string().optional(),
48784
+ fontWeight: external_exports.string().optional(),
48785
+ fontSize: external_exports.string().optional(),
48786
+ lineHeight: external_exports.string().optional(),
48787
+ letterSpacing: external_exports.string().optional()
48788
+ }),
48789
+ /** Why-gate: reason for the override (required, non-empty) */
48790
+ why: external_exports.string().min(1, "Why-gate required: provide a reason for this override"),
48791
+ /** Who made the override */
48792
+ who: external_exports.string().min(1),
48793
+ /** When the override was made (ISO timestamp) */
48794
+ when: external_exports.string()
48795
+ });
48451
48796
  var NamespaceFileSchema = external_exports.object({
48452
48797
  $schema: external_exports.string(),
48453
48798
  namespace: external_exports.string(),
@@ -48456,6 +48801,121 @@ var NamespaceFileSchema = external_exports.object({
48456
48801
  tokens: external_exports.array(TokenSchema)
48457
48802
  });
48458
48803
 
48804
+ // ../design-tokens/src/validators/typography-a11y.ts
48805
+ var SIZE_ORDER = [
48806
+ "xs",
48807
+ "sm",
48808
+ "base",
48809
+ "lg",
48810
+ "xl",
48811
+ "2xl",
48812
+ "3xl",
48813
+ "4xl",
48814
+ "5xl",
48815
+ "6xl",
48816
+ "7xl",
48817
+ "8xl",
48818
+ "9xl"
48819
+ ];
48820
+ function sizeIndex(size) {
48821
+ return SIZE_ORDER.indexOf(size);
48822
+ }
48823
+ var WEIGHT_VALUES = {
48824
+ thin: 100,
48825
+ extralight: 200,
48826
+ light: 300,
48827
+ normal: 400,
48828
+ medium: 500,
48829
+ semibold: 600,
48830
+ bold: 700,
48831
+ extrabold: 800,
48832
+ black: 900
48833
+ };
48834
+ function weightValue(weight) {
48835
+ return WEIGHT_VALUES[weight] ?? 400;
48836
+ }
48837
+ function validateTypographyComposite(mapping, role) {
48838
+ const violations = [];
48839
+ const sizeIdx = sizeIndex(mapping.fontSize);
48840
+ const weight = weightValue(mapping.fontWeight);
48841
+ const isBody = role.startsWith("body-");
48842
+ const isLabel = role.startsWith("label-");
48843
+ const isHeading = role.startsWith("title-") || role.startsWith("display-");
48844
+ if (isBody && sizeIdx < sizeIndex("sm")) {
48845
+ violations.push({
48846
+ rule: "min-body-font-size",
48847
+ severity: "error",
48848
+ message: `Body role "${role}" uses font-size "${mapping.fontSize}" which is below minimum 'sm' (14px)`,
48849
+ wcagCriterion: "1.4.4 Resize Text",
48850
+ property: "fontSize",
48851
+ currentValue: mapping.fontSize,
48852
+ requiredValue: "sm or larger"
48853
+ });
48854
+ }
48855
+ if (isLabel && sizeIdx < sizeIndex("xs")) {
48856
+ violations.push({
48857
+ rule: "min-label-font-size",
48858
+ severity: "error",
48859
+ message: `Label role "${role}" uses font-size "${mapping.fontSize}" which is below minimum 'xs' (12px)`,
48860
+ wcagCriterion: "1.4.4 Resize Text",
48861
+ property: "fontSize",
48862
+ currentValue: mapping.fontSize,
48863
+ requiredValue: "xs or larger"
48864
+ });
48865
+ }
48866
+ if (isBody) {
48867
+ const lhIdx = sizeIndex(mapping.lineHeight);
48868
+ if (lhIdx >= sizeIndex("xl")) {
48869
+ violations.push({
48870
+ rule: "body-line-height",
48871
+ severity: "warning",
48872
+ message: `Body role "${role}" line-height "${mapping.lineHeight}" may be below 1.5 (WCAG 1.4.12 recommends >= 1.5 for body text)`,
48873
+ wcagCriterion: "1.4.12 Text Spacing",
48874
+ property: "lineHeight",
48875
+ currentValue: mapping.lineHeight,
48876
+ requiredValue: "Scale key with line-height >= 1.5 (xs, sm, base, lg)"
48877
+ });
48878
+ }
48879
+ }
48880
+ if (isHeading) {
48881
+ const lhIdx = sizeIndex(mapping.lineHeight);
48882
+ if (lhIdx >= sizeIndex("7xl")) {
48883
+ violations.push({
48884
+ rule: "heading-line-height",
48885
+ severity: "warning",
48886
+ message: `Heading role "${role}" line-height "${mapping.lineHeight}" may be below 1.2`,
48887
+ wcagCriterion: "1.4.12 Text Spacing",
48888
+ property: "lineHeight",
48889
+ currentValue: mapping.lineHeight,
48890
+ requiredValue: "Scale key with line-height >= 1.2"
48891
+ });
48892
+ }
48893
+ }
48894
+ if (weight <= 300 && sizeIdx <= sizeIndex("sm")) {
48895
+ violations.push({
48896
+ rule: "weight-contrast-coupling",
48897
+ severity: "warning",
48898
+ message: `Role "${role}" uses thin weight (${mapping.fontWeight}) at small size (${mapping.fontSize}). This may have insufficient readability. Consider using weight >= medium for small text.`,
48899
+ wcagCriterion: "1.4.3 Contrast (Minimum)",
48900
+ property: "fontWeight",
48901
+ currentValue: mapping.fontWeight,
48902
+ requiredValue: "medium or heavier for small text"
48903
+ });
48904
+ }
48905
+ return violations;
48906
+ }
48907
+ function validateTypographyOverride(override, baseMapping) {
48908
+ const merged = {
48909
+ ...baseMapping,
48910
+ fontFamily: override.overrides.fontFamily ? override.overrides.fontFamily : baseMapping.fontFamily,
48911
+ fontSize: override.overrides.fontSize ?? baseMapping.fontSize,
48912
+ fontWeight: override.overrides.fontWeight ?? baseMapping.fontWeight,
48913
+ lineHeight: override.overrides.lineHeight ?? baseMapping.lineHeight,
48914
+ letterSpacing: override.overrides.letterSpacing ?? baseMapping.letterSpacing
48915
+ };
48916
+ return validateTypographyComposite(merged, override.role);
48917
+ }
48918
+
48459
48919
  // ../design-tokens/src/registry.ts
48460
48920
  var TokenRegistry = class {
48461
48921
  tokens = /* @__PURE__ */ new Map();
@@ -48465,6 +48925,7 @@ var TokenRegistry = class {
48465
48925
  changeCallback;
48466
48926
  adapter;
48467
48927
  dirtyNamespaces = /* @__PURE__ */ new Set();
48928
+ typographyOverrides = /* @__PURE__ */ new Map();
48468
48929
  constructor(initialTokens) {
48469
48930
  if (initialTokens) {
48470
48931
  for (const token of initialTokens) {
@@ -49000,6 +49461,49 @@ var TokenRegistry = class {
49000
49461
  parseRuleDependencies(rule) {
49001
49462
  return this.dependencyGraph.parseRuleDependencies(rule);
49002
49463
  }
49464
+ // ===========================================================================
49465
+ // Typography Element Overrides
49466
+ // ===========================================================================
49467
+ /**
49468
+ * Add a typography element override with why-gate enforcement.
49469
+ * Stores an override for a specific HTML element that diverges from its
49470
+ * assigned typography role.
49471
+ *
49472
+ * @throws If why field is empty (why-gate enforcement)
49473
+ * @throws If role references a non-existent typography composite token
49474
+ */
49475
+ addTypographyOverride(override) {
49476
+ const parsed = TypographyElementOverrideSchema.parse(override);
49477
+ const roleToken = this.get(parsed.role);
49478
+ if (!roleToken || roleToken.namespace !== "typography-composite") {
49479
+ throw new Error(
49480
+ `Typography override for "${parsed.element}" references unknown role "${parsed.role}". Role must be an existing typography-composite token.`
49481
+ );
49482
+ }
49483
+ const baseMapping = DEFAULT_TYPOGRAPHY_COMPOSITE_MAPPINGS[parsed.role];
49484
+ if (baseMapping) {
49485
+ const violations = validateTypographyOverride(parsed, baseMapping);
49486
+ const errors = violations.filter((v) => v.severity === "error");
49487
+ if (errors.length > 0) {
49488
+ throw new Error(
49489
+ `Typography override for "${parsed.element}" violates accessibility: ${errors.map((e) => e.message).join("; ")}`
49490
+ );
49491
+ }
49492
+ }
49493
+ this.typographyOverrides.set(parsed.element, parsed);
49494
+ }
49495
+ /**
49496
+ * Get all typography element overrides.
49497
+ */
49498
+ getTypographyOverrides() {
49499
+ return Array.from(this.typographyOverrides.values());
49500
+ }
49501
+ /**
49502
+ * Remove a typography element override.
49503
+ */
49504
+ removeTypographyOverride(element) {
49505
+ return this.typographyOverrides.delete(element);
49506
+ }
49003
49507
  /**
49004
49508
  * Enhanced validation that includes both registry and rule validation
49005
49509
  */
@@ -58168,6 +58672,54 @@ function generateTypographyTokens(config3, typographyScale, fontWeights) {
58168
58672
  never: ["Use for body text", "Use for UI elements"]
58169
58673
  }
58170
58674
  });
58675
+ tokens.push({
58676
+ name: "font-heading",
58677
+ value: `var(--font-sans)`,
58678
+ category: "typography",
58679
+ namespace: "typography",
58680
+ semanticMeaning: "Font family for headings and display text",
58681
+ usageContext: ["headings", "display-text", "titles"],
58682
+ dependsOn: ["font-sans"],
58683
+ description: "Heading font family. Defaults to sans-serif. Override to change all headings to serif or another family.",
58684
+ generatedAt: timestamp,
58685
+ containerQueryAware: false,
58686
+ usagePatterns: {
58687
+ do: ["Override to set all headings to a different typeface"],
58688
+ never: ["Reference directly in components -- use typography role utilities instead"]
58689
+ }
58690
+ });
58691
+ tokens.push({
58692
+ name: "font-body",
58693
+ value: `var(--font-sans)`,
58694
+ category: "typography",
58695
+ namespace: "typography",
58696
+ semanticMeaning: "Font family for body text and UI elements",
58697
+ usageContext: ["body-text", "labels", "descriptions", "ui-elements"],
58698
+ dependsOn: ["font-sans"],
58699
+ description: "Body font family. Defaults to sans-serif. Override to change all body and UI text.",
58700
+ generatedAt: timestamp,
58701
+ containerQueryAware: false,
58702
+ usagePatterns: {
58703
+ do: ["Override to set all body text to a different typeface"],
58704
+ never: ["Reference directly in components -- use typography role utilities instead"]
58705
+ }
58706
+ });
58707
+ tokens.push({
58708
+ name: "font-code",
58709
+ value: `var(--font-mono)`,
58710
+ category: "typography",
58711
+ namespace: "typography",
58712
+ semanticMeaning: "Font family for code, keyboard shortcuts, and technical content",
58713
+ usageContext: ["code-blocks", "inline-code", "kbd", "shortcuts"],
58714
+ dependsOn: ["font-mono"],
58715
+ description: "Code font family. Defaults to monospace. Override for a custom code typeface.",
58716
+ generatedAt: timestamp,
58717
+ containerQueryAware: false,
58718
+ usagePatterns: {
58719
+ do: ["Override to set all code content to a custom monospace font"],
58720
+ never: ["Reference directly in components -- use typography role utilities instead"]
58721
+ }
58722
+ });
58171
58723
  const baseFontSizeRem = baseFontSize / 16;
58172
58724
  tokens.push({
58173
58725
  name: "font-size-base",
@@ -58284,6 +58836,72 @@ function generateTypographyTokens(config3, typographyScale, fontWeights) {
58284
58836
  };
58285
58837
  }
58286
58838
 
58839
+ // ../design-tokens/src/generators/typography-composite.ts
58840
+ function familyRoleToDependency(role) {
58841
+ switch (role) {
58842
+ case "heading":
58843
+ return "font-heading";
58844
+ case "body":
58845
+ return "font-body";
58846
+ case "code":
58847
+ return "font-code";
58848
+ }
58849
+ }
58850
+ function generateTypographyCompositeTokens(_config) {
58851
+ const tokens = [];
58852
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
58853
+ for (const [name2, mapping] of Object.entries(DEFAULT_TYPOGRAPHY_COMPOSITE_MAPPINGS)) {
58854
+ const violations = validateTypographyComposite(mapping, name2);
58855
+ const errors = violations.filter((v) => v.severity === "error");
58856
+ if (errors.length > 0) {
58857
+ throw new Error(
58858
+ `Typography role "${name2}" violates accessibility: ${errors.map((e) => e.message).join("; ")}`
58859
+ );
58860
+ }
58861
+ const familyDep = familyRoleToDependency(mapping.fontFamily);
58862
+ const dependsOn = [
58863
+ familyDep,
58864
+ `font-size-${mapping.fontSize}`,
58865
+ `font-weight-${mapping.fontWeight}`,
58866
+ `line-height-${mapping.lineHeight}`,
58867
+ `letter-spacing-${mapping.letterSpacing}`
58868
+ ];
58869
+ const compositeValue = JSON.stringify({
58870
+ fontFamily: mapping.fontFamily,
58871
+ fontSize: mapping.fontSize,
58872
+ fontWeight: mapping.fontWeight,
58873
+ lineHeight: mapping.lineHeight,
58874
+ letterSpacing: mapping.letterSpacing,
58875
+ ...mapping.responsive ? { responsive: mapping.responsive } : {}
58876
+ });
58877
+ const consumers = TYPOGRAPHY_ROLE_CONSUMERS[name2] ?? [];
58878
+ tokens.push({
58879
+ name: name2,
58880
+ value: compositeValue,
58881
+ category: "typography",
58882
+ namespace: "typography-composite",
58883
+ semanticMeaning: mapping.meaning,
58884
+ usageContext: mapping.contexts,
58885
+ trustLevel: mapping.trustLevel,
58886
+ consequence: mapping.consequence,
58887
+ dependsOn,
58888
+ applicableComponents: consumers,
58889
+ containerQueryAware: true,
58890
+ generateUtilityClass: true,
58891
+ description: `Typography composite: font-${mapping.fontFamily} text-${mapping.fontSize} font-${mapping.fontWeight}. ${mapping.meaning}`,
58892
+ generatedAt: timestamp,
58893
+ usagePatterns: {
58894
+ do: mapping.do,
58895
+ never: mapping.never
58896
+ }
58897
+ });
58898
+ }
58899
+ return {
58900
+ namespace: "typography-composite",
58901
+ tokens
58902
+ };
58903
+ }
58904
+
58287
58905
  // ../design-tokens/src/generators/index.ts
58288
58906
  function buildAllColorScales(customBases) {
58289
58907
  const bases = customBases ?? DEFAULT_SEMANTIC_COLOR_BASES;
@@ -58313,6 +58931,11 @@ function createGeneratorDefs(colorPaletteBases) {
58313
58931
  },
58314
58932
  // Semantic tokens (depend on color)
58315
58933
  { name: "semantic", generate: (config3) => generateSemanticTokens(config3) },
58934
+ // Typography composites (depend on typography)
58935
+ {
58936
+ name: "typography-composite",
58937
+ generate: (config3) => generateTypographyCompositeTokens(config3)
58938
+ },
58316
58939
  // Derived tokens (depend on spacing/foundation)
58317
58940
  {
58318
58941
  name: "radius",
@@ -58833,13 +59456,13 @@ Container and Grid handle ALL layout. You do not write layout code.
58833
59456
 
58834
59457
  | Instead of | Use |
58835
59458
  |---|---|
58836
- | \`<p className="text-sm text-muted-foreground">\` | \`<Muted>\` |
59459
+ | \`<p className="text-sm text-muted-foreground">\` | \`<P size="sm" color="muted">\` |
58837
59460
  | \`<p>\` | \`<P>\` |
58838
59461
  | \`<h1 className="text-4xl font-bold">\` | \`<H1>\` |
58839
59462
  | \`<h2>\` | \`<H2>\` |
58840
59463
  | \`<h3>\` | \`<H3>\` |
58841
59464
  | \`<span className="text-xs">\` | \`<Small>\` |
58842
- | \`<span className="text-lg font-semibold">\` | \`<Large>\` |
59465
+ | \`<span className="text-lg font-semibold">\` | \`<P size="lg" weight="semibold">\` |
58843
59466
 
58844
59467
  ## Color -- Tokens, Not Values
58845
59468
 
@@ -58850,7 +59473,7 @@ Never use hex, HSL, or palette internals.
58850
59473
 
58851
59474
  \`\`\`tsx
58852
59475
  import { Container, Grid } from "@rafters/ui"
58853
- import { H1, Lead } from "@rafters/ui/components/ui/typography"
59476
+ import { H1, P } from "@rafters/ui/components/ui/typography"
58854
59477
  import { Card } from "@rafters/ui/components/ui/card"
58855
59478
  import { Button } from "@rafters/ui/components/ui/button"
58856
59479
 
@@ -58858,7 +59481,7 @@ export default function Page() {
58858
59481
  return (
58859
59482
  <Container>
58860
59483
  <H1>Title</H1>
58861
- <Lead>Description.</Lead>
59484
+ <P size="xl" color="muted">Description.</P>
58862
59485
  <Grid preset="cards">
58863
59486
  <Card>...</Card>
58864
59487
  <Card>...</Card>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rafters",
3
- "version": "0.0.41",
3
+ "version": "0.0.42",
4
4
  "description": "CLI for Rafters design system - scaffold tokens and add components",
5
5
  "license": "MIT",
6
6
  "type": "module",