Finished prototype local backends and expression editor

This commit is contained in:
Richard Osborne
2026-01-16 12:00:31 +01:00
parent 94c870e5d7
commit 32a0a0885f
48 changed files with 8513 additions and 108 deletions

View File

@@ -7,6 +7,8 @@
border-radius: 4px;
padding: 4px 8px;
flex: 1;
min-width: 0; // Allow flex item to shrink below content size
overflow: hidden; // Prevent content overflow
transition: all 0.15s ease;
&:focus-within {
@@ -62,3 +64,29 @@
color: var(--theme-color-error, #ef4444);
cursor: help;
}
.ExpandButton {
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
width: 20px;
height: 20px;
padding: 0;
border: none;
background: transparent;
color: var(--theme-color-fg-default-shy, rgba(255, 255, 255, 0.5));
cursor: pointer;
border-radius: 2px;
transition: all 0.15s ease;
&:hover {
background-color: var(--theme-color-bg-1, rgba(255, 255, 255, 0.1));
color: var(--theme-color-primary, #6366f1);
}
&:focus {
outline: none;
background-color: var(--theme-color-bg-1, rgba(255, 255, 255, 0.1));
}
}

View File

@@ -30,6 +30,9 @@ export interface ExpressionInputProps extends UnsafeStyleProps {
/** Debounce delay in milliseconds */
debounceMs?: number;
/** Callback when expand button is clicked - opens expression in full editor */
onExpand?: () => void;
}
/**
@@ -56,6 +59,7 @@ export function ExpressionInput({
placeholder = 'Enter expression...',
testId,
debounceMs = 300,
onExpand,
UNSAFE_className,
UNSAFE_style
}: ExpressionInputProps) {
@@ -143,6 +147,13 @@ export function ExpressionInput({
</div>
</Tooltip>
)}
{onExpand && (
<Tooltip content="Edit in code editor">
<button type="button" className={css['ExpandButton']} onClick={onExpand} aria-label="Edit in code editor">
<Icon icon={IconName.Code} size={IconSize.Tiny} />
</button>
</Tooltip>
)}
</div>
);
}

View File

@@ -26,3 +26,42 @@
opacity: 0.5;
cursor: default;
}
.FxButton {
display: flex;
align-items: center;
justify-content: center;
width: 24px;
height: 24px;
padding: 0;
border: none;
background: transparent;
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', 'Courier New', monospace;
font-size: 10px;
font-weight: 600;
color: var(--theme-color-fg-default-shy, rgba(255, 255, 255, 0.5));
cursor: pointer;
border-radius: 4px;
transition: all 0.15s ease;
flex-shrink: 0;
&:hover {
background-color: var(--theme-color-bg-1, rgba(255, 255, 255, 0.1));
color: var(--theme-color-primary, #6366f1);
}
&:focus {
outline: none;
background-color: var(--theme-color-bg-1, rgba(255, 255, 255, 0.1));
}
&.is-disabled {
opacity: 0.4;
cursor: not-allowed;
&:hover {
background: transparent;
color: var(--theme-color-fg-default-shy, rgba(255, 255, 255, 0.5));
}
}
}

View File

@@ -62,23 +62,39 @@ export function ExpressionToggle({
const tooltipContent = isExpressionMode ? 'Switch to fixed value' : 'Switch to expression';
const icon = isExpressionMode ? IconName.Code : IconName.MagicWand;
const variant = isExpressionMode ? IconButtonVariant.Default : IconButtonVariant.OpaqueOnHover;
// When in expression mode, show TextInBox icon (switch to fixed value)
// When in fixed mode, show "fx" text button (switch to expression)
if (isExpressionMode) {
return (
<Tooltip content={tooltipContent}>
<div className={css['Root']} style={UNSAFE_style}>
<IconButton
icon={IconName.TextInBox}
size={IconSize.Tiny}
variant={IconButtonVariant.Default}
onClick={onToggle}
isDisabled={isDisabled}
testId={testId}
UNSAFE_className={css['ExpressionActive']}
/>
</div>
</Tooltip>
);
}
// Fixed mode - show "fx" text button
return (
<Tooltip content={tooltipContent}>
<div className={css['Root']} style={UNSAFE_style}>
<IconButton
icon={icon}
size={IconSize.Tiny}
variant={variant}
onClick={onToggle}
isDisabled={isDisabled}
testId={testId}
UNSAFE_className={isExpressionMode ? css['ExpressionActive'] : UNSAFE_className}
/>
</div>
<button
type="button"
className={`${css['FxButton']} ${isDisabled ? css['is-disabled'] : ''} ${UNSAFE_className || ''}`}
style={UNSAFE_style}
onClick={isDisabled ? undefined : onToggle}
disabled={isDisabled}
data-test={testId}
>
fx
</button>
</Tooltip>
);
}

View File

@@ -24,4 +24,6 @@
.InputContainer {
flex: 1 1 auto;
min-width: 0; // Allow flex item to shrink below content size
overflow: hidden; // Prevent content overflow
}

View File

@@ -74,6 +74,8 @@ export interface PropertyPanelInputProps extends Omit<PropertyPanelBaseInputProp
onExpressionChange?: (expression: string) => void;
/** Whether the expression has an error */
expressionError?: string;
/** Callback when expand button is clicked (opens expression in full editor) */
onExpressionExpand?: () => void;
}
export function PropertyPanelInput({
@@ -90,7 +92,8 @@ export function PropertyPanelInput({
expression = '',
onExpressionModeChange,
onExpressionChange,
expressionError
expressionError,
onExpressionExpand
}: PropertyPanelInputProps) {
const Input = useMemo(() => {
switch (inputType) {
@@ -136,6 +139,7 @@ export function PropertyPanelInput({
onChange={onExpressionChange}
hasError={!!expressionError}
errorMessage={expressionError}
onExpand={onExpressionExpand}
UNSAFE_style={{ flex: 1 }}
/>
);
@@ -165,7 +169,7 @@ export function PropertyPanelInput({
<div className={css['Root']}>
<div className={classNames(css['Label'], isChanged && css['is-changed'])}>{label}</div>
<div className={css['InputContainer']}>
<div style={{ display: 'flex', gap: '4px', alignItems: 'center', width: '100%' }}>
<div style={{ display: 'flex', gap: '4px', alignItems: 'center', minWidth: 0 }}>
{renderInput()}
{showExpressionToggle && (
<ExpressionToggle mode={expressionMode} isConnected={isConnected} onToggle={handleToggleMode} />