@aurora-ds/components 0.18.2 → 0.19.1

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 (51) hide show
  1. package/README.md +1 -0
  2. package/dist/cjs/components/data-display/avatar/Avatar.props.d.ts +1 -1
  3. package/dist/cjs/components/forms/date-picker/calendar/Calendar.props.d.ts +2 -2
  4. package/dist/cjs/components/forms/select/Select.props.d.ts +11 -3
  5. package/dist/cjs/components/index.d.ts +3 -0
  6. package/dist/cjs/components/overlay/alert/Alert.d.ts +32 -0
  7. package/dist/cjs/components/overlay/alert/Alert.props.d.ts +56 -0
  8. package/dist/cjs/components/overlay/alert/Alert.styles.d.ts +4 -0
  9. package/dist/cjs/components/overlay/alert/index.d.ts +2 -0
  10. package/dist/cjs/constants/globalConstants.d.ts +1 -0
  11. package/dist/cjs/hooks/index.d.ts +2 -0
  12. package/dist/cjs/hooks/useAlert.d.ts +23 -0
  13. package/dist/cjs/hooks/useAlert.types.d.ts +56 -0
  14. package/dist/cjs/index.js +340 -5
  15. package/dist/cjs/index.js.map +1 -1
  16. package/dist/cjs/interfaces/alert.types.d.ts +8 -0
  17. package/dist/cjs/interfaces/index.d.ts +1 -0
  18. package/dist/cjs/resources/Icons.d.ts +4 -1
  19. package/dist/cjs/resources/icons/AlertCircleIcon.d.ts +2 -0
  20. package/dist/cjs/resources/icons/AlertTriangleIcon.d.ts +2 -0
  21. package/dist/cjs/resources/icons/CheckCircleIcon.d.ts +2 -0
  22. package/dist/cjs/utils/ui/components/foundation/text/getTruncateTextStyles.utils.d.ts +5 -1
  23. package/dist/cjs/utils/ui/components/overlay/alert/getAlertIcon.utils.d.ts +8 -0
  24. package/dist/cjs/utils/ui/components/overlay/alert/getAlertPositionStyles.utils.d.ts +8 -0
  25. package/dist/cjs/utils/ui/components/overlay/alert/getAlertVariantColors.utils.d.ts +14 -0
  26. package/dist/esm/components/data-display/avatar/Avatar.props.d.ts +1 -1
  27. package/dist/esm/components/forms/date-picker/calendar/Calendar.props.d.ts +2 -2
  28. package/dist/esm/components/forms/select/Select.props.d.ts +11 -3
  29. package/dist/esm/components/index.d.ts +3 -0
  30. package/dist/esm/components/overlay/alert/Alert.d.ts +32 -0
  31. package/dist/esm/components/overlay/alert/Alert.props.d.ts +56 -0
  32. package/dist/esm/components/overlay/alert/Alert.styles.d.ts +4 -0
  33. package/dist/esm/components/overlay/alert/index.d.ts +2 -0
  34. package/dist/esm/constants/globalConstants.d.ts +1 -0
  35. package/dist/esm/hooks/index.d.ts +2 -0
  36. package/dist/esm/hooks/useAlert.d.ts +23 -0
  37. package/dist/esm/hooks/useAlert.types.d.ts +56 -0
  38. package/dist/esm/index.js +339 -7
  39. package/dist/esm/index.js.map +1 -1
  40. package/dist/esm/interfaces/alert.types.d.ts +8 -0
  41. package/dist/esm/interfaces/index.d.ts +1 -0
  42. package/dist/esm/resources/Icons.d.ts +4 -1
  43. package/dist/esm/resources/icons/AlertCircleIcon.d.ts +2 -0
  44. package/dist/esm/resources/icons/AlertTriangleIcon.d.ts +2 -0
  45. package/dist/esm/resources/icons/CheckCircleIcon.d.ts +2 -0
  46. package/dist/esm/utils/ui/components/foundation/text/getTruncateTextStyles.utils.d.ts +5 -1
  47. package/dist/esm/utils/ui/components/overlay/alert/getAlertIcon.utils.d.ts +8 -0
  48. package/dist/esm/utils/ui/components/overlay/alert/getAlertPositionStyles.utils.d.ts +8 -0
  49. package/dist/esm/utils/ui/components/overlay/alert/getAlertVariantColors.utils.d.ts +14 -0
  50. package/dist/index.d.ts +164 -6
  51. package/package.json +1 -1
