Newer
Older
gnexus-tasks / frontend / src / components / InlineField.vue
<script setup lang="ts">
import { ref } from 'vue'
import { useI18n } from 'vue-i18n'

// Универсальный враппер «показ ↔ редактор» для инлайн-правки поля на месте.
// В ките инлайн-компонента нет, а GnInput умеет bare (голый input) — слоты
// позволяют подставить любой редактор (input, select, пара дата+период).
// Сохранение — явными кнопками ✓/✗ (не по blur); Esc — отмена.
// save() может кинуть ошибку: тогда редактор остаётся открытым
// (родитель уже показал тост).

const props = defineProps<{
  /** aria-label/tooltip кнопки входа в правку */
  label: string
  /** Родительский хендлер сохранения; promise → busy, throw → редактор открыт */
  save: () => Promise<unknown> | unknown
}>()

const emit = defineEmits<{ open: [] }>()

const { t } = useI18n()

const open = ref(false)
const busy = ref(false)

function edit() {
  emit('open')
  open.value = true
}

function cancel() {
  if (busy.value) return
  open.value = false
}

async function save() {
  if (busy.value) return
  busy.value = true
  try {
    await props.save()
    open.value = false
  } catch {
    // ошибка уже показана родителем — оставляем редактор открытым
  } finally {
    busy.value = false
  }
}
</script>

<template>
  <span class="inline-field" @keydown.esc="cancel">
    <span v-if="!open" class="inline-display">
      <slot name="display" :edit="edit" />
    </span>
    <span v-else class="inline-editing">
      <slot name="edit" :save="save" :cancel="cancel" :busy="busy" />
      <span class="inline-actions">
        <GnIconButton
          icon="ph-check"
          size="sm"
          :label="t('common.save')"
          :disabled="busy"
          @click="save"
        />
        <GnIconButton
          icon="ph-x"
          size="sm"
          :label="t('common.cancel')"
          :disabled="busy"
          @click="cancel"
        />
      </span>
    </span>
  </span>
</template>

<style scoped>
.inline-field {
  display: inline-flex;
  min-width: 0;
}
/* показ: кликабельно, по hover — подсказка пунктиром */
.inline-display {
  cursor: pointer;
  min-width: 0;
}
.inline-display:hover {
  text-decoration: underline dashed var(--accent, #7aa2f7);
  text-underline-offset: 3px;
}
/* правка: редактор + кнопки в одну строку */
.inline-editing {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  flex-wrap: wrap;
  min-width: 0;
}
.inline-actions {
  display: inline-flex;
  gap: 0.25rem;
  flex: none;
}
</style>