@nxds/core 0.11.0 → 0.12.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.
@@ -2403,8 +2403,10 @@ function rampFromSeed(seedHex, options = {}) {
2403
2403
  }
2404
2404
 
2405
2405
  // src/lib/contrast.ts
2406
+ var CONTRAST_MIN = 0;
2407
+ var CONTRAST_MAX = 100;
2406
2408
  function normalizeContrast(value, fallback = 50) {
2407
- return typeof value === "number" && Number.isFinite(value) ? Math.round(Math.max(0, Math.min(100, value))) : fallback;
2409
+ return typeof value === "number" && Number.isFinite(value) ? Math.round(Math.max(CONTRAST_MIN, Math.min(CONTRAST_MAX, value))) : fallback;
2408
2410
  }
2409
2411
  function contrastTarget(tier, contrast) {
2410
2412
  return TIER_THRESHOLDS[tier] + 15 * contrast / 100;
@@ -2596,6 +2598,7 @@ var DEFAULT_NXDS_APPEARANCE = {
2596
2598
  stroke: "normal",
2597
2599
  prefs: {
2598
2600
  uiFont: '-apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif',
2601
+ headingFont: null,
2599
2602
  codeFont: 'ui-monospace, "SF Mono", Menlo, monospace',
2600
2603
  uiFontSize: 14,
2601
2604
  codeFontSize: 12,
@@ -2604,8 +2607,8 @@ var DEFAULT_NXDS_APPEARANCE = {
2604
2607
  fontSmoothing: false
2605
2608
  }
2606
2609
  };
2607
- var FONT_PX_MIN = 8;
2608
- var FONT_PX_MAX = 32;
2610
+ var FONT_SIZE_PX_MIN = 8;
2611
+ var FONT_SIZE_PX_MAX = 32;
2609
2612
  var APPEARANCE_MODES = /* @__PURE__ */ new Set([
2610
2613
  "light",
2611
2614
  "dark",
@@ -2631,7 +2634,7 @@ var FONT_FAMILY_UNSAFE = /[;{}<]/;
2631
2634
  var fontFamilyOr = (value, fallback) => typeof value === "string" && value.length > 0 && !FONT_FAMILY_UNSAFE.test(value) ? value : fallback;
2632
2635
  var boolOr = (value, fallback) => typeof value === "boolean" ? value : fallback;
2633
2636
  var enumOr = (value, values, fallback) => typeof value === "string" && values.has(value) ? value : fallback;
2634
- var clampFontSize = (value, fallback) => typeof value === "number" && Number.isFinite(value) && value > 0 ? Math.min(FONT_PX_MAX, Math.max(FONT_PX_MIN, value)) : fallback;
2637
+ var clampFontSize = (value, fallback) => typeof value === "number" && Number.isFinite(value) && value > 0 ? Math.min(FONT_SIZE_PX_MAX, Math.max(FONT_SIZE_PX_MIN, value)) : fallback;
2635
2638
  function formatPx(value) {
2636
2639
  return `${Number(value.toFixed(4))}px`;
2637
2640
  }
@@ -2682,6 +2685,7 @@ function sanitizeNxdsAppearancePrefs(raw) {
2682
2685
  const d = DEFAULT_NXDS_APPEARANCE.prefs;
2683
2686
  return {
2684
2687
  uiFont: fontFamilyOr(o.uiFont, d.uiFont),
2688
+ headingFont: fontFamilyOr(o.headingFont, d.headingFont),
2685
2689
  codeFont: fontFamilyOr(o.codeFont, d.codeFont),
2686
2690
  uiFontSize: clampFontSize(o.uiFontSize, d.uiFontSize),
2687
2691
  codeFontSize: clampFontSize(o.codeFontSize, d.codeFontSize),
@@ -2728,6 +2732,7 @@ function appearancePrefsToCss(prefs) {
2728
2732
  const blocks = [
2729
2733
  inRuntimeLayer(`:root {
2730
2734
  --nx-typography-family-font-sans: ${prefs.uiFont};
2735
+ --nx-typography-family-font-heading: ${prefs.headingFont ?? "var(--nx-typography-family-font-sans)"};
2731
2736
  --nx-typography-family-font-mono: ${prefs.codeFont};
2732
2737
  ${typographyScaleVariables(uiPx)}
2733
2738
  }`),
@@ -4828,6 +4833,127 @@ function findBrandColorPreset(color) {
4828
4833
  return BRAND_COLOR_PRESETS.find((preset) => preset.color === hex);
4829
4834
  }
4830
4835
 
4836
+ // src/lib/font-options.ts
4837
+ var SANS_FALLBACK = '-apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif';
4838
+ var SERIF_FALLBACK = "Georgia, serif";
4839
+ var MONO_FALLBACK = 'ui-monospace, "SF Mono", Menlo, monospace';
4840
+ var FONT_OPTIONS = [
4841
+ {
4842
+ value: "geist",
4843
+ label: "Geist",
4844
+ category: "sans",
4845
+ family: `"Geist Variable", ${SANS_FALLBACK}`,
4846
+ package: "@fontsource-variable/geist"
4847
+ },
4848
+ {
4849
+ value: "inter",
4850
+ label: "Inter",
4851
+ category: "sans",
4852
+ family: `"Inter Variable", ${SANS_FALLBACK}`,
4853
+ package: "@fontsource-variable/inter"
4854
+ },
4855
+ {
4856
+ value: "figtree",
4857
+ label: "Figtree",
4858
+ category: "sans",
4859
+ family: `"Figtree Variable", ${SANS_FALLBACK}`,
4860
+ package: "@fontsource-variable/figtree"
4861
+ },
4862
+ {
4863
+ value: "dm-sans",
4864
+ label: "DM Sans",
4865
+ category: "sans",
4866
+ family: `"DM Sans Variable", ${SANS_FALLBACK}`,
4867
+ package: "@fontsource-variable/dm-sans"
4868
+ },
4869
+ {
4870
+ value: "manrope",
4871
+ label: "Manrope",
4872
+ category: "sans",
4873
+ family: `"Manrope Variable", ${SANS_FALLBACK}`,
4874
+ package: "@fontsource-variable/manrope"
4875
+ },
4876
+ {
4877
+ value: "ibm-plex-sans",
4878
+ label: "IBM Plex Sans",
4879
+ category: "sans",
4880
+ family: `"IBM Plex Sans Variable", ${SANS_FALLBACK}`,
4881
+ package: "@fontsource-variable/ibm-plex-sans"
4882
+ },
4883
+ {
4884
+ value: "lora",
4885
+ label: "Lora",
4886
+ category: "serif",
4887
+ family: `"Lora Variable", ${SERIF_FALLBACK}`,
4888
+ package: "@fontsource-variable/lora"
4889
+ },
4890
+ {
4891
+ value: "playfair-display",
4892
+ label: "Playfair Display",
4893
+ category: "serif",
4894
+ family: `"Playfair Display Variable", ${SERIF_FALLBACK}`,
4895
+ package: "@fontsource-variable/playfair-display"
4896
+ },
4897
+ {
4898
+ value: "geist-mono",
4899
+ label: "Geist Mono",
4900
+ category: "mono",
4901
+ family: `"Geist Mono Variable", ${MONO_FALLBACK}`,
4902
+ package: "@fontsource-variable/geist-mono"
4903
+ },
4904
+ {
4905
+ value: "jetbrains-mono",
4906
+ label: "JetBrains Mono",
4907
+ category: "mono",
4908
+ family: `"JetBrains Mono Variable", ${MONO_FALLBACK}`,
4909
+ package: "@fontsource-variable/jetbrains-mono"
4910
+ }
4911
+ ];
4912
+
4913
+ // src/lib/icon-libraries.ts
4914
+ var ICON_LIBRARY_OPTIONS = [
4915
+ { value: "phosphor", label: "Phosphor" },
4916
+ { value: "lucide", label: "Lucide" },
4917
+ { value: "tabler", label: "Tabler Icons" },
4918
+ { value: "hugeicons", label: "HugeIcons" },
4919
+ { value: "remix", label: "Remix Icon" }
4920
+ ];
4921
+ var DEFAULT_ICON_LIBRARY = "phosphor";
4922
+
4923
+ // src/lib/appearance-shuffle.ts
4924
+ function pick(options, random) {
4925
+ const option = options[Math.floor(random() * options.length)];
4926
+ if (option === void 0) throw new Error("No option to pick from.");
4927
+ return option;
4928
+ }
4929
+ function shuffleNxdsAppearance(state, { fonts = FONT_OPTIONS, random = Math.random } = {}) {
4930
+ const textFonts = fonts.filter((font) => font.category !== "mono").map((font) => font.family);
4931
+ if (textFonts.length === 0) {
4932
+ throw new Error(
4933
+ "shuffleNxdsAppearance: `fonts` needs at least one sans or serif font."
4934
+ );
4935
+ }
4936
+ return {
4937
+ ...state,
4938
+ brandColor: pick(BRAND_COLOR_PRESETS, random).color,
4939
+ surfaceTone: pick(BASE_TONE_OPTIONS, random).value,
4940
+ density: pick(DENSITY_OPTIONS, random).value,
4941
+ corners: pick(CORNER_OPTIONS, random).value,
4942
+ elevation: pick(ELEVATION_OPTIONS, random).value,
4943
+ stroke: pick(STROKE_OPTIONS, random).value,
4944
+ prefs: {
4945
+ ...state.prefs,
4946
+ uiFont: pick(textFonts, random),
4947
+ headingFont: pick([null, ...textFonts], random)
4948
+ }
4949
+ };
4950
+ }
4951
+ function shuffleNxdsIconLibrary({
4952
+ random = Math.random
4953
+ } = {}) {
4954
+ return pick(ICON_LIBRARY_OPTIONS, random).value;
4955
+ }
4956
+
4831
4957
  // src/lib/theme-contrast.ts
4832
4958
  var MODES = ["light", "dark"];
4833
4959
  var cssVar = (name) => `--nx-color-${name}`;
@@ -4853,6 +4979,211 @@ function measureThemeContrast(theme) {
4853
4979
  );
4854
4980
  }
4855
4981
 
4982
+ // src/lib/theme-fields.ts
4983
+ var DEFAULT_NXDS_THEME = {
4984
+ appearance: DEFAULT_NXDS_APPEARANCE,
4985
+ iconLibrary: DEFAULT_ICON_LIBRARY
4986
+ };
4987
+ function invalid(flag, value, accepted) {
4988
+ return new Error(`--${flag} "${value}" isn't ${accepted}.`);
4989
+ }
4990
+ function withAppearance(theme, patch) {
4991
+ return { ...theme, appearance: { ...theme.appearance, ...patch } };
4992
+ }
4993
+ function withPrefs(theme, patch) {
4994
+ return withAppearance(theme, {
4995
+ prefs: { ...theme.appearance.prefs, ...patch }
4996
+ });
4997
+ }
4998
+ function oneOf(flag, values, value) {
4999
+ const match = values.find((candidate) => candidate === value);
5000
+ if (match === void 0) {
5001
+ throw invalid(flag, value, `one of ${values.join(", ")}`);
5002
+ }
5003
+ return match;
5004
+ }
5005
+ function enumField(flag, key, description, values) {
5006
+ return {
5007
+ kind: "choice",
5008
+ flag,
5009
+ key,
5010
+ description,
5011
+ values,
5012
+ read: (theme) => theme.appearance[key],
5013
+ write: (theme, value) => withAppearance(theme, {
5014
+ [key]: oneOf(flag, values, value)
5015
+ })
5016
+ };
5017
+ }
5018
+ function integer(flag, value, min, max) {
5019
+ const parsed = Number(value);
5020
+ if (!/^\d+$/.test(value) || parsed < min || parsed > max) {
5021
+ throw invalid(flag, value, `a whole number from ${min} to ${max}`);
5022
+ }
5023
+ return parsed;
5024
+ }
5025
+ function contrastField(flag, key, description) {
5026
+ return {
5027
+ kind: "integer",
5028
+ flag,
5029
+ key,
5030
+ description,
5031
+ min: CONTRAST_MIN,
5032
+ max: CONTRAST_MAX,
5033
+ read: (theme) => String(theme.appearance[key]),
5034
+ write: (theme, value) => withAppearance(theme, {
5035
+ [key]: integer(flag, value, CONTRAST_MIN, CONTRAST_MAX)
5036
+ })
5037
+ };
5038
+ }
5039
+ function fontSizeField(flag, key, description) {
5040
+ return {
5041
+ kind: "integer",
5042
+ flag,
5043
+ key: `prefs.${key}`,
5044
+ description,
5045
+ min: FONT_SIZE_PX_MIN,
5046
+ max: FONT_SIZE_PX_MAX,
5047
+ read: (theme) => String(theme.appearance.prefs[key]),
5048
+ write: (theme, value) => withPrefs(theme, {
5049
+ [key]: integer(flag, value, FONT_SIZE_PX_MIN, FONT_SIZE_PX_MAX)
5050
+ })
5051
+ };
5052
+ }
5053
+ function fontField(flag, key, description, categories, fallback) {
5054
+ const fonts = FONT_OPTIONS.filter(
5055
+ (font) => categories.includes(font.category)
5056
+ );
5057
+ return {
5058
+ kind: "font",
5059
+ flag,
5060
+ key: `prefs.${key}`,
5061
+ description,
5062
+ values: [fallback.value, ...fonts.map((font) => font.value)],
5063
+ categories,
5064
+ fallback,
5065
+ read: (theme) => {
5066
+ const family = theme.appearance.prefs[key];
5067
+ if (family === fallback.family) return fallback.value;
5068
+ return fonts.find((font) => font.family === family)?.value;
5069
+ },
5070
+ write: (theme, value) => {
5071
+ const family = value === fallback.value ? fallback.family : fonts.find((font) => font.value === value)?.family;
5072
+ if (family === void 0) {
5073
+ throw invalid(
5074
+ flag,
5075
+ value,
5076
+ `one of ${[fallback.value, ...fonts.map((font) => font.value)].join(", ")}`
5077
+ );
5078
+ }
5079
+ return withPrefs(theme, { [key]: family });
5080
+ }
5081
+ };
5082
+ }
5083
+ var DEFAULT_PREFS = DEFAULT_NXDS_APPEARANCE.prefs;
5084
+ var TEXT_FONTS = ["sans", "serif"];
5085
+ var NXDS_THEME_FIELDS = [
5086
+ {
5087
+ kind: "color",
5088
+ flag: "brand-color",
5089
+ key: "brandColor",
5090
+ description: 'brand color, as any CSS color, such as "#4f46e5"',
5091
+ read: (theme) => theme.appearance.brandColor,
5092
+ write: (theme, value) => {
5093
+ if (!isColor(value)) throw invalid("brand-color", value, "a CSS color");
5094
+ return withAppearance(theme, { brandColor: value });
5095
+ }
5096
+ },
5097
+ enumField(
5098
+ "surface-tone",
5099
+ "surfaceTone",
5100
+ "neutral tone of backgrounds and borders",
5101
+ BASE_TONE_OPTIONS.map((option) => option.value)
5102
+ ),
5103
+ enumField(
5104
+ "density",
5105
+ "density",
5106
+ "spacing and control size",
5107
+ DENSITY_OPTIONS.map((option) => option.value)
5108
+ ),
5109
+ enumField(
5110
+ "corners",
5111
+ "corners",
5112
+ "corner radius",
5113
+ CORNER_OPTIONS.map((option) => option.value)
5114
+ ),
5115
+ enumField(
5116
+ "elevation",
5117
+ "elevation",
5118
+ "shadow depth",
5119
+ ELEVATION_OPTIONS.map((option) => option.value)
5120
+ ),
5121
+ enumField(
5122
+ "stroke",
5123
+ "stroke",
5124
+ "border width",
5125
+ STROKE_OPTIONS.map((option) => option.value)
5126
+ ),
5127
+ enumField("mode", "mode", "color scheme", ["light", "dark", "system"]),
5128
+ contrastField(
5129
+ "light-contrast",
5130
+ "lightContrast",
5131
+ "light mode contrast, 0 to 100"
5132
+ ),
5133
+ contrastField(
5134
+ "dark-contrast",
5135
+ "darkContrast",
5136
+ "dark mode contrast, 0 to 100"
5137
+ ),
5138
+ fontField("font", "uiFont", "font for UI text", TEXT_FONTS, {
5139
+ value: "system",
5140
+ family: DEFAULT_PREFS.uiFont
5141
+ }),
5142
+ fontField(
5143
+ "heading-font",
5144
+ "headingFont",
5145
+ 'font for headings, or "ui" for the UI font',
5146
+ TEXT_FONTS,
5147
+ { value: "ui", family: null }
5148
+ ),
5149
+ fontField("code-font", "codeFont", "font for code", ["mono"], {
5150
+ value: "system",
5151
+ family: DEFAULT_PREFS.codeFont
5152
+ }),
5153
+ fontSizeField("font-size", "uiFontSize", "UI text size in px"),
5154
+ fontSizeField("code-font-size", "codeFontSize", "code text size in px"),
5155
+ {
5156
+ kind: "choice",
5157
+ flag: "icons",
5158
+ description: "icon library nxds components draw",
5159
+ values: ICON_LIBRARY_OPTIONS.map((option) => option.value),
5160
+ read: (theme) => theme.iconLibrary,
5161
+ write: (theme, value) => ({
5162
+ ...theme,
5163
+ iconLibrary: oneOf(
5164
+ "icons",
5165
+ ICON_LIBRARY_OPTIONS.map((option) => option.value),
5166
+ value
5167
+ )
5168
+ })
5169
+ }
5170
+ ];
5171
+ function nxdsThemeFlags(theme) {
5172
+ return NXDS_THEME_FIELDS.flatMap((field) => {
5173
+ const value = field.read(theme);
5174
+ if (value === void 0 || value === field.read(DEFAULT_NXDS_THEME)) {
5175
+ return [];
5176
+ }
5177
+ return [{ flag: field.flag, value }];
5178
+ });
5179
+ }
5180
+ function applyNxdsThemeFlags(theme, values) {
5181
+ return NXDS_THEME_FIELDS.reduce((current, field) => {
5182
+ const value = values[field.flag];
5183
+ return value === void 0 ? current : field.write(current, value);
5184
+ }, theme);
5185
+ }
5186
+
4856
5187
  // src/lib/token-registry.ts
4857
5188
  var SURFACE_TOKEN_NAMES = [
4858
5189
  "background",
@@ -5002,12 +5333,19 @@ export {
5002
5333
  CORNER_OPTIONS,
5003
5334
  DEFAULT_BRAND_COLOR,
5004
5335
  DEFAULT_COOKIE_KEY,
5336
+ DEFAULT_ICON_LIBRARY,
5005
5337
  DEFAULT_NXDS_APPEARANCE,
5338
+ DEFAULT_NXDS_THEME,
5006
5339
  DEFAULT_STORAGE_KEY,
5007
5340
  DENSITY_OPTIONS,
5008
5341
  ELEVATION_OPTIONS,
5342
+ FONT_OPTIONS,
5343
+ FONT_SIZE_PX_MAX,
5344
+ FONT_SIZE_PX_MIN,
5345
+ ICON_LIBRARY_OPTIONS,
5009
5346
  NXDS_APPEARANCE_COOKIE_MAX_AGE_SECONDS,
5010
5347
  NXDS_APPEARANCE_DATA_ATTRS,
5348
+ NXDS_THEME_FIELDS,
5011
5349
  PALETTE_KEYS,
5012
5350
  RUNTIME_LAYER_ORDER,
5013
5351
  SEMANTIC_TOKEN_REGISTRY,
@@ -5018,6 +5356,7 @@ export {
5018
5356
  apcaLc,
5019
5357
  appearancePrefsToCss,
5020
5358
  applyNxdsAppearance,
5359
+ applyNxdsThemeFlags,
5021
5360
  createDefaultNxdsAppearanceSnapshot,
5022
5361
  createNxdsAppearanceBootstrapScript,
5023
5362
  createNxdsAppearanceSnapshot,
@@ -5030,6 +5369,7 @@ export {
5030
5369
  isColor,
5031
5370
  measureThemeContrast,
5032
5371
  normalizeAppearanceModeIds,
5372
+ nxdsThemeFlags,
5033
5373
  parseNxdsAppearanceStateCookie,
5034
5374
  persistNxdsAppearance,
5035
5375
  prefersDarkColorScheme,
@@ -5041,6 +5381,8 @@ export {
5041
5381
  sanitizeNxdsAppearancePrefs,
5042
5382
  sanitizeNxdsAppearanceSnapshot,
5043
5383
  serializeNxdsAppearanceStateCookie,
5384
+ shuffleNxdsAppearance,
5385
+ shuffleNxdsIconLibrary,
5044
5386
  themeToCss,
5045
5387
  watchColorScheme
5046
5388
  };