@vention/machine-ui 3.35.0 → 3.37.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/index.esm.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
2
- import { Typography, Button, Stack, Box as Box$1, Grid, IconButton, Modal, linearProgressClasses, LinearProgress, InputAdornment, TextField, Radio, FormControlLabel, FormControl, Select, MenuItem, FormHelperText, ButtonGroup, Paper, Card, CardMedia, CardContent, ListItemIcon, ListItemText, Popover, MenuList, ClickAwayListener, useTheme as useTheme$1, Drawer, Tooltip, Checkbox } from '@mui/material';
3
- import { IconCheck, IconAlertTriangleFilled, IconAlertTriangle, IconExclamationMark, IconExternalLink, IconX, IconInfoCircleFilled, IconCircleCheckFilled, IconChevronDown, IconInfoCircle, IconInfoSmall, IconMinus, IconPlus, IconLoader2, IconCaretRightFilled, IconChevronsRight, IconChevronsLeft, IconChevronRight } from '@tabler/icons-react';
4
- import { forwardRef, useState, cloneElement, lazy, memo, Suspense, useMemo, useRef, useCallback, useEffect, createContext, useContext, useImperativeHandle, Children, Fragment as Fragment$1, useReducer } from 'react';
2
+ import { Typography, Button, Stack, Box as Box$1, Grid, IconButton, ClickAwayListener, Modal, linearProgressClasses, LinearProgress, InputAdornment, TextField, Radio, FormControlLabel, FormControl, Select, MenuItem, FormHelperText, ButtonGroup, Paper, Card, CardMedia, CardContent, ListItemIcon, ListItemText, Popover, MenuList, useTheme as useTheme$1, Drawer, Tooltip, Checkbox } from '@mui/material';
3
+ import { IconCheck, IconAlertTriangleFilled, IconAlertTriangle, IconExclamationMark, IconExternalLink, IconX, IconInfoCircleFilled, IconCircleCheckFilled, IconChevronUp, IconChevronDown, IconInfoCircle, IconInfoSmall, IconMinus, IconPlus, IconLoader2, IconCaretRightFilled, IconChevronsRight, IconChevronsLeft, IconChevronRight } from '@tabler/icons-react';
4
+ import { forwardRef, useState, cloneElement, useRef, useCallback, lazy, memo, Suspense, useMemo, useEffect, createContext, useContext, useImperativeHandle, Children, Fragment as Fragment$1, useReducer } from 'react';
5
5
  import { tss } from 'tss-react/mui';
6
6
  import Box from '@mui/material/Box';
7
7
  import CircularProgress from '@mui/material/CircularProgress';
@@ -3365,55 +3365,97 @@ const useStyles$B = tss.withParams().create(({
3365
3365
  };
3366
3366
  });
3367
3367
 
