@dreamtree-org/twreact-ui 1.3.10 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (230) hide show
  1. package/README.md +70 -6
  2. package/ai-skills/dreamtree-ui.md +58 -15
  3. package/codemods/v2.cjs +198 -0
  4. package/dist/ai-schema.json +745 -53
  5. package/dist/charts/index.js +195 -66
  6. package/dist/components/blocks/AppShell.d.ts.map +1 -1
  7. package/dist/components/blocks/BottomActionBar.d.ts +6 -0
  8. package/dist/components/blocks/BottomActionBar.d.ts.map +1 -0
  9. package/dist/components/blocks/ConfirmDialog.d.ts.map +1 -1
  10. package/dist/components/blocks/PageHeader.d.ts.map +1 -1
  11. package/dist/components/charts/CartesianChart.d.ts.map +1 -1
  12. package/dist/components/charts/ChartFrame.d.ts.map +1 -1
  13. package/dist/components/charts/useChartLayout.d.ts +25 -0
  14. package/dist/components/charts/useChartLayout.d.ts.map +1 -0
  15. package/dist/components/core/Button.d.ts.map +1 -1
  16. package/dist/components/core/Checkbox.d.ts.map +1 -1
  17. package/dist/components/core/ColorPicker.d.ts.map +1 -1
  18. package/dist/components/core/DatePicker.d.ts.map +1 -1
  19. package/dist/components/core/DateRangePicker.d.ts.map +1 -1
  20. package/dist/components/core/Form.d.ts.map +1 -1
  21. package/dist/components/core/Input.d.ts.map +1 -1
  22. package/dist/components/core/Loader.d.ts.map +1 -1
  23. package/dist/components/core/LocationPicker.d.ts.map +1 -1
  24. package/dist/components/core/Popover.d.ts.map +1 -1
  25. package/dist/components/core/ProgressBar.d.ts.map +1 -1
  26. package/dist/components/core/Radio.d.ts.map +1 -1
  27. package/dist/components/core/Rate.d.ts.map +1 -1
  28. package/dist/components/core/RoundedTag.d.ts.map +1 -1
  29. package/dist/components/core/SearchField.d.ts +9 -0
  30. package/dist/components/core/SearchField.d.ts.map +1 -0
  31. package/dist/components/core/SegmentedControl.d.ts +8 -0
  32. package/dist/components/core/SegmentedControl.d.ts.map +1 -0
  33. package/dist/components/core/Select.d.ts.map +1 -1
  34. package/dist/components/core/Skeleton.d.ts.map +1 -1
  35. package/dist/components/core/Slider.d.ts.map +1 -1
  36. package/dist/components/core/SpeechToText.d.ts.map +1 -1
  37. package/dist/components/core/SwipeableListItem.d.ts +4 -0
  38. package/dist/components/core/SwipeableListItem.d.ts.map +1 -0
  39. package/dist/components/core/SwipeableListItemGesture.d.ts +58 -0
  40. package/dist/components/core/SwipeableListItemGesture.d.ts.map +1 -0
  41. package/dist/components/core/Switch.d.ts.map +1 -1
  42. package/dist/components/core/Table/ActionMenu.d.ts +10 -3
  43. package/dist/components/core/Table/ActionMenu.d.ts.map +1 -1
  44. package/dist/components/core/Table/MobileCard.d.ts +10 -1
  45. package/dist/components/core/Table/MobileCard.d.ts.map +1 -1
  46. package/dist/components/core/Table/MobileFilters.d.ts +8 -2
  47. package/dist/components/core/Table/MobileFilters.d.ts.map +1 -1
  48. package/dist/components/core/Table/Table.d.ts.map +1 -1
  49. package/dist/components/core/Table/TableRow.d.ts +2 -1
  50. package/dist/components/core/Table/TableRow.d.ts.map +1 -1
  51. package/dist/components/core/Tabs.d.ts +5 -8
  52. package/dist/components/core/Tabs.d.ts.map +1 -1
  53. package/dist/components/core/Textarea.d.ts.map +1 -1
  54. package/dist/components/core/ThreeDotPopover.d.ts +10 -22
  55. package/dist/components/core/ThreeDotPopover.d.ts.map +1 -1
  56. package/dist/components/core/Tooltip.d.ts +2 -1
  57. package/dist/components/core/Tooltip.d.ts.map +1 -1
  58. package/dist/components/core/internal/Calendar.d.ts +77 -0
  59. package/dist/components/core/internal/Calendar.d.ts.map +1 -0
  60. package/dist/components/core/menu/ActionMenuSurface.d.ts +42 -0
  61. package/dist/components/core/menu/ActionMenuSurface.d.ts.map +1 -0
  62. package/dist/components/core/menu/ActionSheetMenu.d.ts +17 -0
  63. package/dist/components/core/menu/ActionSheetMenu.d.ts.map +1 -0
  64. package/dist/components/core/menu/MenuPopover.d.ts +21 -0
  65. package/dist/components/core/menu/MenuPopover.d.ts.map +1 -0
  66. package/dist/components/core/menu/menuItems.d.ts +21 -0
  67. package/dist/components/core/menu/menuItems.d.ts.map +1 -0
  68. package/dist/components/core/menu/menuKeyboard.d.ts +16 -0
  69. package/dist/components/core/menu/menuKeyboard.d.ts.map +1 -0
  70. package/dist/components/feedback/ActionSheet.d.ts +4 -0
  71. package/dist/components/feedback/ActionSheet.d.ts.map +1 -0
  72. package/dist/components/feedback/Alert.d.ts.map +1 -1
  73. package/dist/components/feedback/Dialog.d.ts.map +1 -1
  74. package/dist/components/feedback/Drawer.d.ts.map +1 -1
  75. package/dist/components/feedback/Sheet.d.ts +4 -0
  76. package/dist/components/feedback/Sheet.d.ts.map +1 -0
  77. package/dist/components/feedback/Toast.d.ts +4 -3
  78. package/dist/components/feedback/Toast.d.ts.map +1 -1
  79. package/dist/components/feedback/ToastItem.d.ts +47 -0
  80. package/dist/components/feedback/ToastItem.d.ts.map +1 -0
  81. package/dist/components/feedback/ToastStack.d.ts +20 -0
  82. package/dist/components/feedback/ToastStack.d.ts.map +1 -0
  83. package/dist/components/feedback/ToastSwipe.d.ts +22 -0
  84. package/dist/components/feedback/ToastSwipe.d.ts.map +1 -0
  85. package/dist/components/feedback/Tour.d.ts.map +1 -1
  86. package/dist/components/feedback/sheetCore.d.ts +30 -0
  87. package/dist/components/feedback/sheetCore.d.ts.map +1 -0
  88. package/dist/components/navigation/Breadcrumbs.d.ts.map +1 -1
  89. package/dist/components/navigation/FootNav.d.ts.map +1 -1
  90. package/dist/components/navigation/Navbar.d.ts.map +1 -1
  91. package/dist/components/navigation/Sidebar.d.ts +6 -0
  92. package/dist/components/navigation/Sidebar.d.ts.map +1 -1
  93. package/dist/components/rich-text/RichTextEditor.d.ts.map +1 -1
  94. package/dist/components/rich-text/RichTextToolbar.d.ts +6 -2
  95. package/dist/components/rich-text/RichTextToolbar.d.ts.map +1 -1
  96. package/dist/components/utility/Avatar.d.ts +8 -0
  97. package/dist/components/utility/Avatar.d.ts.map +1 -1
  98. package/dist/components/utility/AvatarGroup.d.ts +4 -0
  99. package/dist/components/utility/AvatarGroup.d.ts.map +1 -0
  100. package/dist/components/utility/Badge.d.ts.map +1 -1
  101. package/dist/components/utility/Card.d.ts.map +1 -1
  102. package/dist/components/utility/Carousel.d.ts.map +1 -1
  103. package/dist/components/utility/FileUpload.d.ts.map +1 -1
  104. package/dist/components/utility/Pagination.d.ts.map +1 -1
  105. package/dist/components/utility/PullToRefresh.d.ts +5 -0
  106. package/dist/components/utility/PullToRefresh.d.ts.map +1 -0
  107. package/dist/components/utility/Sortable.d.ts.map +1 -1
  108. package/dist/components/utility/Stepper.d.ts.map +1 -1
  109. package/dist/esm/components/blocks/AppShell.js +97 -90
  110. package/dist/esm/components/blocks/BottomActionBar.js +132 -0
  111. package/dist/esm/components/blocks/ConfirmDialog.js +40 -4
  112. package/dist/esm/components/blocks/EmptyState.js +1 -1
  113. package/dist/esm/components/blocks/ErrorState.js +1 -1
  114. package/dist/esm/components/blocks/LoadingState.js +4 -4
  115. package/dist/esm/components/blocks/PageHeader.js +16 -3
  116. package/dist/esm/components/blocks/StatCard.js +6 -6
  117. package/dist/esm/components/blocks/StatusPill.js +5 -8
  118. package/dist/esm/components/charts/CartesianChart.js +85 -62
  119. package/dist/esm/components/charts/ChartFrame.js +12 -5
  120. package/dist/esm/components/charts/useChartLayout.js +105 -0
  121. package/dist/esm/components/core/Button.js +49 -25
  122. package/dist/esm/components/core/Checkbox.js +72 -57
  123. package/dist/esm/components/core/ColorPicker.js +31 -42
  124. package/dist/esm/components/core/DatePicker.js +117 -304
  125. package/dist/esm/components/core/DateRangePicker.js +260 -308
  126. package/dist/esm/components/core/Form.js +11 -2
  127. package/dist/esm/components/core/Input.js +100 -38
  128. package/dist/esm/components/core/Loader.js +30 -44
  129. package/dist/esm/components/core/LocationPicker.js +82 -16
  130. package/dist/esm/components/core/Popover.js +59 -48
  131. package/dist/esm/components/core/ProgressBar.js +40 -72
  132. package/dist/esm/components/core/Radio.js +10 -12
  133. package/dist/esm/components/core/Rate.js +77 -14
  134. package/dist/esm/components/core/RoundedTag.js +5 -5
  135. package/dist/esm/components/core/SearchField.js +311 -0
  136. package/dist/esm/components/core/SegmentedControl.js +189 -0
  137. package/dist/esm/components/core/Select.js +185 -190
  138. package/dist/esm/components/core/Skeleton.js +13 -14
  139. package/dist/esm/components/core/Slider.js +24 -6
  140. package/dist/esm/components/core/SpeechToText.js +68 -16
  141. package/dist/esm/components/core/SwipeableListItem.js +286 -0
  142. package/dist/esm/components/core/SwipeableListItemGesture.js +219 -0
  143. package/dist/esm/components/core/Switch.js +98 -28
  144. package/dist/esm/components/core/Table/ActionMenu.js +45 -231
  145. package/dist/esm/components/core/Table/ColumnMenu.js +1 -1
  146. package/dist/esm/components/core/Table/MobileCard.js +66 -16
  147. package/dist/esm/components/core/Table/MobileFilters.js +181 -10
  148. package/dist/esm/components/core/Table/Table.js +149 -102
  149. package/dist/esm/components/core/Table/TableRow.js +10 -2
  150. package/dist/esm/components/core/Tabs.js +186 -39
  151. package/dist/esm/components/core/Textarea.js +10 -2
  152. package/dist/esm/components/core/ThreeDotPopover.js +93 -99
  153. package/dist/esm/components/core/Tooltip.js +138 -145
  154. package/dist/esm/components/core/internal/Calendar.js +381 -0
  155. package/dist/esm/components/core/menu/ActionMenuSurface.js +110 -0
  156. package/dist/esm/components/core/menu/ActionSheetMenu.js +140 -0
  157. package/dist/esm/components/core/menu/MenuPopover.js +177 -0
  158. package/dist/esm/components/core/menu/menuItems.js +61 -0
  159. package/dist/esm/components/core/menu/menuKeyboard.js +54 -0
  160. package/dist/esm/components/feedback/ActionSheet.js +55 -0
  161. package/dist/esm/components/feedback/Alert.js +63 -53
  162. package/dist/esm/components/feedback/Dialog.js +141 -53
  163. package/dist/esm/components/feedback/Drawer.js +130 -132
  164. package/dist/esm/components/feedback/Sheet.js +125 -0
  165. package/dist/esm/components/feedback/Toast.js +21 -291
  166. package/dist/esm/components/feedback/ToastItem.js +348 -0
  167. package/dist/esm/components/feedback/ToastStack.js +130 -0
  168. package/dist/esm/components/feedback/ToastSwipe.js +52 -0
  169. package/dist/esm/components/feedback/Tour.js +107 -30
  170. package/dist/esm/components/feedback/sheetCore.js +494 -0
  171. package/dist/esm/components/navigation/Breadcrumbs.js +97 -35
  172. package/dist/esm/components/navigation/Dropdown.js +1 -1
  173. package/dist/esm/components/navigation/FootNav.js +102 -13
  174. package/dist/esm/components/navigation/Navbar.js +172 -37
  175. package/dist/esm/components/navigation/Sidebar.js +263 -111
  176. package/dist/esm/components/rich-text/RichTextEditor.js +9 -3
  177. package/dist/esm/components/rich-text/RichTextToolbar.js +29 -9
  178. package/dist/esm/components/utility/Avatar.js +14 -12
  179. package/dist/esm/components/utility/AvatarGroup.js +56 -0
  180. package/dist/esm/components/utility/Badge.js +43 -23
  181. package/dist/esm/components/utility/Card.js +32 -8
  182. package/dist/esm/components/utility/Carousel.js +139 -29
  183. package/dist/esm/components/utility/FileUpload.js +106 -31
  184. package/dist/esm/components/utility/Pagination.js +31 -13
  185. package/dist/esm/components/utility/Portal.js +31 -0
  186. package/dist/esm/components/utility/PullToRefresh.js +229 -0
  187. package/dist/esm/components/utility/Sortable.js +6 -2
  188. package/dist/esm/components/utility/Stepper.js +110 -47
  189. package/dist/esm/hooks/useFloatingPosition.js +82 -0
  190. package/dist/esm/hooks/useHaptics.js +65 -0
  191. package/dist/esm/hooks/usePresence.js +58 -0
  192. package/dist/esm/hooks/usePresentation.js +21 -0
  193. package/dist/esm/hooks/useSheetDrag.js +220 -0
  194. package/dist/esm/hooks/useSliderValues.js +8 -2
  195. package/dist/esm/hooks/useSortable.js +107 -5
  196. package/dist/esm/index.js +14 -2
  197. package/dist/esm/utils/position.js +10 -63
  198. package/dist/esm/utils/touchTarget.js +4 -1
  199. package/dist/hooks/useFloatingPosition.d.ts +8 -4
  200. package/dist/hooks/useFloatingPosition.d.ts.map +1 -1
  201. package/dist/hooks/useHaptics.d.ts +19 -0
  202. package/dist/hooks/useHaptics.d.ts.map +1 -0
  203. package/dist/hooks/useSheetDrag.d.ts +31 -0
  204. package/dist/hooks/useSheetDrag.d.ts.map +1 -0
  205. package/dist/hooks/useSliderValues.d.ts +6 -2
  206. package/dist/hooks/useSliderValues.d.ts.map +1 -1
  207. package/dist/hooks/useSortable.d.ts +11 -1
  208. package/dist/hooks/useSortable.d.ts.map +1 -1
  209. package/dist/index.css +1 -1
  210. package/dist/index.d.ts +11 -2
  211. package/dist/index.esm.css +1 -1
  212. package/dist/index.esm.js +10645 -3842
  213. package/dist/index.js +10657 -3845
  214. package/dist/rich-text/index.js +41 -11
  215. package/dist/types/blocks.d.ts +32 -2
  216. package/dist/types/blocks.d.ts.map +1 -1
  217. package/dist/types/charts.d.ts +3 -1
  218. package/dist/types/charts.d.ts.map +1 -1
  219. package/dist/types/index.d.ts +409 -26
  220. package/dist/types/index.d.ts.map +1 -1
  221. package/dist/types/rich-text.d.ts +5 -0
  222. package/dist/types/rich-text.d.ts.map +1 -1
  223. package/dist/utils/position.d.ts +0 -15
  224. package/dist/utils/position.d.ts.map +1 -1
  225. package/doc/README.md +16 -0
  226. package/mcp/catalog.snapshot.json +1540 -407
  227. package/package.json +7 -2
  228. package/dist/esm/hooks/useSwipeToClose.js +0 -66
  229. package/dist/hooks/useSwipeToClose.d.ts +0 -24
  230. package/dist/hooks/useSwipeToClose.d.ts.map +0 -1
