@memelabui/ui 0.7.0 → 0.8.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.
package/dist/index.cjs CHANGED
@@ -139,10 +139,12 @@ function matchModifiers(e, mods) {
139
139
  }
140
140
  function useHotkeys(bindings, options = {}) {
141
141
  const { enabled = true } = options;
142
+ const bindingsRef = React.useRef(bindings);
143
+ bindingsRef.current = bindings;
142
144
  React.useEffect(() => {
143
145
  if (!enabled) return;
144
146
  const onKeyDown = (e) => {
145
- for (const binding of bindings) {
147
+ for (const binding of bindingsRef.current) {
146
148
  if (e.key === binding.key && matchModifiers(e, binding.modifiers)) {
147
149
  binding.handler(e);
148
150
  }
@@ -150,7 +152,7 @@ function useHotkeys(bindings, options = {}) {
150
152
  };
151
153
  document.addEventListener("keydown", onKeyDown);
152
154
  return () => document.removeEventListener("keydown", onKeyDown);
153
- }, [enabled, ...bindings]);
155
+ }, [enabled]);
154
156
  }
155
157
  function useIntersectionObserver(options = {}) {
156
158
  const { root = null, rootMargin = "0px", threshold = 0, enabled = true } = options;
@@ -206,6 +208,30 @@ function useSharedNow(options = {}) {
206
208
  }, [interval, untilMs, enabled]);
207
209
  return now;
208
210
  }
211
+ var lockCount = 0;
212
+ var savedOverflow = "";
213
+ function lockScroll() {
214
+ if (typeof document === "undefined") return;
215
+ if (lockCount === 0) {
216
+ savedOverflow = document.body.style.overflow;
217
+ document.body.style.overflow = "hidden";
218
+ }
219
+ lockCount++;
220
+ }
221
+ function unlockScroll() {
222
+ if (typeof document === "undefined") return;
223
+ lockCount = Math.max(0, lockCount - 1);
224
+ if (lockCount === 0) {
225
+ document.body.style.overflow = savedOverflow;
226
+ }
227
+ }
228
+ function useScrollLock(active) {
229
+ React.useEffect(() => {
230
+ if (!active) return;
231
+ lockScroll();
232
+ return () => unlockScroll();
233
+ }, [active]);
234
+ }
209
235
 
210
236
  // src/tokens/colors.ts
211
237
  var colors = {
@@ -1257,23 +1283,6 @@ var Card = React.forwardRef(function Card2({ hoverable, variant = "surface", pad
1257
1283
  }
1258
1284
  );
1259
1285
  });
