Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions libs/@hashintel/petrinaut/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
"@dnd-kit/core": "6.3.1",
"@dnd-kit/sortable": "10.0.0",
"@dnd-kit/utilities": "3.2.2",
"@floating-ui/react": "0.27.16",
"@hashintel/ds-components": "0.0.1-c",
"@hashintel/refractive": "0.0.0",
"@mantine/hooks": "8.3.5",
Expand Down
24 changes: 21 additions & 3 deletions libs/@hashintel/petrinaut/src/components/disabled-tooltip.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { ReactNode } from "react";
import { type ReactNode } from "react";

import { UI_MESSAGES } from "../constants/ui-messages";
import { Tooltip } from "./tooltip";
Expand All @@ -10,34 +10,52 @@ interface DisabledTooltipProps {
*/
disabled: boolean;
/**
* The content to wrap. Should be a single element.
* The content to wrap.
*/
children: ReactNode;
/**
* Optional custom message. Defaults to the standard readonly mode message.
*/
message?: string;
/**
* If true, completely hides the children when disabled
* instead of showing them with a tooltip.
*/
hide?: boolean;
}

/**
* Wraps children with an explanatory tooltip when disabled.
* Shows an explanatory tooltip around children when disabled.
*
* Use this to wrap disabled form controls to explain why they're disabled
* (e.g., during simulation mode).
*
* @example
* const isReadOnly = useIsReadOnly();
*
* // Show tooltip explaining why input is disabled
* <DisabledTooltip disabled={isReadOnly}>
* <input disabled={isReadOnly} ... />
* </DisabledTooltip>
*
* // Completely hide when disabled
* <DisabledTooltip disabled={isReadOnly} hide>
* <button>Add Item</button>
* </DisabledTooltip>
*/
export const DisabledTooltip: React.FC<DisabledTooltipProps> = ({
disabled,
children,
message = UI_MESSAGES.READ_ONLY_MODE,
hide = false,
}) => {
if (!disabled) {
return children;
}

if (hide) {
return null;
}

return <Tooltip content={message}>{children}</Tooltip>;
};
21 changes: 18 additions & 3 deletions libs/@hashintel/petrinaut/src/components/segment-group.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { SegmentGroup as ArkSegmentGroup } from "@ark-ui/react/segment-group";
import { cva } from "@hashintel/ds-helpers/css";
import { cva, cx } from "@hashintel/ds-helpers/css";

const containerStyle = cva({
base: {
Expand All @@ -19,9 +19,17 @@ const containerStyle = cva({
padding: "[3px]",
},
},
disabled: {
true: {
opacity: "[0.6]",
pointerEvents: "none",
},
false: {},
},
},
defaultVariants: {
size: "md",
disabled: false,
},
});

Expand Down Expand Up @@ -94,24 +102,31 @@ interface SegmentGroupProps {
onChange: (value: string) => void;
/** Size variant. Defaults to "md". */
size?: "md" | "sm";
/** Additional className to apply to the container. */
className?: string;
/** Whether the segment group is disabled. */
disabled?: boolean;
}