@@ -1,10 +1,12 @@
1
1
  'use client';
2
- import { objectWithoutProperties as _objectWithoutProperties, objectSpread2 as _objectSpread2, slicedToArray as _slicedToArray } from '../../_virtual/_rollupPluginBabelHelpers.js';
2
+ import { objectWithoutProperties as _objectWithoutProperties, slicedToArray as _slicedToArray, objectSpread2 as _objectSpread2 } from '../../_virtual/_rollupPluginBabelHelpers.js';
3
3
  import { jsx, jsxs } from 'react/jsx-runtime';
4
4
  import React from 'react';
5
5
  import { cn } from '../../utils/cn.js';
6
+ import { PRESS_FEEDBACK } from '../../utils/touchTarget.js';
6
7
 
7
- var _excluded = ["items", "className"];
8
+ var _excluded = ["items", "linkAs", "hideOnScroll", "badgeMax", "className", "onFocus"],
9
+ _excluded2 = ["className"];
8
10
  var hasBadge = function hasBadge(badge) {
9
11
  return badge === 0 || Boolean(badge);
10
12
  };
@@ -17,34 +19,100 @@ var passthrough = function passthrough(item) {
17
19
  return /^(aria|data)-/.test(key);
18
20
  }));
19
21
  };
22
+ // Numeric counts above `max` read `${max}+` so a big number can't widen the tab.
23
+ var formatBadge = function formatBadge(badge, max) {
24
+ return typeof badge === 'number' && typeof max === 'number' && badge > max ? "".concat(max, "+") : badge;
25
+ };
20
26
  var Badge = function Badge(_ref3) {
21
- var badge = _ref3.badge;
27
+ var badge = _ref3.badge,
28
+ max = _ref3.max;
22
29
  return jsx("span", {
23
- className: "absolute -top-1.5 left-full -ml-2 flex h-4 min-w-4 items-center justify-center rounded-full bg-error-600 px-1 text-micro font-semibold text-white",
30
+ className: "absolute -top-1.5 left-full -ml-2 flex h-4 min-w-4 items-center justify-center whitespace-nowrap rounded-full bg-error-600 px-1 text-micro font-semibold tabular-nums text-white",
24
31
  "data-slot": "badge",
25
- children: badge
32
+ children: formatBadge(badge, max)
26
33
  });
27
34
  };
