@dreamtree-org/twreact-ui 2.0.3 → 2.0.5

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 (55) hide show
  1. package/README.md +17 -6
  2. package/ai-skills/dreamtree-ui.md +12 -5
  3. package/dist/ai-schema.json +150 -1
  4. package/dist/charts/index.d.ts +2 -1
  5. package/dist/charts/index.js +297 -12
  6. package/dist/components/charts/ChartFrame.d.ts +1 -0
  7. package/dist/components/charts/ChartFrame.d.ts.map +1 -1
  8. package/dist/components/charts/ScatterChart.d.ts +4 -0
  9. package/dist/components/charts/ScatterChart.d.ts.map +1 -0
  10. package/dist/components/charts/ScatterTooltip.d.ts +14 -0
  11. package/dist/components/charts/ScatterTooltip.d.ts.map +1 -0
  12. package/dist/components/charts/scatterData.d.ts +17 -0
  13. package/dist/components/charts/scatterData.d.ts.map +1 -0
  14. package/dist/components/core/DatePicker.d.ts.map +1 -1
  15. package/dist/components/core/DateRangePicker.d.ts.map +1 -1
  16. package/dist/components/core/Select.d.ts.map +1 -1
  17. package/dist/components/navigation/Navbar.d.ts.map +1 -1
  18. package/dist/components/navigation/Sidebar.d.ts +2 -0
  19. package/dist/components/navigation/Sidebar.d.ts.map +1 -1
  20. package/dist/components/utility/Sortable.d.ts.map +1 -1
  21. package/dist/components/utility/SortableGroup.d.ts +15 -0
  22. package/dist/components/utility/SortableGroup.d.ts.map +1 -0
  23. package/dist/esm/charts/index.js +1 -0
  24. package/dist/esm/components/charts/ChartFrame.js +5 -5
  25. package/dist/esm/components/charts/ScatterChart.js +204 -0
  26. package/dist/esm/components/charts/ScatterTooltip.js +61 -0
  27. package/dist/esm/components/charts/scatterData.js +43 -0
  28. package/dist/esm/components/core/DatePicker.js +4 -1
  29. package/dist/esm/components/core/DateRangePicker.js +6 -2
  30. package/dist/esm/components/core/Select.js +6 -2
  31. package/dist/esm/components/navigation/Navbar.js +23 -6
  32. package/dist/esm/components/navigation/Sidebar.js +9 -2
  33. package/dist/esm/components/utility/Sortable.js +70 -48
  34. package/dist/esm/components/utility/SortableGroup.js +48 -0
  35. package/dist/esm/hooks/useFocusTrap.js +21 -9
  36. package/dist/esm/hooks/useSortable.js +96 -30
  37. package/dist/esm/hooks/useSortableGroup.js +268 -0
  38. package/dist/esm/index.js +1 -0
  39. package/dist/hooks/useFocusTrap.d.ts.map +1 -1
  40. package/dist/hooks/useSortable.d.ts +12 -3
  41. package/dist/hooks/useSortable.d.ts.map +1 -1
  42. package/dist/hooks/useSortableGroup.d.ts +43 -0
  43. package/dist/hooks/useSortableGroup.d.ts.map +1 -0
  44. package/dist/index.css +1 -1
  45. package/dist/index.d.ts +2 -1
  46. package/dist/index.esm.css +1 -1
  47. package/dist/index.esm.js +936 -498
  48. package/dist/index.js +936 -497
  49. package/dist/types/charts.d.ts +28 -0
  50. package/dist/types/charts.d.ts.map +1 -1
  51. package/dist/types/index.d.ts +40 -1
  52. package/dist/types/index.d.ts.map +1 -1
  53. package/doc/README.md +2 -0
  54. package/mcp/catalog.snapshot.json +252 -14
  55. package/package.json +1 -1
