mirror of
https://github.com/OpenSquawk/OpenSquawk
synced 2026-05-15 03:25:40 +08:00
Improve node editor layout and inspector UX
This commit is contained in:
@@ -41,12 +41,80 @@
|
||||
@pointerdown.stop="(event) => onNodePointerDown(event, node)"
|
||||
@dblclick.prevent="() => emit('select', node.id)"
|
||||
>
|
||||
<button class="node-add node-add--top" @click.stop="emit('add-before', node.id)">
|
||||
<v-icon icon="mdi-plus" size="16" />
|
||||
</button>
|
||||
<button class="node-add node-add--bottom" @click.stop="emit('add-after', node.id)">
|
||||
<v-icon icon="mdi-plus" size="16" />
|
||||
</button>
|
||||
<v-tooltip location="top" :open-delay="120">
|
||||
<template #activator="{ props }">
|
||||
<button
|
||||
class="node-add node-add--top"
|
||||
:class="{ 'node-add--connected': connections(node.id, 'incoming').length > 0 }"
|
||||
v-bind="props"
|
||||
@click.stop="emit('add-before', node.id)"
|
||||
>
|
||||
<v-icon icon="mdi-plus" size="16" />
|
||||
</button>
|
||||
</template>
|
||||
<div class="node-connection-tooltip">
|
||||
<p class="node-connection-tooltip__title">Eingehende Verbindungen</p>
|
||||
<p
|
||||
v-if="!connections(node.id, 'incoming').length"
|
||||
class="node-connection-tooltip__empty"
|
||||
>
|
||||
Keine eingehenden Verbindungen
|
||||
</p>
|
||||
<ul v-else class="node-connection-tooltip__list">
|
||||
<li
|
||||
v-for="connection in connections(node.id, 'incoming')"
|
||||
:key="connection.key"
|
||||
class="node-connection-tooltip__item"
|
||||
>
|
||||
<span class="node-connection-tooltip__id">{{ connection.id }}</span>
|
||||
<span v-if="connection.title" class="node-connection-tooltip__title-text">— {{ connection.title }}</span>
|
||||
<span
|
||||
class="node-connection-tooltip__badge"
|
||||
:class="{ 'node-connection-tooltip__badge--auto': connection.auto }"
|
||||
>
|
||||
{{ connectionLabel(connection) }}
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</v-tooltip>
|
||||
<v-tooltip location="bottom" :open-delay="120">
|
||||
<template #activator="{ props }">
|
||||
<button
|
||||
class="node-add node-add--bottom"
|
||||
:class="{ 'node-add--connected': connections(node.id, 'outgoing').length > 0 }"
|
||||
v-bind="props"
|
||||
@click.stop="emit('add-after', node.id)"
|
||||
>
|
||||
<v-icon icon="mdi-plus" size="16" />
|
||||
</button>
|
||||
</template>
|
||||
<div class="node-connection-tooltip">
|
||||
<p class="node-connection-tooltip__title">Ausgehende Verbindungen</p>
|
||||
<p
|
||||
v-if="!connections(node.id, 'outgoing').length"
|
||||
class="node-connection-tooltip__empty"
|
||||
>
|
||||
Keine ausgehenden Verbindungen
|
||||
</p>
|
||||
<ul v-else class="node-connection-tooltip__list">
|
||||
<li
|
||||
v-for="connection in connections(node.id, 'outgoing')"
|
||||
:key="connection.key"
|
||||
class="node-connection-tooltip__item"
|
||||
>
|
||||
<span class="node-connection-tooltip__id">{{ connection.id }}</span>
|
||||
<span v-if="connection.title" class="node-connection-tooltip__title-text">— {{ connection.title }}</span>
|
||||
<span
|
||||
class="node-connection-tooltip__badge"
|
||||
:class="{ 'node-connection-tooltip__badge--auto': connection.auto }"
|
||||
>
|
||||
{{ connectionLabel(connection) }}
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</v-tooltip>
|
||||
<span class="node-connector node-connector--input" />
|
||||
<span class="node-connector node-connector--output" />
|
||||
<div class="flex h-full flex-col">
|
||||
@@ -105,10 +173,10 @@
|
||||
</div>
|
||||
<div
|
||||
ref="minimapRef"
|
||||
class="pointer-events-auto absolute bottom-4 right-4 rounded-xl border border-white/10 bg-black/40 p-3 shadow-lg"
|
||||
class="pointer-events-auto absolute bottom-4 right-4 z-20 rounded-xl border border-white/10 bg-black/40 p-3 shadow-lg"
|
||||
@click="onMinimapClick"
|
||||
>
|
||||
<svg :width="minimapSize.width" :height="minimapSize.height" class="text-white/70">
|
||||
<svg :width="minimapSize.width" :height="minimapSize.height" class="block text-white/70">
|
||||
<g>
|
||||
<rect
|
||||
v-for="node in preparedNodes"
|
||||
@@ -152,6 +220,8 @@ const NODE_HEIGHT = 160
|
||||
const WORKSPACE_PADDING = 800
|
||||
const MIN_WORKSPACE_WIDTH = 4000
|
||||
const MIN_WORKSPACE_HEIGHT = 2800
|
||||
const MIN_NODE_MARGIN_X = 200
|
||||
const MIN_NODE_MARGIN_Y = 120
|
||||
const DRAG_MARGIN = 200
|
||||
|
||||
interface CanvasNodePreviewTransition {
|
||||
@@ -178,6 +248,16 @@ interface CanvasNodeInput {
|
||||
accent: string
|
||||
}
|
||||
|
||||
interface NodeConnectionPreview {
|
||||
key: string
|
||||
id: string
|
||||
title?: string
|
||||
type: DecisionNodeTransition['type']
|
||||
auto: boolean
|
||||
}
|
||||
|
||||
type ConnectionDirection = 'incoming' | 'outgoing'
|
||||
|
||||
interface CanvasPan {
|
||||
x: number
|
||||
y: number
|
||||
@@ -220,6 +300,18 @@ const MINIMAP_PADDING = 200
|
||||
|
||||
let resizeObserver: ResizeObserver | null = null
|
||||
|
||||
const workspaceOffset = computed(() => {
|
||||
if (!props.nodes.length) {
|
||||
return { x: MIN_NODE_MARGIN_X, y: MIN_NODE_MARGIN_Y }
|
||||
}
|
||||
const minX = Math.min(...props.nodes.map((node) => node.layout?.x ?? 0))
|
||||
const minY = Math.min(...props.nodes.map((node) => node.layout?.y ?? 0))
|
||||
return {
|
||||
x: Math.max(0, MIN_NODE_MARGIN_X - minX),
|
||||
y: Math.max(0, MIN_NODE_MARGIN_Y - minY),
|
||||
}
|
||||
})
|
||||
|
||||
watch(
|
||||
() => props.pan,
|
||||
(value) => {
|
||||
@@ -304,8 +396,9 @@ const viewportRect = computed(() => {
|
||||
}
|
||||
})
|
||||
|
||||
const preparedNodes = computed(() =>
|
||||
props.nodes.map((node) => {
|
||||
const preparedNodes = computed(() => {
|
||||
const offset = workspaceOffset.value
|
||||
return props.nodes.map((node) => {
|
||||
const width = node.layout?.width ?? NODE_WIDTH
|
||||
const height = node.layout?.height ?? NODE_HEIGHT
|
||||
const accent = node.layout?.color || props.roleColors[node.role] || '#22d3ee'
|
||||
@@ -316,15 +409,25 @@ const preparedNodes = computed(() =>
|
||||
class: transitionClass(transition),
|
||||
}))
|
||||
|
||||
const baseLayout: DecisionNodeLayout = { ...(node.layout || {}) }
|
||||
const displayLayout: DecisionNodeLayout = {
|
||||
...baseLayout,
|
||||
x: (baseLayout.x ?? 0) + offset.x,
|
||||
y: (baseLayout.y ?? 0) + offset.y,
|
||||
width,
|
||||
height,
|
||||
}
|
||||
|
||||
return {
|
||||
...node,
|
||||
width,
|
||||
height,
|
||||
accent,
|
||||
layout: displayLayout,
|
||||
previewTransitions: transitions as CanvasNodePreviewTransition[],
|
||||
}
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
const canvasBounds = computed(() => {
|
||||
const defaultWidth = MIN_WORKSPACE_WIDTH - WORKSPACE_PADDING
|
||||
@@ -343,6 +446,46 @@ const canvasBounds = computed(() => {
|
||||
}
|
||||
})
|
||||
|
||||
const nodeConnections = computed(() => {
|
||||
const lookup = new Map<string, CanvasNodeInput>()
|
||||
const result = new Map<string, { incoming: NodeConnectionPreview[]; outgoing: NodeConnectionPreview[] }>()
|
||||
|
||||
for (const node of props.nodes) {
|
||||
lookup.set(node.id, node)
|
||||
result.set(node.id, { incoming: [], outgoing: [] })
|
||||
}
|
||||
|
||||
for (const node of props.nodes) {
|
||||
const transitions = node.model.transitions || []
|
||||
for (const transition of transitions) {
|
||||
const key = transition.key || `${node.id}_${transition.target}_${transition.type}`
|
||||
const outgoingEntry = result.get(node.id)
|
||||
if (outgoingEntry) {
|
||||
outgoingEntry.outgoing.push({
|
||||
key: `out-${key}`,
|
||||
id: transition.target,
|
||||
title: lookup.get(transition.target)?.title,
|
||||
type: transition.type,
|
||||
auto: Boolean(transition.autoTrigger || transition.type === 'auto'),
|
||||
})
|
||||
}
|
||||
|
||||
const targetEntry = result.get(transition.target)
|
||||
if (targetEntry) {
|
||||
targetEntry.incoming.push({
|
||||
key: `in-${key}`,
|
||||
id: node.id,
|
||||
title: node.title,
|
||||
type: transition.type,
|
||||
auto: Boolean(transition.autoTrigger || transition.type === 'auto'),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
})
|
||||
|
||||
interface EdgeDefinition {
|
||||
id: string
|
||||
from: string
|
||||
@@ -475,6 +618,19 @@ function transitionColor(transition: DecisionNodeTransition) {
|
||||
}
|
||||
}
|
||||
|
||||
function connections(nodeId: string, direction: ConnectionDirection) {
|
||||
const entry = nodeConnections.value.get(nodeId)
|
||||
if (!entry) return []
|
||||
return entry[direction]
|
||||
}
|
||||
|
||||
function connectionLabel(connection: NodeConnectionPreview) {
|
||||
if (connection.auto) {
|
||||
return 'AUTO'
|
||||
}
|
||||
return connection.type.toUpperCase()
|
||||
}
|
||||
|
||||
type PathPoint = { x: number; y: number }
|
||||
|
||||
function computeEdgePath(
|
||||
@@ -673,7 +829,8 @@ function onNodePointerMove(event: PointerEvent) {
|
||||
width: dragState.width,
|
||||
height: dragState.height,
|
||||
})
|
||||
emit('node-move', { stateId: dragState.nodeId, x: clamped.x, y: clamped.y })
|
||||
const actual = toActualPosition(clamped)
|
||||
emit('node-move', { stateId: dragState.nodeId, x: actual.x, y: actual.y })
|
||||
}
|
||||
|
||||
function onNodePointerUp(event: PointerEvent) {
|
||||
@@ -688,18 +845,30 @@ function onNodePointerUp(event: PointerEvent) {
|
||||
width: dragState.width,
|
||||
height: dragState.height,
|
||||
})
|
||||
emit('node-drop', { stateId: dragState.nodeId, x: clamped.x, y: clamped.y })
|
||||
const actual = toActualPosition(clamped)
|
||||
emit('node-drop', { stateId: dragState.nodeId, x: actual.x, y: actual.y })
|
||||
}
|
||||
dragState = null
|
||||
window.removeEventListener('pointermove', onNodePointerMove)
|
||||
}
|
||||
|
||||
function clampToWorkspace(x: number, y: number, size: { width: number; height: number }) {
|
||||
const maxX = Math.max(0, canvasBounds.value.width - size.width - DRAG_MARGIN)
|
||||
const maxY = Math.max(0, canvasBounds.value.height - size.height - DRAG_MARGIN)
|
||||
function toActualPosition(position: { x: number; y: number }) {
|
||||
const offset = workspaceOffset.value
|
||||
return {
|
||||
x: Math.min(Math.max(0, x), maxX),
|
||||
y: Math.min(Math.max(0, y), maxY),
|
||||
x: Math.max(0, position.x - offset.x),
|
||||
y: Math.max(0, position.y - offset.y),
|
||||
}
|
||||
}
|
||||
|
||||
function clampToWorkspace(x: number, y: number, size: { width: number; height: number }) {
|
||||
const offset = workspaceOffset.value
|
||||
const minX = offset.x
|
||||
const minY = offset.y
|
||||
const maxX = Math.max(minX, canvasBounds.value.width - size.width - DRAG_MARGIN)
|
||||
const maxY = Math.max(minY, canvasBounds.value.height - size.height - DRAG_MARGIN)
|
||||
return {
|
||||
x: Math.min(Math.max(minX, x), maxX),
|
||||
y: Math.min(Math.max(minY, y), maxY),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -719,13 +888,17 @@ function resetView() {
|
||||
function onMinimapClick(event: MouseEvent) {
|
||||
const container = minimapRef.value
|
||||
if (!container) return
|
||||
const rect = container.getBoundingClientRect()
|
||||
const svgElement = container.querySelector('svg')
|
||||
const target = svgElement || container
|
||||
const rect = target.getBoundingClientRect()
|
||||
const offsetX = event.clientX - rect.left
|
||||
const offsetY = event.clientY - rect.top
|
||||
const clampedX = Math.min(Math.max(0, offsetX), rect.width)
|
||||
const clampedY = Math.min(Math.max(0, offsetY), rect.height)
|
||||
const bounds = minimapBounds.value
|
||||
const scale = minimapScale.value || 1
|
||||
const worldX = offsetX / scale + bounds.minX
|
||||
const worldY = offsetY / scale + bounds.minY
|
||||
const worldX = clampedX / scale + bounds.minX
|
||||
const worldY = clampedY / scale + bounds.minY
|
||||
const zoom = currentZoom.value || 1
|
||||
const panX = viewportSize.value.width / 2 - worldX * zoom
|
||||
const panY = viewportSize.value.height / 2 - worldY * zoom
|
||||
@@ -779,6 +952,10 @@ onBeforeUnmount(() => {
|
||||
@apply pointer-events-auto absolute z-10 flex h-6 w-6 items-center justify-center rounded-full border border-white/20 bg-black/70 text-white opacity-0 transition;
|
||||
}
|
||||
|
||||
.node-add--connected {
|
||||
@apply border-cyan-400/60 bg-cyan-500/20 text-cyan-100;
|
||||
}
|
||||
|
||||
.group:hover .node-add {
|
||||
@apply opacity-100;
|
||||
}
|
||||
@@ -810,4 +987,40 @@ onBeforeUnmount(() => {
|
||||
left: 50%;
|
||||
transform: translate(-50%, 50%);
|
||||
}
|
||||
|
||||
.node-connection-tooltip {
|
||||
@apply max-w-[240px] space-y-2 text-left;
|
||||
}
|
||||
|
||||
.node-connection-tooltip__title {
|
||||
@apply text-xs font-semibold uppercase tracking-[0.35em] text-white/60;
|
||||
}
|
||||
|
||||
.node-connection-tooltip__empty {
|
||||
@apply text-xs text-white/50;
|
||||
}
|
||||
|
||||
.node-connection-tooltip__list {
|
||||
@apply space-y-1;
|
||||
}
|
||||
|
||||
.node-connection-tooltip__item {
|
||||
@apply flex flex-wrap items-center gap-1 text-xs text-white/80;
|
||||
}
|
||||
|
||||
.node-connection-tooltip__id {
|
||||
@apply font-mono text-cyan-200;
|
||||
}
|
||||
|
||||
.node-connection-tooltip__title-text {
|
||||
@apply text-white/60;
|
||||
}
|
||||
|
||||
.node-connection-tooltip__badge {
|
||||
@apply rounded-full bg-white/10 px-2 py-0.5 text-[10px] uppercase tracking-[0.25em] text-white/60;
|
||||
}
|
||||
|
||||
.node-connection-tooltip__badge--auto {
|
||||
@apply bg-cyan-500/25 text-cyan-200;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -16,11 +16,11 @@
|
||||
href="/"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
class="flex h-10 w-10 items-center justify-center rounded-xl border border-cyan-400/40 bg-cyan-500/10 transition hover:border-cyan-300 hover:bg-cyan-500/20"
|
||||
class="flex h-11 w-11 items-center justify-center rounded-xl border border-cyan-400/40 bg-cyan-500/10 transition hover:border-cyan-300 hover:bg-cyan-500/20"
|
||||
>
|
||||
<v-icon icon="mdi-radar" size="22" color="cyan" />
|
||||
</a>
|
||||
<div class="hidden h-10 w-px bg-white/10 lg:block" />
|
||||
<div class="hidden h-11 w-px bg-white/10 lg:block" />
|
||||
<div class="flex min-w-0 items-center gap-2">
|
||||
<v-menu v-model="flowMenuOpen" transition="scale-transition" offset-y>
|
||||
<template #activator="{ props }">
|
||||
@@ -29,7 +29,7 @@
|
||||
color="cyan"
|
||||
variant="tonal"
|
||||
prepend-icon="mdi-sitemap"
|
||||
class="rounded-lg px-4 font-semibold tracking-wide text-white"
|
||||
class="app-bar-button px-4 font-semibold tracking-wide text-white"
|
||||
:loading="flowsLoading"
|
||||
>
|
||||
{{ currentFlowLabel }}
|
||||
@@ -103,13 +103,13 @@
|
||||
<div class="flex min-w-0 flex-1 items-center gap-3">
|
||||
<v-text-field
|
||||
v-model="nodeFilter.search"
|
||||
density="compact"
|
||||
density="comfortable"
|
||||
variant="solo"
|
||||
hide-details
|
||||
clearable
|
||||
prepend-inner-icon="mdi-magnify"
|
||||
placeholder="Nodes durchsuchen"
|
||||
class="max-w-[260px] rounded-xl bg-white/5 text-sm text-white/80"
|
||||
class="app-bar-field flex-1 min-w-[200px] text-sm text-white/80"
|
||||
/>
|
||||
<v-menu v-model="filtersMenuOpen" transition="scale-transition" :close-on-content-click="false" offset-y>
|
||||
<template #activator="{ props }">
|
||||
@@ -122,11 +122,10 @@
|
||||
>
|
||||
<v-btn
|
||||
v-bind="props"
|
||||
size="small"
|
||||
variant="outlined"
|
||||
color="cyan"
|
||||
prepend-icon="mdi-tune"
|
||||
class="rounded-lg border-white/20 text-xs font-semibold uppercase tracking-[0.3em]"
|
||||
class="app-bar-button border-white/20 text-xs font-semibold uppercase tracking-[0.3em]"
|
||||
>
|
||||
Filter
|
||||
</v-btn>
|
||||
@@ -187,7 +186,7 @@
|
||||
icon
|
||||
variant="tonal"
|
||||
color="white"
|
||||
class="rounded-lg border border-white/10 bg-white/5 text-white/80 hover:border-cyan-200"
|
||||
class="app-bar-button app-bar-icon-button border border-white/10 bg-white/5 text-white/80 hover:border-cyan-200"
|
||||
@click="inspectorOpen = !inspectorOpen"
|
||||
>
|
||||
<v-icon :icon="inspectorOpen ? 'mdi-dock-right' : 'mdi-dock-left'" />
|
||||
@@ -195,9 +194,8 @@
|
||||
<v-btn
|
||||
color="white"
|
||||
variant="tonal"
|
||||
size="small"
|
||||
prepend-icon="mdi-auto-fix"
|
||||
class="rounded-lg px-4 font-semibold tracking-wide text-white"
|
||||
class="app-bar-button px-4 font-semibold tracking-wide text-white"
|
||||
:disabled="!flowDetail"
|
||||
@click="autoLayoutNodes"
|
||||
>
|
||||
@@ -356,13 +354,7 @@
|
||||
v-if="inspectorOpen && flowDetail"
|
||||
class="w-[380px] shrink-0 overflow-y-auto border-l border-white/10 bg-[#0b1224]/85 backdrop-blur"
|
||||
>
|
||||
<div class="border-b border-white/10 px-5 py-4">
|
||||
<h2 class="text-lg font-semibold">Node Inspector</h2>
|
||||
<p class="text-xs text-white/50">
|
||||
{{ nodeForm ? 'Bearbeite Knoten und Auto-Trigger' : 'Wähle einen Node auf der Canvas aus' }}
|
||||
</p>
|
||||
</div>
|
||||
<div v-if="nodeForm" class="space-y-5 px-5 py-5">
|
||||
<div v-if="nodeForm" class="space-y-5 px-5 py-6">
|
||||
<div class="flex items-start justify-between gap-3">
|
||||
<div class="min-w-0">
|
||||
<div class="flex items-center gap-2">
|
||||
@@ -426,10 +418,9 @@
|
||||
<v-icon icon="mdi-tag-text-outline" />
|
||||
</v-tab>
|
||||
</v-tabs>
|
||||
<v-window v-model="inspectorTab" class="rounded-xl border border-white/10 bg-white/5 p-4">
|
||||
<v-window v-model="inspectorTab" class="rounded-xl border border-white/10 bg-white/5 p-4">
|
||||
<v-window-item value="general">
|
||||
<div class="space-y-4">
|
||||
<v-text-field v-model="nodeForm.title" label="Titel" hide-details color="cyan" />
|
||||
<v-textarea v-model="nodeForm.summary" label="Kurzbeschreibung" rows="2" hide-details color="cyan" />
|
||||
<div class="grid grid-cols-2 gap-3">
|
||||
<v-select
|
||||
@@ -657,10 +648,10 @@
|
||||
<v-text-field v-model="nodeForm.layout.icon" label="Icon" hide-details color="cyan" />
|
||||
</div>
|
||||
</v-window-item>
|
||||
</v-window>
|
||||
</v-window>
|
||||
</div>
|
||||
<div v-else class="px-5 py-8 text-sm text-white/50">
|
||||
Kein Node ausgewählt.
|
||||
<div v-else class="px-5 py-6 text-sm text-white/50">
|
||||
Wähle einen Node auf der Canvas aus.
|
||||
</div>
|
||||
</aside>
|
||||
</transition>
|
||||
@@ -1039,6 +1030,22 @@ watch(selectedNodeId, (stateId) => {
|
||||
})
|
||||
})
|
||||
|
||||
watch(
|
||||
inspectorOpen,
|
||||
(open) => {
|
||||
if (!open || !flowDetail.value) return
|
||||
if (selectedNodeId.value) return
|
||||
const preferred =
|
||||
flowDetail.value.flow.startState &&
|
||||
flowDetail.value.nodes.some((node) => node.stateId === flowDetail.value!.flow.startState)
|
||||
? flowDetail.value.flow.startState
|
||||
: flowDetail.value.nodes[0]?.stateId
|
||||
if (preferred) {
|
||||
selectedNodeId.value = preferred
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
watch(
|
||||
nodeForm,
|
||||
(value) => {
|
||||
@@ -1056,18 +1063,6 @@ watch(
|
||||
{ deep: true }
|
||||
)
|
||||
|
||||
watch(
|
||||
() => nodeForm.value?.title,
|
||||
(title) => {
|
||||
if (!nodeForm.value) return
|
||||
const suggestion = buildNodeKeyFromText(title || nodeForm.value.stateId)
|
||||
if (!nodeRenaming && nodeIdDraft.value === lastTitleSuggestion) {
|
||||
nodeIdDraft.value = suggestion
|
||||
}
|
||||
lastTitleSuggestion = suggestion
|
||||
}
|
||||
)
|
||||
|
||||
watch(
|
||||
() => flowForm.startState,
|
||||
(start) => {
|
||||
@@ -2184,4 +2179,50 @@ function removePlaceholder(index: number) {
|
||||
.sidebar-button {
|
||||
@apply w-full rounded-2xl border border-white/10 bg-white/5 px-3 py-2 text-left transition hover:border-cyan-400;
|
||||
}
|
||||
|
||||
.app-bar-button {
|
||||
height: 44px;
|
||||
min-height: 44px;
|
||||
border-radius: 0.75rem;
|
||||
}
|
||||
|
||||
.app-bar-button :deep(.v-btn__overlay) {
|
||||
border-radius: 0.75rem;
|
||||
}
|
||||
|
||||
.app-bar-icon-button {
|
||||
width: 44px;
|
||||
min-width: 44px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.app-bar-icon-button :deep(.v-btn__content) {
|
||||
height: 100%;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.app-bar-field {
|
||||
@apply rounded-xl;
|
||||
}
|
||||
|
||||
.app-bar-field :deep(.v-field) {
|
||||
border-radius: 0.75rem;
|
||||
background-color: rgba(255, 255, 255, 0.08);
|
||||
min-height: 44px;
|
||||
}
|
||||
|
||||
.app-bar-field :deep(.v-field__overlay) {
|
||||
opacity: 0;
|
||||
border-radius: 0.75rem;
|
||||
}
|
||||
|
||||
.app-bar-field :deep(.v-field__input) {
|
||||
min-height: 44px;
|
||||
padding-top: 0;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.app-bar-field :deep(.v-field__prepend-inner) {
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user