35
+ // Scroll distance (px) that counts as a direction change, and the offset
36
+ // before the bar may hide (so it never hides at the very top of the page).
37
+ var SCROLL_DELTA = 8;
38
+ var HIDE_AFTER = 64;
39
+ // Hide on scroll down, show on scroll up. Passive listener; SSR-safe (the
40
+ // effect only runs in the browser).
41
+ function useHideOnScroll(enabled) {
42
+ var _React$useState = React.useState(false),
43
+ _React$useState2 = _slicedToArray(_React$useState, 2),
44
+ hidden = _React$useState2[0],
45
+ setHidden = _React$useState2[1];
46
+ React.useEffect(function () {
47
+ if (!enabled || typeof window === 'undefined') {
48
+ setHidden(false);
49
+ return undefined;
50
+ }
51
+ var lastY = window.scrollY || 0;
52
+ var onScroll = function onScroll() {
53
+ var y = window.scrollY || 0;
54
+ var delta = y - lastY;
55
+ if (Math.abs(delta) < SCROLL_DELTA) return;
56
+ setHidden(delta > 0 && y > HIDE_AFTER);
57
+ lastY = y;
58
+ };
59
+ window.addEventListener('scroll', onScroll, {
60
+ passive: true
61
+ });
62
+ return function () {
63
+ return window.removeEventListener('scroll', onScroll);
64
+ };
65
+ }, [enabled]);
66
+ return [hidden, setHidden];
67
+ }
28
68
  var FootNav = /*#__PURE__*/React.forwardRef(function (_ref4, ref) {
29
69
  var _ref4$items = _ref4.items,
30
70
  items = _ref4$items === void 0 ? [] : _ref4$items,
71
+ linkAs = _ref4.linkAs,
72
+ _ref4$hideOnScroll = _ref4.hideOnScroll,
73
+ hideOnScroll = _ref4$hideOnScroll === void 0 ? false : _ref4$hideOnScroll,
74
+ _ref4$badgeMax = _ref4.badgeMax,
75
+ badgeMax = _ref4$badgeMax === void 0 ? 99 : _ref4$badgeMax,
31
76
  className = _ref4.className,
77
+ onFocus = _ref4.onFocus,
32
78
  props = _objectWithoutProperties(_ref4, _excluded);
79
+ var _useHideOnScroll = useHideOnScroll(hideOnScroll),
80
+ _useHideOnScroll2 = _slicedToArray(_useHideOnScroll, 2),
81
+ hidden = _useHideOnScroll2[0],
82
+ setHidden = _useHideOnScroll2[1];
83
+ // Keyboard focus moving into a hidden bar brings it back.
84
+ var handleFocus = function handleFocus(e) {
85
+ if (hidden) setHidden(false);
86
+ onFocus === null || onFocus === void 0 || onFocus(e);
87
+ };
33
88
  return jsx("nav", _objectSpread2(_objectSpread2({
34
89
  ref: ref,
35
- className: cn('fixed bottom-0 left-0 right-0 bg-surface border-t border-line px-2 py-2 md:hidden', className)
90
+ "aria-label": "Primary",
91
+ "data-hidden": hideOnScroll ? String(hidden) : undefined,
92
+ className: cn('fixed bottom-0 left-0 right-0 z-sticky border-t border-line px-2 pb-safe md:hidden',
93
+ // Translucent where backdrop blur is supported, solid surface otherwise.
94
+ 'bg-surface supports-[backdrop-filter]:bg-surface/80 supports-[backdrop-filter]:backdrop-blur', hideOnScroll && 'transition-transform duration-base ease-standard motion-reduce:transition-none', hideOnScroll && hidden && 'translate-y-full', className),
95
+ onFocus: handleFocus
36
96
  }, props), {}, {
37
97
  children: jsx("div", {
38
- className: "flex items-stretch justify-around gap-1",
98
+ className: "flex items-stretch justify-around gap-1 py-2",
39
99
  children: items.map(function (item, index) {
40
- var _item$id;
100
+ var _item$to, _item$id;
41
101
  var isAction = item.variant === 'action';
42
- return jsxs("button", _objectSpread2(_objectSpread2({
43
- type: "button",
102
+ var isLink = item.href !== undefined || item.to !== undefined;
103
+ var Item = isLink ? linkAs || 'a' : 'button';
104
+ var linkProps = isLink ? _objectSpread2({
105
+ href: item.href
106
+ }, linkAs ? {
107
+ to: (_item$to = item.to) !== null && _item$to !== void 0 ? _item$to : item.href
108
+ } : {}) : {
109
+ type: 'button'
110
+ };
111
+ return jsxs(Item, _objectSpread2(_objectSpread2(_objectSpread2({}, linkProps), {}, {
44
112
  "aria-current": item.active ? 'page' : undefined,
45
113
  "data-variant": isAction ? 'action' : undefined
46
114
  }, passthrough(item)), {}, {
47
- className: cn('relative flex min-w-0 flex-1 flex-col items-center gap-1 rounded-md px-1 py-2 text-xs transition-colors', 'focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-500 focus-visible:ring-offset-1 motion-reduce:transition-none', isAction ? 'group justify-start py-0 font-medium text-muted' : item.active ? 'bg-primary-50 font-semibold text-primary-700 dark:bg-primary-900/40 dark:text-primary-300' : 'font-medium text-muted hover:bg-gray-100 hover:text-foreground dark:hover:bg-gray-800'),
115
+ className: cn('relative flex min-w-0 flex-1 flex-col items-center gap-1 rounded-md px-1 py-2 text-xs no-underline', PRESS_FEEDBACK, 'transition duration-fast ease-standard motion-reduce:transition-none', 'focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-500 focus-visible:ring-offset-1 dark:focus-visible:ring-offset-gray-900', isAction ? 'group justify-start py-0 font-medium text-muted' : item.active ? 'bg-primary-50 font-semibold text-primary-700 dark:bg-primary-900/40 dark:text-primary-300' : 'font-medium text-muted hover:bg-gray-100 hover:text-foreground dark:hover:bg-gray-800'),
48
116
  onClick: function onClick() {
49
117
  var _item$onClick;
50
118
  return (_item$onClick = item.onClick) === null || _item$onClick === void 0 ? void 0 : _item$onClick.call(item, item);
@@ -56,13 +124,15 @@ var FootNav = /*#__PURE__*/React.forwardRef(function (_ref4, ref) {
56
124
  className: "relative -mt-7 inline-flex h-14 w-14 items-center justify-center rounded-full bg-primary-600 text-2xl text-white shadow-lg ring-4 ring-surface transition-colors group-hover:bg-primary-700 motion-reduce:transition-none",
57
125
  "data-slot": "icon",
58
126
  children: [item.icon, hasBadge(item.badge) && jsx(Badge, {
59
- badge: item.badge
127
+ badge: item.badge,
128
+ max: badgeMax
60
129
  })]
61
130
  }) : jsxs("span", {
62
131
  className: "relative inline-flex text-lg",
63
132
  "data-slot": "icon",
64
133
  children: [item.icon, hasBadge(item.badge) && jsx(Badge, {
65
- badge: item.badge
134
+ badge: item.badge,
135
+ max: badgeMax
66
136
  })]
67
137
  }), jsx("span", {
68
138
  className: "block max-w-full truncate",
@@ -74,5 +144,24 @@ var FootNav = /*#__PURE__*/React.forwardRef(function (_ref4, ref) {
74
144
  }));
75
145
  });
76
146
  FootNav.displayName = 'FootNav';
147
+ // Reserves the bar's height plus the bottom safe area at the end of the page
148
+ // content, so nothing scrolls under the fixed bar. Hidden from `md` up, like
149
+ // the bar itself.
150
+ var FootNavSpacer = /*#__PURE__*/React.forwardRef(function (_ref5, ref) {
151
+ var className = _ref5.className,
152
+ props = _objectWithoutProperties(_ref5, _excluded2);
153
+ return jsx("div", _objectSpread2(_objectSpread2({
154
+ ref: ref,
155
+ "aria-hidden": "true",
156
+ "data-slot": "footnav-spacer",
157
+ className: cn('pb-safe md:hidden', className)
158
+ }, props), {}, {
159
+ children: jsx("div", {
160
+ className: "h-20"
161
+ })
162
+ }));
163
+ });
164
+ FootNavSpacer.displayName = 'FootNav.Spacer';
165
+ FootNav.Spacer = FootNavSpacer;
77
166
 
78
167
  export { FootNav as default };
@@ -2,15 +2,70 @@
2
2
  import { objectWithoutProperties as _objectWithoutProperties, slicedToArray as _slicedToArray, objectSpread2 as _objectSpread2 } from '../../_virtual/_rollupPluginBabelHelpers.js';
3
3
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
4
4
  import { cn } from '../../utils/cn.js';
5
- import React, { useState, useCallback, useEffect } from 'react';
5
+ import React, { useState, useRef, useCallback, useEffect, useSyncExternalStore } from 'react';
6
6
  import { ChevronLeft, Search, LogIn, LogOut, X, Menu, User, Bell } from 'lucide-react';
7
7
  import { Dropdown } from './Dropdown.js';
8
8
  import Avatar from '../utility/Avatar.js';
9
+ import Sheet from '../feedback/Sheet.js';
10
+ import SearchField from '../core/SearchField.js';
11
+ import { TOUCH_TARGET } from '../../utils/touchTarget.js';
9
12
  import PropTypes from '../../_vendor/prop-types/index.js';
10
13
 
11
14
  var _excluded = ["emitter", "content", "notifications"],
12
- _excluded2 = ["emitter", "logo", "center", "items", "user", "notifications", "searchable", "onSearch", "leftIcon", "rightMenuContent", "onLeftIconClick", "onLoginClick", "onLogoutClick", "className", "onFetch", "isMobileOpen", "setIsMobileOpen"];
15
+ _excluded2 = ["emitter", "logo", "center", "items", "user", "notifications", "searchable", "onSearch", "leftIcon", "rightMenuContent", "onLeftIconClick", "onLoginClick", "onLogoutClick", "className", "onFetch", "isMobileOpen", "setIsMobileOpen", "sticky", "largeTitle"];
13
16
  var NO_NOTIFICATIONS = [];
17
+ // Icon-only bar buttons: 36px on desktop, the 44px touch target on coarse pointers.
18
+ var ICON_BUTTON = cn("inline-flex items-center justify-center rounded-md p-2 text-primary-400 hover:text-primary-500 hover:bg-gray-100 dark:hover:bg-gray-800", "focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-500", TOUCH_TARGET);
19
+ // Badge counts above this read "99+".
20
+ var BADGE_MAX = 99;
21
+ var formatBadgeCount = function formatBadgeCount(count) {
22
+ return count > BADGE_MAX ? "".concat(BADGE_MAX, "+") : String(count);
23
+ };
24
+ // Large title: collapses past COLLAPSE_AT and re-expands under EXPAND_AT. The
25
+ // gap (hysteresis) stops the bar flickering when collapsing it shortens the page.
26
+ var LARGE_TITLE_COLLAPSE_AT = 44;
27
+ var LARGE_TITLE_EXPAND_AT = 8;
28
+ // Per-instance scroll store for useSyncExternalStore: one passive window
29
+ // scroll listener; `false` (expanded) on the server and while hydrating.
30
+ function createScrollCollapseStore() {
31
+ var collapsed = false;
32
+ var read = function read() {
33
+ var y = window.scrollY || window.pageYOffset || 0;
34
+ if (!collapsed && y > LARGE_TITLE_COLLAPSE_AT) collapsed = true;else if (collapsed && y < LARGE_TITLE_EXPAND_AT) collapsed = false;
35
+ };
36
+ return {
37
+ subscribe: function subscribe(onChange) {
38
+ if (typeof window === "undefined") return function () {};
39
+ var handle = function handle() {
40
+ var prev = collapsed;
41
+ read();
42
+ if (prev !== collapsed) onChange();
43
+ };
44
+ handle();
45
+ window.addEventListener("scroll", handle, {
46
+ passive: true
47
+ });
48
+ return function () {
49
+ return window.removeEventListener("scroll", handle);
50
+ };
51
+ },
52
+ getSnapshot: function getSnapshot() {
53
+ return collapsed;
54
+ }
55
+ };
56
+ }
57
+ var NEVER_SUBSCRIBE = function NEVER_SUBSCRIBE() {
58
+ return function () {};
59
+ };
60
+ var ALWAYS_FALSE = function ALWAYS_FALSE() {
61
+ return false;
62
+ };
63
+ function useLargeTitleCollapsed(enabled) {
64
+ var _useState = useState(createScrollCollapseStore),
65
+ _useState2 = _slicedToArray(_useState, 1),
66
+ store = _useState2[0];
67
+ return useSyncExternalStore(enabled ? store.subscribe : NEVER_SUBSCRIBE, enabled ? store.getSnapshot : ALWAYS_FALSE, ALWAYS_FALSE);
68
+ }
14
69
  // Simplified Notifications, uses notifications from props and a single effect for fetch/update
15
70
  var NotificationsDropdown = function NotificationsDropdown(_ref) {
16
71
  var emitter = _ref.emitter,
@@ -20,10 +75,10 @@ var NotificationsDropdown = function NotificationsDropdown(_ref) {
20
75
  onFetch = _ref$onFetch === void 0 ? function () {
21
76
  return console.log("invoked onFetch notifications");
22
77
  } : _ref$onFetch;
23
- var _useState = useState(initialNotifications),
24
- _useState2 = _slicedToArray(_useState, 2),
25
- notifications = _useState2[0],
26
- setNotifications = _useState2[1];
78
+ var _useState3 = useState(initialNotifications),
79
+ _useState4 = _slicedToArray(_useState3, 2),
80
+ notifications = _useState4[0],
81
+ setNotifications = _useState4[1];
27
82
  var onView = function onView() {
28
83
  emitter.emit("notification:view", notifications);
29
84
  };
@@ -46,10 +101,10 @@ var NotificationsDropdown = function NotificationsDropdown(_ref) {
46
101
  };
47
102
  // Reset to the `notifications` prop when it changes (adjust-during-render
48
103
  // pattern), then let onFetch refresh from the server.
49
- var _useState3 = useState(initialNotifications),
50
- _useState4 = _slicedToArray(_useState3, 2),
51
- prevInitial = _useState4[0],
52
- setPrevInitial = _useState4[1];
104
+ var _useState5 = useState(initialNotifications),
105
+ _useState6 = _slicedToArray(_useState5, 2),
106
+ prevInitial = _useState6[0],
107
+ setPrevInitial = _useState6[1];
53
108
  if (initialNotifications !== prevInitial) {
54
109
  setPrevInitial(initialNotifications);
55
110
  setNotifications(initialNotifications);
@@ -67,13 +122,19 @@ var NotificationsDropdown = function NotificationsDropdown(_ref) {
67
122
  // For simplicity, shows notifications in the dropdown, can be expanded
68
123
  return jsx(Dropdown, {
69
124
  trigger: jsxs("button", {
70
- className: "relative rounded-md p-2 text-primary-400 hover:text-primary-500 hover:bg-gray-100 dark:hover:bg-gray-800",
71
- "aria-label": "notifications",
125
+ type: "button",
126
+ className: cn(ICON_BUTTON, "relative"),
127
+ "aria-label": unreadCount > 0 ? "Notifications, ".concat(formatBadgeCount(unreadCount), " unread") : "Notifications",
72
128
  children: [jsx(Bell, {
73
- className: "h-5 w-5"
74
- }), unreadCount > 0 && jsx("span", {
75
- className: "absolute -top-1 -right-1 h-4 w-4 rounded-full bg-error-500 text-xs text-white flex items-center justify-center",
76
- children: unreadCount
129
+ className: "h-5 w-5",
130
+ "aria-hidden": "true"
131
+ }), unreadCount > 0 &&
132
+ // Grows with the count (min-w + padding) and caps at "99+".
133
+ jsx("span", {
134
+ "aria-hidden": "true",
135
+ "data-navbar-badge": "",
136
+ className: "absolute -right-0.5 -top-0.5 inline-flex h-4 min-w-4 items-center justify-center rounded-pill bg-error-500 px-1 text-micro font-semibold leading-none text-white",
137
+ children: formatBadgeCount(unreadCount)
77
138
  })]
78
139
  }),
79
140
  placement: "bottom-end",
@@ -159,7 +220,7 @@ var RightMenuContent = function RightMenuContent(_ref2) {
159
220
  });
160
221
  };
161
222
  var styledClassNames = {
162
- button: "rounded-md p-2 text-primary-400 hover:text-primary-500 hover:bg-gray-100 dark:hover:bg-gray-800",
223
+ button: ICON_BUTTON,
163
224
  mobileItem: "md:hidden",
164
225
  icon: "h-6 w-6",
165
226
  dropdown: "w-56 rounded-md border border-line bg-surface py-1 shadow-lg"
@@ -192,19 +253,29 @@ var Navbar = /*#__PURE__*/React.forwardRef(function (_ref3, ref) {
192
253
  onFetch = _ref3.onFetch;
193
254
  _ref3.isMobileOpen;
194
255
  _ref3.setIsMobileOpen;
195
- var props = _objectWithoutProperties(_ref3, _excluded2);
196
- var _useState5 = useState(false),
197
- _useState6 = _slicedToArray(_useState5, 2),
198
- isMobileMenuOpen = _useState6[0],
199
- setIsMobileMenuOpen = _useState6[1];
200
- var _useState7 = useState(""),
256
+ var _ref3$sticky = _ref3.sticky,
257
+ sticky = _ref3$sticky === void 0 ? false : _ref3$sticky,
258
+ largeTitle = _ref3.largeTitle,
259
+ props = _objectWithoutProperties(_ref3, _excluded2);
260
+ var _useState7 = useState(false),
201
261
  _useState8 = _slicedToArray(_useState7, 2),
202
- searchTerm = _useState8[0],
203
- setSearchTerm = _useState8[1];
204
- var _useState9 = useState(typeof window !== "undefined" ? window.innerWidth < 768 : false),
262
+ isMobileMenuOpen = _useState8[0],
263
+ setIsMobileMenuOpen = _useState8[1];
264
+ var _useState9 = useState(""),
205
265
  _useState0 = _slicedToArray(_useState9, 2),
206
- isMobile = _useState0[0],
207
- setIsMobile = _useState0[1];
266
+ searchTerm = _useState0[0],
267
+ setSearchTerm = _useState0[1];
268
+ var _useState1 = useState(false),
269
+ _useState10 = _slicedToArray(_useState1, 2),
270
+ isMobileSearchOpen = _useState10[0],
271
+ setIsMobileSearchOpen = _useState10[1];
272
+ var mobileSearchRef = useRef(null);
273
+ var hasLargeTitle = largeTitle !== undefined && largeTitle !== null && largeTitle !== false;
274
+ var titleCollapsed = useLargeTitleCollapsed(hasLargeTitle);
275
+ var _useState11 = useState(typeof window !== "undefined" ? window.innerWidth < 768 : false),
276
+ _useState12 = _slicedToArray(_useState11, 2),
277
+ isMobile = _useState12[0],
278
+ setIsMobile = _useState12[1];
208
279
  // Responsive flag update
209
280
  var handleResize = useCallback(function () {
210
281
  setIsMobile(window.innerWidth < 768);
@@ -221,6 +292,12 @@ var Navbar = /*#__PURE__*/React.forwardRef(function (_ref3, ref) {
221
292
  e.preventDefault();
222
293
  onSearch === null || onSearch === void 0 || onSearch(searchTerm);
223
294
  };
295
+ // Phones: the inline field is hidden below `sm`; a search button opens it
296
+ // in a top Sheet instead, sharing the same term and `onSearch`.
297
+ var handleMobileSearch = function handleMobileSearch(term) {
298
+ onSearch === null || onSearch === void 0 || onSearch(term);
299
+ setIsMobileSearchOpen(false);
300
+ };
224
301
  var handleLoginClick = function handleLoginClick() {
225
302
  onLoginClick === null || onLoginClick === void 0 || onLoginClick();
226
303
  };
@@ -262,11 +339,17 @@ var Navbar = /*#__PURE__*/React.forwardRef(function (_ref3, ref) {
262
339
  };
263
340
  return jsxs("nav", _objectSpread2(_objectSpread2({
264
341
  ref: ref,
265
- className: cn("bg-surface text-foreground border-b border-line px-4 sm:px-6 lg:px-8", className)
342
+ className: cn("bg-surface text-foreground border-b border-line px-4 sm:px-6 lg:px-8", sticky && "sticky top-0 z-sticky pt-safe supports-[backdrop-filter]:bg-surface/80 supports-[backdrop-filter]:backdrop-blur", className),
343
+ "data-sticky": sticky ? "" : undefined
266
344
  }, props), {}, {
267
345
  children: [jsxs("div", {
268
- className: "flex h-16 min-w-0 items-center justify-between gap-2 sm:gap-4",
269
- children: [jsxs("div", {
346
+ className: "relative flex h-16 min-w-0 items-center justify-between gap-2 sm:gap-4",
347
+ children: [hasLargeTitle && jsx("div", {
348
+ "aria-hidden": "true",
349
+ "data-navbar-compact-title": "",
350
+ className: cn("pointer-events-none absolute left-1/2 top-1/2 max-w-[50%] -translate-x-1/2 -translate-y-1/2 truncate text-base font-semibold", "transition-opacity duration-base ease-standard motion-reduce:transition-none", titleCollapsed ? "opacity-100" : "opacity-0"),
351
+ children: largeTitle
352
+ }), jsxs("div", {
270
353
  className: "flex min-w-0 shrink items-center",
271
354
  children: [leftIcon !== false && jsx("div", {
272
355
  className: "mr-2 hidden md:block md:mr-4",
@@ -275,8 +358,9 @@ var Navbar = /*#__PURE__*/React.forwardRef(function (_ref3, ref) {
275
358
  className: "flex min-w-0 items-center",
276
359
  children: [leftIcon !== false && jsx("button", {
277
360
  onClick: onLeftIconClick,
278
- className: "shrink-0 md:hidden rounded-md p-2 text-primary-400 hover:text-primary-500 hover:bg-gray-100 dark:hover:bg-gray-800",
279
- "aria-label": "back",
361
+ type: "button",
362
+ className: cn(ICON_BUTTON, "shrink-0 md:hidden"),
363
+ "aria-label": "Go back",
280
364
  children: leftIcon
281
365
  }), logo && jsx("div", {
282
366
  className: "ml-2 min-w-0 truncate sm:ml-4 md:ml-0",
@@ -309,6 +393,20 @@ var Navbar = /*#__PURE__*/React.forwardRef(function (_ref3, ref) {
309
393
  }
310
394
  })]
311
395
  })
396
+ }), searchable && jsx("button", {
397
+ type: "button",
398
+ className: cn(styledClassNames.button, "sm:hidden"),
399
+ onClick: function onClick() {
400
+ return setIsMobileSearchOpen(true);
401
+ },
402
+ "aria-label": "Search",
403
+ "aria-haspopup": "dialog",
404
+ "aria-expanded": isMobileSearchOpen,
405
+ "data-navbar-search-button": "",
406
+ children: jsx(Search, {
407
+ className: "h-5 w-5",
408
+ "aria-hidden": "true"
409
+ })
312
410
  }), jsx(RightMenuComponent, {
313
411
  notifications: notifications,
314
412
  onFetch: onFetch
@@ -333,12 +431,14 @@ var Navbar = /*#__PURE__*/React.forwardRef(function (_ref3, ref) {
333
431
  className: "relative",
334
432
  children: !user ? jsx("button", {
335
433
  className: cn(styledClassNames.button, styledClassNames.mobileItem),
434
+ type: "button",
336
435
  onClick: function onClick() {
337
436
  return setIsMobileMenuOpen(function (s) {
338
437
  return !s;
339
438
  });
340
439
  },
341
- "aria-label": "menu",
440
+ "aria-label": isMobileMenuOpen ? "Close menu" : "Open menu",
441
+ "aria-expanded": isMobileMenuOpen,
342
442
  children: isMobileMenuOpen ? jsx(X, {
343
443
  className: styledClassNames.icon
344
444
  }) : jsx(Menu, {
@@ -346,8 +446,9 @@ var Navbar = /*#__PURE__*/React.forwardRef(function (_ref3, ref) {
346
446
  })
347
447
  }) : jsx(Dropdown, {
348
448
  trigger: jsx("button", {
449
+ type: "button",
349
450
  className: styledClassNames.button,
350
- "aria-label": "user menu",
451
+ "aria-label": "User menu",
351
452
  children: user.avatar || user.name ?
352
453
  // Image when `avatar` is set; otherwise initials from
353
454
  // `name` (Avatar's own fallback logic).
@@ -361,7 +462,7 @@ var Navbar = /*#__PURE__*/React.forwardRef(function (_ref3, ref) {
361
462
  })
362
463
  }),
363
464
  placement: "bottom-end",
364
- className: "z-50",
465
+ className: "z-dropdown",
365
466
  children: jsxs("div", {
366
467
  className: styledClassNames.dropdown,
367
468
  children: [jsxs("div", {
@@ -404,6 +505,38 @@ var Navbar = /*#__PURE__*/React.forwardRef(function (_ref3, ref) {
404
505
  })
405
506
  })]
406
507
  })]
508
+ }), hasLargeTitle && jsx("div", {
509
+ "data-navbar-large-title": "",
510
+ "data-collapsed": titleCollapsed ? "" : undefined,
511
+ className: cn("overflow-hidden transition-[max-height,opacity] duration-base ease-standard motion-reduce:transition-none", titleCollapsed ? "max-h-0 opacity-0" : "max-h-16 opacity-100"),
512
+ children: jsx("h1", {
513
+ className: "truncate pb-3 text-3xl font-bold tracking-tight text-foreground",
514
+ children: largeTitle
515
+ })
516
+ }), searchable && jsx(Sheet, {
517
+ open: isMobileSearchOpen,
518
+ onClose: function onClose() {
519
+ return setIsMobileSearchOpen(false);
520
+ },
521
+ placement: "top",
522
+ initialFocus: mobileSearchRef,
523
+ "aria-label": "Search",
524
+ "data-navbar-search-sheet": "",
525
+ children: jsx("div", {
526
+ className: "px-4 py-3",
527
+ children: jsx(SearchField, {
528
+ ref: mobileSearchRef,
529
+ presentation: "popover",
530
+ showCancel: true,
531
+ placeholder: "Search...",
532
+ value: searchTerm,
533
+ onValueChange: setSearchTerm,
534
+ onSearch: handleMobileSearch,
535
+ onCancel: function onCancel() {
536
+ return setIsMobileSearchOpen(false);
537
+ }
538
+ })
539
+ })
407
540
  }), isMobileMenuOpen && !user && jsx("div", {
408
541
  className: "md:hidden h-full",
409
542
  children: jsx("div", {
@@ -431,7 +564,9 @@ Navbar.propTypes = {
431
564
  user: PropTypes.object,
432
565
  notifications: PropTypes.array,
433
566
  searchable: PropTypes.bool,
434
- onSearch: PropTypes.func
567
+ onSearch: PropTypes.func,
568
+ sticky: PropTypes.bool,
569
+ largeTitle: PropTypes.node
435
570
  };
436
571
 
437
572
  export { Navbar, Navbar as default };