3368
+ const menuTestIds = {
3369
+ menuRootDiv: "menuRootDiv"
3370
+ };
3368
3371
  const VentionDropdownButton = _a => {
3369
3372
  var {
3370
3373
  variant = "simple",
3371
3374
  size = "small",
3372
3375
  className,
3373
3376
  disabled = false,
3374
- onClick,
3377
+ onClose,
3378
+ onOpen,
3375
3379
  icon,
3376
3380
  badgeText,
3377
3381
  label,
3378
3382
  stroke = 1,
3379
- contentColor
3383
+ contentColor,
3384
+ children,
3385
+ menuPlacement = "bottom",
3386
+ menuAlignment = "start",
3387
+ onClick
3380
3388
  } = _a,
3381
- other = __rest(_a, ["variant", "size", "className", "disabled", "onClick", "icon", "badgeText", "label", "stroke", "contentColor"]);
3389
+ other = __rest(_a, ["variant", "size", "className", "disabled", "onClose", "onOpen", "icon", "badgeText", "label", "stroke", "contentColor", "children", "menuPlacement", "menuAlignment", "onClick"]);
3382
3390
  const {
3383
3391
  classes,
3384
3392
  cx
3385
3393
  } = useStyles$A({
3386
3394
  variant,
3387
3395
  size,
3388
- contentColor
3396
+ contentColor,
3397
+ menuPlacement,
3398
+ menuAlignment
3399
+ });
3400
+ const ref = useRef(null);
3401
+ const [showDropdown, setShowDropdownInternal] = useState(false);
3402
+ const Chevron = showDropdown ? IconChevronUp : IconChevronDown;
3403
+ const handleClick = useCallback(event => {
3404
+ if (!showDropdown) {
3405
+ setShowDropdownInternal(true);
3406
+ onOpen === null || onOpen === void 0 ? void 0 : onOpen();
3407
+ } else {
3408
+ setShowDropdownInternal(false);
3409
+ onClose === null || onClose === void 0 ? void 0 : onClose();
3410
+ }
3411
+ onClick === null || onClick === void 0 ? void 0 : onClick(event);
3412
+ }, [showDropdown, onClose, onOpen, onClick]);
3413
+ return jsx(ClickAwayListener, {
3414
+ onClickAway: () => {
3415
+ setShowDropdownInternal(false);
3416
+ onClose === null || onClose === void 0 ? void 0 : onClose();
3417
+ },
3418
+ children: jsxs("div", {
3419
+ style: {
3420
+ display: "inline-block",
3421
+ position: "relative"
3422
+ },
3423
+ children: [jsxs(IconButton, Object.assign({
3424
+ ref: ref,
3425
+ className: cx(classes.root, className),
3426
+ disableRipple: true
3427
+ }, other, {
3428
+ disabled: disabled,
3429
+ onClick: handleClick,
3430
+ children: [icon, label && jsx(Typography, {
3431
+ className: classes.label,
3432
+ variant: LABEL_VARIANTS[size],
3433
+ children: label
3434
+ }), badgeText && jsx(VentionBadge, {
3435
+ size: "small",
3436
+ color: "slate",
3437
+ text: badgeText,
3438
+ variant: "filled",
3439
+ className: classes.badge
3440
+ }), jsx(Chevron, {
3441
+ stroke: stroke,
3442
+ size: 16,
3443
+ color: contentColor
3444
+ })]
3445
+ })), showDropdown && jsx("div", {
3446
+ "data-testid": menuTestIds.menuRootDiv,
3447
+ className: classes.menuContainer,
3448
+ children: children
3449
+ })]
3450
+ })
3389
3451
  });
3390
- return jsxs(IconButton, Object.assign({
3391
- className: cx(classes.root, className),
3392
- disableRipple: true,
3393
- disabled: disabled,
3394
- onClick: onClick
3395
- }, other, {
3396
- children: [icon, label && jsx(Typography, {
3397
- className: classes.label,
3398
- variant: LABEL_VARIANTS[size],
3399
- children: label
3400
- }), badgeText && jsx(VentionBadge, {
3401
- size: "small",
3402
- color: "slate",
3403
- text: badgeText,
3404
- variant: "filled",
3405
- className: classes.badge
3406
- }), jsx(IconChevronDown, {
3407
- stroke: stroke,
3408
- size: 16,
3409
- color: contentColor
3410
- })]
3411
- }));
3412
3452
  };
3413
3453
  const useStyles$A = tss.withParams().create(({
3414
3454
  variant,
3415
3455
  size,
3416
3456
  contentColor,
3457
+ menuPlacement,
3458
+ menuAlignment,
3417
3459
  theme
3418
3460
  }) => {
3419
3461
  const stylingProperties = getButtonStylingPropertiesGivenProps(variant, size, theme);
@@ -3446,9 +3488,51 @@ const useStyles$A = tss.withParams().create(({
3446
3488
  },
3447
3489
  badge: {
3448
3490
  maxWidth: "100px"
3449
- }
3491
+ },
3492
+ menuContainer: Object.assign({
3493
+ position: "absolute",
3494
+ zIndex: theme.zIndex.modal
3495
+ }, getMenuPositionStyles(menuPlacement, menuAlignment))
3450
3496
  };
3451
3497
  });
