Implement widget borders in vue (#7322)
Adds support for displaying the "promoted" and "advanced" border indicators when in vue mode. Requires some (hopefully minor and generally beneficial) styling changes to make sure that the widgets are contained within their border. Note that the 'advanced' functionality sees minimal use and is likely to be revamped in the future. <img width="372" height="417" alt="image" src="https://github.com/user-attachments/assets/8ea1e66b-2a4e-4a16-996f-289a26e39708" /> ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-7322-Implement-widget-borders-in-vue-2c56d73d36508187b881f97e373d433b) by [Unito](https://www.unito.io) --------- Co-authored-by: github-actions <github-actions@github.com>
|
Before Width: | Height: | Size: 128 KiB After Width: | Height: | Size: 129 KiB |
|
Before Width: | Height: | Size: 55 KiB After Width: | Height: | Size: 55 KiB |
|
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 37 KiB |
|
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 37 KiB |
|
Before Width: | Height: | Size: 61 KiB After Width: | Height: | Size: 61 KiB |
|
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 60 KiB |
|
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 60 KiB |
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 62 KiB |
|
Before Width: | Height: | Size: 63 KiB After Width: | Height: | Size: 62 KiB |
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 62 KiB |
|
Before Width: | Height: | Size: 59 KiB After Width: | Height: | Size: 59 KiB |
|
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 58 KiB |
|
Before Width: | Height: | Size: 111 KiB After Width: | Height: | Size: 111 KiB |
|
Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 40 KiB |
|
Before Width: | Height: | Size: 122 KiB After Width: | Height: | Size: 122 KiB |
|
Before Width: | Height: | Size: 122 KiB After Width: | Height: | Size: 122 KiB |
|
Before Width: | Height: | Size: 151 KiB After Width: | Height: | Size: 152 KiB |
|
Before Width: | Height: | Size: 144 KiB After Width: | Height: | Size: 145 KiB |
|
Before Width: | Height: | Size: 122 KiB After Width: | Height: | Size: 122 KiB |
|
Before Width: | Height: | Size: 84 KiB After Width: | Height: | Size: 83 KiB |
@@ -261,6 +261,8 @@
|
||||
--component-node-widget-background-selected: var(--secondary-background-selected);
|
||||
--component-node-widget-background-disabled: var(--color-alpha-ash-500-20);
|
||||
--component-node-widget-background-highlighted: var(--color-ash-500);
|
||||
--component-node-widget-promoted: var(--color-purple-700);
|
||||
--component-node-widget-advanced: var(--color-azure-400);
|
||||
|
||||
/* Default UI element color palette variables */
|
||||
--palette-contrast-mix-color: #fff;
|
||||
@@ -384,6 +386,8 @@
|
||||
--component-node-widget-background-selected: var(--color-charcoal-100);
|
||||
--component-node-widget-background-disabled: var(--color-alpha-charcoal-600-30);
|
||||
--component-node-widget-background-highlighted: var(--color-graphite-400);
|
||||
--component-node-widget-promoted: var(--color-purple-700);
|
||||
--component-node-widget-advanced: var(--color-azure-600);
|
||||
|
||||
--modal-card-background: var(--secondary-background);
|
||||
--modal-card-background-hovered: var(--secondary-background-hover);
|
||||
@@ -490,6 +494,8 @@
|
||||
--color-component-node-widget-background-selected: var(--component-node-widget-background-selected);
|
||||
--color-component-node-widget-background-disabled: var(--component-node-widget-background-disabled);
|
||||
--color-component-node-widget-background-highlighted: var(--component-node-widget-background-highlighted);
|
||||
--color-component-node-widget-promoted: var(--component-node-widget-promoted);
|
||||
--color-component-node-widget-advanced: var(--component-node-widget-advanced);
|
||||
|
||||
/* Semantic tokens */
|
||||
--color-base-foreground: var(--base-foreground);
|
||||
|
||||
@@ -47,6 +47,7 @@ export interface SafeWidgetData {
|
||||
spec?: InputSpec
|
||||
slotMetadata?: WidgetSlotMetadata
|
||||
isDOMWidget?: boolean
|
||||
borderStyle?: string
|
||||
}
|
||||
|
||||
export interface VueNodeData {
|
||||
@@ -105,17 +106,23 @@ export function safeWidgetMapper(
|
||||
}
|
||||
const spec = nodeDefStore.getInputSpecForWidget(node, widget.name)
|
||||
const slotInfo = slotMetadata.get(widget.name)
|
||||
const borderStyle = widget.promoted
|
||||
? 'ring ring-component-node-widget-promoted'
|
||||
: widget.advanced
|
||||
? 'ring ring-component-node-widget-advanced'
|
||||
: undefined
|
||||
|
||||
return {
|
||||
name: widget.name,
|
||||
type: widget.type,
|
||||
value: value,
|
||||
borderStyle,
|
||||
callback: widget.callback,
|
||||
isDOMWidget: isDOMWidget(widget),
|
||||
label: widget.label,
|
||||
options: widget.options,
|
||||
callback: widget.callback,
|
||||
spec,
|
||||
slotMetadata: slotInfo,
|
||||
isDOMWidget: isDOMWidget(widget)
|
||||
slotMetadata: slotInfo
|
||||
}
|
||||
} catch (error) {
|
||||
return {
|
||||
|
||||
@@ -161,7 +161,8 @@ const processedWidgets = computed((): ProcessedWidget[] => {
|
||||
label: widget.label,
|
||||
options: widgetOptions,
|
||||
callback: widget.callback,
|
||||
spec: widget.spec
|
||||
spec: widget.spec,
|
||||
borderStyle: widget.borderStyle
|
||||
}
|
||||
|
||||
function updateHandler(value: WidgetValue) {
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
<template>
|
||||
<FloatLabel
|
||||
variant="in"
|
||||
class="rounded-lg space-y-1 focus-within:ring ring-component-node-widget-background-highlighted transition-all"
|
||||
:class="
|
||||
cn(
|
||||
'rounded-lg space-y-1 focus-within:ring focus-within:ring-component-node-widget-background-highlighted transition-all',
|
||||
widget.borderStyle
|
||||
)
|
||||
"
|
||||
>
|
||||
<Textarea
|
||||
v-bind="filteredProps"
|
||||
|
||||
@@ -3,9 +3,13 @@ import { noop } from 'es-toolkit'
|
||||
import { inject } from 'vue'
|
||||
|
||||
import type { SimplifiedWidget } from '@/types/simplifiedWidget'
|
||||
import { cn } from '@/utils/tailwindUtil'
|
||||
|
||||
defineProps<{
|
||||
widget: Pick<SimplifiedWidget<string | number | undefined>, 'name' | 'label'>
|
||||
widget: Pick<
|
||||
SimplifiedWidget<string | number | undefined>,
|
||||
'name' | 'label' | 'borderStyle'
|
||||
>
|
||||
}>()
|
||||
|
||||
const hideLayoutField = inject<boolean>('hideLayoutField', false)
|
||||
@@ -13,7 +17,7 @@ const hideLayoutField = inject<boolean>('hideLayoutField', false)
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="grid grid-cols-subgrid h-7.5 min-w-0 items-center justify-between gap-1"
|
||||
class="grid grid-cols-subgrid min-w-0 items-center justify-between gap-1"
|
||||
>
|
||||
<div
|
||||
v-if="!hideLayoutField"
|
||||
@@ -21,7 +25,7 @@ const hideLayoutField = inject<boolean>('hideLayoutField', false)
|
||||
>
|
||||
<p
|
||||
v-if="widget.name"
|
||||
class="flex-1 truncate text-xs font-normal text-node-component-slot-text"
|
||||
class="flex-1 truncate text-xs font-normal text-node-component-slot-text my-0"
|
||||
>
|
||||
{{ widget.label || widget.name }}
|
||||
</p>
|
||||
@@ -29,7 +33,12 @@ const hideLayoutField = inject<boolean>('hideLayoutField', false)
|
||||
<!-- basis-full grow -->
|
||||
<div class="relative min-w-0 flex-1">
|
||||
<div
|
||||
class="cursor-default min-w-0 rounded-lg space-y-1 focus-within:ring ring-component-node-widget-background-highlighted transition-all"
|
||||
:class="
|
||||
cn(
|
||||
'cursor-default min-w-0 rounded-lg space-y-1 focus-within:ring focus-within:ring-component-node-widget-background-highlighted transition-all',
|
||||
widget.borderStyle
|
||||
)
|
||||
"
|
||||
@pointerdown.stop="noop"
|
||||
@pointermove.stop="noop"
|
||||
@pointerup.stop="noop"
|
||||
|
||||
@@ -28,21 +28,23 @@ export interface SimplifiedWidget<
|
||||
/** Current value of the widget */
|
||||
value: T
|
||||
|
||||
borderStyle?: string
|
||||
|
||||
/** Callback fired when value changes */
|
||||
callback?: (value: T) => void
|
||||
|
||||
/** Optional method to compute widget size requirements */
|
||||
computeSize?: () => { minHeight: number; maxHeight?: number }
|
||||
|
||||
/** Localized display label (falls back to name if not provided) */
|
||||
label?: string
|
||||
|
||||
/** Widget options including filtered PrimeVue props */
|
||||
options?: O
|
||||
|
||||
/** Callback fired when value changes */
|
||||
callback?: (value: T) => void
|
||||
|
||||
/** Optional input specification backing this widget */
|
||||
spec?: InputSpecV2
|
||||
|
||||
/** Optional serialization method for custom value handling */
|
||||
serializeValue?: () => any
|
||||
|
||||
/** Optional method to compute widget size requirements */
|
||||
computeSize?: () => { minHeight: number; maxHeight?: number }
|
||||
/** Optional input specification backing this widget */
|
||||
spec?: InputSpecV2
|
||||
}
|
||||
|
||||