@astryxdesign/core 0.1.0 → 0.1.1-canary.080d887

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 (170) hide show
  1. package/CHANGELOG.md +66 -0
  2. package/README.md +119 -1
  3. package/dist/AvatarGroup/AvatarGroupOverflow.d.ts +1 -1
  4. package/dist/AvatarGroup/AvatarGroupOverflow.d.ts.map +1 -1
  5. package/dist/AvatarGroup/AvatarGroupOverflow.js +4 -1
  6. package/dist/Banner/Banner.d.ts +7 -0
  7. package/dist/Banner/Banner.d.ts.map +1 -1
  8. package/dist/Banner/Banner.js +9 -2
  9. package/dist/Button/Button.d.ts +12 -2
  10. package/dist/Button/Button.d.ts.map +1 -1
  11. package/dist/Button/Button.js +51 -13
  12. package/dist/Chat/ChatLayoutScrollButton.d.ts.map +1 -1
  13. package/dist/Chat/ChatLayoutScrollButton.js +5 -1
  14. package/dist/ContextMenu/ContextMenu.js +2 -2
  15. package/dist/DropdownMenu/DropdownMenu.js +2 -2
  16. package/dist/DropdownMenu/{renderXDSDropdownItems.d.ts → renderDropdownItems.d.ts} +3 -3
  17. package/dist/DropdownMenu/renderDropdownItems.d.ts.map +1 -0
  18. package/dist/DropdownMenu/{renderXDSDropdownItems.js → renderDropdownItems.js} +2 -2
  19. package/dist/EmptyState/EmptyState.d.ts.map +1 -1
  20. package/dist/EmptyState/EmptyState.js +7 -1
  21. package/dist/HoverCard/HoverCard.d.ts +2 -2
  22. package/dist/HoverCard/HoverCard.d.ts.map +1 -1
  23. package/dist/HoverCard/HoverCard.js +18 -6
  24. package/dist/HoverCard/useHoverCard.d.ts.map +1 -1
  25. package/dist/HoverCard/useHoverCard.js +6 -3
  26. package/dist/Kbd/Kbd.d.ts.map +1 -1
  27. package/dist/Kbd/Kbd.js +1 -1
  28. package/dist/Layer/useLayer.d.ts +13 -0
  29. package/dist/Layer/useLayer.d.ts.map +1 -1
  30. package/dist/Layer/useLayer.js +7 -2
  31. package/dist/Layout/Layout.d.ts +10 -1
  32. package/dist/Layout/Layout.d.ts.map +1 -1
  33. package/dist/Layout/Layout.js +5 -1
  34. package/dist/Markdown/Markdown.d.ts.map +1 -1
  35. package/dist/Markdown/Markdown.js +13 -3
  36. package/dist/MobileNav/MobileNav.d.ts.map +1 -1
  37. package/dist/MobileNav/MobileNav.js +13 -0
  38. package/dist/Outline/Outline.d.ts +3 -2
  39. package/dist/Outline/Outline.d.ts.map +1 -1
  40. package/dist/Outline/Outline.js +23 -4
  41. package/dist/Outline/useScrollSpy.d.ts +14 -1
  42. package/dist/Outline/useScrollSpy.d.ts.map +1 -1
  43. package/dist/Outline/useScrollSpy.js +161 -50
  44. package/dist/Pagination/Pagination.d.ts.map +1 -1
  45. package/dist/Pagination/Pagination.js +31 -27
  46. package/dist/Resizable/useResizable.d.ts.map +1 -1
  47. package/dist/Resizable/useResizable.js +1 -5
  48. package/dist/Selector/Selector.d.ts.map +1 -1
  49. package/dist/Selector/Selector.js +1 -1
  50. package/dist/Slider/Slider.js +2 -2
  51. package/dist/Table/BaseTable.d.ts.map +1 -1
  52. package/dist/Table/BaseTable.js +26 -8
  53. package/dist/Table/Table.d.ts.map +1 -1
  54. package/dist/Table/Table.js +30 -7
  55. package/dist/Table/index.d.ts +3 -1
  56. package/dist/Table/index.d.ts.map +1 -1
  57. package/dist/Table/index.js +1 -0
  58. package/dist/Table/plugins/stickyColumns/index.d.ts +3 -0
  59. package/dist/Table/plugins/stickyColumns/index.d.ts.map +1 -0
  60. package/dist/Table/plugins/stickyColumns/index.js +3 -0
  61. package/dist/Table/plugins/stickyColumns/useTableStickyColumns.d.ts +25 -0
  62. package/dist/Table/plugins/stickyColumns/useTableStickyColumns.d.ts.map +1 -0
  63. package/dist/Table/plugins/stickyColumns/useTableStickyColumns.js +376 -0
  64. package/dist/Table/types.d.ts +90 -5
  65. package/dist/Table/types.d.ts.map +1 -1
  66. package/dist/Table/useBaseTablePlugins.d.ts.map +1 -1
  67. package/dist/Table/useBaseTablePlugins.js +1 -1
  68. package/dist/Timestamp/Timestamp.d.ts +1 -1
  69. package/dist/Timestamp/Timestamp.d.ts.map +1 -1
  70. package/dist/Timestamp/Timestamp.js +28 -3
  71. package/dist/ToggleButton/ToggleButton.d.ts +10 -5
  72. package/dist/ToggleButton/ToggleButton.d.ts.map +1 -1
  73. package/dist/ToggleButton/ToggleButton.js +40 -17
  74. package/dist/astryx.css +20 -0
  75. package/dist/astryx.umd.js +147 -0
  76. package/dist/astryx.umd.js.map +7 -0
  77. package/dist/theme/Theme.js +1 -1
  78. package/dist/theme/defineTheme.d.ts +1 -1
  79. package/dist/theme/defineTheme.d.ts.map +1 -1
  80. package/dist/theme/defineTheme.js +1 -1
  81. package/dist/theme/index.d.ts +1 -1
  82. package/dist/theme/index.d.ts.map +1 -1
  83. package/dist/theme/index.js +1 -1
  84. package/dist/theme/syntax/defineSyntaxTheme.js +1 -1
  85. package/dist/theme/tokens.d.ts +1 -1
  86. package/dist/theme/tokens.js +4 -4
  87. package/dist/theme/useTheme.d.ts +2 -2
  88. package/dist/utils/dateParser.d.ts.map +1 -1
  89. package/dist/utils/dateParser.js +15 -2
  90. package/package.json +7 -3
  91. package/src/AvatarGroup/AvatarGroupOverflow.tsx +3 -0
  92. package/src/Banner/Banner.test.tsx +16 -7
  93. package/src/Banner/Banner.tsx +9 -2
  94. package/src/Button/Button.doc.mjs +6 -0
  95. package/src/Button/Button.test.tsx +105 -11
  96. package/src/Button/Button.tsx +73 -5
  97. package/src/Chat/ChatLayoutScrollButton.tsx +7 -1
  98. package/src/Collapsible/useCollapsible.doc.mjs +2 -2
  99. package/src/ContextMenu/ContextMenu.tsx +2 -2
  100. package/src/DateInput/DateInput.test.tsx +68 -20
  101. package/src/Divider/Divider.doc.mjs +1 -1
  102. package/src/DropdownMenu/DropdownMenu.tsx +2 -2
  103. package/src/DropdownMenu/{renderXDSDropdownItems.tsx → renderDropdownItems.tsx} +2 -2
  104. package/src/EmptyState/EmptyState.test.tsx +4 -2
  105. package/src/EmptyState/EmptyState.tsx +6 -2
  106. package/src/FormLayout/FormLayout.doc.mjs +3 -3
  107. package/src/HoverCard/HoverCard.doc.mjs +3 -0
  108. package/src/HoverCard/HoverCard.test.tsx +178 -2
  109. package/src/HoverCard/HoverCard.tsx +20 -16
  110. package/src/HoverCard/useHoverCard.tsx +12 -10
  111. package/src/Icon/Icon.doc.mjs +12 -4
  112. package/src/Item/Item.doc.mjs +2 -2
  113. package/src/Kbd/Kbd.tsx +2 -9
  114. package/src/Layer/useLayer.doc.mjs +7 -2
  115. package/src/Layer/useLayer.tsx +19 -2
  116. package/src/Layout/Layout.doc.mjs +2 -1
  117. package/src/Layout/Layout.tsx +15 -1
  118. package/src/Layout/__tests__/childrenAsContent.test.tsx +59 -0
  119. package/src/Lightbox/Lightbox.doc.mjs +0 -2
  120. package/src/Link/Link.doc.mjs +3 -3
  121. package/src/Link/LinkProvider.doc.mjs +3 -3
  122. package/src/Markdown/Markdown.doc.mjs +6 -4
  123. package/src/Markdown/Markdown.test.tsx +17 -26
  124. package/src/Markdown/Markdown.tsx +16 -6
  125. package/src/MobileNav/MobileNav.doc.mjs +8 -8
  126. package/src/MobileNav/MobileNav.tsx +13 -0
  127. package/src/MobileNav/MobileNavReopen.test.tsx +118 -0
  128. package/src/Outline/Outline.doc.mjs +1 -1
  129. package/src/Outline/Outline.test.tsx +76 -38
  130. package/src/Outline/Outline.tsx +23 -4
  131. package/src/Outline/useScrollSpy.ts +196 -63
  132. package/src/Pagination/Pagination.test.tsx +137 -13
  133. package/src/Pagination/Pagination.tsx +33 -28
  134. package/src/Resizable/Resizable.doc.mjs +3 -3
  135. package/src/Resizable/useResizable.ts +1 -7
  136. package/src/Selector/Selector.doc.mjs +4 -0
  137. package/src/Selector/Selector.tsx +5 -6
  138. package/src/Skeleton/Skeleton.doc.mjs +11 -1
  139. package/src/Slider/Slider.doc.mjs +4 -0
  140. package/src/Slider/Slider.test.tsx +3 -16
  141. package/src/Slider/Slider.tsx +1 -1
  142. package/src/Table/BaseTable.tsx +50 -24
  143. package/src/Table/Table.doc.mjs +15 -3
  144. package/src/Table/Table.tsx +22 -1
  145. package/src/Table/index.ts +3 -0
  146. package/src/Table/plugins/stickyColumns/index.ts +4 -0
  147. package/src/Table/plugins/stickyColumns/useTableStickyColumns.test.tsx +163 -0
  148. package/src/Table/plugins/stickyColumns/useTableStickyColumns.tsx +414 -0
  149. package/src/Table/types.ts +96 -4
  150. package/src/Table/useBaseTablePlugins.ts +1 -0
  151. package/src/Text/Heading.doc.mjs +13 -0
  152. package/src/Timestamp/Timestamp.test.tsx +44 -18
  153. package/src/Timestamp/Timestamp.tsx +30 -10
  154. package/src/ToggleButton/ToggleButton.doc.mjs +4 -4
  155. package/src/ToggleButton/ToggleButton.test.tsx +172 -9
  156. package/src/ToggleButton/ToggleButton.tsx +57 -31
  157. package/src/Toolbar/Toolbar.doc.mjs +1 -1
  158. package/src/hooks/useEntryAnimation.doc.mjs +3 -3
  159. package/src/hooks/useMediaQuery.doc.mjs +2 -2
  160. package/src/hooks/useStreamingText.doc.mjs +3 -3
  161. package/src/theme/Theme.doc.mjs +2 -2
  162. package/src/theme/Theme.tsx +1 -1
  163. package/src/theme/defineTheme.ts +1 -1
  164. package/src/theme/index.ts +1 -1
  165. package/src/theme/syntax/defineSyntaxTheme.ts +1 -1
  166. package/src/theme/tokens.ts +4 -4
  167. package/src/theme/useTheme.ts +2 -2
  168. package/src/utils/dateParser.test.ts +26 -0
  169. package/src/utils/dateParser.ts +16 -2
  170. package/dist/DropdownMenu/renderXDSDropdownItems.d.ts.map +0 -1