@@ -0,0 +1,204 @@
1
+ 'use client';
2
+ import { objectWithoutProperties as _objectWithoutProperties, slicedToArray as _slicedToArray, objectSpread2 as _objectSpread2, toConsumableArray as _toConsumableArray } from '../../_virtual/_rollupPluginBabelHelpers.js';
3
+ import { jsx, jsxs } from 'react/jsx-runtime';
4
+ import React from 'react';
5
+ import { ResponsiveContainer, ScatterChart as ScatterChart$1, CartesianGrid, XAxis, YAxis, Tooltip, Scatter } from 'recharts';
6
+ import { ChartFrame } from './ChartFrame.js';
7
+ import ScatterTooltipContent from './ScatterTooltip.js';
8
+ import { useChartTheme, useChartId, defaultValueFormatter } from './chartTheme.js';
9
+ import { useElementWidth, tickGapFor, yAxisWidthFor } from './useChartLayout.js';
10
+ import { groupScatter, scatterDomain, textOf } from './scatterData.js';
11
+ import { useReducedMotion } from '../../hooks/useReducedMotion.js';
12
+
13
+ var _excluded = ["data", "x", "y", "index", "category", "colors", "valueFormatter", "xAxisFormatter", "xAxisLabel", "yAxisLabel", "height", "aspectRatio", "showLegend", "showTooltip", "showXAxis", "showYAxis", "showGrid", "yAxisWidth", "animate", "title", "description", "emptyState", "accessibleTable", "className"];
14
+ var ScatterChart = /*#__PURE__*/React.forwardRef(function ScatterChart(_ref, ref) {
15
+ var data = _ref.data,
16
+ x = _ref.x,
17
+ y = _ref.y,
18
+ index = _ref.index,
19
+ category = _ref.category,
20
+ colors = _ref.colors,
21
+ _ref$valueFormatter = _ref.valueFormatter,
22
+ valueFormatter = _ref$valueFormatter === void 0 ? defaultValueFormatter : _ref$valueFormatter,
23
+ _ref$xAxisFormatter = _ref.xAxisFormatter,
24
+ xAxisFormatter = _ref$xAxisFormatter === void 0 ? defaultValueFormatter : _ref$xAxisFormatter,
25
+ xAxisLabel = _ref.xAxisLabel,
26
+ yAxisLabel = _ref.yAxisLabel,
27
+ _ref$height = _ref.height,
28
+ height = _ref$height === void 0 ? 300 : _ref$height,
29
+ aspectRatio = _ref.aspectRatio,
30
+ _ref$showLegend = _ref.showLegend,
31
+ showLegend = _ref$showLegend === void 0 ? true : _ref$showLegend,
32
+ _ref$showTooltip = _ref.showTooltip,
33
+ showTooltip = _ref$showTooltip === void 0 ? true : _ref$showTooltip,
34
+ _ref$showXAxis = _ref.showXAxis,
35
+ showXAxis = _ref$showXAxis === void 0 ? true : _ref$showXAxis,
36
+ _ref$showYAxis = _ref.showYAxis,
37
+ showYAxis = _ref$showYAxis === void 0 ? true : _ref$showYAxis,
38
+ _ref$showGrid = _ref.showGrid,
39
+ showGrid = _ref$showGrid === void 0 ? true : _ref$showGrid,
40
+ yAxisWidth = _ref.yAxisWidth,
41
+ _ref$animate = _ref.animate,
42
+ animate = _ref$animate === void 0 ? true : _ref$animate,
43
+ title = _ref.title,
44
+ description = _ref.description,
45
+ emptyState = _ref.emptyState,
46
+ _ref$accessibleTable = _ref.accessibleTable,
47
+ accessibleTable = _ref$accessibleTable === void 0 ? true : _ref$accessibleTable,
48
+ className = _ref.className,
49
+ rest = _objectWithoutProperties(_ref, _excluded);
50
+ var _groupScatter = groupScatter(data, {
51
+ x: x,
52
+ y: y,
53
+ category: category
54
+ }),
55
+ rows = _groupScatter.rows,
56
+ names = _groupScatter.names,
57
+ groups = _groupScatter.groups;
58
+ var _useChartTheme = useChartTheme(names, colors),
59
+ chrome = _useChartTheme.chrome,
60
+ series = _useChartTheme.series;
61
+ var reducedMotion = useReducedMotion();
62
+ var chartId = useChartId();
63
+ var _useElementWidth = useElementWidth(),
64
+ _useElementWidth2 = _slicedToArray(_useElementWidth, 2),
65
+ measureRef = _useElementWidth2[0],
66
+ width = _useElementWidth2[1];
67
+ var tick = {
68
+ fill: chrome.axis,
69
+ fontSize: 12
70
+ };
71
+ var axisTitle = function axisTitle(value, angle) {
72
+ return value ? {
73
+ value: value,
74
+ angle: angle,
75
+ position: angle ? "insideLeft" : "insideBottom",
76
+ offset: angle ? 10 : -4,
77
+ fill: chrome.axis,
78
+ fontSize: 12
79
+ } : undefined;
80
+ };
81
+ var xLabel = xAxisLabel || x;
82
+ var yLabel = yAxisLabel || y;
83
+ return jsx(ChartFrame, _objectSpread2(_objectSpread2({
84
+ ref: ref,
85
+ className: className,
86
+ title: title,
87
+ description: description,
88
+ defaultTitle: "Scatter chart",
89
+ data: rows,
90
+ height: height,
91
+ aspectRatio: aspectRatio,
92
+ emptyState: emptyState,
93
+ accessibleTable: accessibleTable,
94
+ legend: showLegend && category ? names.map(function (name, i) {
95
+ return {
96
+ name: name,
97
+ color: series[i]
98
+ };
99
+ }) : null,
100
+ tableColumns: [].concat(_toConsumableArray(index ? [{
101
+ key: index,
102
+ label: index,
103
+ format: textOf
104
+ }] : []), _toConsumableArray(category && category !== index ? [{
105
+ key: category,
106
+ label: category,
107
+ format: textOf
108
+ }] : []), [{
109
+ key: x,
110
+ label: xLabel,
111
+ format: xAxisFormatter
112
+ }, {
113
+ key: y,
114
+ label: yLabel,
115
+ format: valueFormatter
116
+ }])
117
+ }, rest), {}, {
118
+ children: jsx("div", {
119
+ className: "h-full w-full",
120
+ ref: measureRef,
121
+ children: jsx(ResponsiveContainer, {
122
+ width: "100%",
123
+ height: "100%",
124
+ minWidth: 0,
125
+ children: jsxs(ScatterChart$1, {
126
+ id: chartId,
127
+ margin: {
128
+ top: 8,
129
+ right: 8,
130
+ bottom: xAxisLabel ? 16 : 0,
131
+ left: 0
132
+ },
133
+ accessibilityLayer: false,
134
+ children: [showGrid && jsx(CartesianGrid, {
135
+ stroke: chrome.grid,
136
+ strokeDasharray: "3 3"
137
+ }), jsx(XAxis, {
138
+ type: "number",
139
+ dataKey: x,
140
+ name: xLabel,
141
+ domain: scatterDomain(rows.map(function (row) {
142
+ return row[x];
143
+ })),
144
+ tick: tick,
145
+ tickLine: false,
146
+ axisLine: false,
147
+ tickFormatter: xAxisFormatter,
148
+ minTickGap: tickGapFor(width),
149
+ hide: !showXAxis,
150
+ label: axisTitle(xAxisLabel, 0)
151
+ }), jsx(YAxis, {
152
+ type: "number",
153
+ dataKey: y,
154
+ name: yLabel,
155
+ domain: scatterDomain(rows.map(function (row) {
156
+ return row[y];
157
+ })),
158
+ tick: tick,
159
+ tickLine: false,
160
+ axisLine: false,
161
+ tickFormatter: valueFormatter,
162
+ width: yAxisWidth !== null && yAxisWidth !== void 0 ? yAxisWidth : yAxisWidthFor(width),
163
+ hide: !showYAxis,
164
+ label: axisTitle(yAxisLabel, -90)
165
+ }), showTooltip && jsx(Tooltip, {
166
+ cursor: {
167
+ stroke: chrome.grid,
168
+ strokeDasharray: "3 3"
169
+ },
170
+ isAnimationActive: false,
171
+ content: jsx(ScatterTooltipContent, {
172
+ index: index,
173
+ category: category,
174
+ colorFor: Object.fromEntries(names.map(function (name, i) {
175
+ return [name, series[i]];
176
+ })),
177
+ x: {
178
+ key: x,
179
+ label: xLabel,
180
+ format: xAxisFormatter
181
+ },
182
+ y: {
183
+ key: y,
184
+ label: yLabel,
185
+ format: valueFormatter
186
+ }
187
+ })
188
+ }), names.map(function (name, i) {
189
+ return jsx(Scatter, {
190
+ id: "".concat(chartId, "-").concat(i),
191
+ name: name,
192
+ data: groups.get(name),
193
+ fill: series[i],
194
+ isAnimationActive: animate && !reducedMotion
195
+ }, name);
196
+ })]
197
+ })
198
+ })
199
+ })
200
+ }));
201
+ });
202
+ ScatterChart.displayName = "ScatterChart";
203
+
204
+ export { ScatterChart as default };
@@ -0,0 +1,61 @@
1
+ 'use client';
2
+ import { toConsumableArray as _toConsumableArray, slicedToArray as _slicedToArray } from '../../_virtual/_rollupPluginBabelHelpers.js';
3
+ import { jsxs, jsx } from 'react/jsx-runtime';
4
+ import 'react';
5
+ import { swatch } from './ChartFrame.js';
6
+ import { textOf } from './scatterData.js';
7
+
8
+ /**
9
+ * Tooltip for one ScatterChart point: the entity (`index`) or series name as the
10
+ * heading, then the series (when both are set) and the formatted x and y values.
11
+ */
12
+ function ScatterTooltipContent(_ref) {
13
+ var active = _ref.active,
14
+ payload = _ref.payload,
15
+ index = _ref.index,
16
+ category = _ref.category,
17
+ _ref$colorFor = _ref.colorFor,
18
+ colorFor = _ref$colorFor === void 0 ? {} : _ref$colorFor,
19
+ x = _ref.x,
20
+ y = _ref.y;
21
+ var row = active && payload && payload.length ? payload[0].payload : null;
22
+ if (!row) return null;
23
+ var seriesName = category ? textOf(row[category]) : null;
24
+ var heading = index ? textOf(row[index]) : seriesName;
25
+ var lines = [].concat(_toConsumableArray(index && seriesName ? [[category, seriesName]] : []), [[x.label, x.format(row[x.key])], [y.label, y.format(row[y.key])]]);
26
+ return jsxs("div", {
27
+ className: "rounded-md border border-line bg-surface px-3 py-2 text-xs text-foreground shadow-md",
28
+ "data-chart-tooltip": "",
29
+ children: [heading && jsxs("p", {
30
+ className: "mb-1 flex items-center gap-2 font-medium",
31
+ children: [seriesName && jsx("span", {
32
+ className: swatch("h-2 w-2"),
33
+ style: {
34
+ "--dt-chart-color": colorFor[seriesName]
35
+ },
36
+ "aria-hidden": "true"
37
+ }), jsx("span", {
38
+ children: heading
39
+ })]
40
+ }), jsx("ul", {
41
+ className: "space-y-0.5",
42
+ children: lines.map(function (_ref2) {
43
+ var _ref3 = _slicedToArray(_ref2, 2),
44
+ name = _ref3[0],
45
+ value = _ref3[1];
46
+ return jsxs("li", {
47
+ className: "flex items-center gap-2",
48
+ children: [jsx("span", {
49
+ className: "text-muted",
50
+ children: name
51
+ }), jsx("span", {
52
+ className: "ml-auto pl-3 font-medium tabular-nums",
53
+ children: value
54
+ })]
55
+ }, name);
56
+ })
57
+ })]
58
+ });
59
+ }
60
+
61
+ export { ScatterTooltipContent as default };
@@ -0,0 +1,43 @@
1
+ import { toConsumableArray as _toConsumableArray } from '../../_virtual/_rollupPluginBabelHelpers.js';
2
+
3
+ // Internal to the charts entry point: data shaping for ScatterChart.
4
+ var isFiniteNumber = function isFiniteNumber(v) {
5
+ return typeof v === "number" && Number.isFinite(v);
6
+ };
7
+ var textOf = function textOf(v) {
8
+ return v == null ? "" : String(v);
9
+ };
10
+ /**
11
+ * Axis domain. recharts draws a zero-width scale for a single distinct value, so
12
+ * pad around it; otherwise let recharts pick nice bounds.
13
+ */
14
+ function scatterDomain(values) {
15
+ if (!values.length) return ["auto", "auto"];
16
+ var min = Math.min.apply(Math, _toConsumableArray(values));
17
+ var max = Math.max.apply(Math, _toConsumableArray(values));
18
+ if (min !== max) return ["auto", "auto"];
19
+ var pad = Math.abs(min) / 2 || 1;
20
+ return [min - pad, max + pad];
21
+ }
22
+ /** Rows with a numeric x and y, split into series by the `category` key (first-seen order). */
23
+ function groupScatter(data, _ref) {
24
+ var x = _ref.x,
25
+ y = _ref.y,
26
+ category = _ref.category;
27
+ var rows = (Array.isArray(data) ? data : []).filter(function (row) {
28
+ return isFiniteNumber(row === null || row === void 0 ? void 0 : row[x]) && isFiniteNumber(row === null || row === void 0 ? void 0 : row[y]);
29
+ });
30
+ var groups = new Map();
31
+ rows.forEach(function (row) {
32
+ var name = category ? textOf(row[category]) : y;
33
+ if (!groups.has(name)) groups.set(name, []);
34
+ groups.get(name).push(row);
35
+ });
36
+ return {
37
+ rows: rows,
38
+ names: _toConsumableArray(groups.keys()),
39
+ groups: groups
40
+ };
41
+ }
42
+
43
+ export { groupScatter, scatterDomain, textOf };
@@ -210,10 +210,13 @@ var DatePicker = /*#__PURE__*/React.forwardRef(function (_ref, forwardedRef) {
210
210
  "aria-invalid": error ? true : undefined,
211
211
  className: cn("flex w-full items-center justify-between rounded-md border border-gray-300 bg-white px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-transparent disabled:cursor-not-allowed disabled:opacity-50", {
212
212
  "border-error-500 focus:ring-error-500": error,
213
- "text-primary-500": !selected,
213
+ // Empty: the placeholder reads muted, like Input's
214
+ // ::placeholder (#159). `className` still merges last.
215
+ "text-muted": !selected,
214
216
  // leave room for the sibling clear button overlaid on the field
215
217
  "pr-16": showClear && selected
216
218
  }, className),
219
+ "data-empty": selected ? undefined : "",
217
220
  onClick: function onClick() {
218
221
  return setIsOpen(function (open) {
219
222
  return !open;
@@ -279,7 +279,8 @@ var DateRangePicker = /*#__PURE__*/forwardRef(function DateRangePicker(_ref, ref
279
279
  }));
280
280
  };
281
281
  // Display placeholder until both start and end are committed (mandatory range).
282
- var triggerText = committed.start && committed.end ? formatRange(committed.start, committed.end) : placeholder;
282
+ var isEmpty = !committed.start || !committed.end;
283
+ var triggerText = isEmpty ? placeholder : formatRange(committed.start, committed.end);
283
284
  var draftText = localStart && localEnd ? formatRange(localStart, localEnd) : placeholder;
284
285
  var presetList = Array.isArray(presets) && presets.length > 0 ? presets : null;
285
286
  var presetChips = presetList ? jsx("div", {
@@ -359,9 +360,12 @@ var DateRangePicker = /*#__PURE__*/forwardRef(function DateRangePicker(_ref, ref
359
360
  "aria-invalid": error ? true : undefined,
360
361
  className: cn("flex w-full items-center justify-between rounded-md border border-gray-300 bg-white px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-transparent disabled:cursor-not-allowed disabled:opacity-50", {
361
362
  "border-error-500 focus:ring-error-500": error,
362
- "text-primary-500": !committed.start || !committed.end,
363
+ // Empty: the placeholder reads muted, like Input's
364
+ // ::placeholder (#159). `className` still merges last.
365
+ "text-muted": isEmpty,
363
366
  "pr-16": showClearButton
364
367
  }, className),
368
+ "data-empty": isEmpty ? "" : undefined,
365
369
  onClick: function onClick() {
366
370
  // The trigger always toggles, even mid-pick (DTUI-35): closing
367
371
  // drops the uncommitted start, reopening starts from the last
@@ -495,7 +495,8 @@ var Select = /*#__PURE__*/React.forwardRef(function (_ref, forwardedRef) {
495
495
  return document.removeEventListener("mousedown", handleClickOutside);
496
496
  };
497
497
  }, []);
498
- var showClearButton = allowClear && !disabled && selectedOptions.length > 0;
498
+ var isEmpty = selectedOptions.length === 0;
499
+ var showClearButton = allowClear && !disabled && !isEmpty;
499
500
  var displayValue = function displayValue() {
500
501
  var _selectedOptions$;
501
502
  if (multiSelect) {
@@ -753,9 +754,12 @@ var Select = /*#__PURE__*/React.forwardRef(function (_ref, forwardedRef) {
753
754
  "aria-invalid": error ? true : undefined,
754
755
  className: cn("flex w-full items-center justify-between rounded-md border border-gray-300 focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-transparent disabled:cursor-not-allowed disabled:opacity-50", SIZE_CLASSES[size] || SIZE_CLASSES.md, {
755
756
  "border-error-500 focus:ring-error-500": error,
756
- "text-primary-500": !selectedOptions.length,
757
+ // Empty: the placeholder reads muted, like Input's
758
+ // ::placeholder (#159). `className` still merges last.
759
+ "text-muted": isEmpty,
757
760
  "pr-14": showClearButton
758
761
  }, className),
762
+ "data-empty": isEmpty ? "" : undefined,
759
763
  onClick: function onClick() {
760
764
  if (!disabled) {
761
765
  setIsOpen(function (prev) {
@@ -12,7 +12,8 @@ import { TOUCH_TARGET } from '../../utils/touchTarget.js';
12
12
  import PropTypes from '../../_vendor/prop-types/index.js';
13
13
 
14
14
  var _excluded = ["emitter", "content", "notifications"],
15
- _excluded2 = ["emitter", "logo", "center", "items", "user", "notifications", "searchable", "onSearch", "leftIcon", "rightMenuContent", "onLeftIconClick", "onLoginClick", "onLogoutClick", "className", "onFetch", "isMobileOpen", "setIsMobileOpen", "sticky", "largeTitle"];
15
+ _excluded2 = ["emitter", "logo", "center", "items", "user", "notifications", "searchable", "onSearch", "leftIcon", "rightMenuContent", "onLeftIconClick", "leftIconLabel", "leftIconProps", "onLoginClick", "onLogoutClick", "className", "onFetch", "isMobileOpen", "setIsMobileOpen", "sticky", "largeTitle"],
16
+ _excluded3 = ["className", "onClick"];
16
17
  var NO_NOTIFICATIONS = [];
17
18
  // Icon-only bar buttons: 36px on desktop, the 44px touch target on coarse pointers.
18
19
  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);
@@ -241,6 +242,9 @@ var Navbar = /*#__PURE__*/React.forwardRef(function (_ref3, ref) {
241
242
  leftIcon = _ref3$leftIcon === void 0 ? jsx(ChevronLeft, {}) : _ref3$leftIcon,
242
243
  RightMenuContentProp = _ref3.rightMenuContent,
243
244
  onLeftIconClick = _ref3.onLeftIconClick,
245
+ _ref3$leftIconLabel = _ref3.leftIconLabel,
246
+ leftIconLabel = _ref3$leftIconLabel === void 0 ? "Go back" : _ref3$leftIconLabel,
247
+ leftIconProps = _ref3.leftIconProps,
244
248
  _ref3$onLoginClick = _ref3.onLoginClick,
245
249
  onLoginClick = _ref3$onLoginClick === void 0 ? function () {
246
250
  return console.log("invoked onLoginClick");
@@ -287,6 +291,18 @@ var Navbar = /*#__PURE__*/React.forwardRef(function (_ref3, ref) {
287
291
  window.removeEventListener("resize", handleResize);
288
292
  };
289
293
  }, [handleResize]);
294
+ // Left button: `leftIconLabel` names it ("Go back" by default; e.g. "Open
295
+ // menu" when it opens a Sidebar drawer). `leftIconProps` lands on the
296
+ // <button> (aria-expanded, aria-controls, data-*, …); its className merges
297
+ // last and its onClick runs after `onLeftIconClick`.
298
+ var _ref4 = leftIconProps || {},
299
+ leftIconClassName = _ref4.className,
300
+ leftIconOnClick = _ref4.onClick,
301
+ leftIconRest = _objectWithoutProperties(_ref4, _excluded3);
302
+ var handleLeftIconClick = function handleLeftIconClick(e) {
303
+ onLeftIconClick === null || onLeftIconClick === void 0 || onLeftIconClick(e);
304
+ leftIconOnClick === null || leftIconOnClick === void 0 || leftIconOnClick(e);
305
+ };
290
306
  // Handle search submit
291
307
  var handleSearch = function handleSearch(e) {
292
308
  e.preventDefault();
@@ -356,13 +372,14 @@ var Navbar = /*#__PURE__*/React.forwardRef(function (_ref3, ref) {
356
372
  "aria-hidden": true
357
373
  }), jsxs("div", {
358
374
  className: "flex min-w-0 items-center",
359
- children: [leftIcon !== false && jsx("button", {
360
- onClick: onLeftIconClick,
375
+ children: [leftIcon !== false && jsx("button", _objectSpread2(_objectSpread2({
361
376
  type: "button",
362
- className: cn(ICON_BUTTON, "shrink-0 md:hidden"),
363
- "aria-label": "Go back",
377
+ "aria-label": leftIconLabel
378
+ }, leftIconRest), {}, {
379
+ onClick: handleLeftIconClick,
380
+ className: cn(ICON_BUTTON, "shrink-0 md:hidden", leftIconClassName),
364
381
  children: leftIcon
365
- }), logo && jsx("div", {
382
+ })), logo && jsx("div", {
366
383
  className: "ml-2 min-w-0 truncate sm:ml-4 md:ml-0",
367
384
  children: logo
368
385
  })]
@@ -11,7 +11,7 @@ import { useScrollLock } from '../../hooks/useScrollLock.js';
11
11
  import { useMediaQuery } from '../../hooks/useMediaQuery.js';
12
12
  import { useSheetDrag, FLICK_VELOCITY } from '../../hooks/useSheetDrag.js';
13
13
 
14
- var _excluded = ["items", "collapsed", "onToggle", "className", "containerClassName", "stickyOffset", "logo", "footer", "user", "onUserClick", "drawerPosition", "isMobileOpen", "setIsMobileOpen", "showCollapsedTooltips", "tooltipOptions", "linkAs", "expandedIds", "defaultExpandedIds", "onExpandedChange", "swipeable"];
14
+ var _excluded = ["items", "collapsed", "collapsible", "onToggle", "className", "containerClassName", "stickyOffset", "logo", "footer", "user", "onUserClick", "drawerPosition", "isMobileOpen", "setIsMobileOpen", "showCollapsedTooltips", "tooltipOptions", "linkAs", "expandedIds", "defaultExpandedIds", "onExpandedChange", "swipeable"];
15
15
  // From `md` up the sidebar is an in-flow sticky column; below it, the same
16
16
  // tree becomes an off-canvas drawer (modal dialog while open).
17
17
  var SIDEBAR_DESKTOP_QUERY = "(min-width: 768px)";
@@ -178,6 +178,8 @@ var hasBadge = function hasBadge(item) {
178
178
  * - linkAs: element type used for items with `href` (default "a"; pass Next.js Link etc.)
179
179
  * - collapsed: boolean (desktop collapsed)
180
180
  * - onToggle: function to toggle collapse
181
+ * - collapsible: boolean (default true) — false hides the md+ collapse/expand
182
+ * toggle (the drawer keeps its "Close navigation" button)
181
183
  * - expandedIds / defaultExpandedIds / onExpandedChange: open groups (ids)
182
184
  * - className, containerClassName, stickyOffset, logo, footer, user, onUserClick
183
185
  * - drawerPosition: 'left' | 'right'
@@ -194,6 +196,8 @@ var Sidebar = /*#__PURE__*/React.forwardRef(function (_ref2, ref) {
194
196
  items = _ref2$items === void 0 ? [] : _ref2$items,
195
197
  _ref2$collapsed = _ref2.collapsed,
196
198
  collapsed = _ref2$collapsed === void 0 ? false : _ref2$collapsed,
199
+ _ref2$collapsible = _ref2.collapsible,
200
+ collapsible = _ref2$collapsible === void 0 ? true : _ref2$collapsible,
197
201
  onToggle = _ref2.onToggle,
198
202
  className = _ref2.className,
199
203
  containerClassName = _ref2.containerClassName,
@@ -525,6 +529,9 @@ var Sidebar = /*#__PURE__*/React.forwardRef(function (_ref2, ref) {
525
529
  if (drawerMode && mobileOpen && setIsMobileOpen) setIsMobileOpen(false);else onToggle === null || onToggle === void 0 || onToggle();
526
530
  };
527
531
  var toggleLabel = drawerMode ? "Close navigation" : effectiveCollapsed ? "Expand sidebar" : "Collapse sidebar";
532
+ // `collapsible={false}`: no collapse/expand toggle on md+. The drawer keeps
533
+ // its close button (44px touch target, the drawer's only visible close).
534
+ var showToggle = drawerMode || collapsible;
528
535
  var CloseChevron = isRight ? ChevronRight : ChevronLeft;
529
536
  var toggleIcon = drawerMode ? jsx(CloseChevron, {
530
537
  className: "h-5 w-5",
@@ -591,7 +598,7 @@ var Sidebar = /*#__PURE__*/React.forwardRef(function (_ref2, ref) {
591
598
  children: [!effectiveCollapsed && logo && jsx("div", {
592
599
  className: "flex min-w-0 items-center",
593
600
  children: logo
594
- }), jsx("button", {
601
+ }), showToggle && jsx("button", {
595
602
  type: "button",
596
603
  onClick: handleToggleClick,
597
604
  className: cn("inline-flex h-11 w-11 shrink-0 items-center justify-center rounded-md", "hover:bg-gray-100 dark:hover:bg-gray-800", "focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-500"),