3498
+ const getMenuPositionStyles = (placement, alignment) => {
3499
+ const gap = `calc(100% + ${BUTTON_MENU_GAP})`;
3500
+ const placementStyles = {
3501
+ bottom: {
3502
+ top: gap
3503
+ },
3504
+ top: {
3505
+ bottom: gap
3506
+ },
3507
+ right: {
3508
+ left: gap
3509
+ },
3510
+ left: {
3511
+ right: gap
3512
+ }
3513
+ };
3514
+ const isHorizontalPlacement = placement === "bottom" || placement === "top";
3515
+ const alignmentStyles = {
3516
+ start: isHorizontalPlacement ? {
3517
+ left: 0
3518
+ } : {
3519
+ top: 0
3520
+ },
3521
+ middle: isHorizontalPlacement ? {
3522
+ left: "50%",
3523
+ transform: "translateX(-50%)"
3524
+ } : {
3525
+ top: "50%",
3526
+ transform: "translateY(-50%)"
3527
+ },
3528
+ end: isHorizontalPlacement ? {
3529
+ right: 0
3530
+ } : {
3531
+ bottom: 0
3532
+ }
3533
+ };
3534
+ return Object.assign(Object.assign({}, placementStyles[placement]), alignmentStyles[alignment]);
3535
+ };
3452
3536
  const LABEL_VARIANTS = {
3453
3537
  small: "uiText12Medium",
3454
3538
  medium: "uiText14Medium",
@@ -3462,8 +3546,9 @@ const BUTTON_PADDINGS = {
3462
3546
  const BUTTON_GAPS = {
3463
3547
  small: "4px",
3464
3548
  medium: "6px",
3465
- large: "6px"
3549
+ large: "8px"
3466
3550
  };
3551
+ const BUTTON_MENU_GAP = "3px";
3467
3552
 
3468
3553
  const defaultProps$6 = {
3469
3554
  size: "medium",
@@ -11996,4 +12081,111 @@ const useStyle = tss$1.withParams().create(({
11996
12081
  };
11997
12082
  });
11998
12083
 
11999
- export { COLORS, DEFAULT_VENTION_TREE_ITEM_HEIGHT, ElementTestIds, IconSizes, VentionAlert, VentionBadge, VentionBanner, VentionButton, VentionCallout, VentionCheckbox, VentionCombobox, VentionCounter, VentionDraggable, VentionDrawer, VentionDropZone, VentionDropdownButton, VentionIcon, VentionIconButton, VentionLink, VentionMenu, VentionModal, VentionModalBanner, VentionModalBase, VentionPopover, VentionProgressBar, VentionRadio, VentionRadioTile, VentionSelect, VentionSidebarItem, VentionSlider, VentionSpinner, VentionStatusDot, VentionStatusIndicator, VentionStepper, VentionSteps, VentionSwitch, VentionTabs, VentionTextInput, VentionTooltip, VentionTreeItem, VentionUploadFile, getSpinnerColor, getVentionBadgeColors, machineUiHmiTheme, machineUiTheme, testIds, useBannerColorStyles, ventionModalBaseTestIds, ventionModalTestIds, ventionPopoverArrowSize };
12084
+ const POSITIONED_MENU_OFFSET = 3;
12085
+ /**
12086
+ * Generic positioned component that can position any React component based on viewport constraints.
12087
+ * It can be positioned relative to an anchor element or a specified position.
12088
+ *
12089
+ * @param props - The properties for the positioned component, including position and children.
12090
+ * @returns The positioned component wrapper.
12091
+ */
12092
+ function VentionPositionedComponent({
12093
+ position,
12094
+ anchorRect,
12095
+ placement = "bottom",
12096
+ alignment = "start",
12097
+ children
12098
+ }) {
12099
+ const ref = useRef(null);
12100
+ const [adjustedPosition, setAdjustedPosition] = useState({
12101
+ top: 0,
12102
+ left: 0
12103
+ });
12104
+ useEffect(() => {
12105
+ const {
12106
+ width,
12107
+ height
12108
+ } = ref.current.getBoundingClientRect();
12109
+ let top = 0;
12110
+ let left = 0;
12111
+ if (position !== undefined) {
12112
+ top = position.top;
12113
+ left = position.left;
12114
+ } else {
12115
+ const calculatedPosition = calcMenuPosition(anchorRect, {
12116
+ width,
12117
+ height
12118
+ }, placement, alignment);
12119
+ top = calculatedPosition.top;
12120
+ left = calculatedPosition.left;
12121
+ }
12122
+ // Clamp to viewport
12123
+ const maxLeft = Math.max(0, window.innerWidth - width);
12124
+ const maxTop = Math.max(0, window.innerHeight - height);
12125
+ left = Math.min(Math.max(left, 0), maxLeft);
12126
+ top = Math.min(Math.max(top, 0), maxTop);
12127
+ setAdjustedPosition({
12128
+ top,
12129
+ left
12130
+ });
12131
+ }, [position, anchorRect, placement, alignment, children]);
12132
+ return jsx("div", {
12133
+ ref: ref,
12134
+ style: {
12135
+ position: "fixed",
12136
+ top: adjustedPosition.top,
12137
+ left: adjustedPosition.left,
12138
+ zIndex: machineUiTheme.zIndex.modal
12139
+ },
12140
+ children: children
12141
+ });
12142
+ }
12143
+ function calcMenuPosition(anchorRect, menuSize, placement, alignment) {
12144
+ const {
12145
+ width,
12146
+ height
12147
+ } = menuSize;
12148
+ const {
12149
+ left,
12150
+ top,
12151
+ right,
12152
+ bottom,
12153
+ width: anchorWidth,
12154
+ height: anchorHeight
12155
+ } = anchorRect;
12156
+ const offset = POSITIONED_MENU_OFFSET;
12157
+ const align = (alignment, start, end, anchorSize, size) => {
12158
+ switch (alignment) {
12159
+ case "start":
12160
+ return start;
12161
+ case "end":
12162
+ return end - size;
12163
+ case "middle":
12164
+ return start + (anchorSize - size) / 2;
12165
+ }
12166
+ };
12167
+ switch (placement) {
12168
+ case "bottom":
12169
+ return {
12170
+ top: bottom + offset,
12171
+ left: align(alignment, left, right, anchorWidth, width)
12172
+ };
12173
+ case "top":
12174
+ return {
12175
+ top: top - height - offset,
12176
+ left: align(alignment, left, right, anchorWidth, width)
12177
+ };
12178
+ case "right":
12179
+ return {
12180
+ left: right + offset,
12181
+ top: align(alignment, top, bottom, anchorHeight, height)
12182
+ };
12183
+ default:
12184
+ return {
12185
+ left: left - width - offset,
12186
+ top: align(alignment, top, bottom, anchorHeight, height)
12187
+ };
12188
+ }
12189
+ }
12190
+
12191
+ export { COLORS, DEFAULT_VENTION_TREE_ITEM_HEIGHT, ElementTestIds, IconSizes, POSITIONED_MENU_OFFSET, VentionAlert, VentionBadge, VentionBanner, VentionButton, VentionCallout, VentionCheckbox, VentionCombobox, VentionCounter, VentionDraggable, VentionDrawer, VentionDropZone, VentionDropdownButton, VentionIcon, VentionIconButton, VentionLink, VentionMenu, VentionModal, VentionModalBanner, VentionModalBase, VentionPopover, VentionPositionedComponent, VentionProgressBar, VentionRadio, VentionRadioTile, VentionSelect, VentionSidebarItem, VentionSlider, VentionSpinner, VentionStatusDot, VentionStatusIndicator, VentionStepper, VentionSteps, VentionSwitch, VentionTabs, VentionTextInput, VentionTooltip, VentionTreeItem, VentionUploadFile, calcMenuPosition, getSpinnerColor, getVentionBadgeColors, machineUiHmiTheme, machineUiTheme, menuTestIds, testIds, useBannerColorStyles, ventionModalBaseTestIds, ventionModalTestIds, ventionPopoverArrowSize };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vention/machine-ui",
3
- "version": "3.35.0",
3
+ "version": "3.37.0",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/VentionCo/machine-cloud.git"
package/src/index.d.ts CHANGED
@@ -36,6 +36,7 @@ export * from "./lib/components/vention-tooltip/vention-tooltip";
36
36
  export * from "./lib/components/vention-checkbox/vention-checkbox";
37
37
  export * from "./lib/components/vention-uploadfile/vention-uploadfile";
38
38
  export * from "./lib/components/vention-dropzone/vention-dropzone";
39
+ export * from "./lib/components/shared/vention-positioned-component";
39
40
  export * from "./lib/theme/machine-ui-theme";
40
41
  export * from "./lib/theme/colors";
41
42
  export { type VentionAlertProps } from "./lib/components/vention-alert/vention-alert-utils";
@@ -0,0 +1,39 @@
1
+ import React from "react";
2
+ export type AnchorPosition = {
3
+ top: number;
4
+ left: number;
5
+ };
6
+ export type PositionedMenuPlacement = "bottom" | "top" | "left" | "right";
7
+ export type PositionedMenuAlignment = "start" | "middle" | "end";
8
+ export declare const POSITIONED_MENU_OFFSET = 3;
9
+ interface BasePositionedProps {
10
+ placement?: PositionedMenuPlacement | undefined;
11
+ alignment?: PositionedMenuAlignment | undefined;
12
+ children: React.ReactNode;
13
+ }
14
+ interface PositionedPropsWithAnchor extends BasePositionedProps {
15
+ anchorRect: DOMRect;
16
+ position?: never;
17
+ }
18
+ interface PositionedPropsWithPosition extends BasePositionedProps {
19
+ anchorRect?: never;
20
+ position: AnchorPosition;
21
+ }
22
+ /**
23
+ * Props for the positioned component.
24
+ * Either position or anchorRect must be defined, but not both.
25
+ */
26
+ export type PositionedComponentProps = PositionedPropsWithAnchor | PositionedPropsWithPosition;
27
+ /**
28
+ * Generic positioned component that can position any React component based on viewport constraints.
29
+ * It can be positioned relative to an anchor element or a specified position.
30
+ *
31
+ * @param props - The properties for the positioned component, including position and children.
32
+ * @returns The positioned component wrapper.
33
+ */
34
+ export declare function VentionPositionedComponent({ position, anchorRect, placement, alignment, children, }: PositionedComponentProps): JSX.Element;
35
+ export declare function calcMenuPosition(anchorRect: DOMRect, menuSize: {
36
+ width: number;
37
+ height: number;
38
+ }, placement: PositionedMenuPlacement, alignment: PositionedMenuAlignment): AnchorPosition;
39
+ export {};
@@ -1,16 +1,29 @@
1
- /// <reference types="react" />
2
1
  import { ButtonProps } from "@mui/material";
3
2
  import { Sizes, StrictExtract } from "../../../theme/machine-ui-theme";
3
+ import { ReactNode } from "react";
4
+ import { PositionedMenuAlignment, PositionedMenuPlacement } from "../../../components/shared/vention-positioned-component";
5
+ export declare const menuTestIds: {
6
+ menuRootDiv: string;
7
+ };
4
8
  export interface VentionDropdownButtonProps extends Omit<ButtonProps, "size" | "variant" | "color"> {
5
9
  variant?: "simple" | "outline" | "shaded";
6
10
  size?: StrictExtract<Sizes, "small" | "medium" | "large">;
7
- icon?: React.ReactNode;
11
+ icon?: ReactNode;
8
12
  stroke?: number | string;
9
13
  disabled?: boolean;
10
14
  className?: string;
11
15
  label?: string;
12
16
  badgeText?: string;
13
17
  contentColor?: string;
14
- onClick?: (event: React.MouseEvent<HTMLButtonElement>) => void;
18
+ /** Required children to show when the button is clicked */
19
+ children: ReactNode;
20
+ /** Callback function called when the dropdown is closed. */
21
+ onClose?: (() => void) | undefined;
22
+ /** Callback function called when the dropdown is opened. */
23
+ onOpen?: (() => void) | undefined;
24
+ /** Placement of the menu relative to the button. */
25
+ menuPlacement?: PositionedMenuPlacement | undefined;
26
+ /** Alignment of the menu relative to the button. */
27
+ menuAlignment?: PositionedMenuAlignment | undefined;
15
28
  }
16
- export declare const VentionDropdownButton: ({ variant, size, className, disabled, onClick, icon, badgeText, label, stroke, contentColor, ...other }: VentionDropdownButtonProps) => import("react/jsx-runtime").JSX.Element;
29
+ export declare const VentionDropdownButton: ({ variant, size, className, disabled, onClose, onOpen, icon, badgeText, label, stroke, contentColor, children, menuPlacement, menuAlignment, onClick, ...other }: VentionDropdownButtonProps) => import("react/jsx-runtime").JSX.Element;
@@ -2,7 +2,7 @@ import { VentionMenuListProps } from "./vention-menu-list";
2
2
  import { MenuSize } from "./vention-menu-styling-helpers";
3
3
  export type VentionMenuProps = VentionMenuListProps & {
4
4
  showMenu?: boolean;
5
- setShowMenu?: (show: boolean) => void;
5
+ setShowMenu: (show: boolean) => void;
6
6
  closeMenuOnClick?: boolean;
7
7
  size?: MenuSize;
8
8
  menuWidth?: string;