@@ -45,6 +45,18 @@ const YEAR = 365 * DAY;
45
45
 
46
46
  /** Default auto threshold: 7 days in seconds */
47
47
  const DEFAULT_AUTO_THRESHOLD = 7 * DAY;
48
+
49
+ /**
50
+ * Tolerance (in seconds) for treating a *future* timestamp as the present.
51
+ * A value only a handful of seconds ahead of our reference clock is almost
52
+ * always clock skew — the displayed `now` lagging the real clock, or the value
53
+ * being produced on a slightly faster clock — not a genuine future event, so
54
+ * it reads as "now" rather than a confusing "in a few seconds". The future
55
+ * side gets a wider window than the past (which only needs to absorb the
56
+ * sub-second render-time lag) because future drift is far more likely to be
57
+ * skew than real.
58
+ */
59
+ const FUTURE_SKEW_TOLERANCE = 30;
48
60
  function parseValue(value) {
49
61
  if (typeof value === 'number') {
50
62
  // Heuristic: if the number is less than 1e12, treat as seconds; otherwise ms.
@@ -55,9 +67,25 @@ function parseValue(value) {
55
67
  }
56
68
  function getRelativeTimeString(date, now) {
57
69
  const diffSeconds = Math.round((now.getTime() - date.getTime()) / 1000);
70
+
71
+ // Treat values at (or a hair before/after) the present as "now". The
72
+ // internal `now` reference is captured at render time, so it can lag the
73
+ // real clock; a value equal to "right now" can land a fraction of a second
74
+ // in the future and round to a small negative delta. Without this clamp,
75
+ // such values fall into the future branch and render "in a few seconds".
76
+ if (Math.abs(diffSeconds) < 10) {
77
+ return 'now';
78
+ }
58
79
  if (diffSeconds < 0) {
59
80
  // Future dates
60
81
  const absDiff = Math.abs(diffSeconds);
82
+ // A value only a few seconds ahead of our clock is almost always skew, not
83
+ // a genuine future event — render it as the present rather than a
84
+ // confusing "in a few seconds". Wider than the past window above on
85
+ // purpose (see FUTURE_SKEW_TOLERANCE).
86
+ if (absDiff <= FUTURE_SKEW_TOLERANCE) {
87
+ return 'now';
88
+ }
61
89
  if (absDiff < MINUTE) {
62
90
  return 'in a few seconds';
63
91
  }
@@ -80,9 +108,6 @@ function getRelativeTimeString(date, now) {
80
108
  const years = Math.round(absDiff / YEAR);
81
109
  return `in ${years} ${years === 1 ? 'year' : 'years'}`;
82
110
  }
83
- if (diffSeconds < 10) {
84
- return 'just now';
85
- }
86
111
  if (diffSeconds < MINUTE) {
87
112
  return `${diffSeconds} seconds ago`;
88
113
  }
@@ -32,14 +32,19 @@ export interface ToggleButtonProps extends BaseProps<HTMLButtonElement> {
32
32
  */
33
33
  isPressed?: boolean;
34
34
  /**
35
- * Called when the pressed state should change.
35
+ * Called when the pressed state should change. Receives the next pressed
36
+ * state and the originating click event. Call `event.preventDefault()` to
37
+ * opt out of running `pressedChangeAction` (e.g. to handle the toggle
38
+ * entirely in this callback).
36
39
  * When used inside ToggleButtonGroup, this is handled by the group
37
40
  * and this prop is ignored.
38
41
  */
39
- onPressedChange?: (isPressed: boolean) => void;
42
+ onPressedChange?: (isPressed: boolean, event: React.MouseEvent<HTMLButtonElement>) => void;
40
43
  /**
41
- * Async action handler for API-backed toggles.
42
- * The button shows a loading spinner while the promise is pending.
44
+ * Action handler for API- or navigation-backed toggles, run inside a
45
+ * transition by Button. The button shows a loading spinner while the action
46
+ * is pending — whether it returns a promise or synchronously triggers a
47
+ * suspending update (e.g. a router navigation that suspends on data).
43
48
  *
44
49
  * @example
45
50
  * ```
@@ -53,7 +58,7 @@ export interface ToggleButtonProps extends BaseProps<HTMLButtonElement> {
53
58
  * />
54
59
  * ```
55
60
  */
56
- pressedChangeAction?: (isPressed: boolean) => Promise<void>;
61
+ pressedChangeAction?: (isPressed: boolean) => void | Promise<void>;
57
62
  /**
58
63
  * The size of the toggle button.
59
64
  * When used inside ToggleButtonGroup, defaults to the group's size.
@@ -1 +1 @@
1
- {"version":3,"file":"ToggleButton.d.ts","sourceRoot":"","sources":["../../src/ToggleButton/ToggleButton.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAE,EAAc,KAAK,SAAS,EAAC,MAAM,OAAO,CAAC;AAIzD,OAAO,EAAS,KAAK,UAAU,EAAC,MAAM,WAAW,CAAC;AAElD,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AA0C5C,MAAM,WAAW,iBAAkB,SAAQ,SAAS,CAAC,iBAAiB,CAAC;IACrE,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC;IACnC;;;OAGG;IACH,KAAK,EAAE,MAAM,CAAC;IAEd;;;;OAIG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IAEpB;;;;OAIG;IACH,eAAe,CAAC,EAAE,CAAC,SAAS,EAAE,OAAO,KAAK,IAAI,CAAC;IAE/C;;;;;;;;;;;;;;;OAeG;IACH,mBAAmB,CAAC,EAAE,CAAC,SAAS,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAE5D;;;;OAIG;IACH,IAAI,CAAC,EAAE,UAAU,CAAC;IAElB;;;;OAIG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IAErB;;;OAGG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IAEpB;;OAEG;IACH,IAAI,CAAC,EAAE,SAAS,CAAC;IAEjB;;;;OAIG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IAErB;;;;;;;;;;OAUG;IACH,WAAW,CAAC,EAAE,SAAS,CAAC;IAExB;;;OAGG;IACH,QAAQ,CAAC,EAAE,SAAS,CAAC;IAErB;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAMD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,YAAY,CAAC,EAC3B,GAAG,EACH,KAAK,EACL,SAAS,EAAE,aAAa,EACxB,eAAe,EAAE,mBAAmB,EACpC,mBAAmB,EACnB,IAAI,EAAE,QAAQ,EACd,UAAU,EAAE,cAAsB,EAClC,SAAiB,EACjB,IAAI,EACJ,UAAkB,EAClB,WAAW,EACX,QAAQ,EACR,OAAO,EACP,KAAK,EACL,MAAM,EACN,SAAS,EAAE,UAAU,EACrB,KAAK,EACL,GAAG,KAAK,EACT,EAAE,iBAAiB,GAAG,SAAS,CAuF/B;yBA1Ge,YAAY"}
1
+ {"version":3,"file":"ToggleButton.d.ts","sourceRoot":"","sources":["../../src/ToggleButton/ToggleButton.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAE,EAAgB,KAAK,SAAS,EAAC,MAAM,OAAO,CAAC;AAI3D,OAAO,EAAS,KAAK,UAAU,EAAC,MAAM,WAAW,CAAC;AAElD,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AA0C5C,MAAM,WAAW,iBAAkB,SAAQ,SAAS,CAAC,iBAAiB,CAAC;IACrE,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC;IACnC;;;OAGG;IACH,KAAK,EAAE,MAAM,CAAC;IAEd;;;;OAIG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IAEpB;;;;;;;OAOG;IACH,eAAe,CAAC,EAAE,CAChB,SAAS,EAAE,OAAO,EAClB,KAAK,EAAE,KAAK,CAAC,UAAU,CAAC,iBAAiB,CAAC,KACvC,IAAI,CAAC;IAEV;;;;;;;;;;;;;;;;;OAiBG;IACH,mBAAmB,CAAC,EAAE,CAAC,SAAS,EAAE,OAAO,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEnE;;;;OAIG;IACH,IAAI,CAAC,EAAE,UAAU,CAAC;IAElB;;;;OAIG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IAErB;;;OAGG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IAEpB;;OAEG;IACH,IAAI,CAAC,EAAE,SAAS,CAAC;IAEjB;;;;OAIG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IAErB;;;;;;;;;;OAUG;IACH,WAAW,CAAC,EAAE,SAAS,CAAC;IAExB;;;OAGG;IACH,QAAQ,CAAC,EAAE,SAAS,CAAC;IAErB;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAMD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,YAAY,CAAC,EAC3B,GAAG,EACH,KAAK,EACL,SAAS,EAAE,aAAa,EACxB,eAAe,EAAE,mBAAmB,EACpC,mBAAmB,EACnB,IAAI,EAAE,QAAQ,EACd,UAAU,EAAE,cAAsB,EAClC,SAAiB,EACjB,IAAI,EACJ,UAAkB,EAClB,WAAW,EACX,QAAQ,EACR,OAAO,EACP,KAAK,EACL,MAAM,EACN,SAAS,EAAE,UAAU,EACrB,KAAK,EACL,GAAG,KAAK,EACT,EAAE,iBAAiB,GAAG,SAAS,CAyG/B;yBA5He,YAAY"}
@@ -19,7 +19,7 @@
19
19
  * - /apps/storybook/stories/ToggleButton.stories.tsx
20
20
  * - /packages/cli/templates/blocks/components/ToggleButton/ (showcase blocks)
21
21
  */
22
- import React, { useCallback } from 'react';
22
+ import React, { useOptimistic } from 'react';
23
23
  import * as stylex from '@stylexjs/stylex';
24
24
  import "../theme/tokens.stylex.js";
25
25
  import { colorVars, fontWeightVars } from "../theme/tokens.stylex.js";
@@ -93,32 +93,53 @@ export function ToggleButton({
93
93
  style,
94
94
  ...props
95
95
  }) {
96
- // Read group context if inside a group
97
96
  const group = useToggleButtonGroup();
98
-
99
- // Resolve state from group or props
100
- const isPressed = group && value != null ? group.selectedValues.has(value) : isPressedProp ?? false;
97
+ const committedPressed = group && value != null ? group.selectedValues.has(value) : isPressedProp ?? false;
101
98
  const size = sizeProp ?? group?.size ?? 'md';
102
99
  const isDisabled = group?.isDisabled ?? isDisabledProp;
100
+
101
+ // Track the pressed state optimistically so the button reflects the intended
102
+ // state immediately while an async action is pending. The optimistic update
103
+ // is applied inside Button's clickAction transition (below), satisfying
104
+ // React's "useOptimistic must run in a transition" rule.
105
+ const [optimisticPressed, setOptimisticPressed] = useOptimistic(committedPressed);
106
+ const isPressed = optimisticPressed;
107
+
108
+ // Next state derives from the *optimistic* value, not the committed one, so a
109
+ // re-click while an action is pending reads the in-flight intent and toggles
110
+ // (true -> false -> true) rather than recomputing from the stale committed
111
+ // state. handleClick and clickAction share this so the callback and the
112
+ // optimistic update always agree within a single click.
113
+ const nextPressed = !isPressed;
103
114
  const resolvedIcon = isPressed && pressedIcon ? pressedIcon : icon;
104
- const handleClick = useCallback(() => {
105
- if (isDisabled || isLoading) {
115
+
116
+ // Synchronous part of the toggle. Button calls onClick before clickAction and
117
+ // skips clickAction when the event is defaultPrevented, so calling
118
+ // preventDefault() inside onPressedChange opts out of pressedChangeAction.
119
+ const handleClick = event => {
120
+ if (isDisabled) {
106
121
  return;
107
122
  }
108
123
  if (group && value != null) {
109
- // Delegate to group context
124
+ // Group mode delegates selection to the group; no async-action path.
110
125
  group.toggle(value);
111
- } else if (onPressedChangeProp) {
112
- // Standalone toggle
113
- const newState = !isPressed;
114
- onPressedChangeProp(newState);
115
- if (pressedChangeAction) {
116
- void pressedChangeAction(newState);
117
- }
126
+ event.preventDefault();
127
+ return;
118
128
  }
119
- }, [isDisabled, isLoading, group, value, onPressedChangeProp, pressedChangeAction, isPressed]);
129
+ onPressedChangeProp?.(nextPressed, event);
130
+ };
131
+
132
+ // Async part of the toggle, run inside Button's transition (so the optimistic
133
+ // update is valid and Button's isPending drives the pending spinner). Skipped
134
+ // automatically by Button when handleClick called preventDefault, and never
135
+ // wired in group mode (where there is no async action). Button is rendered
136
+ // interruptible, so it stays clickable while pending and a re-click starts a
137
+ // fresh transition that interrupts the previous one.
138
+ const clickAction = group && value != null ? undefined : async () => {
139
+ setOptimisticPressed(nextPressed);
140
+ await pressedChangeAction?.(nextPressed);
141
+ };
120
142
 
121
- // Label with font weight shift and width reservation
122
143
  // isIconOnly prop is the source of truth for icon-only rendering.
123
144
  const labelContent = children != null ? /*#__PURE__*/_jsxs("span", {
124
145
  ...{
@@ -166,6 +187,7 @@ export function ToggleButton({
166
187
  size: size,
167
188
  isDisabled: isDisabled,
168
189
  isLoading: isLoading,
190
+ isInterruptible: true,
169
191
  isIconOnly: isIconOnly,
170
192
  "aria-pressed": isPressed,
171
193
  icon: resolvedIcon,
@@ -176,6 +198,7 @@ export function ToggleButton({
176
198
  xstyle: [isPressed ? pressedStyles.background : undefined, xstyle],
177
199
  style: style,
178
200
  onClick: handleClick,
201
+ clickAction: clickAction,
179
202
  ...props,
180
203
  children: labelContent
181
204
  });
package/dist/astryx.css CHANGED
@@ -26,6 +26,7 @@
26
26
  @property --x-transform { syntax: "*"; inherits: false;}
27
27
  @property --x-WebkitLineClamp { syntax: "*"; inherits: false;}
28
28
  @property --x-width { syntax: "*"; inherits: false;}
29
+ @keyframes x188b6p3-B{from{color:inherit;}to{color:transparent;}}
29
30
  @keyframes x13kz0yu-B{0%, 100%{opacity:1;}50%{opacity:.5;}}
30
31
  @keyframes x18re5ia-B{from{opacity:0;}to{opacity:1;}}
31
32
  @keyframes xgnty7z-B{0%{opacity:.25;}100%{opacity:1;}}
@@ -453,17 +454,22 @@
453
454
  .x1y8v6su:not(#\#):not(#\#):not(#\#){align-self:start}
454
455
  .xkh2ocl:not(#\#):not(#\#):not(#\#){align-self:stretch}
455
456
  .x7dpabl:not(#\#):not(#\#):not(#\#){anchor-name:--outline-active}
457
+ .x17yabm6:not(#\#):not(#\#):not(#\#){animation-delay:var(--duration-medium-min)}
456
458
  .xvo38ju:not(#\#):not(#\#):not(#\#){animation-delay:var(--x-animationDelay)}
457
459
  .xpz12be:not(#\#):not(#\#):not(#\#){animation-direction:alternate}
458
460
  .xmg6eyc:not(#\#):not(#\#):not(#\#){animation-duration:1.5s}
461
+ .xjlvqhv:not(#\#):not(#\#):not(#\#){animation-duration:1ms}
459
462
  .x1c74tu6:not(#\#):not(#\#):not(#\#){animation-duration:2s}
460
463
  .x9uej1z:not(#\#):not(#\#):not(#\#){animation-duration:var(--duration-fast-max)}
464
+ .xb2rp9n:not(#\#):not(#\#):not(#\#){animation-duration:var(--duration-fast)}
461
465
  .xqgcaz:not(#\#):not(#\#):not(#\#){animation-duration:var(--duration-medium-max)}
462
466
  .x14qxm4i:not(#\#):not(#\#):not(#\#){animation-duration:var(--duration-slow-min)}
463
467
  .xskzprw:not(#\#):not(#\#):not(#\#){animation-fill-mode:backwards}
468
+ .x10e4vud:not(#\#):not(#\#):not(#\#){animation-fill-mode:forwards}
464
469
  .xa4qsjk:not(#\#):not(#\#):not(#\#){animation-iteration-count:infinite}
465
470
  .x1ewfqum:not(#\#):not(#\#):not(#\#){animation-name:x11lauxq-B}
466
471
  .xcx56e2:not(#\#):not(#\#):not(#\#){animation-name:x13kz0yu-B}
472
+ .x1ffowhz:not(#\#):not(#\#):not(#\#){animation-name:x188b6p3-B}
467
473
  .xqcmdr3:not(#\#):not(#\#):not(#\#){animation-name:x18re5ia-B}
468
474
  .x3psbcj:not(#\#):not(#\#):not(#\#){animation-name:x1ahk7ht-B}
469
475
  .x1dvww92:not(#\#):not(#\#):not(#\#){animation-name:x1b6yrix-B}
@@ -485,6 +491,7 @@
485
491
  .xioom0i:not(#\#):not(#\#):not(#\#){aspect-ratio:var(--button-icon-only-aspect)}
486
492
  .xvn2z4z:not(#\#):not(#\#):not(#\#){backdrop-filter:blur(12px)}
487
493
  .xlp1x4z:not(#\#):not(#\#):not(#\#){backface-visibility:hidden}
494
+ .xx83zyx:not(#\#):not(#\#):not(#\#){background-clip:padding-box}
488
495
  .x1rkw3yq:not(#\#):not(#\#):not(#\#){background-color:color-mix(in srgb,white 60%,transparent)}
489
496
  .xtwfq29:not(#\#):not(#\#):not(#\#){background-color:currentColor}
490
497
  .xgcd1z6:not(#\#):not(#\#):not(#\#){background-color:inherit}
@@ -527,6 +534,7 @@
527
534
  .x16fr6go:not(#\#):not(#\#):not(#\#){background-color:var(--color-text-disabled)}
528
535
  .x19aspcf:not(#\#):not(#\#):not(#\#){background-color:var(--color-text-primary)}
529
536
  .xdomwnj:not(#\#):not(#\#):not(#\#){background-color:var(--color-text-secondary)}
537
+ .xdsb6cv:not(#\#):not(#\#):not(#\#){background-color:var(--color-track)}
530
538
  .x24i8r5:not(#\#):not(#\#):not(#\#){background-color:var(--color-warning-muted)}
531
539
  .x1q8g9m5:not(#\#):not(#\#):not(#\#){background-color:var(--color-warning)}
532
540
  .x1vrk509:not(#\#):not(#\#):not(#\#){background-image:linear-gradient(var(--color-background-muted),var(--color-background-muted))}
@@ -989,6 +997,7 @@
989
997
  .x1ja2u2z:not(#\#):not(#\#):not(#\#){z-index:0}
990
998
  .x1vjfegm:not(#\#):not(#\#):not(#\#){z-index:1}
991
999
  .xhtitgo:not(#\#):not(#\#):not(#\#){z-index:2}
1000
+ .xzkaem6:not(#\#):not(#\#):not(#\#){z-index:3}
992
1001
  .x6cuj84:not(#\#):not(#\#):not(#\#){z-index:500}
993
1002
  .x1q2oy4v:not(#\#):not(#\#):not(#\#){z-index:9999}
994
1003
  .x1v2claj.x1v2claj:where(.x84s7jz:focus-within *):not(#\#):not(#\#):not(#\#){opacity:1}
@@ -1047,6 +1056,7 @@
1047
1056
  .x1tmayz8:active:not(#\#):not(#\#):not(#\#){opacity:.75}
1048
1057
  .x1pdlv7q:active:not(#\#):not(#\#):not(#\#){transform:none}
1049
1058
  .xk4oym4:active:not(#\#):not(#\#):not(#\#){transform:scale(.98)}
1059
+ @media (prefers-reduced-motion: reduce){.x14q22ui.x14q22ui:not(#\#):not(#\#):not(#\#){animation-delay:0s}}
1050
1060
  @media (prefers-reduced-motion: reduce){.xnh0sag.xnh0sag:not(#\#):not(#\#):not(#\#){animation-duration:3s}}
1051
1061
  @media (prefers-reduced-motion: reduce){.x1aquc0h.x1aquc0h:not(#\#):not(#\#):not(#\#){animation-name:none}}
1052
1062
  @media (prefers-contrast: more){.x1r1p5j5.x1r1p5j5:not(#\#):not(#\#):not(#\#){background-color:color-mix(in srgb,var(--color-skeleton),var(--color-text-primary) 30%)}}
@@ -1091,6 +1101,7 @@
1091
1101
  @media (hover: hover){.xhouywz.xhouywz:hover:not(#\#):not(#\#):not(#\#){box-shadow:var(--shadow-med)}}
1092
1102
  @media (hover: hover){.x140uwzg.x140uwzg:hover:not(#\#):not(#\#):not(#\#){color:var(--color-text-primary)}}
1093
1103
  @media (hover: hover){.xzomjkg.xzomjkg:hover:not(#\#):not(#\#):not(#\#){opacity:.85}}
1104
+ .xib2hle:not(#\#):not(#\#):not(#\#):not(#\#){border-bottom-color:var(--color-border-emphasized)}
1094
1105
  .xw8gpjh:not(#\#):not(#\#):not(#\#):not(#\#){border-bottom-color:var(--color-border)}
1095
1106
  .xfrllxf:not(#\#):not(#\#):not(#\#):not(#\#){border-bottom-left-radius:0}
1096
1107
  .x6bqyzk:not(#\#):not(#\#):not(#\#):not(#\#){border-bottom-left-radius:var(--_chat-composer-radius)}
@@ -1420,21 +1431,29 @@
1420
1431
  .x1k48kgn:not(#\#):not(#\#):not(#\#):not(#\#):not(#\#):not(#\#)::after{border-radius:inherit}
1421
1432
  @starting-style{.x5rzir7.x5rzir7:not(#\#):not(#\#):not(#\#):not(#\#):not(#\#):not(#\#){padding-bottom:0}}
1422
1433
  .xvtkv6q:not(#\#):not(#\#):not(#\#):not(#\#):not(#\#):not(#\#)::after{transition:background-color .15s ease,width .15s ease}
1434
+ .x17s1k9h:not(#\#):not(#\#):not(#\#):not(#\#):not(#\#):not(#\#)::after{transition:opacity .15s ease}
1423
1435
  .x1abwkk1:not(#\#):not(#\#):not(#\#):not(#\#):not(#\#):not(#\#):not(#\#)::backdrop{backdrop-filter:blur(2px)}
1424
1436
  .xyhc2n1:not(#\#):not(#\#):not(#\#):not(#\#):not(#\#):not(#\#):not(#\#)::after{background-color:transparent}
1425
1437
  .xnixb3f:not(#\#):not(#\#):not(#\#):not(#\#):not(#\#):not(#\#):not(#\#)::backdrop{background-color:var(--color-overlay)}
1426
1438
  .x1i3206j:not(#\#):not(#\#):not(#\#):not(#\#):not(#\#):not(#\#):not(#\#)::after{background-color:var(--indicator-color,transparent)}
1439
+ .x14ofgck:not(#\#):not(#\#):not(#\#):not(#\#):not(#\#):not(#\#):not(#\#)::after{background-image:linear-gradient(to left,light-dark(rgba(0,0,0,.12),rgba(0,0,0,.32)),transparent)}
1440
+ .x156sm4c:not(#\#):not(#\#):not(#\#):not(#\#):not(#\#):not(#\#):not(#\#)::after{background-image:linear-gradient(to right,light-dark(rgba(0,0,0,.12),rgba(0,0,0,.32)),transparent)}
1427
1441
  .xnf3u0:not(#\#):not(#\#):not(#\#):not(#\#):not(#\#):not(#\#):not(#\#)::placeholder{color:var(--color-text-disabled)}
1428
1442
  .xeyghm5:not(#\#):not(#\#):not(#\#):not(#\#):not(#\#):not(#\#):not(#\#)::placeholder{color:var(--color-text-secondary)}
1429
1443
  .x1cpjm7i:not(#\#):not(#\#):not(#\#):not(#\#):not(#\#):not(#\#):not(#\#)::before{content:""}
1430
1444
  .x1s928wv:not(#\#):not(#\#):not(#\#):not(#\#):not(#\#):not(#\#):not(#\#)::after{content:""}
1431
1445
  .xc2ndz5:not(#\#):not(#\#):not(#\#):not(#\#):not(#\#):not(#\#):not(#\#)::before{content:counter(astryx-list) "."}
1432
1446
  .x1unh1gc:not(#\#):not(#\#):not(#\#):not(#\#):not(#\#):not(#\#):not(#\#)::after{inset-inline-end:0}
1447
+ .x1iygr5g:not(#\#):not(#\#):not(#\#):not(#\#):not(#\#):not(#\#):not(#\#)::after{inset-inline-start:0}
1433
1448
  .xph5o2a:not(#\#):not(#\#):not(#\#):not(#\#):not(#\#):not(#\#):not(#\#)::backdrop{opacity:0}
1434
1449
  .xb3n6bw:not(#\#):not(#\#):not(#\#):not(#\#):not(#\#):not(#\#):not(#\#)::backdrop{opacity:1}
1450
+ .x1c2idit:not(#\#):not(#\#):not(#\#):not(#\#):not(#\#):not(#\#):not(#\#)::after{opacity:var(--table-sticky-shadow-end,0)}
1451
+ .x1sggmfs:not(#\#):not(#\#):not(#\#):not(#\#):not(#\#):not(#\#):not(#\#)::after{opacity:var(--table-sticky-shadow-start,0)}
1435
1452
  .x2q1x1w:not(#\#):not(#\#):not(#\#):not(#\#):not(#\#):not(#\#):not(#\#)::after{pointer-events:none}
1436
1453
  .x1hmns74:not(#\#):not(#\#):not(#\#):not(#\#):not(#\#):not(#\#):not(#\#)::before{position:absolute}
1437
1454
  .x1j6awrg:not(#\#):not(#\#):not(#\#):not(#\#):not(#\#):not(#\#):not(#\#)::after{position:absolute}
1455
+ .xvs0bi2:not(#\#):not(#\#):not(#\#):not(#\#):not(#\#):not(#\#):not(#\#)::after{transform:translateX(-100%)}
1456
+ .x1qyefdi:not(#\#):not(#\#):not(#\#):not(#\#):not(#\#):not(#\#):not(#\#)::after{transform:translateX(100%)}
1438
1457
  .x97pup0:not(#\#):not(#\#):not(#\#):not(#\#):not(#\#):not(#\#):not(#\#)::after{transition-duration:var(--duration-fast)}
1439
1458
  .xft5bk6:not(#\#):not(#\#):not(#\#):not(#\#):not(#\#):not(#\#):not(#\#)::backdrop{transition-duration:var(--duration-medium)}
1440
1459
  .x1ywzrc5:not(#\#):not(#\#):not(#\#):not(#\#):not(#\#):not(#\#):not(#\#)::after{transition-property:background-color}
@@ -1450,5 +1469,6 @@
1450
1469
  .x1ydowbf:not(#\#):not(#\#):not(#\#):not(#\#):not(#\#):not(#\#):not(#\#):not(#\#)::before{right:-2px}
1451
1470
  .x51xajf:not(#\#):not(#\#):not(#\#):not(#\#):not(#\#):not(#\#):not(#\#):not(#\#)::before{top:-2px}
1452
1471
  .x1m1drc7:not(#\#):not(#\#):not(#\#):not(#\#):not(#\#):not(#\#):not(#\#):not(#\#)::after{top:0}
1472
+ .xzkji8o:not(#\#):not(#\#):not(#\#):not(#\#):not(#\#):not(#\#):not(#\#):not(#\#)::after{width:6px}
1453
1473
  .xi8c0p9:not(#\#):not(#\#):not(#\#):not(#\#):not(#\#):not(#\#):not(#\#):not(#\#)::after{width:var(--indicator-width,1px)}
1454
1474
  }