@astryxdesign/core 0.5.2-canary.48cb3a4 → 0.5.2-canary.4a48880

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.
@@ -1 +1 @@
1
- {"version":3,"file":"Step.d.ts","sourceRoot":"","sources":["../../src/Stepper/Step.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,OAAO,EAAY,KAAK,SAAS,EAAC,MAAM,OAAO,CAAC;AAoBhD,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AAM5C,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,cAAc,CAAC;AAE7C;;;;;;;GAOG;AACH,MAAM,MAAM,mBAAmB,GAAG,MAAM,GAAG,QAAQ,GAAG,MAAM,CAAC;AAC7D,MAAM,MAAM,WAAW,GAAG,SAAS,GAAG,UAAU,GAAG,UAAU,CAAC;AAE9D,MAAM,WAAW,SAAU,SAAQ,SAAS,CAAC,aAAa,CAAC;IACzD,wCAAwC;IACxC,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC;IAC/B;;;OAGG;IACH,IAAI,EAAE,MAAM,CAAC;IACb;;OAEG;IACH,KAAK,EAAE,MAAM,CAAC;IACd;;OAEG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;OAGG;IACH,QAAQ,CAAC,EAAE,SAAS,CAAC;IAErB;;;;;;;;;;;;;OAaG;IACH,MAAM,CAAC,EAAE,UAAU,CAAC;IACpB;;;OAGG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;;;OAIG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;;OAGG;IACH,UAAU,CAAC,EAAE,SAAS,CAAC;IACvB;;;;;;;;OAQG;IACH,SAAS,CAAC,EAAE,mBAAmB,GAAG,SAAS,CAAC;IAC5C;;;;;;OAMG;IACH,OAAO,CAAC,EAAE,WAAW,CAAC;CACvB;AAoxBD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,IAAI,CAAC,EACnB,IAAI,EACJ,KAAK,EACL,WAAW,EACX,QAAQ,EACR,MAAM,EACN,UAAkB,EAClB,UAAkB,EAClB,UAAU,EACV,SAAS,EAAE,aAAa,EACxB,OAAO,EAAE,WAAW,EACpB,MAAM,EACN,SAAS,EACT,KAAK,EACL,GAAG,EACH,aAAa,EAAE,UAAU,EACzB,GAAG,IAAI,EACR,EAAE,SAAS,+BAytBX;yBA1uBe,IAAI"}
1
+ {"version":3,"file":"Step.d.ts","sourceRoot":"","sources":["../../src/Stepper/Step.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,OAAO,EAAY,KAAK,SAAS,EAAC,MAAM,OAAO,CAAC;AAoBhD,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AAM5C,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,cAAc,CAAC;AAE7C;;;;;;;GAOG;AACH,MAAM,MAAM,mBAAmB,GAAG,MAAM,GAAG,QAAQ,GAAG,MAAM,CAAC;AAC7D,MAAM,MAAM,WAAW,GAAG,SAAS,GAAG,UAAU,GAAG,UAAU,CAAC;AAE9D,MAAM,WAAW,SAAU,SAAQ,SAAS,CAAC,aAAa,CAAC;IACzD,wCAAwC;IACxC,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC;IAC/B;;;OAGG;IACH,IAAI,EAAE,MAAM,CAAC;IACb;;OAEG;IACH,KAAK,EAAE,MAAM,CAAC;IACd;;OAEG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;OAGG;IACH,QAAQ,CAAC,EAAE,SAAS,CAAC;IAErB;;;;;;;;;;;;;OAaG;IACH,MAAM,CAAC,EAAE,UAAU,CAAC;IACpB;;;OAGG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;;;OAIG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;;OAGG;IACH,UAAU,CAAC,EAAE,SAAS,CAAC;IACvB;;;;;;;;OAQG;IACH,SAAS,CAAC,EAAE,mBAAmB,GAAG,SAAS,CAAC;IAC5C;;;;;;OAMG;IACH,OAAO,CAAC,EAAE,WAAW,CAAC;CACvB;AAgtBD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,IAAI,CAAC,EACnB,IAAI,EACJ,KAAK,EACL,WAAW,EACX,QAAQ,EACR,MAAM,EACN,UAAkB,EAClB,UAAkB,EAClB,UAAU,EACV,SAAS,EAAE,aAAa,EACxB,OAAO,EAAE,WAAW,EACpB,MAAM,EACN,SAAS,EACT,KAAK,EACL,GAAG,EACH,aAAa,EAAE,UAAU,EACzB,GAAG,IAAI,EACR,EAAE,SAAS,+BAqtBX;yBAtuBe,IAAI"}
@@ -124,37 +124,6 @@ function CurrentIcon() {
124
124
  // --- Styles ---
125
125
 
126
126
  const BAR_WIDTH = spacingVars['--spacing-1'];
127
-
128
- /**
129
- * How much of itself a connector gives up where it meets the indicator, read
130
- * from the public `--step-connector-gap` declared on the Stepper root.
131
- *
132
- * Clipped, not padded. The gap has to apply to two layers — the track, which is
133
- * the segment's own background, and the accent fill, which is an absolutely
134
- * placed `::before`. Spending it on each separately meant two declarations on
135
- * two boxes: a percentage then resolved against a different containing block
136
- * for each and stopped them ~1.2px apart. `clip-path` is ONE declaration on the
137
- * segment that clips the element and its pseudo-element together, against one
138
- * reference box — so every accepted value behaves the same way on both layers,
139
- * which is what a public input owes its full value domain.
140
- *
141
- * Clipping also cannot change layout: the segment keeps its box, so the node it
142
- * positions cannot move and the Stepper cannot grow. A raw `1rem` padding grew
143
- * a 180px stepper to 240px.
144
- *
145
- * Still clamped, because the value arrives with nothing in between to reject
146
- * it. Both halves earn their place, and not for the reasons padding needed:
147
- *
148
- * - `max(0px, …)` — `inset()` ACCEPTS a negative length. Chromium computes
149
- * `inset(0 0 -4px 0)` as-is rather than dropping it, which is the opposite
150
- * of padding (there CSS clamps a negative to `0` for you). This normalises
151
- * it to `0` so a negative gap means "no gap" rather than a clip rect
152
- * stretched outside the segment's own box.
153
- * - `min(…, --spacing-2)` — the flexible segment's own `min-height`, so an
154
- * oversized gap leaves a short track rather than an unbounded one.
155
- */
156
- const CONNECTOR_GAP = `max(0px, min(var(--step-connector-gap, 0px), ${spacingVars['--spacing-2']}))`;
157
-
158
127
  // Every indicator — check, ring, custom icon, number badge — occupies the same
159
128
  // box, so the `auto` mode swapping a number for a check as a step completes
160
129
  // never nudges the label or the track.
@@ -534,22 +503,6 @@ const styles = {
534
503
  kaIpWk: "x2u8bby",
535
504
  $$css: true
536
505
  },
537
- otSegGapLeadV: {
538
- kz4h6p: "x1yxrbr4",
539
- $$css: true
540
- },
541
- otSegGapRailV: {
542
- kz4h6p: "x1eyou77",
543
- $$css: true
544
- },
545
- otSegGapLeadH: {
546
- kz4h6p: "x17qoqre x153f7d5",
547
- $$css: true
548
- },
549
- otSegGapRailH: {
550
- kz4h6p: "x13hy1yx x7cmjge",
551
- $$css: true
552
- },
553
506
  otSegFlexV: {
554
507
  kUk6DE: "x98rzlu",
555
508
  kAzted: "x175cymr",
@@ -971,10 +924,10 @@ export function Step({
971
924
  ...stylex.props(styles.otIndicatorColV, styles.otRailBridgeV(densitySpace)),
972
925
  children: [/*#__PURE__*/_jsx("div", {
973
926
  "aria-hidden": "true",
974
- ...mergeProps(themeProps('step-connector'), stylex.props(styles.otSegBaseV, styles.otSegLeadV(densitySpace), styles.connectorTrack, hasIndicator && styles.otSegGapLeadV, beforeSegStyle, beforeTiming, styles.otSegHiddenIfFirst))
927
+ ...mergeProps(themeProps('step-connector'), stylex.props(styles.otSegBaseV, styles.otSegLeadV(densitySpace), styles.connectorTrack, beforeSegStyle, beforeTiming, styles.otSegHiddenIfFirst))
975
928
  }), indicatorNode, /*#__PURE__*/_jsx("div", {
976
929
  "aria-hidden": "true",
977
- ...mergeProps(themeProps('step-connector'), stylex.props(styles.otSegBaseV, styles.otSegFlexV, styles.connectorTrack, hasIndicator && styles.otSegGapRailV, afterSegStyle, railTiming, styles.otSegHiddenIfLast))
930
+ ...mergeProps(themeProps('step-connector'), stylex.props(styles.otSegBaseV, styles.otSegFlexV, styles.connectorTrack, afterSegStyle, railTiming, styles.otSegHiddenIfLast))
978
931
  })]
979
932
  }), /*#__PURE__*/_jsxs("div", {
980
933
  ...{
@@ -1010,10 +963,10 @@ export function Step({
1010
963
  },
1011
964
  children: [/*#__PURE__*/_jsx("div", {
1012
965
  "aria-hidden": "true",
1013
- ...mergeProps(themeProps('step-connector'), stylex.props(styles.otSegH, styles.connectorTrack, hasIndicator && styles.otSegGapLeadH, beforeSegStyle, beforeTiming, styles.otSegHiddenIfFirst))
966
+ ...mergeProps(themeProps('step-connector'), stylex.props(styles.otSegH, styles.connectorTrack, beforeSegStyle, beforeTiming, styles.otSegHiddenIfFirst))
1014
967
  }), indicatorNode, /*#__PURE__*/_jsx("div", {
1015
968
  "aria-hidden": "true",
1016
- ...mergeProps(themeProps('step-connector'), stylex.props(styles.otSegH, styles.connectorTrack, hasIndicator && styles.otSegGapRailH, afterSegStyle, railTiming, styles.otSegHiddenIfLast))
969
+ ...mergeProps(themeProps('step-connector'), stylex.props(styles.otSegH, styles.connectorTrack, afterSegStyle, railTiming, styles.otSegHiddenIfLast))
1017
970
  })]
1018
971
  }), /*#__PURE__*/_jsxs("div", {
1019
972
  ...stylex.props(styles.otLabelWrapH, styles.otMarginTop(densitySpace)),
@@ -1 +1 @@
1
- {"version":3,"file":"Stepper.d.ts","sourceRoot":"","sources":["../../src/Stepper/Stepper.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAAyC,KAAK,SAAS,EAAC,MAAM,OAAO,CAAC;AAK7E,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AAG5C,OAAO,EAEL,KAAK,kBAAkB,EACvB,KAAK,wBAAwB,EAE9B,MAAM,kBAAkB,CAAC;AAE1B,MAAM,WAAW,YAAa,SAAQ,SAAS,CAAC,gBAAgB,CAAC;IAC/D,wCAAwC;IACxC,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC;IAClC;;OAEG;IACH,UAAU,EAAE,MAAM,CAAC;IACnB;;OAEG;IACH,QAAQ,EAAE,SAAS,CAAC;IACpB;;;OAGG;IACH,WAAW,CAAC,EAAE,kBAAkB,CAAC;IACjC;;;OAGG;IACH,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACtC;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;OAGG;IACH,OAAO,CAAC,EAAE,SAAS,GAAG,UAAU,GAAG,UAAU,CAAC;IAC9C;;;;;;;OAOG;IACH,iBAAiB,CAAC,EAAE,wBAAwB,CAAC;CAC9C;AAwCD;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,OAAO,CAAC,EACtB,UAAU,EACV,QAAQ,EACR,WAA0B,EAC1B,WAAW,EACX,KAAK,EAAE,cAAc,EACrB,OAAoB,EACpB,iBAA+B,EAC/B,MAAM,EACN,SAAS,EACT,KAAK,EACL,GAAG,EACH,GAAG,IAAI,EACR,EAAE,YAAY,+BA0Gd;yBAvHe,OAAO"}
1
+ {"version":3,"file":"Stepper.d.ts","sourceRoot":"","sources":["../../src/Stepper/Stepper.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAAyC,KAAK,SAAS,EAAC,MAAM,OAAO,CAAC;AAK7E,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AAG5C,OAAO,EAEL,KAAK,kBAAkB,EACvB,KAAK,wBAAwB,EAE9B,MAAM,kBAAkB,CAAC;AAE1B,MAAM,WAAW,YAAa,SAAQ,SAAS,CAAC,gBAAgB,CAAC;IAC/D,wCAAwC;IACxC,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC;IAClC;;OAEG;IACH,UAAU,EAAE,MAAM,CAAC;IACnB;;OAEG;IACH,QAAQ,EAAE,SAAS,CAAC;IACpB;;;OAGG;IACH,WAAW,CAAC,EAAE,kBAAkB,CAAC;IACjC;;;OAGG;IACH,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACtC;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;OAGG;IACH,OAAO,CAAC,EAAE,SAAS,GAAG,UAAU,GAAG,UAAU,CAAC;IAC9C;;;;;;;OAOG;IACH,iBAAiB,CAAC,EAAE,wBAAwB,CAAC;CAC9C;AAgCD;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,OAAO,CAAC,EACtB,UAAU,EACV,QAAQ,EACR,WAA0B,EAC1B,WAAW,EACX,KAAK,EAAE,cAAc,EACrB,OAAoB,EACpB,iBAA+B,EAC/B,MAAM,EACN,SAAS,EACT,KAAK,EACL,GAAG,EACH,GAAG,IAAI,EACR,EAAE,YAAY,+BA0Gd;yBAvHe,OAAO"}
@@ -36,7 +36,6 @@ const styles = {
36
36
  kH6xsr: "x3ct3a4",
37
37
  kogj98: "x1ghz6dp",
38
38
  kmVPX3: "x1717udv",
39
- "--step-connector-gap": "x152dxt4",
40
39
  $$css: true
41
40
  },
42
41
  horizontal: {
@@ -10,8 +10,8 @@ export interface TimestampProps extends BaseProps<HTMLTimeElement> {
10
10
  /**
11
11
  * Display format.
12
12
  * - `'relative'`: "2 hours ago", "yesterday", "now"
13
- * - `'relative_short'`: "2h ago", "1d ago", "now" in English — the
14
- * locale's native narrow relative-time pattern, for compact,
13
+ * - `'relative_short'`: "2h ago", "1d ago", "now" — the same tiers as
14
+ * `'relative'` with abbreviated units (s/m/h/d/mo/y), for compact,
15
15
  * space-constrained surfaces
16
16
  * - `'auto'`: Relative for recent times, `date_time` for older
17
17
  * - `'date'`: "Mar 21, 2025"
@@ -1 +1 @@
1
- {"version":3,"file":"Timestamp.d.ts","sourceRoot":"","sources":["../../src/Timestamp/Timestamp.tsx"],"names":[],"mappings":"AAsBA,OAAO,KAAK,EAAC,QAAQ,EAAE,QAAQ,EAAE,SAAS,EAAE,UAAU,EAAC,MAAM,gBAAgB,CAAC;AAK9E,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AAK5C,OAAO,KAAK,EACV,qBAAqB,EAEtB,MAAM,kBAAkB,CAAC;AAY1B,MAAM,MAAM,eAAe,GACvB,UAAU,GACV,gBAAgB,GAChB,MAAM,GACN,MAAM,GACN,WAAW,GACX,cAAc,GACd,WAAW,GACX,MAAM,GACN,aAAa,GACb,kBAAkB,GAClB,aAAa,GACb,cAAc,CAAC;AAEnB,MAAM,WAAW,cAAe,SAAQ,SAAS,CAAC,eAAe,CAAC;IAChE,kDAAkD;IAClD,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC;IACjC,uFAAuF;IACvF,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC;IACvB;;;;;;;;;;;;;;;;;;OAkBG;IACH,MAAM,CAAC,EAAE,eAAe,CAAC;IACzB;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;;;OAKG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA4BG;IACH,cAAc,CAAC,EAAE,aAAa,CAAC,qBAAqB,CAAC,CAAC;IACtD;;;;;;;;OAQG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B;;;OAGG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB;;;OAGG;IACH,IAAI,CAAC,EAAE,QAAQ,CAAC;IAChB;;OAEG;IACH,IAAI,CAAC,EAAE,QAAQ,CAAC;IAChB;;;OAGG;IACH,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB;;OAEG;IACH,MAAM,CAAC,EAAE,UAAU,CAAC;IACpB,sCAAsC;IACtC,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AA+ED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,SAAS,CAAC,EACxB,KAAK,EACL,MAAe,EACf,aAAsC,EACtC,UAAiB,EACjB,cAAc,EACd,eAAuB,EACvB,MAAc,EACd,IAAmB,EACnB,IAAI,EACJ,KAAmB,EACnB,MAAM,EACN,MAAM,EACN,SAAS,EACT,KAAK,EACL,GAAG,EACH,aAAa,EAAE,MAAM,EACrB,GAAG,IAAI,EACR,EAAE,cAAc,sCA+JhB;yBAjLe,SAAS"}
1
+ {"version":3,"file":"Timestamp.d.ts","sourceRoot":"","sources":["../../src/Timestamp/Timestamp.tsx"],"names":[],"mappings":"AAqBA,OAAO,KAAK,EAAC,QAAQ,EAAE,QAAQ,EAAE,SAAS,EAAE,UAAU,EAAC,MAAM,gBAAgB,CAAC;AAK9E,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AAI5C,OAAO,KAAK,EACV,qBAAqB,EAEtB,MAAM,kBAAkB,CAAC;AAY1B,MAAM,MAAM,eAAe,GACvB,UAAU,GACV,gBAAgB,GAChB,MAAM,GACN,MAAM,GACN,WAAW,GACX,cAAc,GACd,WAAW,GACX,MAAM,GACN,aAAa,GACb,kBAAkB,GAClB,aAAa,GACb,cAAc,CAAC;AAEnB,MAAM,WAAW,cAAe,SAAQ,SAAS,CAAC,eAAe,CAAC;IAChE,kDAAkD;IAClD,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC;IACjC,uFAAuF;IACvF,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC;IACvB;;;;;;;;;;;;;;;;;;OAkBG;IACH,MAAM,CAAC,EAAE,eAAe,CAAC;IACzB;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;;;OAKG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA4BG;IACH,cAAc,CAAC,EAAE,aAAa,CAAC,qBAAqB,CAAC,CAAC;IACtD;;;;;;;;OAQG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B;;;OAGG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB;;;OAGG;IACH,IAAI,CAAC,EAAE,QAAQ,CAAC;IAChB;;OAEG;IACH,IAAI,CAAC,EAAE,QAAQ,CAAC;IAChB;;;OAGG;IACH,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB;;OAEG;IACH,MAAM,CAAC,EAAE,UAAU,CAAC;IACpB,sCAAsC;IACtC,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAmOD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,SAAS,CAAC,EACxB,KAAK,EACL,MAAe,EACf,aAAsC,EACtC,UAAiB,EACjB,cAAc,EACd,eAAuB,EACvB,MAAc,EACd,IAAmB,EACnB,IAAI,EACJ,KAAmB,EACnB,MAAM,EACN,MAAM,EACN,SAAS,EACT,KAAK,EACL,GAAG,EACH,aAAa,EAAE,MAAM,EACrB,GAAG,IAAI,EACR,EAAE,cAAc,sCA+JhB;yBAjLe,SAAS"}
@@ -4,12 +4,11 @@
4
4
 
5
5
  /**
6
6
  * @file Timestamp.tsx
7
- * @input Uses React, Text, provider locale, and Timestamp formatters
7
+ * @input Uses React, Intl.DateTimeFormat, Text
8
8
  * @output Exports Timestamp component and related types
9
9
  * @position Core implementation; renders formatted timestamps
10
10
  *
11
11
  * SYNC: When modified, update these files to stay in sync:
12
- * - /packages/core/src/Timestamp/formatRelativeTime.ts
13
12
  * - /packages/core/src/Timestamp/Timestamp.doc.mjs
14
13
  * - /packages/core/src/Timestamp/Timestamp.test.tsx
15
14
  * - /packages/core/src/Timestamp/index.ts
@@ -25,7 +24,6 @@ import { useTranslator } from "../i18n/index.js";
25
24
  import { useLocale } from "../i18n/useLocale.js";
26
25
  import { themeProps } from "../utils/themeProps.js";
27
26
  import { formatInstant } from "./formatInstant.js";
28
- import { formatRelativeTime } from "./formatRelativeTime.js";
29
27
  import { formatTooltipLines } from "./tooltipEntries.js";
30
28
  import { useMergedRefs } from "../hooks/useMergedRefs.js";
31
29
  // Load the overlay lazily so a card-less Timestamp — the default — never
@@ -49,9 +47,23 @@ const LazyTimestampHoverCard = /*#__PURE__*/lazy(async () => import("./Timestamp
49
47
  const MINUTE = 60;
50
48
  const HOUR = 3600;
51
49
  const DAY = 86400;
50
+ const MONTH = 30 * DAY;
51
+ const YEAR = 365 * DAY;
52
52
 
53
53
  /** Default auto threshold: 7 days in seconds */
54
54
  const DEFAULT_AUTO_THRESHOLD = 7 * DAY;
55
+
56
+ /**
57
+ * Tolerance (in seconds) for treating a *future* timestamp as the present.
58
+ * A value only a handful of seconds ahead of our reference clock is almost
59
+ * always clock skew — the displayed `now` lagging the real clock, or the value
60
+ * being produced on a slightly faster clock — not a genuine future event, so
61
+ * it reads as "now" rather than a confusing "in a few seconds". The future
62
+ * side gets a wider window than the past (which only needs to absorb the
63
+ * sub-second render-time lag) because future drift is far more likely to be
64
+ * skew than real.
65
+ */
66
+ const FUTURE_SKEW_TOLERANCE = 30;
55
67
  function parseValue(value) {
56
68
  if (typeof value === 'number') {
57
69
  // Heuristic: if the number is less than 1e12, treat as seconds; otherwise ms.
@@ -60,6 +72,135 @@ function parseValue(value) {
60
72
  }
61
73
  return new Date(value);
62
74
  }
75
+ function getRelativeTimeString(date, now) {
76
+ const diffSeconds = Math.round((now.getTime() - date.getTime()) / 1000);
77
+
78
+ // Treat values at (or a hair before/after) the present as "now". The
79
+ // internal `now` reference is captured at render time, so it can lag the
80
+ // real clock; a value equal to "right now" can land a fraction of a second
81
+ // in the future and round to a small negative delta. Without this clamp,
82
+ // such values fall into the future branch and render "in a few seconds".
83
+ if (Math.abs(diffSeconds) < 10) {
84
+ return 'now';
85
+ }
86
+ if (diffSeconds < 0) {
87
+ // Future dates
88
+ const absDiff = Math.abs(diffSeconds);
89
+ // A value only a few seconds ahead of our clock is almost always skew, not
90
+ // a genuine future event — render it as the present rather than a
91
+ // confusing "in a few seconds". Wider than the past window above on
92
+ // purpose (see FUTURE_SKEW_TOLERANCE).
93
+ if (absDiff <= FUTURE_SKEW_TOLERANCE) {
94
+ return 'now';
95
+ }
96
+ if (absDiff < MINUTE) {
97
+ return 'in a few seconds';
98
+ }
99
+ if (absDiff < HOUR) {
100
+ const mins = Math.floor(absDiff / MINUTE);
101
+ return `in ${mins} ${mins === 1 ? 'minute' : 'minutes'}`;
102
+ }
103
+ if (absDiff < DAY) {
104
+ const hours = Math.floor(absDiff / HOUR);
105
+ return `in ${hours} ${hours === 1 ? 'hour' : 'hours'}`;
106
+ }
107
+ if (absDiff < MONTH) {
108
+ const days = Math.floor(absDiff / DAY);
109
+ return `in ${days} ${days === 1 ? 'day' : 'days'}`;
110
+ }
111
+ if (absDiff < YEAR) {
112
+ const months = Math.floor(absDiff / MONTH);
113
+ return `in ${months} ${months === 1 ? 'month' : 'months'}`;
114
+ }
115
+ const years = Math.floor(absDiff / YEAR);
116
+ return `in ${years} ${years === 1 ? 'year' : 'years'}`;
117
+ }
118
+ if (diffSeconds < MINUTE) {
119
+ return `${diffSeconds} seconds ago`;
120
+ }
121
+ if (diffSeconds < HOUR) {
122
+ const mins = Math.floor(diffSeconds / MINUTE);
123
+ return `${mins} ${mins === 1 ? 'minute' : 'minutes'} ago`;
124
+ }
125
+ if (diffSeconds < DAY) {
126
+ const hours = Math.floor(diffSeconds / HOUR);
127
+ return `${hours} ${hours === 1 ? 'hour' : 'hours'} ago`;
128
+ }
129
+ if (diffSeconds < 2 * DAY) {
130
+ return 'yesterday';
131
+ }
132
+ if (diffSeconds < MONTH) {
133
+ const days = Math.floor(diffSeconds / DAY);
134
+ return `${days} days ago`;
135
+ }
136
+ if (diffSeconds < YEAR) {
137
+ const months = Math.floor(diffSeconds / MONTH);
138
+ return `${months} ${months === 1 ? 'month' : 'months'} ago`;
139
+ }
140
+ const years = Math.floor(diffSeconds / YEAR);
141
+ return `${years} ${years === 1 ? 'year' : 'years'} ago`;
142
+ }
143
+
144
+ /**
145
+ * The compact sibling of `getRelativeTimeString`: the same tier boundaries and
146
+ * present/future-skew handling, rendered with abbreviated units for
147
+ * space-constrained surfaces (chat metadata, dense tables, chips).
148
+ *
149
+ * Units follow the common compact convention (and the Microsoft Style Guide):
150
+ * `s` seconds, `m` minutes, `h` hours, `d` days, `mo` months, `y` years.
151
+ * Months use `mo` — not `m` — because `m` already means minutes; a bare `m`
152
+ * for months would be ambiguous. The value is always numeric (no "yesterday"
153
+ * idiom, which belongs to the long form) so the short form stays predictable
154
+ * and easy to scan. The `ago` / `in` affixes are kept so direction stays
155
+ * unambiguous at a glance.
156
+ */
157
+ function getRelativeTimeShortString(date, now) {
158
+ const diffSeconds = Math.round((now.getTime() - date.getTime()) / 1000);
159
+
160
+ // Present clamp — identical to the long form (see getRelativeTimeString).
161
+ if (Math.abs(diffSeconds) < 10) {
162
+ return 'now';
163
+ }
164
+ if (diffSeconds < 0) {
165
+ // Future dates.
166
+ const absDiff = Math.abs(diffSeconds);
167
+ if (absDiff <= FUTURE_SKEW_TOLERANCE) {
168
+ return 'now';
169
+ }
170
+ if (absDiff < MINUTE) {
171
+ return `in ${absDiff}s`;
172
+ }
173
+ if (absDiff < HOUR) {
174
+ return `in ${Math.floor(absDiff / MINUTE)}m`;
175
+ }
176
+ if (absDiff < DAY) {
177
+ return `in ${Math.floor(absDiff / HOUR)}h`;
178
+ }
179
+ if (absDiff < MONTH) {
180
+ return `in ${Math.floor(absDiff / DAY)}d`;
181
+ }
182
+ if (absDiff < YEAR) {
183
+ return `in ${Math.floor(absDiff / MONTH)}mo`;
184
+ }
185
+ return `in ${Math.floor(absDiff / YEAR)}y`;
186
+ }
187
+ if (diffSeconds < MINUTE) {
188
+ return `${diffSeconds}s ago`;
189
+ }
190
+ if (diffSeconds < HOUR) {
191
+ return `${Math.floor(diffSeconds / MINUTE)}m ago`;
192
+ }
193
+ if (diffSeconds < DAY) {
194
+ return `${Math.floor(diffSeconds / HOUR)}h ago`;
195
+ }
196
+ if (diffSeconds < MONTH) {
197
+ return `${Math.floor(diffSeconds / DAY)}d ago`;
198
+ }
199
+ if (diffSeconds < YEAR) {
200
+ return `${Math.floor(diffSeconds / MONTH)}mo ago`;
201
+ }
202
+ return `${Math.floor(diffSeconds / YEAR)}y ago`;
203
+ }
63
204
 
64
205
  /** Returns the interval (in ms) at which a relative timestamp should update. */
65
206
  function getLiveInterval(diffSeconds) {
@@ -149,7 +290,7 @@ export function Timestamp({
149
290
 
150
291
  // Format the display text. No time zone is passed: the visible text always
151
292
  // reads in the viewer's own zone, and only the tooltip names others.
152
- const displayText = !isValidDate ? '' : effectiveFormat === 'relative' ? formatRelativeTime(date, now, locale, 'long') : effectiveFormat === 'relative_short' ? formatRelativeTime(date, now, locale, 'narrow') : isAbsoluteFormat(effectiveFormat) ? formatInstant(date, effectiveFormat, locale, {
293
+ const displayText = !isValidDate ? '' : effectiveFormat === 'relative' ? getRelativeTimeString(date, now) : effectiveFormat === 'relative_short' ? getRelativeTimeShortString(date, now) : isAbsoluteFormat(effectiveFormat) ? formatInstant(date, effectiveFormat, locale, {
153
294
  isTimezoneShown
154
295
  }) : '';
155
296
 
package/dist/astryx.css CHANGED
@@ -340,7 +340,6 @@
340
340
  .xbfzqbu{--spinner-track-color:currentColor}
341
341
  .x1ufpcf6{--spinner-track-color:var(--color-on-dark)}
342
342
  .xspt9s2{--spinner-track-color:var(--color-track)}
343
- .x152dxt4{--step-connector-gap:0px}
344
343
  .xpa4xmd{--tree-list-indent:var(--spacing-4)}
345
344
  .x1xbi2jf{--tree-list-row-gap:var(--spacing-0-5)}
346
345
  .x1wfyzv7:focus-within{--_fade-delay:0s}
@@ -769,11 +768,7 @@
769
768
  .x1afcbsf:not(#\#):not(#\#):not(#\#){box-sizing:content-box}
770
769
  .xbuiw85:not(#\#):not(#\#):not(#\#){caret-color:transparent}
771
770
  .xxugrtn:not(#\#):not(#\#):not(#\#){caret-color:var(--color-accent)}
772
- .x13hy1yx:not(#\#):not(#\#):not(#\#){clip-path:inset(0 0 0 max(0px,min(var(--step-connector-gap,0px),var(--spacing-2))))}
773
- .x1yxrbr4:not(#\#):not(#\#):not(#\#){clip-path:inset(0 0 max(0px,min(var(--step-connector-gap,0px),var(--spacing-2))) 0)}
774
- .x17qoqre:not(#\#):not(#\#):not(#\#){clip-path:inset(0 max(0px,min(var(--step-connector-gap,0px),var(--spacing-2))) 0 0)}
775
771
  .x1hyvwdk:not(#\#):not(#\#):not(#\#){clip-path:inset(50%)}
776
- .x1eyou77:not(#\#):not(#\#):not(#\#){clip-path:inset(max(0px,min(var(--step-connector-gap,0px),var(--spacing-2))) 0 0 0)}
777
772
  .xzpqnlu:not(#\#):not(#\#):not(#\#){clip:rect(0,0,0,0)}
778
773
  .xntwwlm:not(#\#):not(#\#):not(#\#){color-scheme:dark}
779
774
  .x108lcm5:not(#\#):not(#\#):not(#\#){color-scheme:light dark}
@@ -1320,8 +1315,6 @@
1320
1315
  .xuihb5h:focus-within:not(#\#):not(#\#):not(#\#){box-shadow:inset 0 0 0 2px var(--color-accent-muted)}
1321
1316
  .x15ysqaf:focus-within:not(#\#):not(#\#):not(#\#){box-shadow:none}
1322
1317
  .xyavpck:focus-within:not(#\#):not(#\#):not(#\#){box-shadow:var(--shadow-med)}
1323
- .x153f7d5:is([dir="rtl"] *):not(#\#):not(#\#):not(#\#){clip-path:inset(0 0 0 max(0px,min(var(--step-connector-gap,0px),var(--spacing-2))))}
1324
- .x7cmjge:is([dir="rtl"] *):not(#\#):not(#\#):not(#\#){clip-path:inset(0 max(0px,min(var(--step-connector-gap,0px),var(--spacing-2))) 0 0)}
1325
1318
  .x10wafsz:focus-within:not(#\#):not(#\#):not(#\#){clip-path:none}
1326
1319
  .xy5bzlh.xy5bzlh:hover:where(:not(:disabled,[aria-disabled="true"])):not(#\#):not(#\#):not(#\#){color:var(--color-icon-primary)}
1327
1320
  .x9o1np9.x9o1np9:hover:where(:not(:disabled,[aria-disabled="true"])):not(#\#):not(#\#):not(#\#){color:var(--color-text-primary)}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astryxdesign/core",
3
- "version": "0.5.2-canary.48cb3a4",
3
+ "version": "0.5.2-canary.4a48880",
4
4
  "displayName": "Astryx Core",
5
5
  "description": "The component library. Accessible, themeable React components with built-in spacing, dark mode, and StyleX styling.",
6
6
  "author": "Meta Open Source",
@@ -661,7 +661,7 @@
661
661
  "react-dom": ">=19.0.0"
662
662
  },
663
663
  "devDependencies": {
664
- "@astryxdesign/cli": "0.5.2-canary.48cb3a4",
664
+ "@astryxdesign/cli": "0.5.2-canary.4a48880",
665
665
  "@babel/cli": "^8.0.4",
666
666
  "@babel/core": "^7.29.7",
667
667
  "@babel/preset-react": "^8.0.1",
@@ -10,7 +10,7 @@ export const docs = {
10
10
  category: 'Navigation',
11
11
  isHiddenFromOverview: true,
12
12
  description:
13
- "Individual step within a Stepper. Renders a progress-bar segment, an indicator, and a label with optional description. Progress (completed/active/not-started) is derived from the parent Stepper's activeStep and this step's step index.",
13
+ 'Individual step within a Stepper. Renders a progress-bar segment, an indicator, and a label with optional description. Progress (completed/active/not-started) is derived from the parent Stepper\'s activeStep and this step\'s step index.',
14
14
  props: [
15
15
  {
16
16
  name: 'step',
@@ -197,37 +197,6 @@ function CurrentIcon() {
197
197
  // --- Styles ---
198
198
 
199
199
  const BAR_WIDTH = spacingVars['--spacing-1'];
200
-
201
- /**
202
- * How much of itself a connector gives up where it meets the indicator, read
203
- * from the public `--step-connector-gap` declared on the Stepper root.
204
- *
205
- * Clipped, not padded. The gap has to apply to two layers — the track, which is
206
- * the segment's own background, and the accent fill, which is an absolutely
207
- * placed `::before`. Spending it on each separately meant two declarations on
208
- * two boxes: a percentage then resolved against a different containing block
209
- * for each and stopped them ~1.2px apart. `clip-path` is ONE declaration on the
210
- * segment that clips the element and its pseudo-element together, against one
211
- * reference box — so every accepted value behaves the same way on both layers,
212
- * which is what a public input owes its full value domain.
213
- *
214
- * Clipping also cannot change layout: the segment keeps its box, so the node it
215
- * positions cannot move and the Stepper cannot grow. A raw `1rem` padding grew
216
- * a 180px stepper to 240px.
217
- *
218
- * Still clamped, because the value arrives with nothing in between to reject
219
- * it. Both halves earn their place, and not for the reasons padding needed:
220
- *
221
- * - `max(0px, …)` — `inset()` ACCEPTS a negative length. Chromium computes
222
- * `inset(0 0 -4px 0)` as-is rather than dropping it, which is the opposite
223
- * of padding (there CSS clamps a negative to `0` for you). This normalises
224
- * it to `0` so a negative gap means "no gap" rather than a clip rect
225
- * stretched outside the segment's own box.
226
- * - `min(…, --spacing-2)` — the flexible segment's own `min-height`, so an
227
- * oversized gap leaves a short track rather than an unbounded one.
228
- */
229
- const CONNECTOR_GAP = `max(0px, min(var(--step-connector-gap, 0px), ${spacingVars['--spacing-2']}))`;
230
-
231
200
  // Every indicator — check, ring, custom icon, number badge — occupies the same
232
201
  // box, so the `auto` mode swapping a number for a check as a step completes
233
202
  // never nudges the label or the track.
@@ -761,43 +730,6 @@ const styles = stylex.create({
761
730
  flexShrink: 0,
762
731
  borderRadius: 0,
763
732
  },
764
- // The gap a connector leaves where it meets the indicator, spent on the side
765
- // that faces the node so the pair leaves a symmetric hole around it.
766
- //
767
- // Clip the segment rather than changing its box: one `clip-path` clips the
768
- // segment's own track and its absolutely positioned `::before` fill against
769
- // the same reference box. The indicator stays put, the Stepper cannot grow,
770
- // and percentage values cannot resolve differently between the two layers.
771
- // The inherited value is clamped in CONNECTOR_GAP; its default is declared
772
- // once on the Stepper root, where a generated theme override can replace it.
773
- //
774
- // A public var rather than a per-segment theme vocabulary: "do not touch the
775
- // indicator" is one intent, and naming the segments would publish which
776
- // pieces this layout happens to be drawn from today.
777
- otSegGapLeadV: {
778
- clipPath: `inset(0 0 ${CONNECTOR_GAP} 0)`,
779
- },
780
- otSegGapRailV: {
781
- clipPath: `inset(${CONNECTOR_GAP} 0 0 0)`,
782
- },
783
- // The horizontal pair is PHYSICAL: `clip-path: inset()` takes top/right/
784
- // bottom/left and has no logical form, while the row itself reverses under
785
- // `dir="rtl"`. Left unflipped, the leading segment sits to the RIGHT of the
786
- // node in RTL and still clips its right edge — opening the hole at the join
787
- // between steps instead of at the indicator. The block axis needs no such
788
- // handling: `dir` does not reverse it.
789
- otSegGapLeadH: {
790
- clipPath: {
791
- default: `inset(0 ${CONNECTOR_GAP} 0 0)`,
792
- ':is([dir="rtl"] *)': `inset(0 0 0 ${CONNECTOR_GAP})`,
793
- },
794
- },
795
- otSegGapRailH: {
796
- clipPath: {
797
- default: `inset(0 0 0 ${CONNECTOR_GAP})`,
798
- ':is([dir="rtl"] *)': `inset(0 ${CONNECTOR_GAP} 0 0)`,
799
- },
800
- },
801
733
  // Flexible segment (below the node) — grows to fill the step height and
802
734
  // meets the next node's leading segment.
803
735
  otSegFlexV: {
@@ -1420,7 +1352,6 @@ export function Step({
1420
1352
  styles.otSegBaseV,
1421
1353
  styles.otSegLeadV(densitySpace),
1422
1354
  styles.connectorTrack,
1423
- hasIndicator && styles.otSegGapLeadV,
1424
1355
  beforeSegStyle,
1425
1356
  beforeTiming,
1426
1357
  styles.otSegHiddenIfFirst,
@@ -1436,7 +1367,6 @@ export function Step({
1436
1367
  styles.otSegBaseV,
1437
1368
  styles.otSegFlexV,
1438
1369
  styles.connectorTrack,
1439
- hasIndicator && styles.otSegGapRailV,
1440
1370
  afterSegStyle,
1441
1371
  railTiming,
1442
1372
  styles.otSegHiddenIfLast,
@@ -1502,7 +1432,6 @@ export function Step({
1502
1432
  stylex.props(
1503
1433
  styles.otSegH,
1504
1434
  styles.connectorTrack,
1505
- hasIndicator && styles.otSegGapLeadH,
1506
1435
  beforeSegStyle,
1507
1436
  beforeTiming,
1508
1437
  styles.otSegHiddenIfFirst,
@@ -1517,7 +1446,6 @@ export function Step({
1517
1446
  stylex.props(
1518
1447
  styles.otSegH,
1519
1448
  styles.connectorTrack,
1520
- hasIndicator && styles.otSegGapRailH,
1521
1449
  afterSegStyle,
1522
1450
  railTiming,
1523
1451
  styles.otSegHiddenIfLast,
@@ -53,30 +53,12 @@ export const docs = {
53
53
  },
54
54
  ],
55
55
  anatomy: [
56
- {
57
- name: 'Stepper',
58
- required: true,
59
- description:
60
- 'The ordered list holding the steps. Owns the orientation and the indicator placement the whole flow is laid out on.',
61
- },
62
- {
63
- name: 'Step',
64
- required: true,
65
- description:
66
- 'One step in the flow, and the element carrying its status. Wraps the indicator, label, description, and the track segments belonging to it.',
67
- },
68
56
  {
69
57
  name: 'Progress bar',
70
58
  required: true,
71
59
  description:
72
60
  'A 4px segmented bar per step. Filled for completed and active steps. Advancing one step grows the fill along the track it just covered, so the movement reads as progress rather than a bar changing color. Every other change applies at once: going back, jumping forward by more than one step, mounting mid-flow, and any change at all under prefers-reduced-motion. Where a span is drawn by more than one segment (the on-track layouts split it between two steps, three when a content slot sits between them), the segments run in track order at one constant speed, so the fill reads as a single line growing rather than pieces lighting in turn.',
73
61
  },
74
- {
75
- name: 'Connector',
76
- required: false,
77
- description:
78
- 'The track drawn between indicators in the on-track layouts. Each connector paints an unfilled line and, over it, the accent fill covering the progress made. How many pieces a connector is drawn from is an implementation detail of the layout, not a themeable part; use --step-connector-gap to hold the track off the indicator.',
79
- },
80
62
  {
81
63
  name: 'Indicator',
82
64
  required: false,
@@ -106,14 +88,6 @@ export const docs = {
106
88
  {className: 'astryx-step-bar'},
107
89
  {className: 'astryx-step-connector'},
108
90
  ],
109
- vars: [
110
- {
111
- name: '--step-connector-gap',
112
- description:
113
- 'Gap a connector leaves where it meets the indicator, spent on the side facing it. Applies to the on-track layouts, whose connector is drawn as one segment either side of the node; 0 leaves the track running unbroken through it.',
114
- default: '0px',
115
- },
116
- ],
117
91
  },
118
92
  components: [
119
93
  {
@@ -286,14 +260,6 @@ export const docsZh = {
286
260
  {className: 'astryx-step-bar'},
287
261
  {className: 'astryx-step-connector'},
288
262
  ],
289
- vars: [
290
- {
291
- name: '--step-connector-gap',
292
- description:
293
- '连接线与指示器相接处留出的间隙,落在朝向指示器的一侧。适用于 on-track 布局——其连接线由节点两侧各一段绘制;取 0 时轨道将不间断地穿过节点。',
294
- default: '0px',
295
- },
296
- ],
297
263
  },
298
264
  components: [
299
265
  {