package/dist/esm/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { jsx, jsxs, Fragment as Fragment$1 } from 'react/jsx-runtime';
2
- import React, { Children, isValidElement, cloneElement, createElement, Fragment, useMemo, memo, useCallback, forwardRef, useState, useRef, useEffect, useLayoutEffect } from 'react';
2
+ import React, { Children, isValidElement, cloneElement, createElement, Fragment, useMemo, memo, useCallback, forwardRef, useState, useRef, useEffect, useLayoutEffect, createContext, useContext } from 'react';
3
3
  import { createStyles, useTheme, keyframes, colors } from '@aurora-ds/theme';
4
4
  import { createPortal } from 'react-dom';
5
5
 
@@ -141,6 +141,10 @@ const getTruncateTextStyles = (maxLines) => (maxLines === 1
141
141
  display: '-webkit-box',
142
142
  WebkitLineClamp: maxLines,
143
143
  WebkitBoxOrient: 'vertical',
144
+ // Improve wrapping behavior for multi-line clamp compatibility
145
+ whiteSpace: 'normal',
146
+ overflowWrap: 'break-word',
147
+ wordBreak: 'break-word',
144
148
  lineClamp: maxLines,
145
149
  });
146
150
 
@@ -216,8 +220,11 @@ const Text = ({ children, variant = 'span', color, fontSize, fontFamily, maxLine
216
220
  const variantStyles = useMemo(() => getTextVariantStyles(theme), [theme]);
217
221
  const tag = variantStyles[variant].tag;
218
222
  const parsedChildren = useMemo(() => parseTextWithBold(children), [children]);
223
+ // Force inline truncate styles when needed (fix for multi-line clamp not applied in some envs)
224
+ const truncateStyles = maxLines ? getTruncateTextStyles(maxLines) : undefined;
219
225
  return createElement(tag, {
220
226
  className: TEXT_STYLES.root({ variant, color, fontSize, fontFamily, maxLines, underline, preserveWhitespace }),
227
+ style: truncateStyles,
221
228
  'aria-label': ariaLabel,
222
229
  'aria-labelledby': ariaLabelledBy,
223
230
  'aria-describedby': ariaDescribedBy,
@@ -418,6 +425,7 @@ const Chip = ({ label, icon, variant = 'filled', color = 'default', size = 'md',
418
425
  Chip.displayName = 'Chip';
419
426
 
420
427
  const BUTTON_SIZE = 36;
428
+ const ALERT_MAX_WIDTH = 320;
421
429
  const MENU_ITEM_SIZE = 32;
422
430
  const DRAWER_ITEM_HEIGHT = 32;
423
431
  const DEFAULT_TRANSITION_DURATION_MS = 150;
@@ -476,7 +484,11 @@ const Avatar = ({ image, label, onClick, size = 'medium', color, borderColor, ba
476
484
  const AVATAR_SIZES = getAvatarSizes(theme);
477
485
  const hasImage = !!image;
478
486
  const clickable = !!onClick;
479
- return (jsx("div", { className: AVATAR_STYLES.root({ hasImage, clickable, size, color, borderColor, backgroundColor }), onClick: onClick, children: hasImage ? (jsx("img", { src: image, alt: label || 'Avatar', className: AVATAR_STYLES.image })) : (jsx(Text, { variant: 'label', fontSize: AVATAR_SIZES[size].fontSize, children: label || '?' })) }));
487
+ return (jsx("div", { className: AVATAR_STYLES.root({ hasImage, clickable, size, color, borderColor, backgroundColor }), onClick: (event) => {
488
+ if (onClick) {
489
+ onClick(event);
490
+ }
491
+ }, children: hasImage ? (jsx("img", { src: image, alt: label || 'Avatar', className: AVATAR_STYLES.image })) : (jsx(Text, { variant: 'label', fontSize: AVATAR_SIZES[size].fontSize, children: label || '?' })) }));
480
492
  };
481
493
  Avatar.displayName = 'Avatar';
482
494
 
@@ -935,10 +947,22 @@ const Stack = ({ children, direction = 'row', gap = 'sm', width, height, align =
935
947
  };
936
948
  Stack.displayName = 'Stack';
937
949
 
950
+ const AlertCircleIcon = () => {
951
+ return (jsxs("svg", { xmlns: 'http://www.w3.org/2000/svg', width: '100%', height: '100%', viewBox: '0 0 24 24', fill: 'none', stroke: 'currentColor', strokeWidth: '2', strokeLinecap: 'round', strokeLinejoin: 'round', children: [jsx("circle", { cx: '12', cy: '12', r: '10' }), jsx("line", { x1: '12', y1: '8', x2: '12', y2: '12' }), jsx("line", { x1: '12', y1: '16', x2: '12.01', y2: '16' })] }));
952
+ };
953
+
954
+ const AlertTriangleIcon = () => {
955
+ return (jsxs("svg", { xmlns: 'http://www.w3.org/2000/svg', width: '100%', height: '100%', viewBox: '0 0 24 24', fill: 'none', stroke: 'currentColor', strokeWidth: '2', strokeLinecap: 'round', strokeLinejoin: 'round', children: [jsx("path", { d: 'M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z' }), jsx("line", { x1: '12', y1: '9', x2: '12', y2: '13' }), jsx("line", { x1: '12', y1: '17', x2: '12.01', y2: '17' })] }));
956
+ };
957
+
938
958
  const CalendarIcon = () => {
939
959
  return (jsxs("svg", { xmlns: 'http://www.w3.org/2000/svg', width: '24', height: '24', viewBox: '0 0 24 24', fill: 'none', stroke: 'currentColor', strokeWidth: '2', strokeLinecap: 'round', strokeLinejoin: 'round', children: [jsx("path", { d: 'M8 2v4' }), jsx("path", { d: 'M16 2v4' }), jsx("rect", { width: '18', height: '18', x: '3', y: '4', rx: '2' }), jsx("path", { d: 'M3 10h18' })] }));
940
960
  };
941
961
 
962
+ const CheckCircleIcon = () => {
963
+ return (jsxs("svg", { xmlns: 'http://www.w3.org/2000/svg', width: '100%', height: '100%', viewBox: '0 0 24 24', fill: 'none', stroke: 'currentColor', strokeWidth: '2', strokeLinecap: 'round', strokeLinejoin: 'round', children: [jsx("circle", { cx: '12', cy: '12', r: '10' }), jsx("polyline", { points: '9 12 11 14 15 10' })] }));
964
+ };
965
+
942
966
  const ChevronDownIcon = () => {
943
967
  return (jsx("svg", { xmlns: 'http://www.w3.org/2000/svg', width: '100%', height: '100%', viewBox: '0 0 24 24', fill: 'none', stroke: 'currentColor', strokeWidth: '2', strokeLinecap: 'round', strokeLinejoin: 'round', className: 'lucide lucide-chevron-down-icon lucide-chevron-down', children: jsx("path", { d: 'm6 9 6 6 6-6' }) }));
944
968
  };
@@ -961,6 +985,10 @@ const EyeOffIcon = () => {
961
985
  return (jsxs("svg", { xmlns: 'http://www.w3.org/2000/svg', width: '24', height: '24', viewBox: '0 0 24 24', fill: 'none', stroke: 'currentColor', strokeWidth: '2', strokeLinecap: 'round', strokeLinejoin: 'round', className: 'lucide lucide-eye-off-icon lucide-eye-off', children: [jsx("path", { d: 'M10.733 5.076a10.744 10.744 0 0 1 11.205 6.575 1 1 0 0 1 0 .696 10.747 10.747 0 0 1-1.444 2.49' }), jsx("path", { d: 'M14.084 14.158a3 3 0 0 1-4.242-4.242' }), jsx("path", { d: 'M17.479 17.499a10.75 10.75 0 0 1-15.417-5.151 1 1 0 0 1 0-.696 10.75 10.75 0 0 1 4.446-5.143' }), jsx("path", { d: 'm2 2 20 20' })] }));
962
986
  };
963
987
 
988
+ const InfoIcon = () => {
989
+ return (jsxs("svg", { xmlns: 'http://www.w3.org/2000/svg', width: '100%', height: '100%', viewBox: '0 0 24 24', fill: 'none', stroke: 'currentColor', strokeWidth: '2', strokeLinecap: 'round', strokeLinejoin: 'round', children: [jsx("circle", { cx: '12', cy: '12', r: '10' }), jsx("line", { x1: '12', y1: '16', x2: '12', y2: '12' }), jsx("line", { x1: '12', y1: '8', x2: '12.01', y2: '8' })] }));
990
+ };
991
+
964
992
  const MoreHorizontalIcon = () => {
965
993
  return (jsxs("svg", { xmlns: 'http://www.w3.org/2000/svg', width: '24', height: '24', viewBox: '0 0 24 24', fill: 'none', stroke: 'currentColor', strokeWidth: '2', strokeLinecap: 'round', strokeLinejoin: 'round', className: 'lucide lucide-ellipsis-icon lucide-ellipsis', children: [jsx("circle", { cx: '12', cy: '12', r: '1' }), jsx("circle", { cx: '19', cy: '12', r: '1' }), jsx("circle", { cx: '5', cy: '12', r: '1' })] }));
966
994
  };
@@ -1375,10 +1403,11 @@ MenuItem.displayName = 'MenuItem';
1375
1403
  /**
1376
1404
  * Select component that uses Menu for dropdown
1377
1405
  */
1378
- const Select = ({ options, value, onChange, placeholder = 'Select an option', disabled = false, width }) => {
1406
+ const Select = ({ options, value, onChange, label, mandatory = false, placeholder = 'Select an option', disabled = false, width }) => {
1379
1407
  const [isOpen, setIsOpen] = useState(false);
1380
1408
  const triggerRef = useRef(null);
1381
1409
  const selectedOption = options.find(option => option.value === value);
1410
+ const menuWidth = triggerRef.current?.offsetWidth ?? width;
1382
1411
  const handleTriggerClick = () => {
1383
1412
  if (!disabled) {
1384
1413
  setIsOpen(!isOpen);
@@ -1388,10 +1417,10 @@ const Select = ({ options, value, onChange, placeholder = 'Select an option', di
1388
1417
  setIsOpen(false);
1389
1418
  };
1390
1419
  const handleSelect = (selectedValue) => {
1391
- onChange?.(selectedValue);
1420
+ onChange(selectedValue);
1392
1421
  setIsOpen(false);
1393
1422
  };
1394
- return (jsxs(Fragment$1, { children: [jsxs("div", { ref: triggerRef, className: SELECT_STYLES.root({ disabled, width }), onClick: handleTriggerClick, role: 'button', tabIndex: disabled ? -1 : 0, "aria-expanded": isOpen, "aria-haspopup": 'listbox', children: [jsx("div", { className: SELECT_STYLES.trigger, children: jsx(Text, { variant: 'p', maxLines: 1, color: selectedOption ? 'text' : 'textSecondary', children: selectedOption ? selectedOption.label : placeholder }) }), jsx(Icon, { children: jsx(ChevronDownIcon, {}) })] }), jsx(Menu, { anchor: isOpen ? triggerRef.current : null, onClose: handleClose, width: width, children: jsx(MenuGroup, { children: options.map(option => (jsx(SelectItem, { option: option, isSelected: option.value === value, onSelect: handleSelect }, option.value))) }) })] }));
1423
+ return (jsxs(Stack, { direction: 'column', gap: 'xs', align: 'stretch', width: width ?? '100%', children: [label && (jsxs(Stack, { direction: 'row', gap: 'xs', align: 'center', children: [jsx(Text, { variant: 'label', fontSize: 'sm', children: label }), mandatory && (jsx(Text, { variant: 'label', fontSize: 'sm', color: 'error', children: "*" }))] })), jsxs("div", { ref: triggerRef, className: SELECT_STYLES.root({ disabled, width }), onClick: handleTriggerClick, role: 'button', tabIndex: disabled ? -1 : 0, "aria-expanded": isOpen, "aria-haspopup": 'listbox', children: [jsx("div", { className: SELECT_STYLES.trigger, children: jsx(Text, { variant: 'p', maxLines: 1, color: selectedOption ? 'text' : 'textSecondary', children: selectedOption ? selectedOption.label : placeholder }) }), jsx(Icon, { children: jsx(ChevronDownIcon, {}) })] }), jsx(Menu, { anchor: isOpen ? triggerRef.current : null, onClose: handleClose, width: menuWidth, children: jsx(MenuGroup, { children: options.map(option => (jsx(SelectItem, { option: option, isSelected: option.value === value, onSelect: handleSelect }, option.value))) }) })] }));
1395
1424
  };
1396
1425
  Select.displayName = 'Select';
1397
1426
 
@@ -1667,7 +1696,7 @@ const Calendar$1 = ({ value, onDateSelect, minDate, maxDate, locale = 'fr-FR', }
1667
1696
  const year = value?.getFullYear() ?? new Date().getFullYear();
1668
1697
  return Math.floor(year / 12) * 12;
1669
1698
  });
1670
- const calendarDays = useMemo(() => getCalendarDays(currentMonth.getFullYear(), currentMonth.getMonth(), value, minDate, maxDate), [currentMonth, value, minDate, maxDate]);
1699
+ const calendarDays = useMemo(() => getCalendarDays(currentMonth.getFullYear(), currentMonth.getMonth(), value, minDate ?? undefined, maxDate ?? undefined), [currentMonth, value, minDate, maxDate]);
1671
1700
  const headerLabel = useMemo(() => {
1672
1701
  if (view === 'days') {
1673
1702
  return currentMonth.toLocaleDateString(locale, { month: 'long', year: 'numeric' });
@@ -2129,6 +2158,160 @@ const Accordion = ({ title, children, expanded, defaultExpanded = false, onChang
2129
2158
  };
2130
2159
  Accordion.displayName = 'Accordion';
2131
2160
 
2161
+ const ALERT_OFFSET = 16;
2162
+ /**
2163
+ * Get position styles for alert
2164
+ * @param position - Alert position
2165
+ * @param offsetY - Vertical offset for stacking (in pixels)
2166
+ * @returns Object with CSS position styles (top/bottom + left/right)
2167
+ */
2168
+ const getAlertPositionStyles = (position, offsetY) => {
2169
+ const verticalPosition = `${ALERT_OFFSET + offsetY}px`;
2170
+ switch (position) {
2171
+ case 'top-left':
2172
+ return { top: verticalPosition, left: `${ALERT_OFFSET}px` };
2173
+ case 'top-right':
2174
+ return { top: verticalPosition, right: `${ALERT_OFFSET}px` };
2175
+ case 'bottom-left':
2176
+ return { bottom: verticalPosition, left: `${ALERT_OFFSET}px` };
2177
+ case 'bottom-right':
2178
+ return { bottom: verticalPosition, right: `${ALERT_OFFSET}px` };
2179
+ default:
2180
+ return { top: verticalPosition, right: `${ALERT_OFFSET}px` };
2181
+ }
2182
+ };
2183
+
2184
+ /**
2185
+ * Get colors for alert variants
2186
+ * @param theme - Aurora theme
2187
+ * @param variant - Alert variant
2188
+ * @returns Colors for the variant
2189
+ */
2190
+ const getAlertVariantColors = (theme, variant) => {
2191
+ const variantColors = {
2192
+ default: {
2193
+ background: theme.colors.surface,
2194
+ border: theme.colors.border,
2195
+ iconColor: 'text'
2196
+ },
2197
+ info: {
2198
+ background: theme.colors.infoSubtle,
2199
+ border: theme.colors.info,
2200
+ iconColor: 'info'
2201
+ },
2202
+ warning: {
2203
+ background: theme.colors.warningSubtle,
2204
+ border: theme.colors.warning,
2205
+ iconColor: 'warning'
2206
+ },
2207
+ error: {
2208
+ background: theme.colors.errorSubtle,
2209
+ border: theme.colors.error,
2210
+ iconColor: 'error'
2211
+ },
2212
+ success: {
2213
+ background: theme.colors.successSubtle,
2214
+ border: theme.colors.success,
2215
+ iconColor: 'success'
2216
+ }
2217
+ };
2218
+ return variantColors[variant];
2219
+ };
2220
+
2221
+ const ALERT_STYLES = createStyles((theme) => ({
2222
+ root: ({ variant, position, isVisible, offsetY, maxWidth }) => {
2223
+ const colors = getAlertVariantColors(theme, variant);
2224
+ const positionStyles = getAlertPositionStyles(position, offsetY);
2225
+ return {
2226
+ position: 'fixed',
2227
+ ...positionStyles,
2228
+ display: 'flex',
2229
+ alignItems: 'center',
2230
+ gap: theme.spacing.sm,
2231
+ padding: theme.spacing.sm,
2232
+ backgroundColor: colors.background,
2233
+ border: `1px solid ${colors.border}`,
2234
+ borderRadius: theme.radius.md,
2235
+ boxShadow: theme.shadows.lg,
2236
+ minWidth: ALERT_MAX_WIDTH,
2237
+ maxWidth,
2238
+ overflow: 'hidden',
2239
+ zIndex: theme.zIndex.toast,
2240
+ opacity: isVisible ? 1 : 0,
2241
+ transform: isVisible ? 'translateY(0)' :
2242
+ (position.startsWith('top') ? 'translateY(-8px)' : 'translateY(8px)'),
2243
+ transition: `all ${theme.transition.normal}, opacity ${theme.transition.fast}`,
2244
+ pointerEvents: isVisible ? 'auto' : 'none'
2245
+ };
2246
+ }
2247
+ }));
2248
+
2249
+ // Pre-create icon instances to avoid recreation on every call
2250
+ const ALERT_ICONS = {
2251
+ default: jsx(InfoIcon, {}),
2252
+ info: jsx(InfoIcon, {}),
2253
+ warning: jsx(AlertTriangleIcon, {}),
2254
+ error: jsx(AlertCircleIcon, {}),
2255
+ success: jsx(CheckCircleIcon, {})
2256
+ };
2257
+ /**
2258
+ * Get icon component for alert variant
2259
+ * @param variant - Alert variant
2260
+ * @returns JSX Element for the icon
2261
+ */
2262
+ const getAlertIcon = (variant) => {
2263
+ return ALERT_ICONS[variant];
2264
+ };
2265
+
2266
+ /**
2267
+ * Alert component - Display notifications with different variants
2268
+ *
2269
+ * **Variants:**
2270
+ * - `default`: Standard alert with neutral styling
2271
+ * - `info`: Informational alert with blue styling
2272
+ * - `warning`: Warning alert with yellow/orange styling
2273
+ * - `error`: Error alert with red styling
2274
+ * - `success`: Success alert with green styling
2275
+ *
2276
+ * **Features:**
2277
+ * - Auto-dismisses after 3 seconds
2278
+ * - Positioned at screen corners
2279
+ * - Supports stacking with offsetY
2280
+ * - Icon based on variant
2281
+ * - Smooth animations
2282
+ * - Dynamic height calculation for proper stacking
2283
+ *
2284
+ * @example
2285
+ * ```tsx
2286
+ * <Alert
2287
+ * text="Operation completed successfully"
2288
+ * variant="success"
2289
+ * position="top-right"
2290
+ * isVisible={true}
2291
+ * />
2292
+ * ```
2293
+ */
2294
+ const Alert = memo(({ text, variant = 'default', position = 'top-right', isVisible = false, offsetY = 0, maxWidth = ALERT_MAX_WIDTH, alertId, onHeightChange }) => {
2295
+ const theme = useTheme();
2296
+ const alertRef = useRef(null);
2297
+ const lastHeightRef = useRef(0);
2298
+ const icon = useMemo(() => getAlertIcon(variant), [variant]);
2299
+ const colors = useMemo(() => getAlertVariantColors(theme, variant), [theme, variant]);
2300
+ // Report height changes to parent (only when height actually changes)
2301
+ useEffect(() => {
2302
+ if (alertRef.current && onHeightChange && isVisible && alertId) {
2303
+ const height = alertRef.current.offsetHeight;
2304
+ // Only call onHeightChange if height has actually changed
2305
+ if (height !== lastHeightRef.current) {
2306
+ lastHeightRef.current = height;
2307
+ onHeightChange(alertId, height);
2308
+ }
2309
+ }
2310
+ }, [isVisible, text, maxWidth, alertId, onHeightChange]);
2311
+ return (jsxs("div", { ref: alertRef, className: ALERT_STYLES.root({ variant, position, isVisible, offsetY, maxWidth }), role: 'alert', "aria-live": 'polite', children: [jsx(Icon, { size: 'sm', color: colors.iconColor, children: icon }), jsx(Text, { variant: 'span', fontSize: 'sm', color: colors.iconColor, maxLines: 4, children: text })] }));
2312
+ });
2313
+ Alert.displayName = 'Alert';
2314
+
2132
2315
  const MODAL_STYLES = createStyles((theme) => ({
2133
2316
  background: (isFadingIn) => ({
2134
2317
  background: 'rgba(0, 0, 0, 0.6)',
@@ -2639,5 +2822,154 @@ const Pagination = ({ currentPage, totalPages, onPageChange, onPrevious, onNext,
2639
2822
  };
2640
2823
  Pagination.displayName = 'Pagination';
2641
2824
 
2642
- export { Accordion, Avatar, AvatarGroup, Breadcrumb, BreadcrumbEllipsis, BreadcrumbLink, BreadcrumbPage, BreadcrumbSeparator, Button, Card, Chip, DatePicker_default as DatePicker, DrawerItem, Form, Grid, Icon, IconButton, Input_default as Input, Menu, MenuGroup, MenuItem, Modal, Page, PageSection, Pagination, Select, Separator, Skeleton, Stack, TabItem, Tabs, Text, TextArea_default as TextArea, useAnchorPosition, useClickOutside, useTransitionRender };
2825
+ const AlertContext = createContext(undefined);
2826
+ const ALERT_DURATION = 3000;
2827
+ const MAX_ALERTS = 2;
2828
+ const ALERT_HEIGHT = 48; // Approximate height with padding (px)
2829
+ const ALERT_SPACING = 6; // Spacing between alerts (px)
2830
+ /**
2831
+ * Alert Provider Component
2832
+ * Manages alert queue and renders alerts in a portal
2833
+ */
2834
+ const AlertProvider = ({ children }) => {
2835
+ const [alerts, setAlerts] = useState([]);
2836
+ const [visibleAlerts, setVisibleAlerts] = useState(new Set());
2837
+ const [alertHeights, setAlertHeights] = useState(new Map());
2838
+ const timersRef = useRef(new Map());
2839
+ const showAlert = useCallback((options) => {
2840
+ const { text, variant = 'default', position = 'top-right', duration = ALERT_DURATION, maxWidth = 300 } = options;
2841
+ const id = `alert-${Date.now()}-${Math.random()}`;
2842
+ const newAlert = {
2843
+ id,
2844
+ text,
2845
+ variant,
2846
+ position,
2847
+ timestamp: Date.now(),
2848
+ maxWidth
2849
+ };
2850
+ setAlerts((prev) => {
2851
+ const updatedAlerts = [...prev, newAlert];
2852
+ // Keep only MAX_ALERTS for the same position
2853
+ const samePositionAlerts = updatedAlerts.filter(a => a.position === position);
2854
+ if (samePositionAlerts.length > MAX_ALERTS) {
2855
+ // Remove the oldest alert for this position
2856
+ const oldestAlert = samePositionAlerts[0];
2857
+ const timerToRemove = timersRef.current.get(oldestAlert.id);
2858
+ if (timerToRemove) {
2859
+ clearTimeout(timerToRemove);
2860
+ timersRef.current.delete(oldestAlert.id);
2861
+ }
2862
+ setVisibleAlerts((prevVisible) => {
2863
+ const newVisible = new Set(prevVisible);
2864
+ newVisible.delete(oldestAlert.id);
2865
+ return newVisible;
2866
+ });
2867
+ // Remove from alerts array after animation
2868
+ setTimeout(() => {
2869
+ setAlerts((current) => current.filter(a => a.id !== oldestAlert.id));
2870
+ setAlertHeights((prevHeights) => {
2871
+ const newMap = new Map(prevHeights);
2872
+ newMap.delete(oldestAlert.id);
2873
+ return newMap;
2874
+ });
2875
+ }, 300);
2876
+ return updatedAlerts.filter(a => a.id !== oldestAlert.id);
2877
+ }
2878
+ return updatedAlerts;
2879
+ });
2880
+ // Show alert after a small delay for animation
2881
+ setTimeout(() => {
2882
+ setVisibleAlerts((prev) => new Set(prev).add(id));
2883
+ }, 10);
2884
+ // Auto-dismiss timer
2885
+ const timer = setTimeout(() => {
2886
+ setVisibleAlerts((prev) => {
2887
+ const newVisible = new Set(prev);
2888
+ newVisible.delete(id);
2889
+ return newVisible;
2890
+ });
2891
+ // Remove from DOM after animation
2892
+ setTimeout(() => {
2893
+ setAlerts((prev) => prev.filter(a => a.id !== id));
2894
+ setAlertHeights((prev) => {
2895
+ const newMap = new Map(prev);
2896
+ newMap.delete(id);
2897
+ return newMap;
2898
+ });
2899
+ timersRef.current.delete(id);
2900
+ }, 300);
2901
+ }, duration);
2902
+ timersRef.current.set(id, timer);
2903
+ }, []);
2904
+ // Handle height changes from alerts (memoized to avoid re-creating on every render)
2905
+ const handleHeightChange = useCallback((alertId, height) => {
2906
+ setAlertHeights((prev) => {
2907
+ const currentHeight = prev.get(alertId);
2908
+ // Only update if height actually changed
2909
+ if (currentHeight !== height) {
2910
+ const newMap = new Map(prev);
2911
+ newMap.set(alertId, height);
2912
+ return newMap;
2913
+ }
2914
+ return prev;
2915
+ });
2916
+ }, []);
2917
+ // Cleanup timers on unmount
2918
+ useEffect(() => {
2919
+ return () => {
2920
+ timersRef.current.forEach(timer => clearTimeout(timer));
2921
+ timersRef.current.clear();
2922
+ };
2923
+ }, []);
2924
+ const contextValue = useMemo(() => ({ showAlert }), [showAlert]);
2925
+ // Group alerts by position
2926
+ const alertsByPosition = useMemo(() => {
2927
+ const grouped = {};
2928
+ alerts.forEach(alert => {
2929
+ if (!grouped[alert.position]) {
2930
+ grouped[alert.position] = [];
2931
+ }
2932
+ grouped[alert.position].push(alert);
2933
+ });
2934
+ return grouped;
2935
+ }, [alerts]);
2936
+ return (jsxs(AlertContext.Provider, { value: contextValue, children: [children, typeof window !== 'undefined' && createPortal(jsx(Fragment$1, { children: Object.entries(alertsByPosition).map(([position, positionAlerts]) => {
2937
+ // positionAlerts is ordered from oldest -> newest
2938
+ const isTopPosition = position.startsWith('top');
2939
+ const ordered = isTopPosition ? [...positionAlerts].reverse() : positionAlerts;
2940
+ return ordered.map((alert, index) => {
2941
+ // Calculate offset based on actual heights of previous alerts
2942
+ let offset = 0;
2943
+ for (let i = 0; i < index; i++) {
2944
+ const prevAlert = ordered[i];
2945
+ const height = alertHeights.get(prevAlert.id) || ALERT_HEIGHT;
2946
+ offset += height + ALERT_SPACING;
2947
+ }
2948
+ return (jsx(Alert, { text: alert.text, variant: alert.variant, position: alert.position, isVisible: visibleAlerts.has(alert.id), offsetY: offset, maxWidth: alert.maxWidth, alertId: alert.id, onHeightChange: handleHeightChange }, alert.id));
2949
+ });
2950
+ }) }), document.body)] }));
2951
+ };
2952
+ /**
2953
+ * Hook to access alert functionality
2954
+ *
2955
+ * @example
2956
+ * ```tsx
2957
+ * const { showAlert } = useAlert()
2958
+ *
2959
+ * showAlert({
2960
+ * text: 'Operation completed',
2961
+ * variant: 'success',
2962
+ * position: 'top-right'
2963
+ * })
2964
+ * ```
2965
+ */
2966
+ const useAlert = () => {
2967
+ const context = useContext(AlertContext);
2968
+ if (!context) {
2969
+ throw new Error('useAlert must be used within an AlertProvider');
2970
+ }
2971
+ return context;
2972
+ };
2973
+
2974
+ export { Accordion, Alert, AlertProvider, Avatar, AvatarGroup, Breadcrumb, BreadcrumbEllipsis, BreadcrumbLink, BreadcrumbPage, BreadcrumbSeparator, Button, Card, Chip, DatePicker_default as DatePicker, DrawerItem, Form, Grid, Icon, IconButton, Input_default as Input, Menu, MenuGroup, MenuItem, Modal, Page, PageSection, Pagination, Select, Separator, Skeleton, Stack, TabItem, Tabs, Text, TextArea_default as TextArea, useAlert, useAnchorPosition, useClickOutside, useTransitionRender };
2643
2975
  //# sourceMappingURL=index.js.map