@multiplatform.one/components 6.4.0 → 6.4.2

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 (39) hide show
  1. package/dist/cjs/icons/index.cjs +2 -1
  2. package/dist/cjs/icons/index.native.js +1 -0
  3. package/dist/cjs/icons/index.native.js.map +1 -1
  4. package/dist/esm/icons/index.mjs +1 -0
  5. package/dist/esm/icons/index.mjs.map +1 -1
  6. package/dist/esm/icons/index.native.js +1 -0
  7. package/dist/esm/icons/index.native.js.map +1 -1
  8. package/dist/jsx/icons/index.mjs +1 -0
  9. package/dist/jsx/icons/index.mjs.map +1 -1
  10. package/dist/jsx/icons/index.native.js +1 -0
  11. package/dist/jsx/icons/index.native.js.map +1 -1
  12. package/package.json +9 -9
  13. package/src/Carousel/index.tsx +1 -1
  14. package/src/DotIndicator/index.tsx +3 -1
  15. package/src/Pagination.tsx +5 -1
  16. package/src/ProgressSteps/index.tsx +6 -3
  17. package/src/Skeleton/Skeleton.stories.tsx +7 -1
  18. package/src/Skeleton/SkeletonCircle.stories.tsx +5 -1
  19. package/src/Skeleton/SkeletonText.stories.tsx +4 -1
  20. package/src/Wheel/index.tsx +1 -1
  21. package/src/charts/Axis.tsx +4 -2
  22. package/src/charts/BarChart.tsx +8 -2
  23. package/src/charts/LineChart.tsx +8 -2
  24. package/src/charts/PieChart.tsx +2 -1
  25. package/src/charts/accessibility.ts +3 -2
  26. package/src/charts/index.ts +1 -0
  27. package/src/charts/math.ts +21 -2
  28. package/src/componentColors.ts +11 -3
  29. package/src/icons/index.ts +4 -0
  30. package/src/layouts/ViewSwitcher.tsx +35 -2
  31. package/src/layouts/page.tsx +15 -10
  32. package/src/views/Calendar.tsx +6 -23
  33. package/src/views/Dashboard.tsx +29 -15
  34. package/src/views/ReportBuilder.tsx +24 -17
  35. package/src/views/TreeView.tsx +5 -3
  36. package/src/views/calendarMath.ts +9 -7
  37. package/src/views/ganttMath.ts +20 -12
  38. package/types/icons/index.d.ts +1 -0
  39. package/types/icons/index.d.ts.map +1 -1
@@ -19,4 +19,5 @@ var icons_exports = {};
19
19
  module.exports = __toCommonJS(icons_exports);
20
20
  __reExport(icons_exports, require("./CheckRegular.cjs"), module.exports);
21
21
  __reExport(icons_exports, require("./MinusRegular.cjs"), module.exports);
22
- __reExport(icons_exports, require("./lucide.cjs"), module.exports);
22
+ __reExport(icons_exports, require("./lucide.cjs"), module.exports);
23
+ __reExport(icons_exports, require("./themed.cjs"), module.exports);
@@ -22,4 +22,5 @@ module.exports = __toCommonJS(icons_exports);
22
22
  __reExport(icons_exports, require("./CheckRegular.native.js"), module.exports);
23
23
  __reExport(icons_exports, require("./MinusRegular.native.js"), module.exports);
24
24
  __reExport(icons_exports, require("./lucide.native.js"), module.exports);
25
+ __reExport(icons_exports, require("./themed.native.js"), module.exports);
25
26
  //# sourceMappingURL=index.native.js.map
@@ -1 +1 @@
1
- {"version":3,"names":["__toCommonJS","mod","__copyProps","__defProp","value","icons_exports","module","exports","__reExport","require"],"sources":["../../../src/icons/index.ts"],"sourcesContent":[null],"mappings":";;;;;;;;;;;;;;;;AAAA,IAAAA,YAAA,GAAAC,GAAA,IAAAC,WAAA,CAAAC,SAAA;EAAAC,KAAA;AAAA,IAAAH,GAAA;AAAA,IAAAI,aAAA;AAAAC,MAAA,CAAAC,OAAA,GAAAP,YAAc,CAAAK,aAAA;AACdG,UAAA,CAAAH,aAAA,EAAcI,OAAA,8BADdH,MAAA,CAAAC,OAAA;AAEAC,UAAA,CAAAH,aAAA,EAAcI,OAAA,2BAFd,GAAAH,MAAA,CAAAC,OAAA","ignoreList":[]}
1
+ {"version":3,"names":["__toCommonJS","mod","__copyProps","__defProp","value","icons_exports","module","exports","__reExport","require"],"sources":["../../../src/icons/index.ts"],"sourcesContent":[null],"mappings":";;;;;;;;;;;;;;;;AAAA,IAAAA,YAAA,GAAAC,GAAA,IAAAC,WAAA,CAAAC,SAAA;EAAAC,KAAA;AAAA,IAAAH,GAAA;AAAA,IAAAI,aAAA;AAAAC,MAAA,CAAAC,OAAA,GAAAP,YAAc,CAAAK,aAAA;AACdG,UAAA,CAAAH,aAAA,EAAcI,OAAA,8BADdH,MAAA,CAAAC,OAAA;AAEAC,UAAA,CAAAH,aAAA,EAAcI,OAAA,2BAFd,GAAAH,MAAA,CAAAC,OAAA;AAMAC,UAAA,CAAAH,aAAA,EAAcI,OAAA,wBANdH,MAAA,CAAAC,OAAA","ignoreList":[]}
@@ -1,4 +1,5 @@
1
1
  export * from "./CheckRegular.mjs";
2
2
  export * from "./MinusRegular.mjs";
3
3
  export * from "./lucide.mjs";
4
+ export * from "./themed.mjs";
4
5
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"names":[],"sources":["../../../src/icons/index.ts"],"sourcesContent":[null],"mappings":"AAAA,cAAc;AACd,cAAc;AACd,cAAc","ignoreList":[]}
1
+ {"version":3,"names":[],"sources":["../../../src/icons/index.ts"],"sourcesContent":[null],"mappings":"AAAA,cAAc;AACd,cAAc;AACd,cAAc;AAId,cAAc","ignoreList":[]}
@@ -1,4 +1,5 @@
1
1
  export * from "./CheckRegular.native.js";
2
2
  export * from "./MinusRegular.native.js";
3
3
  export * from "./lucide.native.js";
4
+ export * from "./themed.native.js";
4
5
  //# sourceMappingURL=index.native.js.map
