@aurora-ds/components 0.15.4 → 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/README.md +1 -0
  2. package/dist/cjs/components/data-display/skeleton/Skeleton.d.ts +4 -0
  3. package/dist/cjs/components/data-display/skeleton/Skeleton.props.d.ts +7 -0
  4. package/dist/cjs/components/data-display/skeleton/Skeleton.styles.d.ts +3 -0
  5. package/dist/cjs/components/data-display/skeleton/index.d.ts +2 -0
  6. package/dist/cjs/components/forms/select/Select.d.ts +7 -0
  7. package/dist/cjs/components/forms/select/Select.props.d.ts +31 -0
  8. package/dist/cjs/components/forms/select/Select.styles.d.ts +10 -0
  9. package/dist/cjs/components/forms/select/SelectItem/SelectItem.d.ts +7 -0
  10. package/dist/cjs/components/forms/select/SelectItem/SelectItem.props.d.ts +19 -0
  11. package/dist/cjs/components/forms/select/SelectItem/SelectItem.styles.d.ts +7 -0
  12. package/dist/cjs/components/forms/select/SelectItem/index.d.ts +2 -0
  13. package/dist/cjs/components/forms/select/index.d.ts +2 -0
  14. package/dist/cjs/components/index.d.ts +3 -0
  15. package/dist/cjs/components/overlay/modal/Modal.d.ts +4 -0
  16. package/dist/cjs/components/overlay/modal/Modal.props.d.ts +13 -0
  17. package/dist/cjs/components/overlay/modal/Modal.styles.d.ts +4 -0
  18. package/dist/cjs/components/overlay/modal/index.d.ts +2 -0
  19. package/dist/cjs/constants/animations.d.ts +1 -0
  20. package/dist/cjs/index.js +380 -169
  21. package/dist/cjs/index.js.map +1 -1
  22. package/dist/cjs/interfaces/index.d.ts +1 -0
  23. package/dist/cjs/interfaces/select.types.d.ts +5 -0
  24. package/dist/cjs/resources/Icons.d.ts +2 -1
  25. package/dist/cjs/resources/icons/CloseIcon.d.ts +2 -0
  26. package/dist/esm/components/data-display/skeleton/Skeleton.d.ts +4 -0
  27. package/dist/esm/components/data-display/skeleton/Skeleton.props.d.ts +7 -0
  28. package/dist/esm/components/data-display/skeleton/Skeleton.styles.d.ts +3 -0
  29. package/dist/esm/components/data-display/skeleton/index.d.ts +2 -0
  30. package/dist/esm/components/forms/select/Select.d.ts +7 -0
  31. package/dist/esm/components/forms/select/Select.props.d.ts +31 -0
  32. package/dist/esm/components/forms/select/Select.styles.d.ts +10 -0
  33. package/dist/esm/components/forms/select/SelectItem/SelectItem.d.ts +7 -0
  34. package/dist/esm/components/forms/select/SelectItem/SelectItem.props.d.ts +19 -0
  35. package/dist/esm/components/forms/select/SelectItem/SelectItem.styles.d.ts +7 -0
  36. package/dist/esm/components/forms/select/SelectItem/index.d.ts +2 -0
  37. package/dist/esm/components/forms/select/index.d.ts +2 -0
  38. package/dist/esm/components/index.d.ts +3 -0
  39. package/dist/esm/components/overlay/modal/Modal.d.ts +4 -0
  40. package/dist/esm/components/overlay/modal/Modal.props.d.ts +13 -0
  41. package/dist/esm/components/overlay/modal/Modal.styles.d.ts +4 -0
  42. package/dist/esm/components/overlay/modal/index.d.ts +2 -0
  43. package/dist/esm/constants/animations.d.ts +1 -0
  44. package/dist/esm/index.js +381 -173
  45. package/dist/esm/index.js.map +1 -1
  46. package/dist/esm/interfaces/index.d.ts +1 -0
  47. package/dist/esm/interfaces/select.types.d.ts +5 -0
  48. package/dist/esm/resources/Icons.d.ts +2 -1
  49. package/dist/esm/resources/icons/CloseIcon.d.ts +2 -0
  50. package/dist/index.d.ts +64 -3
  51. package/package.json +1 -1
package/dist/esm/index.js CHANGED
@@ -1,6 +1,7 @@
1
- import { jsx, jsxs } from 'react/jsx-runtime';
1
+ import { jsx, jsxs, Fragment as Fragment$1 } from 'react/jsx-runtime';
2
2
  import React, { Children, isValidElement, cloneElement, createElement, Fragment, useMemo, memo, useCallback, forwardRef, useState, useRef, useEffect, useLayoutEffect } from 'react';