export const SegmentGroup: React.FC<SegmentGroupProps> = ({
value,
options,
onChange,
size = "md",
className,
disabled = false,
}) => {
return (
<ArkSegmentGroup.Root
value={value}
onValueChange={(details) => {
if (details.value) {
if (details.value && !disabled) {
onChange(details.value);
}
}}
disabled={disabled}
>
<div className={containerStyle({ size })}>
<div className={cx(containerStyle({ size, disabled }), className)}>
<ArkSegmentGroup.Indicator className={indicatorStyle({ size })} />
{options.map((option) => (
<ArkSegmentGroup.Item
Expand Down
105 changes: 72 additions & 33 deletions libs/@hashintel/petrinaut/src/components/tooltip.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,21 @@
import { Tooltip as ArkTooltip } from "@ark-ui/react/tooltip";
import {
autoUpdate,
flip,
FloatingPortal,
offset,
shift,
useDismiss,
useFloating,
useFocus,
useHover,
useInteractions,
useRole,
} from "@floating-ui/react";
import { css } from "@hashintel/ds-helpers/css";
import type { SvgIconProps } from "@mui/material";
import { SvgIcon, Tooltip as MuiTooltip } from "@mui/material";
import { SvgIcon } from "@mui/material";
import type { FunctionComponent, ReactNode } from "react";
import { useState } from "react";

const tooltipContentStyle = css({
backgroundColor: "core.gray.90",
Expand All @@ -14,25 +27,60 @@ const tooltipContentStyle = css({
padding: "[6px 10px]",
});

const wrapperStyle = css({
display: "contents",
});

interface TooltipProps {
content: string;
children: ReactNode;
}

export const Tooltip: React.FC<TooltipProps> = ({ content, children }) => {
const [isOpen, setIsOpen] = useState(false);

const { refs, floatingStyles, context } = useFloating({
open: isOpen,
onOpenChange: setIsOpen,
placement: "top",
whileElementsMounted: autoUpdate,
middleware: [offset(6), flip(), shift({ padding: 8 })],
});

const hover = useHover(context, { delay: { open: 200, close: 0 } });
const focus = useFocus(context);
const dismiss = useDismiss(context);
const role = useRole(context, { role: "tooltip" });

const { getReferenceProps, getFloatingProps } = useInteractions([
hover,
focus,
dismiss,
role,
]);

return (
<ArkTooltip.Root
openDelay={200}
closeDelay={0}
positioning={{ placement: "top" }}
>
<ArkTooltip.Trigger asChild>{children}</ArkTooltip.Trigger>
<ArkTooltip.Positioner>
<ArkTooltip.Content className={tooltipContentStyle}>
{content}
</ArkTooltip.Content>
</ArkTooltip.Positioner>
</ArkTooltip.Root>
<>
<span
ref={refs.setReference}
className={wrapperStyle}
{...getReferenceProps()}
>
{children}
</span>
{isOpen && (
<FloatingPortal>
<div
ref={refs.setFloating}
style={floatingStyles}
className={tooltipContentStyle}
{...getFloatingProps()}
>
{content}
</div>
</FloatingPortal>
)}
</>
);
};

Expand All @@ -50,26 +98,17 @@ const CircleInfoIcon: FunctionComponent<SvgIconProps> = (props) => {
);
};

const infoIconStyle = css({
fontSize: "[11px]",
color: "[rgb(160, 160, 160)]",
marginLeft: "[6.4px]",
marginBottom: "[1.6px]",
});

export const InfoIconTooltip = ({ tooltip }: { tooltip: string }) => {
return (
<MuiTooltip
title={tooltip}
placement="top"
componentsProps={{
tooltip: {
sx: {
background: "rgb(23, 23, 23)",
fontSize: 13,
borderRadius: "12px",
px: "10px",
py: "6px",
},
},
}}
>
<CircleInfoIcon
sx={{ fontSize: 11, color: "rgb(160, 160, 160)", ml: 0.8, mb: 0.2 }}
/>
</MuiTooltip>
<Tooltip content={tooltip}>
<CircleInfoIcon className={infoIconStyle} />
</Tooltip>
);
};
6 changes: 5 additions & 1 deletion libs/@hashintel/petrinaut/src/state/use-is-read-only.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,13 @@ export const useIsReadOnly = (): boolean => {
const globalMode = useEditorStore((state) => state.globalMode);
const simulationState = useSimulationStore((state) => state.state);

const isSimulationComplete = simulationState === "Complete";

const isSimulationActive =
simulationState === "Running" || simulationState === "Paused";

const isReadOnly = globalMode === "simulate" || isSimulationActive;
const isReadOnly =
globalMode === "simulate" || isSimulationActive || isSimulationComplete;

return isReadOnly;
};
Original file line number Diff line number Diff line change
Expand Up @@ -494,7 +494,14 @@ export const PlaceProperties: React.FC<PlacePropertiesProps> = ({
<div className={sectionContainerStyle}>
<div className={switchRowStyle}>
<div className={switchContainerStyle}>
<DisabledTooltip disabled={isReadOnly}>
<DisabledTooltip
disabled={isReadOnly || place.colorId === null}
message={
place.colorId === null
? "Select a token type first to enable dynamics"
: undefined
}
>
<Switch
checked={!!place.colorId && place.dynamicsEnabled}
disabled={isReadOnly || place.colorId === null}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { css, cva } from "@hashintel/ds-helpers/css";
import { MdDragIndicator } from "react-icons/md";
import { TbTrash } from "react-icons/tb";

import { DisabledTooltip } from "../../../../components/disabled-tooltip";
import { FEATURE_FLAGS } from "../../../../feature-flags";

const containerStyle = css({
Expand Down Expand Up @@ -151,20 +152,22 @@ export const SortableArcItem: React.FC<SortableArcItemProps> = ({
<div className={placeNameStyle}>{placeName}</div>
<div className={weightContainerStyle}>
<span className={weightLabelStyle}>weight</span>
<input
type="number"
min="1"
step="1"
value={weight}
disabled={disabled}
onChange={(event) => {
const newWeight = Number.parseInt(event.target.value, 10);
if (!Number.isNaN(newWeight) && newWeight >= 1) {
onWeightChange(newWeight);
}
}}
className={weightInputStyle({ isDisabled: disabled })}
/>
<DisabledTooltip disabled={disabled}>
<input
type="number"
min="1"
step="1"
value={weight}
disabled={disabled}
onChange={(event) => {
const newWeight = Number.parseInt(event.target.value, 10);
if (!Number.isNaN(newWeight) && newWeight >= 1) {
onWeightChange(newWeight);
}
}}
className={weightInputStyle({ isDisabled: disabled })}
/>
</DisabledTooltip>
</div>
{onDelete && !disabled && (
<button type="button" onClick={onDelete} className={deleteButtonStyle}>
Expand Down
Loading
Loading