@@ -1 +1 @@
1
- {"version":3,"names":[],"sources":["../../../src/icons/index.ts"],"sourcesContent":[null],"mappings":"AAAA,cAAc;AACd,cAAc;AACd,cAAc","ignoreList":[]}
1
+ {"version":3,"names":[],"sources":["../../../src/icons/index.ts"],"sourcesContent":[null],"mappings":"AAAA,cAAc;AACd,cAAc;AACd,cAAc;AAId,cAAc","ignoreList":[]}
@@ -1,4 +1,5 @@
1
1
  export * from "./CheckRegular.mjs";
2
2
  export * from "./MinusRegular.mjs";
3
3
  export * from "./lucide.mjs";
4
+ export * from "./themed.mjs";
4
5
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"names":[],"sources":["../../../src/icons/index.ts"],"sourcesContent":[null],"mappings":"AAAA,cAAc;AACd,cAAc;AACd,cAAc","ignoreList":[]}
1
+ {"version":3,"names":[],"sources":["../../../src/icons/index.ts"],"sourcesContent":[null],"mappings":"AAAA,cAAc;AACd,cAAc;AACd,cAAc;AAId,cAAc","ignoreList":[]}
@@ -22,4 +22,5 @@ module.exports = __toCommonJS(icons_exports);
22
22
  __reExport(icons_exports, require("./CheckRegular.native.js"), module.exports);
23
23
  __reExport(icons_exports, require("./MinusRegular.native.js"), module.exports);
24
24
  __reExport(icons_exports, require("./lucide.native.js"), module.exports);
25
+ __reExport(icons_exports, require("./themed.native.js"), module.exports);
25
26
  //# sourceMappingURL=index.native.js.map