3
- import { createStyles, useTheme, colors } from '@aurora-ds/theme';
3
+ import { createStyles, useTheme, keyframes, colors } from '@aurora-ds/theme';
4
+ import { createPortal } from 'react-dom';
4
5
 
5
6
  const ICON_STYLES = createStyles((theme) => ({
6
7
  root: (size, color, backgroundColor, padding, borderRadius) => ({
@@ -533,6 +534,31 @@ const AvatarGroup = ({ children, limit, size = 'medium' }) => {
533
534
  };
534
535
  AvatarGroup.displayName = 'AvatarGroup';
535
536
 
537
+ const shimmerAnimation = keyframes({
538
+ '0%': {
539
+ backgroundPosition: '-200% 0',
540
+ },
541
+ '100%': {
542
+ backgroundPosition: '200% 0',
543
+ },
544
+ });
545
+
546
+ const SKELETON_STYLES = createStyles((theme) => ({
547
+ root: (width, height, borderRadius) => ({
548
+ width,
549
+ height,
550
+ borderRadius: borderRadius ? theme.radius[borderRadius] : theme.radius.md,
551
+ background: `linear-gradient(90deg, ${theme.colors.surfaceActive} 25%, ${theme.colors.surfaceHover} 50%, ${theme.colors.surfaceActive} 75%)`,
552
+ backgroundSize: '200% 100%',
553
+ animation: `${shimmerAnimation} 1.2s ease-in-out infinite`,
554
+ }),
555
+ }));
556
+
557
+ const Skeleton = ({ width, height, borderRadius }) => {
558
+ return (jsx("div", { className: SKELETON_STYLES.root(width, height, borderRadius) }));
559
+ };
560
+ Skeleton.displayName = 'Skeleton';
561
+
536
562
  const getButtonSizeStyles = () => ({
537
563
  small: {
538
564
  height: BUTTON_SIZE - 8,
@@ -764,15 +790,15 @@ const FORM_STYLES = createStyles(() => ({
764
790
  /**
765
791
  * Form component - A transparent wrapper for form elements with automatic preventDefault handling
766
792
  */
767
- const Form = ({ children, onSubmit, ariaLabel, }) => {
793
+ const Form$1 = ({ children, onSubmit, ariaLabel, }) => {
768
794
  const handleSubmit = useCallback((e) => {
769
795
  e.preventDefault();
770
796
  onSubmit?.(e);
771
797
  }, [onSubmit]);
772
798
  return (jsx("form", { onSubmit: handleSubmit, className: FORM_STYLES.root, "aria-label": ariaLabel, children: children }));
773
799
  };
774
- Form.displayName = 'Form';
775
- var Form_default = memo(Form);
800
+ Form$1.displayName = 'Form';
801
+ var Form = memo(Form$1);
776
802
 
777
803
  /**
778
804
  * Input styles using createStyles from @aurora-ds/theme
@@ -897,6 +923,8 @@ const ChevronRightIcon = () => {
897
923
  return (jsx("svg", { xmlns: 'http://www.w3.org/2000/svg', width: '24', height: '24', viewBox: '0 0 24 24', fill: 'none', stroke: 'currentColor', "stroke-width": '2', "stroke-linecap": 'round', "stroke-linejoin": 'round', className: 'lucide lucide-chevron-right-icon lucide-chevron-right', children: jsx("path", { d: 'm9 18 6-6-6-6' }) }));
898
924
  };
899
925
 
926
+ const CloseIcon = () => (jsx("svg", { width: '16', height: '16', viewBox: '0 0 16 16', fill: 'none', xmlns: 'http://www.w3.org/2000/svg', children: jsx("path", { d: 'M12 4L4 12M4 4l8 8', stroke: 'currentColor', strokeWidth: '2', strokeLinecap: 'round', strokeLinejoin: 'round' }) }));
927
+
900
928
  const EyeIcon = () => {
901
929
  return (jsxs("svg", { xmlns: 'http://www.w3.org/2000/svg', width: '24', height: '24', viewBox: '0 0 24 24', fill: 'none', stroke: 'currentColor', "stroke-width": '2', "stroke-linecap": 'round', "stroke-linejoin": 'round', className: 'lucide lucide-eye-icon lucide-eye', children: [jsx("path", { d: 'M2.062 12.348a1 1 0 0 1 0-.696 10.75 10.75 0 0 1 19.876 0 1 1 0 0 1 0 .696 10.75 10.75 0 0 1-19.876 0' }), jsx("circle", { cx: '12', cy: '12', r: '3' })] }));
902
930
  };
@@ -1002,6 +1030,302 @@ const TextArea = forwardRef(({ value, onChange, label, mandatory = false, placeh
1002
1030
  TextArea.displayName = 'TextArea';
1003
1031
  var TextArea_default = memo(TextArea);
1004
1032
 
1033
+ /**
1034
+ * Select styles using createStyles from @aurora-ds/theme
1035
+ */
1036
+ const SELECT_STYLES = createStyles((theme) => ({
1037
+ root: ({ disabled = false, width }) => ({
1038
+ position: 'relative',
1039
+ display: 'inline-flex',
1040
+ alignItems: 'center',
1041
+ justifyContent: 'space-between',
1042
+ boxSizing: 'border-box',
1043
+ width: width ?? '100%',
1044
+ padding: `${theme.spacing.sm} ${theme.spacing.md}`,
1045
+ border: `1px solid ${theme.colors.border}`,
1046
+ borderRadius: theme.radius.md,
1047
+ fontSize: theme.fontSize.md,
1048
+ fontFamily: 'inherit',
1049
+ backgroundColor: theme.colors.surface,
1050
+ color: theme.colors.text,
1051
+ cursor: disabled ? 'not-allowed' : 'pointer',
1052
+ transition: `border-color ${theme.transition.fast}`,
1053
+ outline: 'none',
1054
+ minHeight: BUTTON_SIZE,
1055
+ maxHeight: BUTTON_SIZE,
1056
+ gap: theme.spacing.md,
1057
+ lineHeight: theme.lineHeight.none,
1058
+ textOverflow: 'ellipsis',
1059
+ overflow: 'hidden',
1060
+ whiteSpace: 'nowrap',
1061
+ ...(disabled && {
1062
+ color: theme.colors.disabled,
1063
+ cursor: 'not-allowed',
1064
+ opacity: theme.opacity.high
1065
+ }),
1066
+ ...(!disabled && {
1067
+ ':hover': {
1068
+ borderColor: theme.colors.primaryHover,
1069
+ },
1070
+ }),
1071
+ }),
1072
+ trigger: {
1073
+ display: 'flex',
1074
+ alignItems: 'center',
1075
+ gap: theme.spacing.sm,
1076
+ flex: 1
1077
+ },
1078
+ value: {
1079
+ flex: 1,
1080
+ fontSize: theme.fontSize.md,
1081
+ color: theme.colors.text
1082
+ },
1083
+ placeholder: {
1084
+ flex: 1,
1085
+ fontSize: theme.fontSize.md,
1086
+ color: theme.colors.textSecondary
1087
+ }
1088
+ }));
1089
+
1090
+ /**
1091
+ * SelectItem styles using createStyles from @aurora-ds/theme
1092
+ */
1093
+ const SELECT_ITEM_STYLES = createStyles((theme) => ({
1094
+ root: ({ isSelected = false, disabled = false }) => ({
1095
+ display: 'flex',
1096
+ alignItems: 'center',
1097
+ gap: theme.spacing.sm,
1098
+ padding: theme.spacing.sm,
1099
+ borderRadius: theme.radius.sm,
1100
+ backgroundColor: isSelected ? theme.colors.primary : theme.colors.surface,
1101
+ outline: 'none',
1102
+ border: 'none',
1103
+ cursor: disabled ? 'not-allowed' : 'pointer',
1104
+ minHeight: MENU_ITEM_SIZE,
1105
+ maxHeight: MENU_ITEM_SIZE,
1106
+ flexShrink: 0,
1107
+ transition: 'background-color 150ms ease-in-out',
1108
+ color: disabled ? theme.colors.disabled : (isSelected ? theme.colors.surface : theme.colors.text),
1109
+ opacity: disabled ? 0.5 : 1,
1110
+ ':hover': {
1111
+ backgroundColor: disabled ? theme.colors.surface : (isSelected ? theme.colors.primary : theme.colors.surfaceHover),
1112
+ },
1113
+ })
1114
+ }));
1115
+
1116
+ /**
1117
+ * SelectItem component for use inside Select dropdown
1118
+ */
1119
+ const SelectItem = ({ option, isSelected = false, onSelect }) => {
1120
+ const handleClick = () => {
1121
+ if (!option.disabled && onSelect) {
1122
+ onSelect(option.value);
1123
+ }
1124
+ };
1125
+ return (jsx("button", { className: SELECT_ITEM_STYLES.root({ isSelected, disabled: option.disabled }), onClick: handleClick, role: 'option', "aria-selected": isSelected, children: jsx(Text, { variant: 'label', maxLines: 1, children: option.label }) }));
1126
+ };
1127
+ SelectItem.displayName = 'SelectItem';
1128
+
1129
+ const MENU_STYLES = createStyles((theme) => ({
1130
+ root: (isFadingIn, anchorOrigin, position, width) => ({
1131
+ position: 'absolute',
1132
+ zIndex: theme.zIndex.dropdown,
1133
+ marginTop: theme.spacing.xs,
1134
+ backgroundColor: theme.colors.surface,
1135
+ borderRadius: theme.radius.md,
1136
+ border: `1px solid ${theme.colors.border}`,
1137
+ boxShadow: theme.shadows.md,
1138
+ minWidth: 150,
1139
+ maxHeight: MENU_ITEM_SIZE * 8,
1140
+ overflowY: 'auto',
1141
+ width,
1142
+ opacity: isFadingIn ? 1 : 0,
1143
+ transform: isFadingIn ? 'scale(1)' : 'scale(0.95)',
1144
+ transformOrigin: anchorOrigin === 'right' ? 'top right' : 'top left',
1145
+ transition: `opacity ${DEFAULT_TRANSITION_DURATION_MS}ms ease-out, transform ${DEFAULT_TRANSITION_DURATION_MS}ms ease-out`,
1146
+ top: position?.top ?? 0,
1147
+ left: position?.left ?? 0,
1148
+ visibility: position ? 'visible' : 'hidden',
1149
+ }),
1150
+ }));
1151
+
1152
+ /**
1153
+ * Hook pour calculer la position d'un élément par rapport à son ancre
1154
+ * @param anchor - Élément HTML servant d'ancre
1155
+ * @param menuRef - Référence vers l'élément à positionner
1156
+ * @param anchorOrigin - Origine de l'ancrage ('left' ou 'right')
1157
+ * @param isVisible - Si l'élément est visible (pour recalculer la position)
1158
+ */
1159
+ const useAnchorPosition = ({ anchor, menuRef, anchorOrigin = 'right', isVisible }) => {
1160
+ const [position, setPosition] = useState(null);
1161
+ useLayoutEffect(() => {
1162
+ if (anchor && isVisible) {
1163
+ const anchorRect = anchor.getBoundingClientRect();
1164
+ const menuWidth = menuRef.current?.offsetWidth || 150;
1165
+ const left = anchorOrigin === 'right'
1166
+ ? anchorRect.right - menuWidth + window.scrollX
1167
+ : anchorRect.left + window.scrollX;
1168
+ setPosition({
1169
+ top: anchorRect.bottom + window.scrollY,
1170
+ left
1171
+ });
1172
+ }
1173
+ else {
1174
+ setPosition(null);
1175
+ }
1176
+ }, [anchor, anchorOrigin, isVisible, menuRef]);
1177
+ return position;
1178
+ };
1179
+
1180
+ /**
1181
+ * Gestion d'un click en dehors d'un ou plusieurs éléments
1182
+ * @param refs Tableau de références à écouter
1183
+ * @param onClickOutside Callback si clic à l'extérieur
1184
+ * @param shouldTrigger Activation du comportement
1185
+ */
1186
+ const useClickOutside = (refs, onClickOutside, shouldTrigger = true) => {
1187
+ useEffect(() => {
1188
+ if (!shouldTrigger) {
1189
+ return;
1190
+ }
1191
+ const handleClick = (event) => {
1192
+ const isInside = refs.some(ref => ref.current?.contains(event.target));
1193
+ if (!isInside) {
1194
+ onClickOutside();
1195
+ }
1196
+ };
1197
+ document.addEventListener('mousedown', handleClick);
1198
+ return () => {
1199
+ document.removeEventListener('mousedown', handleClick);
1200
+ };
1201
+ }, [refs, onClickOutside, shouldTrigger]);
1202
+ };
1203
+
1204
+ /**
1205
+ * Hook pour gérer les animations de transition lors des renders
1206
+ * @param isOpen - État d'ouverture
1207
+ * @param duration - Durée de la transition en ms
1208
+ */
1209
+ const useTransitionRender = (isOpen, duration = DEFAULT_TRANSITION_DURATION_MS) => {
1210
+ const [isVisible, setIsVisible] = useState(false);
1211
+ const [isFadingIn, setIsFadingIn] = useState(false);
1212
+ useEffect(() => {
1213
+ if (isOpen) {
1214
+ setIsVisible(true);
1215
+ const timeout = setTimeout(() => {
1216
+ setIsFadingIn(true);
1217
+ }, 10);
1218
+ return () => {
1219
+ clearTimeout(timeout);
1220
+ };
1221
+ }
1222
+ else {
1223
+ setIsFadingIn(false);
1224
+ const timeout = setTimeout(() => {
1225
+ setIsVisible(false);
1226
+ }, duration);
1227
+ return () => {
1228
+ clearTimeout(timeout);
1229
+ };
1230
+ }
1231
+ }, [isOpen, duration]);
1232
+ return {
1233
+ isVisible,
1234
+ isFadingIn
1235
+ };
1236
+ };
1237
+
1238
+ const Menu = ({ anchor, onClose, children, anchorOrigin = 'right', width }) => {
1239
+ // refs
1240
+ const menuRef = useRef(null);
1241
+ const anchorRef = useRef(null);
1242
+ // variables
1243
+ const isOpen = Boolean(anchor);
1244
+ const refs = useMemo(() => [menuRef, anchorRef], []);
1245
+ // hooks
1246
+ const { isVisible, isFadingIn } = useTransitionRender(isOpen);
1247
+ const position = useAnchorPosition({ anchor: anchorRef.current, menuRef, anchorOrigin, isVisible });
1248
+ // useEffects
1249
+ useEffect(() => {
1250
+ if (anchor) {
1251
+ anchorRef.current = anchor;
1252
+ }
1253
+ }, [anchor]);
1254
+ useEffect(() => {
1255
+ if (!isVisible) {
1256
+ anchorRef.current = null;
1257
+ }
1258
+ }, [isVisible]);
1259
+ useClickOutside(refs, onClose, isVisible);
1260
+ if (!isVisible) {
1261
+ return null;
1262
+ }
1263
+ return (jsx("div", { ref: menuRef, className: MENU_STYLES.root(isFadingIn, anchorOrigin, position, width), children: children }));
1264
+ };
1265
+ Menu.displayName = 'Menu';
1266
+
1267
+ const MENU_GROUP_STYLES = createStyles((theme) => ({
1268
+ root: {
1269
+ display: 'flex',
1270
+ flexDirection: 'column',
1271
+ padding: theme.spacing.xs,
1272
+ },
1273
+ }));
1274
+
1275
+ const MenuGroup = ({ children }) => {
1276
+ return (jsx("div", { className: MENU_GROUP_STYLES.root, children: children }));
1277
+ };
1278
+ MenuGroup.displayName = 'MenuGroup';
1279
+
1280
+ const MENU_ITEM_STYLES = createStyles((theme) => ({
1281
+ root: {
1282
+ display: 'flex',
1283
+ alignItems: 'center',
1284
+ gap: theme.spacing.sm,
1285
+ padding: theme.spacing.sm,
1286
+ borderRadius: theme.radius.sm,
1287
+ backgroundColor: theme.colors.surface,
1288
+ outline: 'none',
1289
+ border: 'none',
1290
+ cursor: 'pointer',
1291
+ minHeight: MENU_ITEM_SIZE,
1292
+ maxHeight: MENU_ITEM_SIZE,
1293
+ flexShrink: 0,
1294
+ transition: 'background-color 150ms ease-in-out',
1295
+ ':hover': {
1296
+ backgroundColor: theme.colors.surfaceHover,
1297
+ },
1298
+ },
1299
+ }));
1300
+
1301
+ const MenuItem = ({ label, icon, onClick, textColor, iconColor }) => {
1302
+ return (jsxs("button", { className: MENU_ITEM_STYLES.root, onClick: onClick, children: [icon && (jsx(Icon, { color: iconColor ?? 'text', size: 'sm', children: icon })), jsx(Text, { variant: 'label', color: textColor ?? 'text', maxLines: 1, fontSize: 'sm', children: label })] }));
1303
+ };
1304
+ MenuItem.displayName = 'MenuItem';
1305
+
1306
+ /**
1307
+ * Select component that uses Menu for dropdown
1308
+ */
1309
+ const Select = ({ options, value, onChange, placeholder = 'Select an option', disabled = false, width }) => {
1310
+ const [isOpen, setIsOpen] = useState(false);
1311
+ const triggerRef = useRef(null);
1312
+ const selectedOption = options.find(option => option.value === value);
1313
+ const handleTriggerClick = () => {
1314
+ if (!disabled) {
1315
+ setIsOpen(!isOpen);
1316
+ }
1317
+ };
1318
+ const handleClose = () => {
1319
+ setIsOpen(false);
1320
+ };
1321
+ const handleSelect = (selectedValue) => {
1322
+ onChange?.(selectedValue);
1323
+ setIsOpen(false);
1324
+ };
1325
+ 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))) }) })] }));
1326
+ };
1327
+ Select.displayName = 'Select';
1328
+
1005
1329
  /**
1006
1330
  * Card styles using createStyles from @aurora-ds/theme
1007
1331
  */
@@ -1033,7 +1357,7 @@ const CARD_STYLES = createStyles((theme) => ({
1033
1357
  * - `column`: Vertical layout (default)
1034
1358
  * - `row`: Horizontal layout
1035
1359
  */
1036
- const Card = ({ children, direction = 'column', padding = 'md', width, height, gap, radius = 'md', shadow = 'sm', align, justify, backgroundColor = 'surface', borderColor, ariaLabel, ariaLabelledBy, ariaDescribedBy, role, tabIndex, }) => {
1360
+ const Card = ({ children, direction = 'column', padding = 'md', width, height, gap, radius = 'md', shadow = 'none', align, justify, backgroundColor = 'surface', borderColor = 'border', ariaLabel, ariaLabelledBy, ariaDescribedBy, role, tabIndex, }) => {
1037
1361
  return (jsx("div", { className: CARD_STYLES.root({
1038
1362
  direction,
1039
1363
  padding,
@@ -1263,182 +1587,66 @@ const Accordion = ({ title, children, expanded, defaultExpanded = false, onChang
1263
1587
  };
1264
1588
  Accordion.displayName = 'Accordion';
1265
1589
 
1266
- const MENU_STYLES = createStyles((theme) => ({
1267
- root: (isFadingIn, anchorOrigin, position, width) => ({
1268
- position: 'absolute',
1269
- zIndex: theme.zIndex.dropdown,
1270
- marginTop: theme.spacing.xs,
1271
- backgroundColor: theme.colors.surface,
1272
- borderRadius: theme.radius.md,
1273
- border: `1px solid ${theme.colors.border}`,
1274
- boxShadow: theme.shadows.md,
1275
- minWidth: 150,
1276
- maxHeight: MENU_ITEM_SIZE * 8,
1277
- overflowY: 'auto',
1278
- width,
1590
+ const MODAL_STYLES = createStyles((theme) => ({
1591
+ background: (isFadingIn) => ({
1592
+ background: 'rgba(0, 0, 0, 0.6)',
1593
+ display: 'flex',
1594
+ alignItems: 'center',
1595
+ justifyContent: 'center',
1596
+ position: 'fixed',
1597
+ top: 0,
1598
+ left: 0,
1599
+ zIndex: 999999,
1600
+ transition: 'opacity 150ms ease-in-out',
1601
+ width: '100vw',
1602
+ height: '100vh',
1279
1603
  opacity: isFadingIn ? 1 : 0,
1280
- transform: isFadingIn ? 'scale(1)' : 'scale(0.95)',
1281
- transformOrigin: anchorOrigin === 'right' ? 'top right' : 'top left',
1282
- transition: `opacity ${DEFAULT_TRANSITION_DURATION_MS}ms ease-out, transform ${DEFAULT_TRANSITION_DURATION_MS}ms ease-out`,
1283
- top: position?.top ?? 0,
1284
- left: position?.left ?? 0,
1285
- visibility: position ? 'visible' : 'hidden',
1604
+ }),
1605
+ content: (isFadingIn) => ({
1606
+ width: '100%',
1607
+ maxWidth: 500,
1608
+ borderRadius: 8,
1609
+ position: 'absolute',
1610
+ left: '50%',
1611
+ top: '50%',
1612
+ transform: isFadingIn ? 'translate(-50%, -50%) scale(1)' : 'translate(-50%, -50%) scale(0.95)',
1613
+ transformOrigin: 'center center',
1614
+ transition: 'transform 150ms ease-in-out',
1615
+ backgroundColor: theme.colors.background,
1616
+ padding: theme.spacing.md,
1617
+ display: 'flex',
1618
+ flexDirection: 'column',
1619
+ height: 'fit-content',
1620
+ alignItems: 'flex-start',
1621
+ gap: theme.spacing.md,
1286
1622
  }),
1287
1623
  }));
1288
1624
 
1289
- /**
1290
- * Hook pour calculer la position d'un élément par rapport à son ancre
1291
- * @param anchor - Élément HTML servant d'ancre
1292
- * @param menuRef - Référence vers l'élément à positionner
1293
- * @param anchorOrigin - Origine de l'ancrage ('left' ou 'right')
1294
- * @param isVisible - Si l'élément est visible (pour recalculer la position)
1295
- */
1296
- const useAnchorPosition = ({ anchor, menuRef, anchorOrigin = 'right', isVisible }) => {
1297
- const [position, setPosition] = useState(null);
1298
- useLayoutEffect(() => {
1299
- if (anchor && isVisible) {
1300
- const anchorRect = anchor.getBoundingClientRect();
1301
- const menuWidth = menuRef.current?.offsetWidth || 150;
1302
- const left = anchorOrigin === 'right'
1303
- ? anchorRect.right - menuWidth + window.scrollX
1304
- : anchorRect.left + window.scrollX;
1305
- setPosition({
1306
- top: anchorRect.bottom + window.scrollY,
1307
- left
1308
- });
1309
- }
1310
- else {
1311
- setPosition(null);
1312
- }
1313
- }, [anchor, anchorOrigin, isVisible, menuRef]);
1314
- return position;
1315
- };
1316
-
1317
- /**
1318
- * Gestion d'un click en dehors d'un ou plusieurs éléments
1319
- * @param refs Tableau de références à écouter
1320
- * @param onClickOutside Callback si clic à l'extérieur
1321
- * @param shouldTrigger Activation du comportement
1322
- */
1323
- const useClickOutside = (refs, onClickOutside, shouldTrigger = true) => {
1324
- useEffect(() => {
1325
- if (!shouldTrigger) {
1326
- return;
1327
- }
1328
- const handleClick = (event) => {
1329
- const isInside = refs.some(ref => ref.current?.contains(event.target));
1330
- if (!isInside) {
1331
- onClickOutside();
1332
- }
1333
- };
1334
- document.addEventListener('mousedown', handleClick);
1335
- return () => {
1336
- document.removeEventListener('mousedown', handleClick);
1337
- };
1338
- }, [refs, onClickOutside, shouldTrigger]);
1339
- };
1340
-
1341
- /**
1342
- * Hook pour gérer les animations de transition lors des renders
1343
- * @param isOpen - État d'ouverture
1344
- * @param duration - Durée de la transition en ms
1345
- */
1346
- const useTransitionRender = (isOpen, duration = DEFAULT_TRANSITION_DURATION_MS) => {
1347
- const [isVisible, setIsVisible] = useState(false);
1348
- const [isFadingIn, setIsFadingIn] = useState(false);
1349
- useEffect(() => {
1350
- if (isOpen) {
1351
- setIsVisible(true);
1352
- const timeout = setTimeout(() => {
1353
- setIsFadingIn(true);
1354
- }, 10);
1355
- return () => {
1356
- clearTimeout(timeout);
1357
- };
1358
- }
1359
- else {
1360
- setIsFadingIn(false);
1361
- const timeout = setTimeout(() => {
1362
- setIsVisible(false);
1363
- }, duration);
1364
- return () => {
1365
- clearTimeout(timeout);
1366
- };
1367
- }
1368
- }, [isOpen, duration]);
1369
- return {
1370
- isVisible,
1371
- isFadingIn
1372
- };
1373
- };
1374
-
1375
- const Menu = ({ anchor, onClose, children, anchorOrigin = 'right', width }) => {
1625
+ const Modal = ({ isOpen, onClose, label, children, isForm, action }) => {
1376
1626
  // refs
1377
- const menuRef = useRef(null);
1378
- const anchorRef = useRef(null);
1379
- // variables
1380
- const isOpen = Boolean(anchor);
1381
- const refs = useMemo(() => [menuRef, anchorRef], []);
1627
+ const modalRef = useRef(null);
1382
1628
  // hooks
1383
1629
  const { isVisible, isFadingIn } = useTransitionRender(isOpen);
1384
- const position = useAnchorPosition({ anchor: anchorRef.current, menuRef, anchorOrigin, isVisible });
1385
- // useEffects
1386
1630
  useEffect(() => {
1387
- if (anchor) {
1388
- anchorRef.current = anchor;
1389
- }
1390
- }, [anchor]);
1391
- useEffect(() => {
1392
- if (!isVisible) {
1393
- anchorRef.current = null;
1631
+ const handleKeyDown = (event) => {
1632
+ if (event.key === 'Escape' && isOpen) {
1633
+ onClose();
1634
+ }
1635
+ };
1636
+ document.addEventListener('keydown', handleKeyDown);
1637
+ return () => document.removeEventListener('keydown', handleKeyDown);
1638
+ }, [isOpen, onClose]);
1639
+ // components
1640
+ const Wrapper = isForm ? Form : Fragment;
1641
+ const wrapperProps = isForm ? {
1642
+ onSubmit: (e) => {
1643
+ e.preventDefault();
1644
+ action?.onClick();
1394
1645
  }
1395
- }, [isVisible]);
1396
- useClickOutside(refs, onClose, isVisible);
1397
- if (!isVisible) {
1398
- return null;
1399
- }
1400
- return (jsx("div", { ref: menuRef, className: MENU_STYLES.root(isFadingIn, anchorOrigin, position, width), children: children }));
1401
- };
1402
- Menu.displayName = 'Menu';
1403
-
1404
- const MENU_GROUP_STYLES = createStyles((theme) => ({
1405
- root: {
1406
- display: 'flex',
1407
- flexDirection: 'column',
1408
- padding: theme.spacing.xs,
1409
- },
1410
- }));
1411
-
1412
- const MenuGroup = ({ children }) => {
1413
- return (jsx("div", { className: MENU_GROUP_STYLES.root, children: children }));
1646
+ } : {};
1647
+ return createPortal(jsx(Fragment, { children: isVisible ? (jsx("div", { className: MODAL_STYLES.background(isFadingIn), ref: modalRef, children: jsx("div", { className: MODAL_STYLES.content(isFadingIn), children: jsxs(Wrapper, { ...wrapperProps, children: [jsxs(Stack, { justify: 'space-between', height: BUTTON_SIZE, width: '100%', children: [jsx(Text, { variant: 'h3', children: label }), !action && (jsx(IconButton, { icon: jsx(CloseIcon, {}), onClick: onClose, size: 'small', variant: 'text', textColor: 'text' }))] }), children, action && (jsxs(Stack, { justify: 'flex-end', children: [jsx(Button, { label: 'Cancel', onClick: onClose, variant: 'outlined' }), jsx(Button, { label: action.label, onClick: !isForm ? action.onClick : undefined, type: isForm ? 'submit' : 'button', disabled: action.disabled })] }))] }) }) })) : null }), document.body);
1414
1648
  };
1415
- MenuGroup.displayName = 'MenuGroup';
1416
-
1417
- const MENU_ITEM_STYLES = createStyles((theme) => ({
1418
- root: {
1419
- display: 'flex',
1420
- alignItems: 'center',
1421
- gap: theme.spacing.sm,
1422
- padding: theme.spacing.sm,
1423
- borderRadius: theme.radius.sm,
1424
- backgroundColor: theme.colors.surface,
1425
- outline: 'none',
1426
- border: 'none',
1427
- cursor: 'pointer',
1428
- minHeight: MENU_ITEM_SIZE,
1429
- maxHeight: MENU_ITEM_SIZE,
1430
- flexShrink: 0,
1431
- transition: 'background-color 150ms ease-in-out',
1432
- ':hover': {
1433
- backgroundColor: theme.colors.surfaceHover,
1434
- },
1435
- },
1436
- }));
1437
-
1438
- const MenuItem = ({ label, icon, onClick, textColor, iconColor }) => {
1439
- return (jsxs("button", { className: MENU_ITEM_STYLES.root, onClick: onClick, children: [icon && (jsx(Icon, { color: iconColor ?? 'text', size: 'sm', children: icon })), jsx(Text, { variant: 'label', color: textColor ?? 'text', maxLines: 1, fontSize: 'sm', children: label })] }));
1440
- };
1441
- MenuItem.displayName = 'MenuItem';
1649
+ Modal.displayName = 'Modal';
1442
1650
 
1443
1651
  /**
1444
1652
  * DrawerItem styles using createStyles from @aurora-ds/theme
@@ -1751,5 +1959,5 @@ const TabItem = ({ label, isActive = false, onClick, }) => {
1751
1959
  };
1752
1960
  TabItem.displayName = 'TabItem';
1753
1961
 
1754
- export { Accordion, Avatar, AvatarGroup, Breadcrumb, BreadcrumbEllipsis, BreadcrumbLink, BreadcrumbPage, BreadcrumbSeparator, Button, Card, Chip, DrawerItem, Form_default as Form, Grid, Icon, IconButton, Input_default as Input, Menu, MenuGroup, MenuItem, Page, PageSection, Separator, Stack, TabItem, Tabs, Text, TextArea_default as TextArea, useAnchorPosition, useClickOutside, useTransitionRender };
1962
+ export { Accordion, Avatar, AvatarGroup, Breadcrumb, BreadcrumbEllipsis, BreadcrumbLink, BreadcrumbPage, BreadcrumbSeparator, Button, Card, Chip, DrawerItem, Form, Grid, Icon, IconButton, Input_default as Input, Menu, MenuGroup, MenuItem, Modal, Page, PageSection, Select, Separator, Skeleton, Stack, TabItem, Tabs, Text, TextArea_default as TextArea, useAnchorPosition, useClickOutside, useTransitionRender };
1755
1963
  //# sourceMappingURL=index.js.map