@dreamtree-org/twreact-ui 1.1.72 → 1.1.73
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.
- package/README.md +15 -4
- package/dist/components/feedback/Toast.d.ts +6 -8
- package/dist/components/feedback/Toast.d.ts.map +1 -1
- package/dist/index.css +1 -1
- package/dist/index.esm.css +1 -1
- package/dist/index.esm.js +310 -115
- package/dist/index.js +309 -114
- package/mcp/catalog.snapshot.json +38 -8
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -247,6 +247,22 @@ const Calendar = createLucideIcon$1("Calendar", [
|
|
|
247
247
|
*/
|
|
248
248
|
|
|
249
249
|
|
|
250
|
+
const CheckCircle2 = createLucideIcon$1("CheckCircle2", [
|
|
251
|
+
[
|
|
252
|
+
"path",
|
|
253
|
+
{
|
|
254
|
+
d: "M12 22c5.523 0 10-4.477 10-10S17.523 2 12 2 2 6.477 2 12s4.477 10 10 10z",
|
|
255
|
+
key: "14v8dr"
|
|
256
|
+
}
|
|
257
|
+
],
|
|
258
|
+
["path", { d: "m9 12 2 2 4-4", key: "dzmm74" }]
|
|
259
|
+
]);
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* lucide-react v0.0.1 - ISC
|
|
263
|
+
*/
|
|
264
|
+
|
|
265
|
+
|
|
250
266
|
const CheckCircle = createLucideIcon$1("CheckCircle", [
|
|
251
267
|
["path", { d: "M22 11.08V12a10 10 0 1 1-5.93-9.14", key: "g774vq" }],
|
|
252
268
|
["polyline", { points: "22 4 12 14.01 9 11.01", key: "6xbx8j" }]
|
|
@@ -657,6 +673,17 @@ const User = createLucideIcon$1("User", [
|
|
|
657
673
|
*/
|
|
658
674
|
|
|
659
675
|
|
|
676
|
+
const XCircle = createLucideIcon$1("XCircle", [
|
|
677
|
+
["circle", { cx: "12", cy: "12", r: "10", key: "1mglay" }],
|
|
678
|
+
["path", { d: "m15 9-6 6", key: "1uzhvr" }],
|
|
679
|
+
["path", { d: "m9 9 6 6", key: "z0biqf" }]
|
|
680
|
+
]);
|
|
681
|
+
|
|
682
|
+
/**
|
|
683
|
+
* lucide-react v0.0.1 - ISC
|
|
684
|
+
*/
|
|
685
|
+
|
|
686
|
+
|
|
660
687
|
const X = createLucideIcon$1("X", [
|
|
661
688
|
["path", { d: "M18 6 6 18", key: "1bl5f8" }],
|
|
662
689
|
["path", { d: "m6 6 12 12", key: "d8bk6v" }]
|
|
@@ -19220,13 +19247,57 @@ Dialog.Footer = function (_ref6) {
|
|
|
19220
19247
|
}));
|
|
19221
19248
|
};
|
|
19222
19249
|
|
|
19223
|
-
var _excluded$7 = ["id", "title", "message", "type", "duration", "onClose", "position"];
|
|
19250
|
+
var _excluded$7 = ["id", "title", "message", "description", "type", "duration", "onClose", "position", "statusIcon", "actions", "color", "footerNote", "showFooterNote", "portal", "className"];
|
|
19224
19251
|
function ownKeys$b(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
|
|
19225
19252
|
function _objectSpread$b(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys$b(Object(t), true).forEach(function (r) { _defineProperty$4(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys$b(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
|
|
19253
|
+
var STATUS_CONFIG = {
|
|
19254
|
+
success: {
|
|
19255
|
+
icon: CheckCircle2,
|
|
19256
|
+
iconClass: 'text-emerald-500 dark:text-emerald-400',
|
|
19257
|
+
progressClass: 'bg-emerald-500 dark:bg-emerald-400'
|
|
19258
|
+
},
|
|
19259
|
+
error: {
|
|
19260
|
+
icon: XCircle,
|
|
19261
|
+
iconClass: 'text-rose-500 dark:text-rose-400',
|
|
19262
|
+
progressClass: 'bg-rose-500 dark:bg-rose-400'
|
|
19263
|
+
},
|
|
19264
|
+
warning: {
|
|
19265
|
+
icon: AlertTriangle,
|
|
19266
|
+
iconClass: 'text-amber-500 dark:text-amber-400',
|
|
19267
|
+
progressClass: 'bg-amber-500 dark:bg-amber-400'
|
|
19268
|
+
},
|
|
19269
|
+
info: {
|
|
19270
|
+
icon: Info,
|
|
19271
|
+
iconClass: 'text-sky-500 dark:text-sky-400',
|
|
19272
|
+
progressClass: 'bg-sky-500 dark:bg-sky-400'
|
|
19273
|
+
},
|
|
19274
|
+
"default": {
|
|
19275
|
+
icon: Bell,
|
|
19276
|
+
iconClass: 'text-primary-500 dark:text-primary-400',
|
|
19277
|
+
progressClass: 'bg-primary-500 dark:bg-primary-400'
|
|
19278
|
+
}
|
|
19279
|
+
};
|
|
19280
|
+
var POSITION_CLASSES = {
|
|
19281
|
+
'top-right': 'top-4 right-4 items-end',
|
|
19282
|
+
'top-left': 'top-4 left-4 items-start',
|
|
19283
|
+
'bottom-right': 'bottom-4 right-4 items-end',
|
|
19284
|
+
'bottom-left': 'bottom-4 left-4 items-start',
|
|
19285
|
+
'top-center': 'top-4 left-1/2 transform -translate-x-1/2 items-center',
|
|
19286
|
+
'bottom-center': 'bottom-4 left-1/2 transform -translate-x-1/2 items-center'
|
|
19287
|
+
};
|
|
19288
|
+
var STANDALONE_POSITION_CLASSES = {
|
|
19289
|
+
'top-right': 'top-4 right-4',
|
|
19290
|
+
'top-left': 'top-4 left-4',
|
|
19291
|
+
'bottom-right': 'bottom-4 right-4',
|
|
19292
|
+
'bottom-left': 'bottom-4 left-4',
|
|
19293
|
+
'top-center': 'top-4 left-1/2 transform -translate-x-1/2',
|
|
19294
|
+
'bottom-center': 'bottom-4 left-1/2 transform -translate-x-1/2'
|
|
19295
|
+
};
|
|
19226
19296
|
var Toast = /*#__PURE__*/React.forwardRef(function (_ref, ref) {
|
|
19227
19297
|
var id = _ref.id,
|
|
19228
19298
|
title = _ref.title,
|
|
19229
19299
|
message = _ref.message,
|
|
19300
|
+
description = _ref.description,
|
|
19230
19301
|
_ref$type = _ref.type,
|
|
19231
19302
|
type = _ref$type === void 0 ? 'info' : _ref$type,
|
|
19232
19303
|
_ref$duration = _ref.duration,
|
|
@@ -19234,153 +19305,277 @@ var Toast = /*#__PURE__*/React.forwardRef(function (_ref, ref) {
|
|
|
19234
19305
|
onClose = _ref.onClose,
|
|
19235
19306
|
_ref$position = _ref.position,
|
|
19236
19307
|
position = _ref$position === void 0 ? 'top-right' : _ref$position,
|
|
19308
|
+
statusIcon = _ref.statusIcon,
|
|
19309
|
+
_ref$actions = _ref.actions,
|
|
19310
|
+
actions = _ref$actions === void 0 ? [] : _ref$actions,
|
|
19311
|
+
color = _ref.color,
|
|
19312
|
+
footerNote = _ref.footerNote,
|
|
19313
|
+
_ref$showFooterNote = _ref.showFooterNote,
|
|
19314
|
+
showFooterNote = _ref$showFooterNote === void 0 ? false : _ref$showFooterNote,
|
|
19315
|
+
_ref$portal = _ref.portal,
|
|
19316
|
+
portal = _ref$portal === void 0 ? true : _ref$portal,
|
|
19317
|
+
_ref$className = _ref.className,
|
|
19318
|
+
className = _ref$className === void 0 ? '' : _ref$className,
|
|
19237
19319
|
props = _objectWithoutProperties$1(_ref, _excluded$7);
|
|
19238
19320
|
var _useState = React.useState(true),
|
|
19239
19321
|
_useState2 = _slicedToArray(_useState, 2),
|
|
19240
19322
|
isVisible = _useState2[0],
|
|
19241
19323
|
setIsVisible = _useState2[1];
|
|
19242
|
-
React.
|
|
19243
|
-
|
|
19244
|
-
|
|
19245
|
-
|
|
19246
|
-
|
|
19247
|
-
|
|
19248
|
-
|
|
19249
|
-
|
|
19250
|
-
|
|
19251
|
-
|
|
19252
|
-
var
|
|
19253
|
-
|
|
19324
|
+
var _useState3 = React.useState(false),
|
|
19325
|
+
_useState4 = _slicedToArray(_useState3, 2),
|
|
19326
|
+
isLeaving = _useState4[0],
|
|
19327
|
+
setIsLeaving = _useState4[1];
|
|
19328
|
+
var _useState5 = React.useState(false),
|
|
19329
|
+
_useState6 = _slicedToArray(_useState5, 2),
|
|
19330
|
+
isPaused = _useState6[0],
|
|
19331
|
+
setIsPaused = _useState6[1];
|
|
19332
|
+
var progressBarRef = React.useRef(null);
|
|
19333
|
+
var timerTextRef = React.useRef(null);
|
|
19334
|
+
var startTimeRef = React.useRef(null);
|
|
19335
|
+
var remainingRef = React.useRef(duration);
|
|
19336
|
+
var timerRef = React.useRef(null);
|
|
19337
|
+
var animFrameRef = React.useRef(null);
|
|
19338
|
+
var handleClose = React.useCallback(function () {
|
|
19339
|
+
if (isLeaving) return;
|
|
19340
|
+
setIsLeaving(true);
|
|
19254
19341
|
setTimeout(function () {
|
|
19342
|
+
setIsVisible(false);
|
|
19255
19343
|
onClose === null || onClose === void 0 || onClose(id);
|
|
19256
19344
|
}, 300);
|
|
19257
|
-
};
|
|
19258
|
-
var
|
|
19259
|
-
|
|
19260
|
-
|
|
19261
|
-
|
|
19262
|
-
|
|
19263
|
-
|
|
19264
|
-
|
|
19265
|
-
|
|
19266
|
-
|
|
19267
|
-
|
|
19268
|
-
|
|
19269
|
-
|
|
19270
|
-
|
|
19271
|
-
|
|
19272
|
-
|
|
19273
|
-
|
|
19274
|
-
|
|
19275
|
-
|
|
19276
|
-
|
|
19277
|
-
|
|
19278
|
-
|
|
19279
|
-
|
|
19345
|
+
}, [id, onClose, isLeaving]);
|
|
19346
|
+
var handleStopTimer = React.useCallback(function (e) {
|
|
19347
|
+
e === null || e === void 0 || e.stopPropagation();
|
|
19348
|
+
setIsPaused(true);
|
|
19349
|
+
if (timerRef.current) clearTimeout(timerRef.current);
|
|
19350
|
+
if (animFrameRef.current) cancelAnimationFrame(animFrameRef.current);
|
|
19351
|
+
}, []);
|
|
19352
|
+
// Direct DOM-based progress updates to prevent 60 FPS React re-renders and glitches
|
|
19353
|
+
React.useEffect(function () {
|
|
19354
|
+
if (duration <= 0) return;
|
|
19355
|
+
if (isPaused) {
|
|
19356
|
+
if (timerRef.current) clearTimeout(timerRef.current);
|
|
19357
|
+
if (animFrameRef.current) cancelAnimationFrame(animFrameRef.current);
|
|
19358
|
+
return;
|
|
19359
|
+
}
|
|
19360
|
+
startTimeRef.current = Date.now();
|
|
19361
|
+
timerRef.current = setTimeout(function () {
|
|
19362
|
+
handleClose();
|
|
19363
|
+
}, remainingRef.current);
|
|
19364
|
+
var _updateProgress = function updateProgress() {
|
|
19365
|
+
var elapsed = startTimeRef.current ? Date.now() - startTimeRef.current : 0;
|
|
19366
|
+
var currentRemaining = Math.max(0, remainingRef.current - elapsed);
|
|
19367
|
+
var pct = currentRemaining / duration * 100;
|
|
19368
|
+
if (progressBarRef.current) {
|
|
19369
|
+
progressBarRef.current.style.width = "".concat(pct, "%");
|
|
19370
|
+
}
|
|
19371
|
+
if (timerTextRef.current) {
|
|
19372
|
+
var secs = Math.ceil(currentRemaining / 1000);
|
|
19373
|
+
timerTextRef.current.textContent = "This message will close in ".concat(secs, " seconds. ");
|
|
19374
|
+
}
|
|
19375
|
+
if (currentRemaining > 0 && !isPaused) {
|
|
19376
|
+
animFrameRef.current = requestAnimationFrame(_updateProgress);
|
|
19377
|
+
}
|
|
19378
|
+
};
|
|
19379
|
+
animFrameRef.current = requestAnimationFrame(_updateProgress);
|
|
19380
|
+
return function () {
|
|
19381
|
+
if (timerRef.current) clearTimeout(timerRef.current);
|
|
19382
|
+
if (animFrameRef.current) cancelAnimationFrame(animFrameRef.current);
|
|
19383
|
+
if (!isPaused && startTimeRef.current) {
|
|
19384
|
+
var elapsed = Date.now() - startTimeRef.current;
|
|
19385
|
+
remainingRef.current = Math.max(0, remainingRef.current - elapsed);
|
|
19386
|
+
}
|
|
19387
|
+
};
|
|
19388
|
+
}, [duration, isPaused, handleClose]);
|
|
19280
19389
|
if (!isVisible) return null;
|
|
19281
|
-
|
|
19390
|
+
var statusStyle = STATUS_CONFIG[type] || STATUS_CONFIG.info || STATUS_CONFIG["default"];
|
|
19391
|
+
var StatusIconComponent = statusStyle.icon;
|
|
19392
|
+
var bodyText = description || message;
|
|
19393
|
+
var toastContent = jsxRuntime.jsxs("div", _objectSpread$b(_objectSpread$b({
|
|
19282
19394
|
ref: ref,
|
|
19283
|
-
|
|
19395
|
+
onMouseEnter: function onMouseEnter() {
|
|
19396
|
+
return setIsPaused(true);
|
|
19397
|
+
},
|
|
19398
|
+
onMouseLeave: function onMouseLeave() {
|
|
19399
|
+
return setIsPaused(false);
|
|
19400
|
+
},
|
|
19401
|
+
className: cn$1('relative overflow-hidden rounded-xl border border-slate-200/90 bg-white/95 shadow-xl backdrop-blur-md transition-all duration-300', 'dark:border-slate-800/80 dark:bg-[#18181b] dark:text-slate-100 dark:shadow-2xl', 'max-w-md w-full', isLeaving ? 'opacity-0 scale-95 translate-y-1' : 'animate-slide-up opacity-100 scale-100', portal ? ['fixed z-50', STANDALONE_POSITION_CLASSES[position] || STANDALONE_POSITION_CLASSES['top-right']] : '', className)
|
|
19284
19402
|
}, props), {}, {
|
|
19285
|
-
children: jsxRuntime.
|
|
19286
|
-
className: "
|
|
19287
|
-
children:
|
|
19288
|
-
className: "flex-
|
|
19289
|
-
children:
|
|
19290
|
-
|
|
19291
|
-
|
|
19292
|
-
|
|
19293
|
-
|
|
19294
|
-
|
|
19295
|
-
|
|
19296
|
-
|
|
19297
|
-
|
|
19403
|
+
children: [jsxRuntime.jsx("div", {
|
|
19404
|
+
className: "p-4",
|
|
19405
|
+
children: jsxRuntime.jsxs("div", {
|
|
19406
|
+
className: "flex items-start gap-3 justify-between",
|
|
19407
|
+
children: [jsxRuntime.jsx("span", {
|
|
19408
|
+
className: "flex-shrink-0 mt-0.5",
|
|
19409
|
+
children: statusIcon ? statusIcon : jsxRuntime.jsx(StatusIconComponent, {
|
|
19410
|
+
className: cn$1('h-5 w-5', statusStyle.iconClass)
|
|
19411
|
+
})
|
|
19412
|
+
}), jsxRuntime.jsxs("div", {
|
|
19413
|
+
className: "flex-1 min-w-0 pr-2",
|
|
19414
|
+
children: [title && jsxRuntime.jsx("h4", {
|
|
19415
|
+
className: "text-sm font-semibold text-slate-900 dark:text-slate-100 leading-snug",
|
|
19416
|
+
children: title
|
|
19417
|
+
}), bodyText && jsxRuntime.jsx("p", {
|
|
19418
|
+
className: "mt-1 text-xs text-slate-600 dark:text-slate-400 leading-relaxed whitespace-pre-line",
|
|
19419
|
+
children: bodyText
|
|
19420
|
+
}), actions && actions.length > 0 && jsxRuntime.jsx("div", {
|
|
19421
|
+
className: "mt-3 flex items-center gap-2 flex-wrap",
|
|
19422
|
+
children: actions.map(function (actItem, idx) {
|
|
19423
|
+
return jsxRuntime.jsx("button", {
|
|
19424
|
+
type: "button",
|
|
19425
|
+
onClick: function onClick(e) {
|
|
19426
|
+
var _actItem$onClick;
|
|
19427
|
+
(_actItem$onClick = actItem.onClick) === null || _actItem$onClick === void 0 || _actItem$onClick.call(actItem, e);
|
|
19428
|
+
if (actItem.dismiss !== false) {
|
|
19429
|
+
handleClose();
|
|
19430
|
+
}
|
|
19431
|
+
},
|
|
19432
|
+
className: cn$1('px-3 py-1.5 text-xs font-medium rounded-lg transition-colors border shadow-xs', actItem.variant === 'primary' ? 'bg-primary-600 hover:bg-primary-700 text-white border-primary-600' : 'bg-slate-100 dark:bg-zinc-800/80 text-slate-700 dark:text-zinc-200 border-slate-200 dark:border-zinc-700 hover:bg-slate-200 dark:hover:bg-zinc-700', actItem.className),
|
|
19433
|
+
children: actItem.label
|
|
19434
|
+
}, actItem.id || actItem.label || "act-".concat(idx));
|
|
19435
|
+
})
|
|
19436
|
+
})]
|
|
19437
|
+
}), jsxRuntime.jsx("button", {
|
|
19438
|
+
type: "button",
|
|
19439
|
+
"aria-label": "Close notification",
|
|
19440
|
+
className: "flex-shrink-0 rounded-lg p-1 text-slate-400 hover:text-slate-600 dark:hover:text-slate-200 hover:bg-slate-100 dark:hover:bg-zinc-800/70 transition-colors outline-none focus:ring-2 focus:ring-primary-500",
|
|
19441
|
+
onClick: handleClose,
|
|
19442
|
+
children: jsxRuntime.jsx(X, {
|
|
19443
|
+
className: "h-4 w-4"
|
|
19444
|
+
})
|
|
19298
19445
|
})]
|
|
19299
|
-
})
|
|
19300
|
-
|
|
19301
|
-
|
|
19302
|
-
|
|
19303
|
-
children: jsxRuntime.
|
|
19304
|
-
|
|
19446
|
+
})
|
|
19447
|
+
}), (showFooterNote || footerNote) && duration > 0 && jsxRuntime.jsx("div", {
|
|
19448
|
+
className: "px-4 py-2 bg-slate-50 dark:bg-zinc-950/40 border-t border-slate-100 dark:border-zinc-800/50 text-[11px] text-slate-500 dark:text-zinc-400 flex items-center justify-between",
|
|
19449
|
+
children: jsxRuntime.jsx("span", {
|
|
19450
|
+
children: footerNote ? footerNote : jsxRuntime.jsxs(jsxRuntime.Fragment, {
|
|
19451
|
+
children: [jsxRuntime.jsxs("span", {
|
|
19452
|
+
ref: timerTextRef,
|
|
19453
|
+
children: ["This message will close in ", Math.ceil(duration / 1000), " seconds.", ' ']
|
|
19454
|
+
}), jsxRuntime.jsx("button", {
|
|
19455
|
+
type: "button",
|
|
19456
|
+
onClick: handleStopTimer,
|
|
19457
|
+
className: "font-medium text-slate-700 dark:text-zinc-200 hover:underline inline",
|
|
19458
|
+
children: "Click to stop."
|
|
19459
|
+
})]
|
|
19305
19460
|
})
|
|
19306
|
-
})
|
|
19307
|
-
})
|
|
19308
|
-
|
|
19461
|
+
})
|
|
19462
|
+
}), duration > 0 && jsxRuntime.jsx("div", {
|
|
19463
|
+
className: "absolute bottom-0 left-0 right-0 h-0.5 bg-slate-100 dark:bg-zinc-800/80 overflow-hidden rounded-b-xl",
|
|
19464
|
+
children: jsxRuntime.jsx("div", {
|
|
19465
|
+
ref: progressBarRef,
|
|
19466
|
+
className: cn$1('h-full transition-all ease-linear', color ? '' : statusStyle.progressClass),
|
|
19467
|
+
style: {
|
|
19468
|
+
width: '100%',
|
|
19469
|
+
backgroundColor: color || undefined
|
|
19470
|
+
}
|
|
19471
|
+
})
|
|
19472
|
+
})]
|
|
19473
|
+
}));
|
|
19474
|
+
if (portal) {
|
|
19475
|
+
return /*#__PURE__*/ReactDOM.createPortal(toastContent, document.body);
|
|
19476
|
+
}
|
|
19477
|
+
return toastContent;
|
|
19309
19478
|
});
|
|
19310
19479
|
Toast.displayName = 'Toast';
|
|
19311
|
-
// Toast Container Component
|
|
19480
|
+
// Toast Container Component for vertical stacking & scrollable overflow without visible scrollbars
|
|
19312
19481
|
var ToastContainer = function ToastContainer(_ref2) {
|
|
19313
19482
|
var _ref2$toasts = _ref2.toasts,
|
|
19314
19483
|
toasts = _ref2$toasts === void 0 ? [] : _ref2$toasts,
|
|
19315
19484
|
_ref2$position = _ref2.position,
|
|
19316
19485
|
position = _ref2$position === void 0 ? 'top-right' : _ref2$position,
|
|
19317
|
-
onRemove = _ref2.onRemove
|
|
19318
|
-
|
|
19319
|
-
className
|
|
19320
|
-
|
|
19321
|
-
|
|
19322
|
-
|
|
19323
|
-
|
|
19324
|
-
|
|
19325
|
-
|
|
19486
|
+
onRemove = _ref2.onRemove,
|
|
19487
|
+
_ref2$className = _ref2.className,
|
|
19488
|
+
className = _ref2$className === void 0 ? '' : _ref2$className;
|
|
19489
|
+
if (!toasts || toasts.length === 0) {
|
|
19490
|
+
return null;
|
|
19491
|
+
}
|
|
19492
|
+
var posClass = POSITION_CLASSES[position] || POSITION_CLASSES['top-right'];
|
|
19493
|
+
return /*#__PURE__*/ReactDOM.createPortal(jsxRuntime.jsx("div", {
|
|
19494
|
+
className: cn$1('fixed z-50 flex flex-col gap-3 max-h-[calc(100vh-2rem)] overflow-y-auto overflow-x-hidden p-2 pointer-events-none', '[scrollbar-width:none] [-ms-overflow-style:none] [&::-webkit-scrollbar]:hidden', posClass, className),
|
|
19495
|
+
children: toasts.map(function (t) {
|
|
19496
|
+
return jsxRuntime.jsx("div", {
|
|
19497
|
+
className: "pointer-events-auto w-full max-w-md",
|
|
19498
|
+
children: jsxRuntime.jsx(Toast, _objectSpread$b(_objectSpread$b({}, t), {}, {
|
|
19499
|
+
portal: false,
|
|
19500
|
+
position: position,
|
|
19501
|
+
onClose: onRemove
|
|
19502
|
+
}))
|
|
19503
|
+
}, t.id);
|
|
19326
19504
|
})
|
|
19327
|
-
});
|
|
19505
|
+
}), document.body);
|
|
19328
19506
|
};
|
|
19329
19507
|
// Toast Hook
|
|
19330
19508
|
var useToast = function useToast() {
|
|
19331
|
-
var
|
|
19332
|
-
|
|
19333
|
-
toasts =
|
|
19334
|
-
setToasts =
|
|
19335
|
-
var addToast = function
|
|
19336
|
-
var id = Date.now().toString();
|
|
19509
|
+
var _useState7 = React.useState([]),
|
|
19510
|
+
_useState8 = _slicedToArray(_useState7, 2),
|
|
19511
|
+
toasts = _useState8[0],
|
|
19512
|
+
setToasts = _useState8[1];
|
|
19513
|
+
var addToast = React.useCallback(function (toastOptions) {
|
|
19514
|
+
var id = toastOptions.id || "".concat(Date.now(), "-").concat(Math.random().toString(36).slice(2, 7));
|
|
19515
|
+
var newToast = _objectSpread$b(_objectSpread$b({}, toastOptions), {}, {
|
|
19516
|
+
id: id
|
|
19517
|
+
});
|
|
19337
19518
|
setToasts(function (prev) {
|
|
19338
|
-
return [].concat(_toConsumableArray$1(prev), [
|
|
19339
|
-
id: id
|
|
19340
|
-
})]);
|
|
19519
|
+
return [].concat(_toConsumableArray$1(prev), [newToast]);
|
|
19341
19520
|
});
|
|
19342
|
-
|
|
19343
|
-
|
|
19521
|
+
return id;
|
|
19522
|
+
}, []);
|
|
19523
|
+
var removeToast = React.useCallback(function (id) {
|
|
19344
19524
|
setToasts(function (prev) {
|
|
19345
|
-
return prev.filter(function (
|
|
19346
|
-
return
|
|
19525
|
+
return prev.filter(function (t) {
|
|
19526
|
+
return t.id !== id;
|
|
19347
19527
|
});
|
|
19348
19528
|
});
|
|
19529
|
+
}, []);
|
|
19530
|
+
var clearToasts = React.useCallback(function () {
|
|
19531
|
+
setToasts([]);
|
|
19532
|
+
}, []);
|
|
19533
|
+
var toast = React.useCallback(function (options) {
|
|
19534
|
+
return addToast(typeof options === 'string' ? {
|
|
19535
|
+
message: options
|
|
19536
|
+
} : options);
|
|
19537
|
+
}, [addToast]);
|
|
19538
|
+
toast.success = function (title, message, options) {
|
|
19539
|
+
return addToast(_objectSpread$b({
|
|
19540
|
+
title: title,
|
|
19541
|
+
message: message,
|
|
19542
|
+
type: 'success'
|
|
19543
|
+
}, _typeof$4(options) === 'object' ? options : {}));
|
|
19349
19544
|
};
|
|
19350
|
-
|
|
19351
|
-
|
|
19352
|
-
|
|
19353
|
-
|
|
19354
|
-
|
|
19355
|
-
|
|
19356
|
-
|
|
19357
|
-
|
|
19358
|
-
|
|
19359
|
-
|
|
19360
|
-
|
|
19361
|
-
|
|
19362
|
-
|
|
19363
|
-
|
|
19364
|
-
|
|
19365
|
-
|
|
19366
|
-
|
|
19367
|
-
|
|
19368
|
-
|
|
19369
|
-
|
|
19370
|
-
|
|
19371
|
-
|
|
19372
|
-
|
|
19373
|
-
|
|
19374
|
-
|
|
19375
|
-
|
|
19376
|
-
|
|
19377
|
-
}, options));
|
|
19378
|
-
}
|
|
19545
|
+
toast.error = function (title, message, options) {
|
|
19546
|
+
return addToast(_objectSpread$b({
|
|
19547
|
+
title: title,
|
|
19548
|
+
message: message,
|
|
19549
|
+
type: 'error'
|
|
19550
|
+
}, _typeof$4(options) === 'object' ? options : {}));
|
|
19551
|
+
};
|
|
19552
|
+
toast.warning = function (title, message, options) {
|
|
19553
|
+
return addToast(_objectSpread$b({
|
|
19554
|
+
title: title,
|
|
19555
|
+
message: message,
|
|
19556
|
+
type: 'warning'
|
|
19557
|
+
}, _typeof$4(options) === 'object' ? options : {}));
|
|
19558
|
+
};
|
|
19559
|
+
toast.info = function (title, message, options) {
|
|
19560
|
+
return addToast(_objectSpread$b({
|
|
19561
|
+
title: title,
|
|
19562
|
+
message: message,
|
|
19563
|
+
type: 'info'
|
|
19564
|
+
}, _typeof$4(options) === 'object' ? options : {}));
|
|
19565
|
+
};
|
|
19566
|
+
toast["default"] = function (title, message, options) {
|
|
19567
|
+
return addToast(_objectSpread$b({
|
|
19568
|
+
title: title,
|
|
19569
|
+
message: message,
|
|
19570
|
+
type: 'default'
|
|
19571
|
+
}, _typeof$4(options) === 'object' ? options : {}));
|
|
19379
19572
|
};
|
|
19380
19573
|
return {
|
|
19381
19574
|
toast: toast,
|
|
19382
19575
|
toasts: toasts,
|
|
19383
|
-
removeToast: removeToast
|
|
19576
|
+
removeToast: removeToast,
|
|
19577
|
+
clearToasts: clearToasts,
|
|
19578
|
+
addToast: addToast
|
|
19384
19579
|
};
|
|
19385
19580
|
};
|
|
19386
19581
|
|
|
@@ -2611,7 +2611,7 @@
|
|
|
2611
2611
|
"useToast"
|
|
2612
2612
|
],
|
|
2613
2613
|
"hasDoc": true,
|
|
2614
|
-
"description": "Toast notification component and container. Use with `ToastContainer` and optional `useToast`
|
|
2614
|
+
"description": "Toast notification component and container. Use with `ToastContainer` and optional `useToast` hook to show temporary notifications with top status header, message body, action buttons, and animated bottom progress bar with hover-pause functionality.",
|
|
2615
2615
|
"props": [
|
|
2616
2616
|
{
|
|
2617
2617
|
"prop": "id",
|
|
@@ -2623,25 +2623,49 @@
|
|
|
2623
2623
|
"prop": "title",
|
|
2624
2624
|
"type": "`string`",
|
|
2625
2625
|
"default": "",
|
|
2626
|
-
"description": "Toast title"
|
|
2626
|
+
"description": "Toast title rendered in header"
|
|
2627
2627
|
},
|
|
2628
2628
|
{
|
|
2629
2629
|
"prop": "message",
|
|
2630
2630
|
"type": "`string`",
|
|
2631
2631
|
"default": "",
|
|
2632
|
-
"description": "Toast message"
|
|
2632
|
+
"description": "Toast message body (alias for `description`)"
|
|
2633
|
+
},
|
|
2634
|
+
{
|
|
2635
|
+
"prop": "description",
|
|
2636
|
+
"type": "`string`",
|
|
2637
|
+
"default": "",
|
|
2638
|
+
"description": "Toast body text"
|
|
2633
2639
|
},
|
|
2634
2640
|
{
|
|
2635
2641
|
"prop": "type",
|
|
2636
2642
|
"type": "`string`",
|
|
2637
2643
|
"default": "\"info\"",
|
|
2638
|
-
"description": "`success`, `error`, `warning`, `info`"
|
|
2644
|
+
"description": "Toast status type: `success`, `error`, `warning`, `info`, `default`"
|
|
2639
2645
|
},
|
|
2640
2646
|
{
|
|
2641
2647
|
"prop": "duration",
|
|
2642
2648
|
"type": "`number`",
|
|
2643
2649
|
"default": "5000",
|
|
2644
|
-
"description": "Auto-close ms (0 = no auto-close)"
|
|
2650
|
+
"description": "Auto-close timer in ms (0 = no auto-close)"
|
|
2651
|
+
},
|
|
2652
|
+
{
|
|
2653
|
+
"prop": "appIcon",
|
|
2654
|
+
"type": "`ReactNode`",
|
|
2655
|
+
"default": "<Sparkles />",
|
|
2656
|
+
"description": "Icon displayed in header next to status icon badge"
|
|
2657
|
+
},
|
|
2658
|
+
{
|
|
2659
|
+
"prop": "statusIcon",
|
|
2660
|
+
"type": "`ReactNode`",
|
|
2661
|
+
"default": "",
|
|
2662
|
+
"description": "Custom status icon component (overrides default type badge)"
|
|
2663
|
+
},
|
|
2664
|
+
{
|
|
2665
|
+
"prop": "actions",
|
|
2666
|
+
"type": "`Array`",
|
|
2667
|
+
"default": "[]",
|
|
2668
|
+
"description": "Action button definitions `[{ label, onClick, variant, dismiss }]`"
|
|
2645
2669
|
},
|
|
2646
2670
|
{
|
|
2647
2671
|
"prop": "onClose",
|
|
@@ -2653,10 +2677,16 @@
|
|
|
2653
2677
|
"prop": "position",
|
|
2654
2678
|
"type": "`string`",
|
|
2655
2679
|
"default": "\"topright\"",
|
|
2656
|
-
"description": "Position key
|
|
2680
|
+
"description": "Position key: `top-right`, `top-left`, `bottom-right`, `bottom-left`, `top-center`, `bottom-center`"
|
|
2681
|
+
},
|
|
2682
|
+
{
|
|
2683
|
+
"prop": "portal",
|
|
2684
|
+
"type": "`boolean`",
|
|
2685
|
+
"default": "true",
|
|
2686
|
+
"description": "Whether to render via React Portal (set to `false` when inside `ToastContainer`)"
|
|
2657
2687
|
}
|
|
2658
2688
|
],
|
|
2659
|
-
"examples": "```jsx\nfunction App() {\n const
|
|
2689
|
+
"examples": "```jsx\nfunction App() {\n const { toast, toasts, removeToast } = useToast();\n\n const handleShowToast = () => {\n toast.info('System Update', 'A new update is available for download.', {\n duration: 6000,\n actions: [\n { label: 'Update Now', variant: 'primary', onClick: () => alert('Updating...') },\n { label: 'Later', variant: 'secondary', onClick: () => alert('Postponed') },\n ],\n });\n };\n\n return (\n <>\n <Button onClick={handleShowToast}>\n Show Notification\n </Button>\n <ToastContainer toasts={toasts} onRemove={removeToast} position=\"top-right\" />\n </>\n );\n}\n```\n\n[← Component overview](README.md)"
|
|
2660
2690
|
},
|
|
2661
2691
|
{
|
|
2662
2692
|
"name": "Alert",
|
|
@@ -3201,7 +3231,7 @@
|
|
|
3201
3231
|
"Tabs": "# Tabs\n\nTabbed content with list and panels. Use subcomponents: `Tabs.List`, `Tabs.Tab`, `Tabs.Panels`, `Tabs.Panel`. Supports controlled (`index` + `onChange`) or uncontrolled (`defaultIndex`), keyboard navigation, and variants.\n\n## Import\n\n```jsx\nimport { Tabs } from '@dreamtree-org/twreact-ui';\n```\n\n## Props (Tabs root)\n\n| Prop | Type | Default | Description |\n|------|------|---------|-------------|\n| `children` | `ReactNode` | — | Must include `Tabs.List` and `Tabs.Panels`. |\n| `defaultIndex` | `number` | `0` | Uncontrolled initial active tab index. |\n| `index` | `number` | — | Controlled active index. |\n| `onChange` | `(index: number) => void` | — | Called when tab changes. |\n| `orientation` | `\"horizontal\"` \\| `\"vertical\"` | `\"horizontal\"` | Layout direction. |\n| `size` | `\"sm\"` \\| `\"md\"` \\| `\"lg\"` | `\"md\"` | Tab size. |\n| `variant` | `\"line\"` \\| `\"pills\"` \\| `\"unstyled\"` | `\"line\"` | Tab style. |\n| `animated` | `boolean` | `true` | Fade animation on panel change. |\n| `className` | `string` | `\"\"` | Wrapper CSS classes. |\n\n## Usage\n\n```jsx\n<Tabs defaultIndex={0} variant=\"pills\" size=\"md\">\n <Tabs.List>\n <Tabs.Tab>Home</Tabs.Tab>\n <Tabs.Tab>Profile</Tabs.Tab>\n <Tabs.Tab>Settings</Tabs.Tab>\n </Tabs.List>\n <Tabs.Panels>\n <Tabs.Panel>Home content</Tabs.Panel>\n <Tabs.Panel>Profile content</Tabs.Panel>\n <Tabs.Panel>Settings content</Tabs.Panel>\n </Tabs.Panels>\n</Tabs>\n```\n\nControlled:\n\n```jsx\nconst [index, setIndex] = useState(0);\n<Tabs index={index} onChange={setIndex}>\n ...\n</Tabs>\n```\n\n[← Component overview](README.md)\n",
|
|
3202
3232
|
"TextToSpeech": "# TextToSpeech\n\nText-to-speech using the browser’s Speech Synthesis API. Renders a default \"Speak\" / \"Stop\" button or a custom button via `renderButton`. Toggling speak while playing stops playback.\n\n## Import\n\n```jsx\nimport { TextToSpeech } from '@dreamtree-org/twreact-ui';\n```\n\n## Props\n\n| Prop | Type | Default | Description |\n|------|------|---------|-------------|\n| `text` | `string` | `\"\"` | Text to speak (used as initial input if component manages input). |\n| `rate` | `number` | `1` | Speech rate (0.1–10). |\n| `pitch` | `number` | `0.5` | Pitch (0–2). |\n| `onSpeak` | `(text: string) => void` | `() => {}` | Called when speech starts (with the text spoken). |\n| `renderButton` | `(props: { onClick, isSpeaking }) => ReactNode` | — | Custom button; receives `onClick` and `isSpeaking`. |\n\n## Behavior\n\n- Component keeps internal `inputText` state initialized from `text`; speaking uses that.\n- Clicking the button toggles: if not speaking, starts `SpeechSynthesisUtterance` with `inputText`; if speaking, calls `speechSynthesis.cancel()` and sets speaking to false.\n- `onSpeak` is called when speech starts.\n\n## Example\n\n```jsx\n<TextToSpeech text=\"Hello, world!\" rate={1} onSpeak={(t) => console.log('Speaking:', t)} />\n\n<TextToSpeech\n text={content}\n renderButton={({ onClick, isSpeaking }) => (\n <Button onClick={onClick}>{isSpeaking ? 'Stop' : 'Play'} audio</Button>\n )}\n/>\n```\n\n[← Component overview](README.md)\n",
|
|
3203
3233
|
"ThreeDotPopover": "# ThreeDotPopover\n\nDropdown menu triggered by a three-dot (kebab) button. Use for row actions (Edit, Delete, etc.). Closes on outside click and Escape; optional close on item select.\n\n## Import\n\n```jsx\nimport { ThreeDotPopover } from '@dreamtree-org/twreact-ui';\n```\n\n## Props\n\n| Prop | Type | Default | Description |\n|------|------|---------|-------------|\n| `items` | `Array` | `[]` | Menu items: `{ key, label, icon?, onClick?, disabled?, destructive? }`. `icon` can be component (e.g. `Edit2`). |\n| `trigger` | `ReactNode` | — | Custom trigger; default is three-dot button. |\n| `className` | `string` | `\"\"` | CSS classes for the **default** trigger button (merged via `cn()`). |\n| `wrapperClassName` | `string` | `\"\"` | CSS classes merged (via `cn()`) onto the root container element. |\n| `menuClass` | `string` | `\"\"` | Menu container CSS classes. |\n| `menuItemClass` | `string` | `\"\"` | Each menu item CSS classes. |\n| `closeOnSelect` | `boolean` | `true` | Close menu when an item is selected. |\n| `ariaLabel` | `string` | `\"More options\"` | Trigger button aria-label. |\n\n## Item shape\n\n- `key` (string): Unique key.\n- `label` (string): Display text.\n- `icon` (optional): React component or node.\n- `onClick(item)` (optional): Called when item is clicked.\n- `disabled` (boolean): Disable the item.\n- `destructive` (boolean): Style as destructive (e.g. red for Delete).\n\n## Example\n\n```jsx\nimport { Edit2, Eye, Trash } from 'lucide-react';\n\n<ThreeDotPopover\n items={[\n { key: 'edit', label: 'Edit', icon: Edit2, onClick: () => edit(row) },\n { key: 'view', label: 'View', icon: Eye, onClick: () => view(row) },\n { key: 'delete', label: 'Delete', destructive: true, onClick: () => remove(row) },\n ]}\n closeOnSelect\n/>\n```\n\n## Notes\n\n- Forwards `ref` to the root container element and spreads unknown props\n (`...rest`) onto it.\n\n[← Component overview](README.md)\n",
|
|
3204
|
-
"Toast": "# Toast\n\nToast notification component and container. Use with `ToastContainer` and optional `useToast`
|
|
3234
|
+
"Toast": "# Toast\n\nToast notification component and container. Use with `ToastContainer` and optional `useToast` hook to show temporary notifications with top status header, message body, action buttons, and animated bottom progress bar with hover-pause functionality.\n\n## Import\n\n```jsx\nimport { Toast, ToastContainer, useToast } from '@dreamtree-org/twreact-ui';\n```\n\n## Props (Toast)\n\n| Prop | Type | Default | Description |\n|------|------|---------|-------------|\n| `id` | `string` | — | Unique id (for key/onClose) |\n| `title` | `string` | — | Toast title rendered in header |\n| `message` | `string` | — | Toast message body (alias for `description`) |\n| `description` | `string` | — | Toast body text |\n| `type` | `string` | `\"info\"` | Toast status type: `success`, `error`, `warning`, `info`, `default` |\n| `duration` | `number` | `5000` | Auto-close timer in ms (0 = no auto-close) |\n| `appIcon` | `ReactNode` | `<Sparkles />` | Icon displayed in header next to status icon badge |\n| `statusIcon` | `ReactNode` | — | Custom status icon component (overrides default type badge) |\n| `actions` | `Array` | `[]` | Action button definitions `[{ label, onClick, variant, dismiss }]` |\n| `onClose` | `function` | — | Called when toast closes |\n| `position` | `string` | `\"top-right\"` | Position key: `top-right`, `top-left`, `bottom-right`, `bottom-left`, `top-center`, `bottom-center` |\n| `portal` | `boolean` | `true` | Whether to render via React Portal (set to `false` when inside `ToastContainer`) |\n\n## ToastContainer\n\n| Prop | Type | Default | Description |\n|------|------|---------|-------------|\n| `toasts` | `Array` | `[]` | Array of toast objects `{ id, title, message, description, type, actions, ... }` |\n| `position` | `string` | `\"top-right\"` | Stacking position on screen |\n| `onRemove` | `function` | — | Callback invoked when a toast is dismissed (passes toast `id`) |\n\n## Example\n\n```jsx\nfunction App() {\n const { toast, toasts, removeToast } = useToast();\n\n const handleShowToast = () => {\n toast.info('System Update', 'A new update is available for download.', {\n duration: 6000,\n actions: [\n { label: 'Update Now', variant: 'primary', onClick: () => alert('Updating...') },\n { label: 'Later', variant: 'secondary', onClick: () => alert('Postponed') },\n ],\n });\n };\n\n return (\n <>\n <Button onClick={handleShowToast}>\n Show Notification\n </Button>\n <ToastContainer toasts={toasts} onRemove={removeToast} position=\"top-right\" />\n </>\n );\n}\n```\n\n[← Component overview](README.md)\n\n",
|
|
3205
3235
|
"Tooltip": "# Tooltip\n\nTooltip shown on hover, focus, or click. Supports placement (auto/top/bottom/left/right), delay, offset, and optional portal. Content can be string or render prop `({ close }) => ReactNode`.\n\n## Import\n\n```jsx\nimport { Tooltip } from '@dreamtree-org/twreact-ui';\n```\n\n## Props\n\n| Prop | Type | Default | Description |\n|------|------|---------|-------------|\n| `position` | `\"auto\"` \\| `\"top\"` \\| `\"bottom\"` \\| `\"left\"` \\| `\"right\"` | `\"auto\"` | Preferred placement; `auto` flips to fit. |\n| `trigger` | `\"hover\"` \\| `\"click\"` \\| `\"focus\"` \\| `Array` | `\"hover\"` | How tooltip opens. |\n| `content` | `ReactNode` \\| `({ close }) => ReactNode` | — | Tooltip content. |\n| `customTrigger` | `ReactElement` | — | Use instead of children as trigger. |\n| `children` | `ReactNode` | — | Trigger element when customTrigger not used. |\n| `open` | `boolean` | — | Controlled open state. |\n| `defaultOpen` | `boolean` | `false` | Uncontrolled initial open. |\n| `onChange` | `(open: boolean) => void` | — | Called when open state changes. |\n| `delay` | `number` | `80` | Delay in ms before opening (hover). |\n| `offset` | `number` | `8` | Spacing in px between trigger and tooltip. |\n| `textColor` | `string` (hex) | — | Tooltip text color. |\n| `bgColor` | `string` (hex) | `\"#ffffff\"` | Tooltip background color. |\n| `className` | `string` | `\"\"` | Tooltip box CSS classes. |\n| `portal` | `boolean` | `true` | Render tooltip in document.body. |\n| `id` | `string` | — | Tooltip id (auto-generated if omitted). |\n\n## Example\n\n```jsx\n<Tooltip content=\"Save your changes\">\n <Button>Save</Button>\n</Tooltip>\n\n<Tooltip trigger=\"click\" content={({ close }) => <span>Click to close <button onClick={close}>OK</button></span>}>\n <span>Click me</span>\n</Tooltip>\n\n<Tooltip position=\"bottom\" delay={200} content=\"Delayed tip\">\n <Input placeholder=\"Focus me\" />\n</Tooltip>\n```\n\n[← Component overview](README.md)\n"
|
|
3206
3236
|
},
|
|
3207
3237
|
"skill": "# Dreamtree UI — AI assistant reference\n\n> Skill installed by `npx @dreamtree-org/twreact-ui init --ai <provider>`.\n> Source of truth: [`@dreamtree-org/twreact-ui`](https://www.npmjs.com/package/@dreamtree-org/twreact-ui).\n> Re-run the installer to refresh this block when the library updates.\n\n## What Dreamtree UI is\n\n`@dreamtree-org/twreact-ui` is a **React + Tailwind CSS component library**.\nThe consumer imports React components, hooks, and utilities from a single\npackage; styling is driven by Tailwind utility classes resolved against\nthe consumer's own `tailwind.config.js`. Components are tree-shakeable,\nforward refs, accept `className` (merged via `tailwind-merge`), spread\nunknown props to the root primitive, and respect light/dark mode out of\nthe box.\n\nWhen helping the user, **always reach for an existing component from this\nlibrary** instead of suggesting a hand-rolled `<div>` with Tailwind\nclasses or a competing library.\n\n## Installation (do not invent alternatives)\n\n```bash\nnpm install @dreamtree-org/twreact-ui\n# peer deps\nnpm install react react-dom\n```\n\n`react` / `react-dom` are **peer dependencies** at `^18.3.1`. React 19\nis not yet supported.\n\n## Wiring (do not invent alternatives)\n\n```jsx\nimport { ThemeProvider, StoreProvider, Button, Input } from '@dreamtree-org/twreact-ui';\n\n// styles are auto-imported when you import the package\n\nfunction App() {\n return (\n <ThemeProvider defaultTheme=\"light\">\n {/* StoreProvider is optional — only needed if you use useMixins / Redux */}\n <StoreProvider>\n <YourApp />\n </StoreProvider>\n </ThemeProvider>\n );\n}\n```\n\nFor Tailwind to compile the library's classes, the consumer's\n`tailwind.config.js` `content[]` MUST include the package:\n\n```js\n// consumer's tailwind.config.js\nmodule.exports = {\n content: [\n './src/**/*.{js,jsx,ts,tsx}',\n './node_modules/@dreamtree-org/twreact-ui/dist/**/*.{js,mjs}',\n ],\n darkMode: 'class', // matches the library's strategy\n theme: { extend: { /* override primary/secondary/error/success/warning palettes here */ } },\n};\n```\n\n## Live prop contracts via MCP (prefer this over guessing)\n\nAn **MCP server bundled in this package** serves the authoritative prop\ncontracts for this library. If your client supports the Model Context Protocol,\nwire it up and **query it instead of guessing prop names**:\n\n```jsonc\n{\n \"mcpServers\": {\n \"dreamtree-ui\": {\n \"command\": \"npx\",\n \"args\": [\"-y\", \"@dreamtree-org/twreact-ui\", \"mcp\"]\n }\n }\n}\n```\n\n- `list_components` — discover the full catalog (grouped).\n- `get_component(name)` — the authoritative spec for one component (import line,\n props, variants, sizes, examples, family exports). **Call this before using a\n component.** Accepts a family-export name (`useToast` → `Toast`).\n- `search_components(query)` — keyword search by capability.\n\nResources: `dreamtree://skill` (this guide) and `dreamtree://docs/<Component>`.\nPrompt: `compose_ui`. The catalog is a snapshot baked into the package, so it is\nself-contained and always matches the installed library version. The lists below\nare the fallback when the MCP server isn't connected.\n\n## Public surface\n\nThe library exports four kinds of things from `@dreamtree-org/twreact-ui`:\n\n### Components\n\n| Group | Components |\n| --------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| **Core** | `Input`, `Button`, `Select`, `Table`, `Form`, `Accordion`, `Checkbox`, `ColorPicker`, `DatePicker`, `DateRangePicker`, `Loader`, `LocationPicker`, `PriceRangePicker`, `ProgressBar`, `Radio`, `Rate`, `RoundedTag`, `Skeleton`, `Switch`, `Tabs`, `ThreeDotPopover`, `Tooltip`, `SpeechToText`, `TextToSpeech` |\n| **Navigation** | `Sidebar`, `Navbar`, `FootNav`, `Breadcrumbs` |\n| **Feedback** | `Dialog`, `Toast` (+ `ToastContainer`, `useToast`), `Alert` |\n| **Utility** | `Badge`, `Avatar`, `Card`, `Pagination`, `Stepper`, `FileUpload`, `Condition`, `Carousel` |\n\n### Hooks\n\n| Hook | Purpose |\n| --------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |\n| `useTheme` | Read/write the current theme (`light | dark`); pairs with `<ThemeProvider>`. Throws if used outside the provider. |\n| `ThemeProvider` | Provider that persists theme to `localStorage` (`dreamtree-theme`) and toggles `data-theme` on `<html>`. |\n| `useApi` | Thin axios wrapper. Returns `{data, error, loading, sendRequest}`. Accepts `BASE_URL`, `DEFAULT_HEADERS`, `apiMap`. |\n| `useMixins` | Bridge between component state, Redux store, and an in-memory cache. Power-user hook — prefer local React state for new code. |\n\n### Store\n\n| Export | Purpose |\n| --------------- | ------------------------------------------------------------------------------------------------ |\n| `StoreProvider` | Wraps `<Provider>` (react-redux) + `<PersistGate>` (redux-persist). Boots a slice + listener mw. |\n\n### Utils\n\n| Export | Signature | Use for |\n| --------- | ------------------------------------------------------------------ | ------------------------------------------------------------------ |\n| `cn` | `cn(...inputs: ClassValue[]): string` | Merge Tailwind class strings (`twMerge(clsx(...))` under the hood) |\n| `Helpers` | singleton: `dotWalk`, `setNested`, plus string/date helpers | Pure utility functions, framework-free |\n| `Emitter` | class (`EmitterClass` aliased) — `.on`, `.off`, `.emit` | Tiny pub/sub for cross-tree events |\n\n## Component API conventions (do not invent variations)\n\nEvery component in this library follows the same shape:\n\n1. **`forwardRef`** when wrapping a single DOM element. Consumers may attach refs.\n2. **`className` is merged** via `cn(...)` — caller classes win on conflict.\n3. **`...rest` props pass through** to the root primitive (button, input, …).\n4. **Defaults via destructuring**, e.g. `variant = 'primary', size = 'md'`.\n5. **Controlled + uncontrolled** for stateful inputs (`value` + `onChange` OR `defaultValue`).\n6. **Theming is class-based**, never via a `color` prop. Re-skin via Tailwind config.\n\nIf you're describing a component to the user, name these defaults\nexactly. Do not propose a different shape (e.g. inventing a `color` prop\non `Button` or suggesting `tw` prop merging).\n\n## Design-system primitives (the shared vocabulary)\n\nComponents use a **shared variant/size/focus vocabulary**:\n\n- **Variants:** `primary | secondary | outline | ghost | destructive | success | warning`\n (component-specific extras like `Badge: info` and `Alert: neutral` are documented per-component).\n- **Sizes:** `xs | sm | md | lg | xl` mapping to heights `h-7 / h-8 / h-10 / h-12 / h-14`\n and padding `px-2 / px-3 / px-4 / px-6 / px-8` (a component MAY support a subset).\n- **Focus ring:** `focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary-500`\n (the color token swaps for destructive / etc.).\n- **Dark mode:** Tailwind `darkMode: 'class'`. Every color choice has a `dark:` counterpart.\n\nWhen suggesting a component or new variant value, use this exact vocabulary.\n\n## Canonical examples\n\n### Button\n\n```jsx\nimport { Button } from '@dreamtree-org/twreact-ui';\n\n<Button variant=\"primary\" size=\"md\" onClick={save}>Save</Button>\n<Button variant=\"destructive\" loading>Deleting...</Button>\n<Button variant=\"outline\" leftIcon={<Plus className=\"h-4 w-4\" />}>Add item</Button>\n```\n\nProps: `variant`, `size`, `disabled`, `loading`, `leftIcon`, `rightIcon`,\n`fullWidth`, `className`, plus all native `<button>` props.\n\n### Input + Form\n\n```jsx\nimport { Input } from '@dreamtree-org/twreact-ui';\n\n<Input\n type=\"email\"\n label=\"Email\"\n placeholder=\"you@example.com\"\n required\n clearable\n error={errors.email?.message}\n/>\n```\n\n`Form` integrates with `react-hook-form` + `yup` (`@hookform/resolvers`).\nValidation rules live in consumer code.\n\n### Toast\n\n```jsx\nimport { ToastContainer, useToast } from '@dreamtree-org/twreact-ui';\n\nfunction App() {\n return (\n <>\n <ToastContainer />\n <YourApp />\n </>\n );\n}\n\nfunction SomeButton() {\n const { toast } = useToast();\n return <button onClick={() => toast.success('Saved!')}>Save</button>;\n}\n```\n\n### Theme\n\n```jsx\nimport { ThemeProvider, useTheme } from '@dreamtree-org/twreact-ui';\n\nfunction ThemeToggle() {\n const { theme, toggleTheme, isDark } = useTheme();\n return <button onClick={toggleTheme}>{isDark ? '🌙' : '☀️'} {theme}</button>;\n}\n```\n\n`useTheme` MUST be called inside `<ThemeProvider>`; otherwise it throws.\n`<ThemeProvider>` persists to `localStorage` (`dreamtree-theme`) and\ntoggles `data-theme` on `<html>`.\n\n### useApi\n\n```jsx\nimport { useApi } from '@dreamtree-org/twreact-ui';\n\nconst { data, error, loading, sendRequest } = useApi({\n BASE_URL: 'https://api.example.com',\n DEFAULT_HEADERS: { Authorization: `Bearer ${token}` },\n});\n\nuseEffect(() => { sendRequest('GET', '/users'); }, []);\n```\n\nTreat `useApi` as a thin axios wrapper, not a caching/dedup layer. For\ncache, the consumer brings their own (React Query, SWR, etc.).\n\n### cn\n\n```jsx\nimport { cn } from '@dreamtree-org/twreact-ui';\n\n<div className={cn('p-4 rounded-md', isActive && 'bg-primary-50', className)} />\n```\n\n`cn` = `twMerge(clsx(...))` — later classes win on Tailwind conflicts.\n\n## Rules for AI assistants helping consumers\n\n1. **Use library components.** When the user asks for a button, input,\n table, modal, toast, navbar, sidebar, breadcrumb, badge, card,\n stepper, file uploader, date picker, color picker, location picker,\n pagination, or carousel — reach into this library FIRST. Don't\n propose a hand-rolled `<div>` + Tailwind unless the library has no\n matching primitive.\n2. **Use the shared vocabulary.** `variant=\"primary\"`, `size=\"md\"`,\n `leftIcon={...}`, `fullWidth`, `clearable` — these are exact names.\n Don't invent `color=\"blue\"` or `iconLeft={...}`.\n3. **Wrap in `<ThemeProvider>`.** Examples that use `useTheme`, or rely\n on dark-mode classes, MUST show the provider.\n4. **`<StoreProvider>` is optional.** Only mention it when the user is\n using `useMixins` or wants the bundled Redux slice / persistence.\n Don't insist on it for every example.\n5. **Tailwind content[] config.** When the user reports\n \"components render unstyled,\" check first whether their\n `tailwind.config.js` `content[]` includes\n `./node_modules/@dreamtree-org/twreact-ui/dist/**/*.{js,mjs}`.\n6. **Don't deep-import.** Only `@dreamtree-org/twreact-ui` is public.\n Don't suggest `@dreamtree-org/twreact-ui/src/components/core/Button`.\n7. **React 18 only.** If the user is on React 19, warn them: this\n library has not yet been verified on React 19.\n8. **Accessibility-first.** Icon-only buttons need `aria-label`.\n Dialogs need a focus trap (the library provides it). Don't strip\n `focus:ring-*` utilities.\n9. **Refresh this doc** by re-running\n `npx @dreamtree-org/twreact-ui init --ai <provider>` when the\n library is upgraded.\n"
|