@@ -1 +1 @@
1
- {"version":3,"names":["__defProp","Object","defineProperty","__getOwnPropDesc","getOwnPropertyDescriptor"],"sources":["../../../src/icons/index.ts"],"sourcesContent":[null],"mappings":"AAAA;;AACA,IAAAA,SAAA,GAAcC,MAAA,CAAAC,cAAA;AACd,IAAAC,gBAAc,GAAAF,MAAA,CAAAG,wBAAA","ignoreList":[]}
1
+ {"version":3,"names":["__defProp","Object","defineProperty","__getOwnPropDesc","getOwnPropertyDescriptor","__getOwnPropNames","getOwnPropertyNames"],"sources":["../../../src/icons/index.ts"],"sourcesContent":[null],"mappings":"AAAA;;AACA,IAAAA,SAAA,GAAcC,MAAA,CAAAC,cAAA;AACd,IAAAC,gBAAc,GAAAF,MAAA,CAAAG,wBAAA;AAId,IAAAC,iBAAc,GAAAJ,MAAA,CAAAK,mBAAA","ignoreList":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@multiplatform.one/components",
3
- "version": "6.4.0",
3
+ "version": "6.4.2",
4
4
  "description": "multiplatform.one ui components",
5
5
  "keywords": [
6
6
  "components",
@@ -74,11 +74,11 @@
74
74
  "link": "^2.1.2",
75
75
  "shiki": "^4.0.2",
76
76
  "tamagui": "2.0.0-rc.41",
77
- "@multiplatform.one/forms": "6.4.0",
78
- "@multiplatform.one/i18n": "6.4.0",
79
- "@multiplatform.one/router": "6.4.0",
80
- "@multiplatform.one/theme": "6.4.0",
81
- "@multiplatform.one/platform": "6.4.0"
77
+ "@multiplatform.one/forms": "6.4.2",
78
+ "@multiplatform.one/i18n": "6.4.2",
79
+ "@multiplatform.one/router": "6.4.2",
80
+ "@multiplatform.one/theme": "6.4.2",
81
+ "@multiplatform.one/platform": "6.4.2"
82
82
  },
83
83
  "devDependencies": {
84
84
  "@tamagui/build": "2.0.0-rc.41",
@@ -96,9 +96,9 @@
96
96
  "react-native-svg": "15.15.4",
97
97
  "react-native-web": "^0.21.2",
98
98
  "vitest": "^4.1.5",
99
- "@multiplatform.one/storybook": "6.4.0",
100
- "@multiplatform.one/config": "6.4.0",
101
- "@multiplatform.one/test-utils": "6.4.0"
99
+ "@multiplatform.one/test-utils": "6.4.2",
100
+ "@multiplatform.one/config": "6.4.2",
101
+ "@multiplatform.one/storybook": "6.4.2"
102
102
  },
103
103
  "peerDependencies": {
104
104
  "@mdx-js/mdx": "^3.1.1",
@@ -207,7 +207,7 @@ export function Carousel({
207
207
 
208
208
  return (
209
209
  <View>
210
- <CarouselContainer {...knobProps.borderRadius}>
210
+ <CarouselContainer {...knobProps.containerRadius}>
211
211
  <ScrollView
212
212
  ref={trackRef}
213
213
  horizontal
@@ -81,7 +81,9 @@ const Dot = styled(View, {
81
81
  variants: {
82
82
  active: {
83
83
  true: {
84
- backgroundColor: componentColors.indicator.active,
84
+ // LC-05: the active dot is the selected mark, so it carries the accent
85
+ // (it read $color9 neutral — indistinguishable from idle chrome).
86
+ backgroundColor: componentColors.indicator.selected,
85
87
  },
86
88
  },
87
89
  variant: {
@@ -334,11 +334,15 @@ export function Pagination({
334
334
  width: isActive ? activeDotSize : dotSize,
335
335
  height: dotSize,
336
336
  borderRadius: "$10",
337
+ // LC-05 / D-01 rider (a): the current dot speaks the SAME selected
338
+ // language as this component's numbered body (accent) — it used to
339
+ // paint ink while the numbered page button painted accent, so one
340
+ // component taught two meanings for "current".
337
341
  backgroundColor:
338
342
  isCompleted && !isActive
339
343
  ? componentColors.indicator.completed
340
344
  : isActive
341
- ? componentColors.indicator.current
345
+ ? componentColors.indicator.selected
342
346
  : componentColors.indicator.track,
343
347
  transition: motionTransition,
344
348
  cursor: canJump ? "pointer" : undefined,
@@ -1,5 +1,5 @@
1
1
  import { CheckIcon } from "@phosphor-icons/react";
2
- import { useResolvedKnobs } from "@multiplatform.one/theme";
2
+ import { useReadableTextOn, useResolvedKnobs } from "@multiplatform.one/theme";
3
3
  import { useTranslation } from "react-i18next";
4
4
  import type { ReactNode } from "react";
5
5
  import type { GetProps } from "tamagui";
@@ -100,6 +100,9 @@ export function ProgressSteps({
100
100
  }: ProgressStepsProps) {
101
101
  const { t } = useTranslation();
102
102
  const { knobProps } = useResolvedKnobs({ component: "ProgressSteps", compact });
103
+ // LC-05: the current marker rides the accent fill, so its step number takes
104
+ // the luminance-computed paper/ink anchor rather than an assumed `$color1`.
105
+ const onSelected = useReadableTextOn(componentColors.indicator.selected);
103
106
  const markerSize = markerSizeMap[knobProps.sizeToken] ?? 28;
104
107
  const markerIconSize = markerIconSizeMap[knobProps.sizeToken] ?? 14;
105
108
  const markerTextSize = markerTextSizeMap[knobProps.sizeToken] ?? "$2";
@@ -125,7 +128,7 @@ export function ProgressSteps({
125
128
  state === "completed"
126
129
  ? componentColors.indicator.completed
127
130
  : state === "current"
128
- ? componentColors.indicator.current
131
+ ? componentColors.indicator.selected
129
132
  : componentColors.interactive.background;
130
133
  const markerBorder = state === "upcoming" ? componentColors.indicator.track : markerBg;
131
134
  const markerFg = state === "upcoming" ? componentColors.text.muted : "$color1";
@@ -146,7 +149,7 @@ export function ProgressSteps({
146
149
  <SizableText
147
150
  fontSize={markerTextSize as any}
148
151
  fontWeight="600"
149
- color={state === "current" ? "$color1" : markerFg}
152
+ color={state === "current" ? (onSelected ?? "$color1") : markerFg}
150
153
  >
151
154
  {index + 1}
152
155
  </SizableText>
@@ -7,10 +7,16 @@ const meta: Meta<typeof Skeleton> = {
7
7
  component: Skeleton,
8
8
  parameters: {
9
9
  status: { type: "stable" },
10
+ // D-10 SKELETONS-RESOLVE carve-out: this is the component's own anatomy
11
+ // catalog, so its skeletons are the subject rather than a promise about
12
+ // data that never arrives. Explicitly labeled so the skeleton-at-rest
13
+ // probe exempts it; every non-specimen surface must show zero skeletons
14
+ // after settle.
15
+ skeletonSpecimen: true,
10
16
  docs: {
11
17
  description: {
12
18
  component:
13
- "A loading placeholder component that displays a pulsing animation to indicate content is being loaded. Supports multiple variants for different content types.",
19
+ "SKELETON SPECIMEN (D-10). A loading placeholder that pulses to indicate content is being loaded. Supports multiple variants for different content types. The skeletons on this page persist by design because the skeleton itself is the subject — in product surfaces a skeleton may only persist while a real pending source exists.",
14
20
  },
15
21
  },
16
22
  },
@@ -7,9 +7,13 @@ const meta: Meta<typeof SkeletonCircle> = {
7
7
  component: SkeletonCircle,
8
8
  parameters: {
9
9
  status: { type: "beta" },
10
+ // D-10 SKELETONS-RESOLVE: explicitly-labeled skeleton specimen (the
11
+ // skeleton is the subject here, not a promise about pending data).
12
+ skeletonSpecimen: true,
10
13
  docs: {
11
14
  description: {
12
- component: "Circular loading placeholder (avatar-shaped). Default diameter 40px.",
15
+ component:
16
+ "SKELETON SPECIMEN (D-10). Circular loading placeholder (avatar-shaped). Default diameter 40px. Skeletons persist on this page by design; product surfaces must show zero skeletons once their pending source settles.",
13
17
  },
14
18
  },
15
19
  },
@@ -7,10 +7,13 @@ const meta: Meta<typeof SkeletonText> = {
7
7
  component: SkeletonText,
8
8
  parameters: {
9
9
  status: { type: "beta" },
10
+ // D-10 SKELETONS-RESOLVE: explicitly-labeled skeleton specimen (the
11
+ // skeleton is the subject here, not a promise about pending data).
12
+ skeletonSpecimen: true,
10
13
  docs: {
11
14
  description: {
12
15
  component:
13
- "Multi-line text loading placeholder. Last line renders at lastLineWidth (default 70%).",
16
+ "SKELETON SPECIMEN (D-10). Multi-line text loading placeholder. Last line renders at lastLineWidth (default 70%). Skeletons persist on this page by design; product surfaces must show zero skeletons once their pending source settles.",
14
17
  },
15
18
  },
16
19
  },
@@ -666,7 +666,7 @@ export function Wheel<T = string | number>({
666
666
  return (
667
667
  <WheelContainer
668
668
  height={containerHeight}
669
- {...knobProps.borderRadius}
669
+ {...knobProps.containerRadius}
670
670
  data-testid="wheel"
671
671
  data-disabled={disabled ? true : undefined}
672
672
  aria-disabled={disabled || undefined}
@@ -1,5 +1,5 @@
1
1
  import type { ScaleBand, ScaleLinear, ScalePoint } from "d3-scale";
2
- import { getLinearTicks, truncateLabel } from "./math";
2
+ import { formatAxisTick, getLinearTicks, truncateLabel } from "./math";
3
3
  import { G, Line, SvgText } from "./svg";
4
4
  import type { PlotArea } from "./types";
5
5
  import { useChartTheme } from "./useChartTheme";
@@ -122,7 +122,9 @@ export function YAxis({
122
122
  }: YAxisProps) {
123
123
  const { axisColor, gridColor, labelColor, labelFontFamily, labelFontSize } = useChartTheme();
124
124
  const tickNumbers = getLinearTicks(scale, ticks);
125
- const format = tickFormat ?? ((value: number) => value.toLocaleString());
125
+ // Axis ticks ride the charts package's own density register — the declared
126
+ // exemption to the house number channel (see `formatAxisTick`).
127
+ const format = tickFormat ?? ((value: number) => formatAxisTick(value));
126
128
 
127
129
  return (
128
130
  <G>
@@ -4,7 +4,13 @@ import { XAxis, YAxis } from "./Axis";
4
4
  import { ChartSurface } from "./ChartSurface";
5
5
  import { describeChart, describeDatum } from "./accessibility";
6
6
  import { computeCartesianPlot, resolveSeriesFills, thinCategories } from "./composeChart";
7
- import { createBandScale, createLinearScale, getLinearTicks, valueDomain } from "./math";
7
+ import {
8
+ createBandScale,
9
+ createLinearScale,
10
+ formatChartValue,
11
+ getLinearTicks,
12
+ valueDomain,
13
+ } from "./math";
8
14
  import { BarSeries } from "./series";
9
15
  import type { ChartDatum, ChartDatumEvents } from "./types";
10
16
  import { useChartTheme } from "./useChartTheme";
@@ -64,7 +70,7 @@ export function BarChart({
64
70
  const { t } = useTranslation();
65
71
  const { palette, labelFontSize } = useChartTheme();
66
72
  const fills = resolveSeriesFills(data, palette, { color, colors, categorical });
67
- const format = valueFormatter ?? ((value: number) => value.toLocaleString());
73
+ const format = valueFormatter ?? formatChartValue;
68
74
  const ariaLabel =
69
75
  label ?? describeChart({ typeLabel: t("Bar chart"), title, data, formatValue: valueFormatter });
70
76
  const interactive = Boolean(onDatumPress || onDatumHoverIn || onDatumHoverOut);
@@ -4,7 +4,13 @@ import { XAxis, YAxis } from "./Axis";
4
4
  import { ChartSurface } from "./ChartSurface";
5
5
  import { describeChart, describeDatum } from "./accessibility";
6
6
  import { computeCartesianPlot, thinCategories } from "./composeChart";
7
- import { createLinearScale, createPointScale, getLinearTicks, valueDomain } from "./math";
7
+ import {
8
+ createLinearScale,
9
+ createPointScale,
10
+ formatChartValue,
11
+ getLinearTicks,
12
+ valueDomain,
13
+ } from "./math";
8
14
  import { AreaSeries, LineSeries } from "./series";
9
15
  import type { ChartCurve, ChartDatum, ChartDatumEvents } from "./types";
10
16
  import { useChartTheme } from "./useChartTheme";
@@ -71,7 +77,7 @@ export function LineChart({
71
77
  // One line = one series: explicit `color` is a data value and passes
72
78
  // through; the default is the theme identity (LC-27).
73
79
  const stroke = color ?? palette.single;
74
- const format = valueFormatter ?? ((value: number) => value.toLocaleString());
80
+ const format = valueFormatter ?? formatChartValue;
75
81
  const ariaLabel =
76
82
  label ??
77
83
  describeChart({
@@ -5,6 +5,7 @@ import { componentColors } from "../componentColors";
5
5
  import { ChartSurface } from "./ChartSurface";
6
6
  import { describeChart, describeDatum } from "./accessibility";
7
7
  import { resolveSeriesFills } from "./composeChart";
8
+ import { formatChartValue } from "./math";
8
9
  import { PieSeries } from "./series";
9
10
  import type { ChartDatum, ChartDatumEvents } from "./types";
10
11
  import { useChartTheme } from "./useChartTheme";
@@ -65,7 +66,7 @@ export function PieChart({
65
66
  const { knobProps } = useResolvedKnobs();
66
67
  const { palette } = useChartTheme();
67
68
  const fills = resolveSeriesFills(data, palette, { colors, categorical: true });
68
- const format = valueFormatter ?? ((value: number) => value.toLocaleString());
69
+ const format = valueFormatter ?? formatChartValue;
69
70
  const total = data.reduce((sum, datum) => sum + datum.value, 0);
70
71
  const ariaLabel =
71
72
  label ??
@@ -4,7 +4,8 @@
4
4
  * Every chart surface exposes `role="img"` with a generated text summary so
5
5
  * screen-reader users get the shape of the data, not silence. The functions
6
6
  * are pure; components pass already-translated type/title labels in, and the
7
- * numbers format through `toLocaleString` like every other catalog value.
7
+ * numbers format through the house number register (`formatChartValue`) so a
8
+ * summary speaks the same value a sighted reader sees.
8
9
  */
9
10
 
10
11
  import { formatChartValue } from "./math";
@@ -16,7 +17,7 @@ export interface DescribeChartInput {
16
17
  /** Optional already-translated chart title. */
17
18
  title?: string;
18
19
  data: ChartDatum[];
19
- /** Value formatter (defaults to `toLocaleString`). */
20
+ /** Value formatter (defaults to the house number register). */
20
21
  formatValue?: (value: number) => string;
21
22
  /**
22
23
  * Include each datum's share of the total (pie/donut summaries).
@@ -8,6 +8,7 @@ export {
8
8
  createLinearScale,
9
9
  createPointScale,
10
10
  createTimeScale,
11
+ formatAxisTick,
11
12
  formatChartValue,
12
13
  getLinearTicks,
13
14
  getTimeTicks,
@@ -7,6 +7,7 @@
7
7
  * tests exercise them directly.
8
8
  */
9
9
 
10
+ import { formatNumber } from "@multiplatform.one/theme";
10
11
  import { scaleBand, scaleLinear, scalePoint, scaleTime } from "d3-scale";
11
12
  import type { ScaleBand, ScaleLinear, ScalePoint, ScaleTime } from "d3-scale";
12
13
  import {
@@ -132,11 +133,29 @@ export function getTimeTicks(scale: ScaleTime<number, number>, count = 4): Date[
132
133
  return scale.ticks(count);
133
134
  }
134
135
 
135
- /** Locale-aware default value format (matches catalog `toLocaleString` use). */
136
- export function formatChartValue(value: number): string {
136
+ /**
137
+ * Chart AXIS tick register — the one DECLARED exemption to the house number
138
+ * channel (LC-12 render channel / D-04). Tick labels answer to axis density,
139
+ * not to the human-register rules the catalog applies elsewhere: this
140
+ * package owns the right to thin, shorten or re-scale them (k/M suffixes,
141
+ * dropped fraction digits) as ticks crowd, without dragging every number in
142
+ * the catalog along. Keeping the exemption in one named function is what
143
+ * makes it a decision instead of an accident — anything outside this
144
+ * function belongs on the shared channel.
145
+ */
146
+ export function formatAxisTick(value: number): string {
137
147
  return value.toLocaleString();
138
148
  }
139
149
 
150
+ /**
151
+ * Chart data VALUE labels — NOT exempt. A value printed next to a mark is
152
+ * the same number class as the same figure in a table cell or a KPI tile,
153
+ * so it rides the shared register (`formatNumber`).
154
+ */
155
+ export function formatChartValue(value: number): string {
156
+ return formatNumber(value);
157
+ }
158
+
140
159
  // ── Shape generators ──────────────────────────────────────────
141
160
 
142
161
  const curveFactories: Record<ChartCurve, CurveFactory> = {
@@ -40,13 +40,21 @@ export const componentColors = {
40
40
  highlightLine: "$yellow3",
41
41
  },
42
42
 
43
- /** Dot/step indicators */
43
+ /**
44
+ * Dot/step indicators.
45
+ *
46
+ * LC-05 ONE-EMPHASIS: the selected/current mark IS the active accent, so
47
+ * every strip in the catalog (DotIndicator, Pagination dots, ProgressSteps)
48
+ * reads one selected-state language. This is deliberately ONE key — the
49
+ * former `active` ($color9) / `current` ($color12) pair was two dialects for
50
+ * one concept and let the catalog drift back into ink (LC-16). Completion
51
+ * keeps its own semantic ramp; idle/track/hover stay neutral chrome.
52
+ */
44
53
  indicator: {
45
- active: "$color9",
54
+ selected: "$accentBackground",
46
55
  inactive: "$color5",
47
56
  hover: "$color6",
48
57
  completed: "$green9",
49
- current: "$color12",
50
58
  track: "$color8",
51
59
  },
52
60
 
@@ -1,3 +1,7 @@
1
1
  export * from "./CheckRegular";
2
2
  export * from "./MinusRegular";
3
3
  export * from "./lucide";
4
+ // Token-resolving icon wrapper (LC-53 GLYPH-SLOT): icon-only controls slot
5
+ // `themed(Icon)` elements and color them with bare theme keys ("red9"),
6
+ // instead of baseline-shifting Text wrappers around raw icons.
7
+ export * from "./themed";
@@ -4,6 +4,8 @@ import {
4
4
  ensureKeyboardModalityTracking,
5
5
  keyboardFocusRingProps,
6
6
  pressTargetHitSlop,
7
+ useAccentTintedSurface,
8
+ useReadableTextOn,
7
9
  useResolvedKnobs,
8
10
  wasKeyboardFocus,
9
11
  } from "@multiplatform.one/theme";
@@ -46,6 +48,17 @@ export function ViewSwitcher({ views, currentView, onViewChange }: ViewSwitcherP
46
48
  // segment); when focus leaves, the anchor returns to the selection.
47
49
  const [focusValue, setFocusValue] = useState<string | null>(null);
48
50
  if (isWeb) ensureKeyboardModalityTracking();
51
+ // LC-05 / D-01 rider (b): the selected segment carried a neutral `$color3`
52
+ // -tier fill, so a segmented control marked its choice in the same grey it
53
+ // marks hover with. Selection is the accent language; segmented-control
54
+ // STRENGTH is pinned at tinted (not the solid an accent CTA takes), so the
55
+ // chip resolves the accent-ramp analogue of that neutral surface tier and
56
+ // its label/icon take the luminance-computed anchor for the actual tint.
57
+ const selectedTint = useAccentTintedSurface();
58
+ const onSelectedTint = useReadableTextOn(selectedTint);
59
+ const selectedChip = selectedTint
60
+ ? { backgroundColor: selectedTint, ...(onSelectedTint ? { color: onSelectedTint } : undefined) }
61
+ : undefined;
49
62
  const sz = knobProps.sizeToken;
50
63
  // Segment inset ~$3 at medium; size knob steps it (small→$2, large→$4).
51
64
  // Vertical pad is floored so the segment hits DG-LAY-04 (≥44) even at small.
@@ -103,6 +116,7 @@ export function ViewSwitcher({ views, currentView, onViewChange }: ViewSwitcherP
103
116
  return activeIdx >= 0 ? activeIdx : 0;
104
117
  })();
105
118
  const isTabStop = viewIndex === anchorIndex;
119
+ const isSelected = view.type === currentView;
106
120
  return (
107
121
  // no size prop: ToggleGroup.Item's size makes square icon-buttons
108
122
  // (fixed width) that clip text labels -- let content + pad size the item.
@@ -119,6 +133,12 @@ export function ViewSwitcher({ views, currentView, onViewChange }: ViewSwitcherP
119
133
  flexGrow={0}
120
134
  alignItems="center"
121
135
  justifyContent="center"
136
+ // The selected fill is re-applied through hover/press so the
137
+ // neutral state layer never paints over the accent chip (the
138
+ // LC-05 rule the checked Switch track already follows).
139
+ {...(isSelected && selectedChip
140
+ ? { ...selectedChip, hoverStyle: selectedChip, pressStyle: selectedChip }
141
+ : undefined)}
122
142
  {...(isWeb
123
143
  ? {
124
144
  onFocus: () => {
@@ -133,9 +153,22 @@ export function ViewSwitcher({ views, currentView, onViewChange }: ViewSwitcherP
133
153
  : undefined)}
134
154
  {...(kbFocusValue === view.type ? keyboardFocusRingProps : undefined)}
135
155
  >
136
- <XStack alignItems="center" gap={rowGap} flexShrink={0}>
156
+ <XStack
157
+ alignItems="center"
158
+ gap={rowGap}
159
+ flexShrink={0}
160
+ // Icons paint `currentColor`, so the row carries the resolved
161
+ // on-tint anchor for the glyph and the label takes it explicitly
162
+ // (tamagui Text resolves `$color` from the theme, not by CSS
163
+ // inheritance).
164
+ {...(isSelected && onSelectedTint ? { color: onSelectedTint } : undefined)}
165
+ >
137
166
  {Icon && <Icon size={iconPx} />}
138
- <Text fontSize={labelSize} numberOfLines={1}>
167
+ <Text
168
+ fontSize={labelSize}
169
+ numberOfLines={1}
170
+ {...(isSelected && onSelectedTint ? { color: onSelectedTint } : undefined)}
171
+ >
139
172
  {view.label}
140
173
  </Text>
141
174
  </XStack>
@@ -19,7 +19,9 @@ import {
19
19
  hairline,
20
20
  layoutBreakpoints,
21
21
  readingWidthStyle,
22
+ resolvePageTitleScale,
22
23
  useResolvedKnobs,
24
+ type PageTitleScale,
23
25
  } from "@multiplatform.one/theme";
24
26
  import type { ReactNode } from "react";
25
27
  import type { ScrollViewProps as TamaguiScrollViewProps, XStackProps, YStackProps } from "tamagui";
@@ -156,13 +158,14 @@ export interface PageHeaderProps extends YStackProps {
156
158
  */
157
159
  headingLevel?: PageHeadingLevel;
158
160
  /**
159
- * Visual scale of the built-in title (D-11 hero-H1 dial). "display" keeps
160
- * the heading scale's own size (H1 = 64px marketing/hero surfaces);
161
- * "moderate" pins the title to the $8 heading step (32px) the page-title
162
- * class product screens should use. Semantics (headingLevel) are unchanged.
163
- * Default: "display" (the catalog default is a separate owner dial).
161
+ * Per-instance eject for the page-title step (D-11 hero-H1 dial).
162
+ * Omitted — the default the title follows the `pageTitleScale` knob, which
163
+ * ships `moderate` ($8 = 32px): a page title is wayfinding, not content.
164
+ * Pass `"display"` ($10 = 64px) for a single marketing hero, or reach for
165
+ * the `hero` preset / a `pageTitleScale` override to re-scale a whole
166
+ * surface. Semantics (headingLevel) are unchanged either way.
164
167
  */
165
- titleScale?: "display" | "moderate";
168
+ titleScale?: PageTitleScale;
166
169
  }
167
170
 
168
171
  /**
@@ -176,7 +179,7 @@ export function PageHeader({
176
179
  actions,
177
180
  divider = true,
178
181
  headingLevel = 1,
179
- titleScale = "display",
182
+ titleScale,
180
183
  children,
181
184
  ...rest
182
185
  }: PageHeaderProps) {
@@ -196,9 +199,11 @@ export function PageHeader({
196
199
  // heading scale).
197
200
  const headingFamily = isWeb ? null : { fontFamily: knobProps.heading.fontFamily };
198
201
  const TitleTag = PAGE_HEADING_TAGS[headingLevel];
199
- // "moderate" pins the visual step to $8 (32px on the heading scale) while
200
- // the semantic tag still follows headingLevel wayfinding, not display.
201
- const titleSize = titleScale === "moderate" ? ({ size: "$8" } as const) : null;
202
+ // D-11: the page-title step comes from the knob (`moderate` by default), so
203
+ // one dial re-scales every composed screen; `titleScale` is the explicit
204
+ // per-instance eject and resolves through the same table. The semantic tag
205
+ // still follows headingLevel — the step is emphasis, not structure.
206
+ const titleSize = titleScale ? resolvePageTitleScale(titleScale) : knobProps.pageTitle;
202
207
  const titleNode =
203
208
  heading ??
204
209
  (title ? (
@@ -18,6 +18,10 @@ import {
18
18
  type CalendarRange,
19
19
  } from "./calendarMath";
20
20
  import {
21
+ formatDayLong,
22
+ formatTimeOfDay,
23
+ getMonthNames,
24
+ getWeekdayNames,
21
25
  hairline,
22
26
  hairlineWidth,
23
27
  pressTargetHitSlop,
@@ -389,34 +393,13 @@ const DAY_DISC_PX = 22;
389
393
  /** Month-view chip visual floor (hit slop fills the 44px target, DG-LAY-04). */
390
394
  const MONTH_CHIP_MIN_PX = 24;
391
395
 
392
- function getWeekdayNames(): string[] {
393
- const formatter = new Intl.DateTimeFormat(undefined, { weekday: "short" });
394
- return Array.from({ length: 7 }, (_, i) => formatter.format(new Date(2004, 0, 4 + i)));
395
- }
396
-
397
- function getMonthNames(): string[] {
398
- const formatter = new Intl.DateTimeFormat(undefined, { month: "long" });
399
- return Array.from({ length: 12 }, (_, i) => formatter.format(new Date(2000, i, 1)));
400
- }
401
-
402
396
  function getHours(): number[] {
403
397
  return Array.from({ length: 24 }, (_, i) => i);
404
398
  }
405
399
 
400
+ /** Hour ticks speak the house time-of-day register without minutes ("2 PM"). */
406
401
  function formatHour(hour: number): string {
407
- const date = new Date(2000, 0, 1, hour, 0, 0);
408
- return new Intl.DateTimeFormat(undefined, {
409
- hour: "numeric",
410
- hour12: true,
411
- }).format(date);
412
- }
413
-
414
- function formatDayLong(date: Date): string {
415
- return new Intl.DateTimeFormat(undefined, {
416
- weekday: "long",
417
- month: "long",
418
- day: "numeric",
419
- }).format(date);
402
+ return formatTimeOfDay(new Date(2000, 0, 1, hour, 0, 0), { minutes: false });
420
403
  }
421
404
 
422
405
  const weekdayNames = getWeekdayNames();
@@ -3,8 +3,13 @@ import { LineChart } from "../charts/LineChart";
3
3
  import { PieChart } from "../charts/PieChart";
4
4
  import { componentColors, sectionHeading } from "../componentColors";
5
5
  import { AsyncBoundary } from "../layouts/AsyncBoundary";
6
- import { useResolvedKnobs } from "@multiplatform.one/theme";
7
- import React, { useMemo, type ReactNode } from "react";
6
+ import {
7
+ formatCurrency,
8
+ formatNumber,
9
+ formatPercent,
10
+ useResolvedKnobs,
11
+ } from "@multiplatform.one/theme";
12
+ import React, { useCallback, useMemo, type ReactNode } from "react";
8
13
  import { useTranslation } from "react-i18next";
9
14
  import {
10
15
  Card,
@@ -168,18 +173,27 @@ export function resolveTrendColor(
168
173
  */
169
174
  export function KPICard({ config, ...stackProps }: KPICardProps) {
170
175
  const { knobProps } = useResolvedKnobs();
171
- const formattedValue = useMemo(() => {
172
- if (typeof config.value === "string") return config.value;
173
-
174
- const num = config.value;
175
- if (config.format === "currency") {
176
- return `${config.currencySymbol || "$"}${num.toLocaleString()}`;
177
- }
178
- if (config.format === "percent") {
179
- return `${num.toLocaleString()}%`;
180
- }
181
- return num.toLocaleString();
182
- }, [config.value, config.format, config.currencySymbol]);
176
+ // One register for the metric, wherever the card shows it (LC-12 render
177
+ // channel): the headline value and the "vs previous" comparison are the
178
+ // same data class and may not speak in two voices. Summary tiles take the
179
+ // money register's `auto` variant — cents only when the value has them.
180
+ const formatMetric = useCallback(
181
+ (num: number) => {
182
+ if (config.format === "currency") {
183
+ return formatCurrency(num, {
184
+ symbol: config.currencySymbol || "$",
185
+ fractionDigits: "auto",
186
+ });
187
+ }
188
+ if (config.format === "percent") return formatPercent(num);
189
+ return formatNumber(num);
190
+ },
191
+ [config.format, config.currencySymbol],
192
+ );
193
+ const formattedValue = useMemo(
194
+ () => (typeof config.value === "string" ? config.value : formatMetric(config.value)),
195
+ [config.value, formatMetric],
196
+ );
183
197
 
184
198
  const trendColor = useMemo(
185
199
  () => resolveTrendColor(config.trend, config.goodDirection),
@@ -240,7 +254,7 @@ export function KPICard({ config, ...stackProps }: KPICardProps) {
240
254
  )}
241
255
  {config.previousValue !== undefined && (
242
256
  <Text fontSize="$2" color={componentColors.text.muted}>
243
- vs {config.previousValue.toLocaleString()}
257
+ vs {formatMetric(config.previousValue)}
244
258
  </Text>
245
259
  )}
246
260
  </XStack>
@@ -2,7 +2,12 @@ import { BarChart } from "../charts/BarChart";
2
2
  import { componentColors } from "../componentColors";
3
3
  import { AsyncBoundary, AsyncSkeleton, resolveAsyncStatus } from "../layouts/AsyncBoundary";
4
4
  import { EmptyState } from "../layouts/page";
5
- import { useResolvedKnobs } from "@multiplatform.one/theme";
5
+ import {
6
+ formatCurrency,
7
+ formatNumber,
8
+ formatPercent,
9
+ useResolvedKnobs,
10
+ } from "@multiplatform.one/theme";
6
11
  // House Button (not raw tamagui): carries the DG-LAY-04 / D-02 ≥44px press
7
12
  // floor — the raw tamagui Button rendered these toolbar actions at 28px.
8
13
  import { Button } from "@multiplatform.one/forms";
@@ -114,21 +119,22 @@ function formatCellValue(value: unknown, fieldtype: string): string {
114
119
  switch (fieldtype) {
115
120
  case "Check":
116
121
  return value ? "Yes" : "No";
122
+ // Report cells take the fixed-precision variants of the house number
123
+ // registers (LC-12 render channel): money keeps its two digits so a
124
+ // column stays aligned, floats keep report precision, ints stay whole.
117
125
  case "Currency":
118
- return Number(value).toLocaleString(undefined, {
119
- minimumFractionDigits: 2,
120
- maximumFractionDigits: 2,
121
- });
126
+ // No symbol travels with a report column, so the money register
127
+ // renders the amount alone — still the money register, not a
128
+ // hand-rolled one.
129
+ return formatCurrency(value as number);
122
130
  case "Float":
123
- return Number(value).toLocaleString(undefined, {
124
- maximumFractionDigits: 4,
125
- });
131
+ return formatNumber(value as number, { maximumFractionDigits: 4 });
126
132
  case "Int":
127
- return Number(value).toLocaleString();
133
+ return formatNumber(value as number, { maximumFractionDigits: 0 });
128
134
  case "Percent":
129
- return `${Number(value).toFixed(1)}%`;
135
+ return formatPercent(value as number, { precision: 1 });
130
136
  case "Rating":
131
- return `${(Number(value) * 5).toFixed(1)} / 5`;
137
+ return `${formatNumber(Number(value) * 5, { precision: 1 })} / 5`;
132
138
  default:
133
139
  return String(value);
134
140
  }
@@ -409,14 +415,15 @@ export function ReportBuilder({
409
415
 
410
416
  return Object.entries(groupedData).map(([groupKey, group]) => {
411
417
  const isCollapsed = collapsedGroups.has(groupKey);
418
+ // Group aggregates are the same number class as the cells below them,
419
+ // so they take the same register (LC-12 render channel) instead of
420
+ // dropping into raw digits.
412
421
  const aggDisplay =
413
422
  groupBy.aggregation === "count"
414
- ? `${aggLabel}: ${group.aggregationValue}`
415
- : `${aggLabel}(${aggFieldCol?.label ?? groupBy.aggregationField ?? ""}): ${
416
- Number.isInteger(group.aggregationValue)
417
- ? group.aggregationValue
418
- : group.aggregationValue.toFixed(2)
419
- }`;
423
+ ? `${aggLabel}: ${formatNumber(group.aggregationValue, { maximumFractionDigits: 0 })}`
424
+ : `${aggLabel}(${aggFieldCol?.label ?? groupBy.aggregationField ?? ""}): ${formatNumber(
425
+ group.aggregationValue,
426
+ )}`;
420
427
 
421
428
  return (
422
429
  <YStack key={groupKey}>
@@ -1038,10 +1038,12 @@ export function TreeView<T = unknown>({
1038
1038
  const handleOutset = Math.max(0, (MIN_PRESS_TARGET - estimateSize) / 2);
1039
1039
 
1040
1040
  // Rows span edge-to-edge inside the frame (SP-EDGE / LC-01): the container
1041
- // owns only its border; rows own their inset, so hover/selection fills and
1042
- // hit targets reach both edges.
1041
+ // owns only the corner clip; rows own their inset, so hover/selection fills
1042
+ // and hit targets reach both edges. The tree is SF-TRANSPARENT, so it takes
1043
+ // the radius-only fragment — a border-bearing one would be unlicensed chrome
1044
+ // on a chromeless part (D-03).
1043
1045
  return (
1044
- <YStack {...knobProps.borderRadius} overflow="hidden" {...stackProps}>
1046
+ <YStack {...knobProps.containerRadius} overflow="hidden" {...stackProps}>
1045
1047
  <View
1046
1048
  ref={containerRef as any}
1047
1049
  role="tree"
@@ -10,8 +10,14 @@
10
10
  * values — the component layer tweens between them with `knobProps.transition`
11
11
  * (LC-25 CONTROL-SMOOTH-JUMP) while raw pointer tracking stays 1:1
12
12
  * (A-GESTURE).
13
+ *
14
+ * Every visible/spoken date string here routes through the canonical
15
+ * registers in `@multiplatform.one/theme` (LC-12 render channel) — the
16
+ * calendar speaks the same date voice as tables, feeds and pickers.
13
17
  */
14
18
 
19
+ import { formatDayLong, formatTimeOfDay } from "@multiplatform.one/theme";
20
+
15
21
  export const MINUTES_PER_DAY = 1440;
16
22
 
17
23
  /** Wall-clock minutes since midnight for a date. */
@@ -201,9 +207,9 @@ export function moveFocusSlot(
201
207
 
202
208
  // ── Formatting / accessible names ─────────────────────────────
203
209
 
204
- /** "2:00 PM" (locale-aware, minutes only when meaningful). */
210
+ /** "2:00 PM" the house time-of-day register. */
205
211
  export function formatTimeShort(date: Date): string {
206
- return new Intl.DateTimeFormat(undefined, { hour: "numeric", minute: "2-digit" }).format(date);
212
+ return formatTimeOfDay(date);
207
213
  }
208
214
 
209
215
  /** "2:00 PM – 3:30 PM" visible label used by resize / create sketches. */
@@ -217,11 +223,7 @@ export function formatTimeRangeLabel(start: Date, end: Date): string {
217
223
  * Untimed events fall back to the day name alone.
218
224
  */
219
225
  export function eventAccessibleLabel(event: { title: string; start: Date; end?: Date }): string {
220
- const dayLabel = new Intl.DateTimeFormat(undefined, {
221
- weekday: "long",
222
- month: "long",
223
- day: "numeric",
224
- }).format(event.start);
226
+ const dayLabel = formatDayLong(event.start);
225
227
  if (!event.end || event.end.getTime() === event.start.getTime()) {
226
228
  return `${event.title}, ${dayLabel}`;
227
229
  }
@@ -9,8 +9,13 @@
9
9
  * All day math is LOCAL wall-clock (date-component construction, deltas
10
10
  * rounded) so ranges survive DST boundaries — the Calendar view's
11
11
  * `computeEventMoveRange` convention.
12
+ *
13
+ * Visible and spoken labels route through the canonical date registers in
14
+ * `@multiplatform.one/theme` (LC-12 render channel).
12
15
  */
13
16
 
17
+ import { formatAbsoluteDate, formatDayOfMonth, formatMonthYear } from "@multiplatform.one/theme";
18
+
14
19
  export type GanttZoom = "day" | "week" | "month";
15
20
 
16
21
  export const MS_PER_DAY = 86_400_000;
@@ -241,9 +246,12 @@ export interface GanttTick {
241
246
  label?: string;
242
247
  }
243
248
 
244
- const dayLabelFormatter = new Intl.DateTimeFormat(undefined, { day: "numeric" });
245
- const monthDayFormatter = new Intl.DateTimeFormat(undefined, { month: "short", day: "numeric" });
246
- const monthYearFormatter = new Intl.DateTimeFormat(undefined, { month: "short", year: "numeric" });
249
+ // Timeline labels ride the canonical date registers (LC-12 render channel).
250
+ // Ticks take `year: "never"` the header states the year, and a widened
251
+ // "Mar 10, 2025" tick would collide with its neighbour.
252
+ const dayLabel = (date: Date) => formatDayOfMonth(date);
253
+ const monthDayLabel = (date: Date) => formatAbsoluteDate(date, { year: "never" });
254
+ const monthYearLabel = (date: Date) => formatMonthYear(date);
247
255
 
248
256
  /**
249
257
  * Axis ticks for a range at a zoom level.
@@ -271,7 +279,7 @@ export function generateTicks(
271
279
  x,
272
280
  date,
273
281
  major: true,
274
- label: monthEdge ? monthDayFormatter.format(date) : dayLabelFormatter.format(date),
282
+ label: monthEdge ? monthDayLabel(date) : dayLabel(date),
275
283
  });
276
284
  continue;
277
285
  }
@@ -281,7 +289,7 @@ export function generateTicks(
281
289
  x,
282
290
  date,
283
291
  major: weekStart,
284
- label: weekStart ? monthDayFormatter.format(date) : undefined,
292
+ label: weekStart ? monthDayLabel(date) : undefined,
285
293
  });
286
294
  continue;
287
295
  }
@@ -292,7 +300,7 @@ export function generateTicks(
292
300
  x,
293
301
  date,
294
302
  major: monthStart,
295
- label: monthStart ? monthYearFormatter.format(date) : undefined,
303
+ label: monthStart ? monthYearLabel(date) : undefined,
296
304
  });
297
305
  }
298
306
  return ticks;
@@ -300,8 +308,8 @@ export function generateTicks(
300
308
 
301
309
  /** Visible header label for the chart range: "Mar 2026 – Jun 2026". */
302
310
  export function formatRangeLabel(range: GanttRange): string {
303
- const start = monthYearFormatter.format(range.start);
304
- const end = monthYearFormatter.format(addDays(range.end, -1));
311
+ const start = monthYearLabel(range.start);
312
+ const end = monthYearLabel(addDays(range.end, -1));
305
313
  return start === end ? start : `${start} – ${end}`;
306
314
  }
307
315
 
@@ -312,8 +320,8 @@ export function formatRangeLabel(range: GanttRange): string {
312
320
  export function ganttTaskAccessibleLabel(task: { title: string; start: Date; end: Date }): string {
313
321
  const { startEdge, endEdge } = normalizeSpanEdges(task);
314
322
  const lastDay = addDays(endEdge, -1);
315
- const startLabel = monthDayFormatter.format(startEdge);
316
- const endLabel = monthDayFormatter.format(
323
+ const startLabel = monthDayLabel(startEdge);
324
+ const endLabel = monthDayLabel(
317
325
  lastDay.getTime() >= startEdge.getTime() ? lastDay : startEdge,
318
326
  );
319
327
  return startLabel === endLabel
@@ -325,8 +333,8 @@ export function ganttTaskAccessibleLabel(task: { title: string; start: Date; end
325
333
  export function formatSpanLabel(span: GanttSpan): string {
326
334
  const { startEdge, endEdge } = normalizeSpanEdges(span);
327
335
  const lastDay = addDays(endEdge, -1);
328
- const startLabel = monthDayFormatter.format(startEdge);
329
- const endLabel = monthDayFormatter.format(
336
+ const startLabel = monthDayLabel(startEdge);
337
+ const endLabel = monthDayLabel(
330
338
  lastDay.getTime() >= startEdge.getTime() ? lastDay : startEdge,
331
339
  );
332
340
  return startLabel === endLabel ? startLabel : `${startLabel} – ${endLabel}`;
@@ -1,4 +1,5 @@
1
1
  export * from "./CheckRegular";
2
2
  export * from "./MinusRegular";
3
3
  export * from "./lucide";
4
+ export * from "./themed";
4
5
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/icons/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAC;AAC/B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,UAAU,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/icons/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAC;AAC/B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,UAAU,CAAC;AAIzB,cAAc,UAAU,CAAC"}