1260
- var scrollLockCount = 0;
1261
- var savedOverflow = "";
1262
- function lockScroll() {
1263
- if (typeof document === "undefined") return;
1264
- if (scrollLockCount === 0) {
1265
- savedOverflow = document.body.style.overflow;
1266
- document.body.style.overflow = "hidden";
1267
- }
1268
- scrollLockCount++;
1269
- }
1270
- function unlockScroll() {
1271
- if (typeof document === "undefined") return;
1272
- scrollLockCount = Math.max(0, scrollLockCount - 1);
1273
- if (scrollLockCount === 0) {
1274
- document.body.style.overflow = savedOverflow;
1275
- }
1276
- }
1277
1286
  function Modal({
1278
1287
  isOpen,
1279
1288
  onClose,
@@ -1305,11 +1314,7 @@ function Modal({
1305
1314
  if (lastActive?.isConnected) focusSafely(lastActive);
1306
1315
  };
1307
1316
  }, [isOpen]);
1308
- React.useEffect(() => {
1309
- if (!isOpen) return;
1310
- lockScroll();
1311
- return () => unlockScroll();
1312
- }, [isOpen]);
1317
+ useScrollLock(isOpen);
1313
1318
  if (!isOpen) return null;
1314
1319
  return /* @__PURE__ */ jsxRuntime.jsx(
1315
1320
  "div",
@@ -1458,17 +1463,32 @@ function Tooltip({ content, delayMs = 500, placement = "top", className, childre
1458
1463
  const el = anchorRef.current;
1459
1464
  if (!el) return;
1460
1465
  const r = el.getBoundingClientRect();
1466
+ const vw = window.innerWidth;
1461
1467
  const centerX = r.left + r.width / 2;
1462
- const preferTop = placement === "top";
1468
+ const centerY = r.top + r.height / 2;
1463
1469
  const topY = r.top - 10;
1464
1470
  const bottomY = r.bottom + 10;
1465
- const canTop = topY > 8;
1466
- const effPlacement = preferTop ? canTop ? "top" : "bottom" : "bottom";
1467
- setPos({
1468
- left: Math.round(centerX),
1469
- top: Math.round(effPlacement === "top" ? topY : bottomY),
1470
- placement: effPlacement
1471
- });
1471
+ const leftX = r.left - 10;
1472
+ const rightX = r.right + 10;
1473
+ let effPlacement = placement;
1474
+ if (placement === "top" || placement === "bottom") {
1475
+ const canTop = topY > 8;
1476
+ effPlacement = placement === "top" ? canTop ? "top" : "bottom" : "bottom";
1477
+ setPos({
1478
+ left: Math.round(centerX),
1479
+ top: Math.round(effPlacement === "top" ? topY : bottomY),
1480
+ placement: effPlacement
1481
+ });
1482
+ } else {
1483
+ const canLeft = leftX > 8;
1484
+ const canRight = rightX < vw - 8;
1485
+ effPlacement = placement === "left" ? canLeft ? "left" : "right" : canRight ? "right" : "left";
1486
+ setPos({
1487
+ left: Math.round(effPlacement === "left" ? leftX : rightX),
1488
+ top: Math.round(centerY),
1489
+ placement: effPlacement
1490
+ });
1491
+ }
1472
1492
  }, [placement]);
1473
1493
  const scheduleOpen = React.useCallback(() => {
1474
1494
  clearTimer();
@@ -1542,7 +1562,7 @@ function Tooltip({ content, delayMs = 500, placement = "top", className, childre
1542
1562
  style: pos ? {
1543
1563
  left: pos.left,
1544
1564
  top: pos.top,
1545
- transform: pos.placement === "top" ? "translate(-50%, -100%)" : "translate(-50%, 0%)"
1565
+ transform: pos.placement === "top" ? "translate(-50%, -100%)" : pos.placement === "bottom" ? "translate(-50%, 0%)" : pos.placement === "left" ? "translate(-100%, -50%)" : "translate(0%, -50%)"
1546
1566
  } : { left: 0, top: 0, transform: "translate(-9999px, -9999px)" },
1547
1567
  children: content
1548
1568
  }
@@ -2085,7 +2105,7 @@ function StatCard({ value, label, icon, trend, className }) {
2085
2105
  "div",
2086
2106
  {
2087
2107
  className: cn(
2088
- "bg-white/5 ring-1 ring-white/10 rounded-xl p-4 flex items-start gap-3",
2108
+ "glass rounded-xl p-4 flex items-start gap-3",
2089
2109
  className
2090
2110
  ),
2091
2111
  children: [
@@ -3818,23 +3838,6 @@ function Popover({
3818
3838
  ) : null
3819
3839
  ] });
3820
3840
  }
3821
- var scrollLockCount2 = 0;
3822
- var savedOverflow2 = "";
3823
- function lockScroll2() {
3824
- if (typeof document === "undefined") return;
3825
- if (scrollLockCount2 === 0) {
3826
- savedOverflow2 = document.body.style.overflow;
3827
- document.body.style.overflow = "hidden";
3828
- }
3829
- scrollLockCount2++;
3830
- }
3831
- function unlockScroll2() {
3832
- if (typeof document === "undefined") return;
3833
- scrollLockCount2 = Math.max(0, scrollLockCount2 - 1);
3834
- if (scrollLockCount2 === 0) {
3835
- document.body.style.overflow = savedOverflow2;
3836
- }
3837
- }
3838
3841
  var sizeClass8 = {
3839
3842
  left: { sm: "w-64", md: "w-80", lg: "w-96", full: "w-screen" },
3840
3843
  right: { sm: "w-64", md: "w-80", lg: "w-96", full: "w-screen" },
@@ -3884,11 +3887,7 @@ function Drawer({
3884
3887
  if (lastActive?.isConnected) focusSafely(lastActive);
3885
3888
  };
3886
3889
  }, [isOpen]);
3887
- React.useEffect(() => {
3888
- if (!isOpen) return;
3889
- lockScroll2();
3890
- return () => unlockScroll2();
3891
- }, [isOpen]);
3890
+ useScrollLock(isOpen);
3892
3891
  if (!isOpen) return null;
3893
3892
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "fixed inset-0 z-50", role: "presentation", children: [
3894
3893
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -4271,6 +4270,61 @@ function VisuallyHidden({ children, as: Tag = "span", style, ...props }) {
4271
4270
  }
4272
4271
  );
4273
4272
  }
4273
+ var ErrorBoundary = class extends React.Component {
4274
+ constructor() {
4275
+ super(...arguments);
4276
+ this.state = { error: null };
4277
+ this.reset = () => {
4278
+ this.setState({ error: null });
4279
+ };
4280
+ }
4281
+ static getDerivedStateFromError(error) {
4282
+ return { error };
4283
+ }
4284
+ componentDidCatch(error, errorInfo) {
4285
+ this.props.onError?.(error, errorInfo);
4286
+ }
4287
+ render() {
4288
+ const { error } = this.state;
4289
+ if (!error) return this.props.children;
4290
+ const { fallback } = this.props;
4291
+ if (typeof fallback === "function") {
4292
+ return fallback(error, this.reset);
4293
+ }
4294
+ if (fallback !== void 0) {
4295
+ return fallback;
4296
+ }
4297
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { role: "alert", className: cn("glass rounded-xl p-6 text-center"), children: [
4298
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "text-lg font-semibold text-white mb-2", children: "Something went wrong" }),
4299
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-white/60 mb-4", children: error.message }),
4300
+ /* @__PURE__ */ jsxRuntime.jsx(
4301
+ "button",
4302
+ {
4303
+ type: "button",
4304
+ onClick: this.reset,
4305
+ className: "inline-flex items-center justify-center gap-2 rounded-xl font-semibold leading-none px-3.5 py-2.5 text-sm bg-gradient-to-r from-violet-600 to-purple-600 text-white shadow-glow hover:shadow-glow-lg hover:scale-[1.02] transition-[transform,background-color,box-shadow,opacity] active:translate-y-[0.5px]",
4306
+ children: "Try again"
4307
+ }
4308
+ )
4309
+ ] });
4310
+ }
4311
+ };
4312
+ function LoadingScreen({ message, size = "lg", className }) {
4313
+ return /* @__PURE__ */ jsxRuntime.jsxs(
4314
+ "div",
4315
+ {
4316
+ role: "status",
4317
+ className: cn(
4318
+ "flex flex-col items-center justify-center min-h-screen bg-ml-bg gap-4",
4319
+ className
4320
+ ),
4321
+ children: [
4322
+ /* @__PURE__ */ jsxRuntime.jsx(Spinner, { size }),
4323
+ message && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-white/60", children: message })
4324
+ ]
4325
+ }
4326
+ );
4327
+ }
4274
4328
 
4275
4329
  exports.ActiveFilterPills = ActiveFilterPills;
4276
4330
  exports.Alert = Alert;
@@ -4297,10 +4351,12 @@ exports.DropdownMenu = DropdownMenu;
4297
4351
  exports.DropdownSeparator = DropdownSeparator;
4298
4352
  exports.DropdownTrigger = DropdownTrigger;
4299
4353
  exports.EmptyState = EmptyState;
4354
+ exports.ErrorBoundary = ErrorBoundary;
4300
4355
  exports.FormField = FormField;
4301
4356
  exports.Heading = Heading;
4302
4357
  exports.IconButton = IconButton;
4303
4358
  exports.Input = Input;
4359
+ exports.LoadingScreen = LoadingScreen;
4304
4360
  exports.Modal = Modal;
4305
4361
  exports.MutationOverlay = MutationOverlay;
4306
4362
  exports.Navbar = Navbar;
@@ -4353,5 +4409,6 @@ exports.useDisclosure = useDisclosure;
4353
4409
  exports.useHotkeys = useHotkeys;
4354
4410
  exports.useIntersectionObserver = useIntersectionObserver;
4355
4411
  exports.useMediaQuery = useMediaQuery;
4412
+ exports.useScrollLock = useScrollLock;
4356
4413
  exports.useSharedNow = useSharedNow;
4357
4414
  exports.useToast = useToast;
package/dist/index.d.cts CHANGED
@@ -1,6 +1,6 @@
1
1
  export { MemelabColors, colors } from './tokens/index.cjs';
2
2
  import * as react from 'react';
3
- import { ButtonHTMLAttributes, ReactNode, InputHTMLAttributes, SelectHTMLAttributes, TextareaHTMLAttributes, HTMLAttributes, ReactElement, ComponentType } from 'react';
3
+ import { ButtonHTMLAttributes, ReactNode, InputHTMLAttributes, SelectHTMLAttributes, TextareaHTMLAttributes, HTMLAttributes, ReactElement, ComponentType, Component, ErrorInfo } from 'react';
4
4
  import * as react_jsx_runtime from 'react/jsx-runtime';
5
5
 
6
6
  type ClassValue = string | number | null | undefined | boolean | Record<string, boolean | null | undefined> | ClassValue[];
@@ -95,6 +95,9 @@ type UseSharedNowOptions = {
95
95
  */
96
96
  declare function useSharedNow(options?: UseSharedNowOptions): number;
97
97
 
98
+ /** Ref-counted body scroll lock. Safe for nested overlays (Modal + Drawer). */
99
+ declare function useScrollLock(active: boolean): void;
100
+
98
101
  type Size = 'sm' | 'md' | 'lg';
99
102
 
100
103
  type AvatarSize = 'sm' | 'md' | 'lg' | 'xl';
@@ -376,7 +379,7 @@ type ConfirmDialogProps = {
376
379
  };
377
380
  declare function ConfirmDialog({ isOpen, onClose, onConfirm, title, message, confirmText, cancelText, loadingText, variant, isLoading, }: ConfirmDialogProps): react_jsx_runtime.JSX.Element;
378
381
 
379
- type TooltipPlacement = 'top' | 'bottom';
382
+ type TooltipPlacement = 'top' | 'bottom' | 'left' | 'right';
380
383
  type TooltipProps = {
381
384
  content: ReactNode;
382
385
  delayMs?: number;
@@ -919,4 +922,27 @@ type VisuallyHiddenProps = HTMLAttributes<HTMLSpanElement> & {
919
922
  */
920
923
  declare function VisuallyHidden({ children, as: Tag, style, ...props }: VisuallyHiddenProps): react_jsx_runtime.JSX.Element;
921
924
 
922
- export { ActiveFilterPills, type ActiveFilterPillsProps, Alert, type AlertProps, type AlertVariant, Avatar, type AvatarProps, type AvatarSize, Badge, type BadgeProps, type BadgeSize, type BadgeVariant, type BreadcrumbItem, Breadcrumbs, type BreadcrumbsProps, Button, type ButtonProps, type ButtonSize, type ButtonVariant, Card, type CardPadding, type CardProps, type CardVariant, Checkbox, type CheckboxProps, CollapsibleSection, type CollapsibleSectionProps, ColorInput, type ColorInputProps, Combobox, type ComboboxOption, type ComboboxProps, ConfirmDialog, type ConfirmDialogProps, type ConfirmDialogVariant, CooldownRing, type CooldownRingProps, type CooldownRingSize, CopyField, type CopyFieldProps, DashboardLayout, type DashboardLayoutProps, Divider, type DividerProps, DotIndicator, type DotIndicatorProps, Drawer, type DrawerProps, type DrawerSide, type DrawerSize, DropZone, type DropZoneProps, Dropdown, DropdownItem, type DropdownItemProps, DropdownMenu, type DropdownMenuProps, type DropdownProps, DropdownSeparator, type DropdownSeparatorProps, DropdownTrigger, type DropdownTriggerProps, EmptyState, type EmptyStateProps, type FilterPill, FormField, type FormFieldProps, Heading, type HeadingLevel, type HeadingProps, type HotkeyBinding, type HotkeyModifiers, IconButton, type IconButtonProps, Input, type InputProps, Modal, type ModalProps, MutationOverlay, type MutationOverlayProps, type MutationOverlayStatus, Navbar, type NavbarProps, NotificationBell, type NotificationBellProps, PageShell, type PageShellProps, type PageShellVariant, Pagination, type PaginationProps, Pill, Popover, type PopoverPlacement, type PopoverProps, ProgressBar, type ProgressBarProps, type ProgressBarVariant, ProgressButton, type ProgressButtonProps, RadioGroup, type RadioGroupProps, RadioItem, type RadioItemProps, ScrollArea, type ScrollAreaProps, SearchInput, type SearchInputProps, SectionCard, type SectionCardProps, Select, type SelectProps, Sidebar, type SidebarProps, type Size, Skeleton, type SkeletonProps, Slider, type SliderProps, Spinner, type SpinnerProps, type SpinnerSize, Stack, type StackProps, StageProgress, type StageProgressProps, StatCard, type StatCardProps, type StatCardTrend, type Step, Stepper, type StepperProps, Tab, TabList, type TabListProps, TabPanel, type TabPanelProps, type TabProps, Table, TableBody, type TableBodyProps, TableCell, type TableCellProps, TableHead, type TableHeadProps, TableHeader, type TableHeaderProps, type TableProps, TableRow, type TableRowProps, Tabs, type TabsProps, type TabsVariant, TagInput, type TagInputProps, Text, type TextColor, type TextProps, type TextSize, Textarea, type TextareaProps, type ToastData, type ToastPosition, ToastProvider, type ToastProviderProps, type ToastVariant, Toggle, type ToggleProps, type ToggleSize, Tooltip, type TooltipPlacement, type TooltipProps, Transition, type TransitionPreset, type TransitionProps, type UseClipboardReturn, type UseDisclosureReturn, type UseHotkeysOptions, type UseIntersectionObserverOptions, type UseIntersectionObserverReturn, type UseSharedNowOptions, VisuallyHidden, type VisuallyHiddenProps, cn, focusSafely, getFocusableElements, useClipboard, useDebounce, useDisclosure, useHotkeys, useIntersectionObserver, useMediaQuery, useSharedNow, useToast };
925
+ type ErrorBoundaryProps = {
926
+ children: ReactNode;
927
+ fallback?: ReactNode | ((error: Error, reset: () => void) => ReactNode);
928
+ onError?: (error: Error, errorInfo: ErrorInfo) => void;
929
+ };
930
+ type State = {
931
+ error: Error | null;
932
+ };
933
+ declare class ErrorBoundary extends Component<ErrorBoundaryProps, State> {
934
+ state: State;
935
+ static getDerivedStateFromError(error: Error): State;
936
+ componentDidCatch(error: Error, errorInfo: ErrorInfo): void;
937
+ private reset;
938
+ render(): string | number | boolean | react_jsx_runtime.JSX.Element | Iterable<ReactNode> | null | undefined;
939
+ }
940
+
941
+ type LoadingScreenProps = {
942
+ message?: string;
943
+ size?: SpinnerSize;
944
+ className?: string;
945
+ };
946
+ declare function LoadingScreen({ message, size, className }: LoadingScreenProps): react_jsx_runtime.JSX.Element;
947
+
948
+ export { ActiveFilterPills, type ActiveFilterPillsProps, Alert, type AlertProps, type AlertVariant, Avatar, type AvatarProps, type AvatarSize, Badge, type BadgeProps, type BadgeSize, type BadgeVariant, type BreadcrumbItem, Breadcrumbs, type BreadcrumbsProps, Button, type ButtonProps, type ButtonSize, type ButtonVariant, Card, type CardPadding, type CardProps, type CardVariant, Checkbox, type CheckboxProps, CollapsibleSection, type CollapsibleSectionProps, ColorInput, type ColorInputProps, Combobox, type ComboboxOption, type ComboboxProps, ConfirmDialog, type ConfirmDialogProps, type ConfirmDialogVariant, CooldownRing, type CooldownRingProps, type CooldownRingSize, CopyField, type CopyFieldProps, DashboardLayout, type DashboardLayoutProps, Divider, type DividerProps, DotIndicator, type DotIndicatorProps, Drawer, type DrawerProps, type DrawerSide, type DrawerSize, DropZone, type DropZoneProps, Dropdown, DropdownItem, type DropdownItemProps, DropdownMenu, type DropdownMenuProps, type DropdownProps, DropdownSeparator, type DropdownSeparatorProps, DropdownTrigger, type DropdownTriggerProps, EmptyState, type EmptyStateProps, ErrorBoundary, type ErrorBoundaryProps, type FilterPill, FormField, type FormFieldProps, Heading, type HeadingLevel, type HeadingProps, type HotkeyBinding, type HotkeyModifiers, IconButton, type IconButtonProps, Input, type InputProps, LoadingScreen, type LoadingScreenProps, Modal, type ModalProps, MutationOverlay, type MutationOverlayProps, type MutationOverlayStatus, Navbar, type NavbarProps, NotificationBell, type NotificationBellProps, PageShell, type PageShellProps, type PageShellVariant, Pagination, type PaginationProps, Pill, Popover, type PopoverPlacement, type PopoverProps, ProgressBar, type ProgressBarProps, type ProgressBarVariant, ProgressButton, type ProgressButtonProps, RadioGroup, type RadioGroupProps, RadioItem, type RadioItemProps, ScrollArea, type ScrollAreaProps, SearchInput, type SearchInputProps, SectionCard, type SectionCardProps, Select, type SelectProps, Sidebar, type SidebarProps, type Size, Skeleton, type SkeletonProps, Slider, type SliderProps, Spinner, type SpinnerProps, type SpinnerSize, Stack, type StackProps, StageProgress, type StageProgressProps, StatCard, type StatCardProps, type StatCardTrend, type Step, Stepper, type StepperProps, Tab, TabList, type TabListProps, TabPanel, type TabPanelProps, type TabProps, Table, TableBody, type TableBodyProps, TableCell, type TableCellProps, TableHead, type TableHeadProps, TableHeader, type TableHeaderProps, type TableProps, TableRow, type TableRowProps, Tabs, type TabsProps, type TabsVariant, TagInput, type TagInputProps, Text, type TextColor, type TextProps, type TextSize, Textarea, type TextareaProps, type ToastData, type ToastPosition, ToastProvider, type ToastProviderProps, type ToastVariant, Toggle, type ToggleProps, type ToggleSize, Tooltip, type TooltipPlacement, type TooltipProps, Transition, type TransitionPreset, type TransitionProps, type UseClipboardReturn, type UseDisclosureReturn, type UseHotkeysOptions, type UseIntersectionObserverOptions, type UseIntersectionObserverReturn, type UseSharedNowOptions, VisuallyHidden, type VisuallyHiddenProps, cn, focusSafely, getFocusableElements, useClipboard, useDebounce, useDisclosure, useHotkeys, useIntersectionObserver, useMediaQuery, useScrollLock, useSharedNow, useToast };
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  export { MemelabColors, colors } from './tokens/index.js';
2
2
  import * as react from 'react';
3
- import { ButtonHTMLAttributes, ReactNode, InputHTMLAttributes, SelectHTMLAttributes, TextareaHTMLAttributes, HTMLAttributes, ReactElement, ComponentType } from 'react';
3
+ import { ButtonHTMLAttributes, ReactNode, InputHTMLAttributes, SelectHTMLAttributes, TextareaHTMLAttributes, HTMLAttributes, ReactElement, ComponentType, Component, ErrorInfo } from 'react';
4
4
  import * as react_jsx_runtime from 'react/jsx-runtime';
5
5
 
6
6
  type ClassValue = string | number | null | undefined | boolean | Record<string, boolean | null | undefined> | ClassValue[];
@@ -95,6 +95,9 @@ type UseSharedNowOptions = {
95
95
  */
96
96
  declare function useSharedNow(options?: UseSharedNowOptions): number;
97
97
 
98
+ /** Ref-counted body scroll lock. Safe for nested overlays (Modal + Drawer). */
99
+ declare function useScrollLock(active: boolean): void;
100
+
98
101
  type Size = 'sm' | 'md' | 'lg';
99
102
 
100
103
  type AvatarSize = 'sm' | 'md' | 'lg' | 'xl';
@@ -376,7 +379,7 @@ type ConfirmDialogProps = {
376
379
  };
377
380
  declare function ConfirmDialog({ isOpen, onClose, onConfirm, title, message, confirmText, cancelText, loadingText, variant, isLoading, }: ConfirmDialogProps): react_jsx_runtime.JSX.Element;
378
381
 
379
- type TooltipPlacement = 'top' | 'bottom';
382
+ type TooltipPlacement = 'top' | 'bottom' | 'left' | 'right';
380
383
  type TooltipProps = {
381
384
  content: ReactNode;
382
385
  delayMs?: number;
@@ -919,4 +922,27 @@ type VisuallyHiddenProps = HTMLAttributes<HTMLSpanElement> & {
919
922
  */
920
923
  declare function VisuallyHidden({ children, as: Tag, style, ...props }: VisuallyHiddenProps): react_jsx_runtime.JSX.Element;
921
924
 
922
- export { ActiveFilterPills, type ActiveFilterPillsProps, Alert, type AlertProps, type AlertVariant, Avatar, type AvatarProps, type AvatarSize, Badge, type BadgeProps, type BadgeSize, type BadgeVariant, type BreadcrumbItem, Breadcrumbs, type BreadcrumbsProps, Button, type ButtonProps, type ButtonSize, type ButtonVariant, Card, type CardPadding, type CardProps, type CardVariant, Checkbox, type CheckboxProps, CollapsibleSection, type CollapsibleSectionProps, ColorInput, type ColorInputProps, Combobox, type ComboboxOption, type ComboboxProps, ConfirmDialog, type ConfirmDialogProps, type ConfirmDialogVariant, CooldownRing, type CooldownRingProps, type CooldownRingSize, CopyField, type CopyFieldProps, DashboardLayout, type DashboardLayoutProps, Divider, type DividerProps, DotIndicator, type DotIndicatorProps, Drawer, type DrawerProps, type DrawerSide, type DrawerSize, DropZone, type DropZoneProps, Dropdown, DropdownItem, type DropdownItemProps, DropdownMenu, type DropdownMenuProps, type DropdownProps, DropdownSeparator, type DropdownSeparatorProps, DropdownTrigger, type DropdownTriggerProps, EmptyState, type EmptyStateProps, type FilterPill, FormField, type FormFieldProps, Heading, type HeadingLevel, type HeadingProps, type HotkeyBinding, type HotkeyModifiers, IconButton, type IconButtonProps, Input, type InputProps, Modal, type ModalProps, MutationOverlay, type MutationOverlayProps, type MutationOverlayStatus, Navbar, type NavbarProps, NotificationBell, type NotificationBellProps, PageShell, type PageShellProps, type PageShellVariant, Pagination, type PaginationProps, Pill, Popover, type PopoverPlacement, type PopoverProps, ProgressBar, type ProgressBarProps, type ProgressBarVariant, ProgressButton, type ProgressButtonProps, RadioGroup, type RadioGroupProps, RadioItem, type RadioItemProps, ScrollArea, type ScrollAreaProps, SearchInput, type SearchInputProps, SectionCard, type SectionCardProps, Select, type SelectProps, Sidebar, type SidebarProps, type Size, Skeleton, type SkeletonProps, Slider, type SliderProps, Spinner, type SpinnerProps, type SpinnerSize, Stack, type StackProps, StageProgress, type StageProgressProps, StatCard, type StatCardProps, type StatCardTrend, type Step, Stepper, type StepperProps, Tab, TabList, type TabListProps, TabPanel, type TabPanelProps, type TabProps, Table, TableBody, type TableBodyProps, TableCell, type TableCellProps, TableHead, type TableHeadProps, TableHeader, type TableHeaderProps, type TableProps, TableRow, type TableRowProps, Tabs, type TabsProps, type TabsVariant, TagInput, type TagInputProps, Text, type TextColor, type TextProps, type TextSize, Textarea, type TextareaProps, type ToastData, type ToastPosition, ToastProvider, type ToastProviderProps, type ToastVariant, Toggle, type ToggleProps, type ToggleSize, Tooltip, type TooltipPlacement, type TooltipProps, Transition, type TransitionPreset, type TransitionProps, type UseClipboardReturn, type UseDisclosureReturn, type UseHotkeysOptions, type UseIntersectionObserverOptions, type UseIntersectionObserverReturn, type UseSharedNowOptions, VisuallyHidden, type VisuallyHiddenProps, cn, focusSafely, getFocusableElements, useClipboard, useDebounce, useDisclosure, useHotkeys, useIntersectionObserver, useMediaQuery, useSharedNow, useToast };
925
+ type ErrorBoundaryProps = {
926
+ children: ReactNode;
927
+ fallback?: ReactNode | ((error: Error, reset: () => void) => ReactNode);
928
+ onError?: (error: Error, errorInfo: ErrorInfo) => void;
929
+ };
930
+ type State = {
931
+ error: Error | null;
932
+ };
933
+ declare class ErrorBoundary extends Component<ErrorBoundaryProps, State> {
934
+ state: State;
935
+ static getDerivedStateFromError(error: Error): State;
936
+ componentDidCatch(error: Error, errorInfo: ErrorInfo): void;
937
+ private reset;
938
+ render(): string | number | boolean | react_jsx_runtime.JSX.Element | Iterable<ReactNode> | null | undefined;
939
+ }
940
+
941
+ type LoadingScreenProps = {
942
+ message?: string;
943
+ size?: SpinnerSize;
944
+ className?: string;
945
+ };
946
+ declare function LoadingScreen({ message, size, className }: LoadingScreenProps): react_jsx_runtime.JSX.Element;
947
+
948
+ export { ActiveFilterPills, type ActiveFilterPillsProps, Alert, type AlertProps, type AlertVariant, Avatar, type AvatarProps, type AvatarSize, Badge, type BadgeProps, type BadgeSize, type BadgeVariant, type BreadcrumbItem, Breadcrumbs, type BreadcrumbsProps, Button, type ButtonProps, type ButtonSize, type ButtonVariant, Card, type CardPadding, type CardProps, type CardVariant, Checkbox, type CheckboxProps, CollapsibleSection, type CollapsibleSectionProps, ColorInput, type ColorInputProps, Combobox, type ComboboxOption, type ComboboxProps, ConfirmDialog, type ConfirmDialogProps, type ConfirmDialogVariant, CooldownRing, type CooldownRingProps, type CooldownRingSize, CopyField, type CopyFieldProps, DashboardLayout, type DashboardLayoutProps, Divider, type DividerProps, DotIndicator, type DotIndicatorProps, Drawer, type DrawerProps, type DrawerSide, type DrawerSize, DropZone, type DropZoneProps, Dropdown, DropdownItem, type DropdownItemProps, DropdownMenu, type DropdownMenuProps, type DropdownProps, DropdownSeparator, type DropdownSeparatorProps, DropdownTrigger, type DropdownTriggerProps, EmptyState, type EmptyStateProps, ErrorBoundary, type ErrorBoundaryProps, type FilterPill, FormField, type FormFieldProps, Heading, type HeadingLevel, type HeadingProps, type HotkeyBinding, type HotkeyModifiers, IconButton, type IconButtonProps, Input, type InputProps, LoadingScreen, type LoadingScreenProps, Modal, type ModalProps, MutationOverlay, type MutationOverlayProps, type MutationOverlayStatus, Navbar, type NavbarProps, NotificationBell, type NotificationBellProps, PageShell, type PageShellProps, type PageShellVariant, Pagination, type PaginationProps, Pill, Popover, type PopoverPlacement, type PopoverProps, ProgressBar, type ProgressBarProps, type ProgressBarVariant, ProgressButton, type ProgressButtonProps, RadioGroup, type RadioGroupProps, RadioItem, type RadioItemProps, ScrollArea, type ScrollAreaProps, SearchInput, type SearchInputProps, SectionCard, type SectionCardProps, Select, type SelectProps, Sidebar, type SidebarProps, type Size, Skeleton, type SkeletonProps, Slider, type SliderProps, Spinner, type SpinnerProps, type SpinnerSize, Stack, type StackProps, StageProgress, type StageProgressProps, StatCard, type StatCardProps, type StatCardTrend, type Step, Stepper, type StepperProps, Tab, TabList, type TabListProps, TabPanel, type TabPanelProps, type TabProps, Table, TableBody, type TableBodyProps, TableCell, type TableCellProps, TableHead, type TableHeadProps, TableHeader, type TableHeaderProps, type TableProps, TableRow, type TableRowProps, Tabs, type TabsProps, type TabsVariant, TagInput, type TagInputProps, Text, type TextColor, type TextProps, type TextSize, Textarea, type TextareaProps, type ToastData, type ToastPosition, ToastProvider, type ToastProviderProps, type ToastVariant, Toggle, type ToggleProps, type ToggleSize, Tooltip, type TooltipPlacement, type TooltipProps, Transition, type TransitionPreset, type TransitionProps, type UseClipboardReturn, type UseDisclosureReturn, type UseHotkeysOptions, type UseIntersectionObserverOptions, type UseIntersectionObserverReturn, type UseSharedNowOptions, VisuallyHidden, type VisuallyHiddenProps, cn, focusSafely, getFocusableElements, useClipboard, useDebounce, useDisclosure, useHotkeys, useIntersectionObserver, useMediaQuery, useScrollLock, useSharedNow, useToast };
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import React, { forwardRef, useState, useId, useMemo, useRef, useEffect, createContext, useCallback, isValidElement, cloneElement, useReducer, useContext } from 'react';
1
+ import React, { forwardRef, useState, useId, useMemo, useRef, useEffect, createContext, useCallback, isValidElement, cloneElement, useReducer, useContext, Component } from 'react';
2
2
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
3
3
  import { createPortal } from 'react-dom';
4
4
 
@@ -133,10 +133,12 @@ function matchModifiers(e, mods) {
133
133
  }
134
134
  function useHotkeys(bindings, options = {}) {
135
135
  const { enabled = true } = options;
136
+ const bindingsRef = useRef(bindings);
137
+ bindingsRef.current = bindings;
136
138
  useEffect(() => {
137
139
  if (!enabled) return;
138
140
  const onKeyDown = (e) => {
139
- for (const binding of bindings) {
141
+ for (const binding of bindingsRef.current) {
140
142
  if (e.key === binding.key && matchModifiers(e, binding.modifiers)) {
141
143
  binding.handler(e);
142
144
  }
@@ -144,7 +146,7 @@ function useHotkeys(bindings, options = {}) {
144
146
  };
145
147
  document.addEventListener("keydown", onKeyDown);
146
148
  return () => document.removeEventListener("keydown", onKeyDown);
147
- }, [enabled, ...bindings]);
149
+ }, [enabled]);
148
150
  }
149
151
  function useIntersectionObserver(options = {}) {
150
152
  const { root = null, rootMargin = "0px", threshold = 0, enabled = true } = options;
@@ -200,6 +202,30 @@ function useSharedNow(options = {}) {
200
202
  }, [interval, untilMs, enabled]);
201
203
  return now;
202
204
  }
205
+ var lockCount = 0;
206
+ var savedOverflow = "";
207
+ function lockScroll() {
208
+ if (typeof document === "undefined") return;
209
+ if (lockCount === 0) {
210
+ savedOverflow = document.body.style.overflow;
211
+ document.body.style.overflow = "hidden";
212
+ }
213
+ lockCount++;
214
+ }
215
+ function unlockScroll() {
216
+ if (typeof document === "undefined") return;
217
+ lockCount = Math.max(0, lockCount - 1);
218
+ if (lockCount === 0) {
219
+ document.body.style.overflow = savedOverflow;
220
+ }
221
+ }
222
+ function useScrollLock(active) {
223
+ useEffect(() => {
224
+ if (!active) return;
225
+ lockScroll();
226
+ return () => unlockScroll();
227
+ }, [active]);
228
+ }
203
229
 
204
230
  // src/tokens/colors.ts
205
231
  var colors = {
@@ -1251,23 +1277,6 @@ var Card = forwardRef(function Card2({ hoverable, variant = "surface", padding =
1251
1277
  }
1252
1278
  );
1253
1279
  });
1254
- var scrollLockCount = 0;
1255
- var savedOverflow = "";
1256
- function lockScroll() {
1257
- if (typeof document === "undefined") return;
1258
- if (scrollLockCount === 0) {
1259
- savedOverflow = document.body.style.overflow;
1260
- document.body.style.overflow = "hidden";
1261
- }
1262
- scrollLockCount++;
1263
- }
1264
- function unlockScroll() {
1265
- if (typeof document === "undefined") return;
1266
- scrollLockCount = Math.max(0, scrollLockCount - 1);
1267
- if (scrollLockCount === 0) {
1268
- document.body.style.overflow = savedOverflow;
1269
- }
1270
- }
1271
1280
  function Modal({
1272
1281
  isOpen,
1273
1282
  onClose,
@@ -1299,11 +1308,7 @@ function Modal({
1299
1308
  if (lastActive?.isConnected) focusSafely(lastActive);
1300
1309
  };
1301
1310
  }, [isOpen]);
1302
- useEffect(() => {
1303
- if (!isOpen) return;
1304
- lockScroll();
1305
- return () => unlockScroll();
1306
- }, [isOpen]);
1311
+ useScrollLock(isOpen);
1307
1312
  if (!isOpen) return null;
1308
1313
  return /* @__PURE__ */ jsx(
1309
1314
  "div",
@@ -1452,17 +1457,32 @@ function Tooltip({ content, delayMs = 500, placement = "top", className, childre
1452
1457
  const el = anchorRef.current;
1453
1458
  if (!el) return;
1454
1459
  const r = el.getBoundingClientRect();
1460
+ const vw = window.innerWidth;
1455
1461
  const centerX = r.left + r.width / 2;
1456
- const preferTop = placement === "top";
1462
+ const centerY = r.top + r.height / 2;
1457
1463
  const topY = r.top - 10;
1458
1464
  const bottomY = r.bottom + 10;
1459
- const canTop = topY > 8;
1460
- const effPlacement = preferTop ? canTop ? "top" : "bottom" : "bottom";
1461
- setPos({
1462
- left: Math.round(centerX),
1463
- top: Math.round(effPlacement === "top" ? topY : bottomY),
1464
- placement: effPlacement
1465
- });
1465
+ const leftX = r.left - 10;
1466
+ const rightX = r.right + 10;
1467
+ let effPlacement = placement;
1468
+ if (placement === "top" || placement === "bottom") {
1469
+ const canTop = topY > 8;
1470
+ effPlacement = placement === "top" ? canTop ? "top" : "bottom" : "bottom";
1471
+ setPos({
1472
+ left: Math.round(centerX),
1473
+ top: Math.round(effPlacement === "top" ? topY : bottomY),
1474
+ placement: effPlacement
1475
+ });
1476
+ } else {
1477
+ const canLeft = leftX > 8;
1478
+ const canRight = rightX < vw - 8;
1479
+ effPlacement = placement === "left" ? canLeft ? "left" : "right" : canRight ? "right" : "left";
1480
+ setPos({
1481
+ left: Math.round(effPlacement === "left" ? leftX : rightX),
1482
+ top: Math.round(centerY),
1483
+ placement: effPlacement
1484
+ });
1485
+ }
1466
1486
  }, [placement]);
1467
1487
  const scheduleOpen = useCallback(() => {
1468
1488
  clearTimer();
@@ -1536,7 +1556,7 @@ function Tooltip({ content, delayMs = 500, placement = "top", className, childre
1536
1556
  style: pos ? {
1537
1557
  left: pos.left,
1538
1558
  top: pos.top,
1539
- transform: pos.placement === "top" ? "translate(-50%, -100%)" : "translate(-50%, 0%)"
1559
+ transform: pos.placement === "top" ? "translate(-50%, -100%)" : pos.placement === "bottom" ? "translate(-50%, 0%)" : pos.placement === "left" ? "translate(-100%, -50%)" : "translate(0%, -50%)"
1540
1560
  } : { left: 0, top: 0, transform: "translate(-9999px, -9999px)" },
1541
1561
  children: content
1542
1562
  }
@@ -2079,7 +2099,7 @@ function StatCard({ value, label, icon, trend, className }) {
2079
2099
  "div",
2080
2100
  {
2081
2101
  className: cn(
2082
- "bg-white/5 ring-1 ring-white/10 rounded-xl p-4 flex items-start gap-3",
2102
+ "glass rounded-xl p-4 flex items-start gap-3",
2083
2103
  className
2084
2104
  ),
2085
2105
  children: [
@@ -3812,23 +3832,6 @@ function Popover({
3812
3832
  ) : null
3813
3833
  ] });
3814
3834
  }
3815
- var scrollLockCount2 = 0;
3816
- var savedOverflow2 = "";
3817
- function lockScroll2() {
3818
- if (typeof document === "undefined") return;
3819
- if (scrollLockCount2 === 0) {
3820
- savedOverflow2 = document.body.style.overflow;
3821
- document.body.style.overflow = "hidden";
3822
- }
3823
- scrollLockCount2++;
3824
- }
3825
- function unlockScroll2() {
3826
- if (typeof document === "undefined") return;
3827
- scrollLockCount2 = Math.max(0, scrollLockCount2 - 1);
3828
- if (scrollLockCount2 === 0) {
3829
- document.body.style.overflow = savedOverflow2;
3830
- }
3831
- }
3832
3835
  var sizeClass8 = {
3833
3836
  left: { sm: "w-64", md: "w-80", lg: "w-96", full: "w-screen" },
3834
3837
  right: { sm: "w-64", md: "w-80", lg: "w-96", full: "w-screen" },
@@ -3878,11 +3881,7 @@ function Drawer({
3878
3881
  if (lastActive?.isConnected) focusSafely(lastActive);
3879
3882
  };
3880
3883
  }, [isOpen]);
3881
- useEffect(() => {
3882
- if (!isOpen) return;
3883
- lockScroll2();
3884
- return () => unlockScroll2();
3885
- }, [isOpen]);
3884
+ useScrollLock(isOpen);
3886
3885
  if (!isOpen) return null;
3887
3886
  return /* @__PURE__ */ jsxs("div", { className: "fixed inset-0 z-50", role: "presentation", children: [
3888
3887
  /* @__PURE__ */ jsx(
@@ -4265,5 +4264,60 @@ function VisuallyHidden({ children, as: Tag = "span", style, ...props }) {
4265
4264
  }
4266
4265
  );
4267
4266
  }
4267
+ var ErrorBoundary = class extends Component {
4268
+ constructor() {
4269
+ super(...arguments);
4270
+ this.state = { error: null };
4271
+ this.reset = () => {
4272
+ this.setState({ error: null });
4273
+ };
4274
+ }
4275
+ static getDerivedStateFromError(error) {
4276
+ return { error };
4277
+ }
4278
+ componentDidCatch(error, errorInfo) {
4279
+ this.props.onError?.(error, errorInfo);
4280
+ }
4281
+ render() {
4282
+ const { error } = this.state;
4283
+ if (!error) return this.props.children;
4284
+ const { fallback } = this.props;
4285
+ if (typeof fallback === "function") {
4286
+ return fallback(error, this.reset);
4287
+ }
4288
+ if (fallback !== void 0) {
4289
+ return fallback;
4290
+ }
4291
+ return /* @__PURE__ */ jsxs("div", { role: "alert", className: cn("glass rounded-xl p-6 text-center"), children: [
4292
+ /* @__PURE__ */ jsx("h2", { className: "text-lg font-semibold text-white mb-2", children: "Something went wrong" }),
4293
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-white/60 mb-4", children: error.message }),
4294
+ /* @__PURE__ */ jsx(
4295
+ "button",
4296
+ {
4297
+ type: "button",
4298
+ onClick: this.reset,
4299
+ className: "inline-flex items-center justify-center gap-2 rounded-xl font-semibold leading-none px-3.5 py-2.5 text-sm bg-gradient-to-r from-violet-600 to-purple-600 text-white shadow-glow hover:shadow-glow-lg hover:scale-[1.02] transition-[transform,background-color,box-shadow,opacity] active:translate-y-[0.5px]",
4300
+ children: "Try again"
4301
+ }
4302
+ )
4303
+ ] });
4304
+ }
4305
+ };
4306
+ function LoadingScreen({ message, size = "lg", className }) {
4307
+ return /* @__PURE__ */ jsxs(
4308
+ "div",
4309
+ {
4310
+ role: "status",
4311
+ className: cn(
4312
+ "flex flex-col items-center justify-center min-h-screen bg-ml-bg gap-4",
4313
+ className
4314
+ ),
4315
+ children: [
4316
+ /* @__PURE__ */ jsx(Spinner, { size }),
4317
+ message && /* @__PURE__ */ jsx("p", { className: "text-sm text-white/60", children: message })
4318
+ ]
4319
+ }
4320
+ );
4321
+ }
4268
4322
 
4269
- export { ActiveFilterPills, Alert, Avatar, Badge, Breadcrumbs, Button, Card, Checkbox, CollapsibleSection, ColorInput, Combobox, ConfirmDialog, CooldownRing, CopyField, DashboardLayout, Divider, DotIndicator, Drawer, DropZone, Dropdown, DropdownItem, DropdownMenu, DropdownSeparator, DropdownTrigger, EmptyState, FormField, Heading, IconButton, Input, Modal, MutationOverlay, Navbar, NotificationBell, PageShell, Pagination, Pill, Popover, ProgressBar, ProgressButton, RadioGroup, RadioItem, ScrollArea, SearchInput, SectionCard, Select, Sidebar, Skeleton, Slider, Spinner, Stack, StageProgress, StatCard, Stepper, Tab, TabList, TabPanel, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, Tabs, TagInput, Text, Textarea, ToastProvider, Toggle, Tooltip, Transition, VisuallyHidden, cn, colors, focusSafely, getFocusableElements, useClipboard, useDebounce, useDisclosure, useHotkeys, useIntersectionObserver, useMediaQuery, useSharedNow, useToast };
4323
+ export { ActiveFilterPills, Alert, Avatar, Badge, Breadcrumbs, Button, Card, Checkbox, CollapsibleSection, ColorInput, Combobox, ConfirmDialog, CooldownRing, CopyField, DashboardLayout, Divider, DotIndicator, Drawer, DropZone, Dropdown, DropdownItem, DropdownMenu, DropdownSeparator, DropdownTrigger, EmptyState, ErrorBoundary, FormField, Heading, IconButton, Input, LoadingScreen, Modal, MutationOverlay, Navbar, NotificationBell, PageShell, Pagination, Pill, Popover, ProgressBar, ProgressButton, RadioGroup, RadioItem, ScrollArea, SearchInput, SectionCard, Select, Sidebar, Skeleton, Slider, Spinner, Stack, StageProgress, StatCard, Stepper, Tab, TabList, TabPanel, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, Tabs, TagInput, Text, Textarea, ToastProvider, Toggle, Tooltip, Transition, VisuallyHidden, cn, colors, focusSafely, getFocusableElements, useClipboard, useDebounce, useDisclosure, useHotkeys, useIntersectionObserver, useMediaQuery, useScrollLock, useSharedNow, useToast };