Add inline ticket property dropdowns
This commit is contained in:
@@ -2,11 +2,9 @@
|
||||
import {
|
||||
ArrowDown,
|
||||
ArrowUp,
|
||||
Check,
|
||||
CircleAlert,
|
||||
IndentDecrease,
|
||||
IndentIncrease,
|
||||
LoaderCircle,
|
||||
Plus,
|
||||
Trash2,
|
||||
} from 'lucide-vue-next';
|
||||
@@ -465,21 +463,7 @@ const handleIndentKeyboard = (index: number, event: KeyboardEvent): void => {
|
||||
<span v-else />
|
||||
|
||||
<span
|
||||
v-if="saveState === 'saving'"
|
||||
class="text-muted-foreground flex items-center gap-1.5 text-xs"
|
||||
>
|
||||
<LoaderCircle class="size-3.5 animate-spin" />
|
||||
Saving
|
||||
</span>
|
||||
<span
|
||||
v-else-if="saveState === 'saved'"
|
||||
class="text-primary flex items-center gap-1.5 text-xs"
|
||||
>
|
||||
<Check class="size-3.5" />
|
||||
Saved
|
||||
</span>
|
||||
<span
|
||||
v-else-if="saveState === 'error' || error"
|
||||
v-if="saveState === 'error' || error"
|
||||
class="text-destructive flex items-center gap-1.5 text-xs"
|
||||
>
|
||||
<CircleAlert class="size-3.5" />
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { Check, CircleAlert, LoaderCircle } from 'lucide-vue-next';
|
||||
import { CircleAlert } from 'lucide-vue-next';
|
||||
import type { InlineSaveStatusProps } from '@/types';
|
||||
|
||||
withDefaults(defineProps<InlineSaveStatusProps>(), {
|
||||
@@ -9,22 +9,10 @@ withDefaults(defineProps<InlineSaveStatusProps>(), {
|
||||
|
||||
<template>
|
||||
<div
|
||||
v-if="state !== 'idle' || error"
|
||||
v-if="state === 'error' || error"
|
||||
class="flex items-center gap-1.5 pt-1 text-[11px]"
|
||||
>
|
||||
<template v-if="state === 'saving'">
|
||||
<LoaderCircle class="text-muted-foreground size-3 animate-spin" />
|
||||
<span class="text-muted-foreground">Saving</span>
|
||||
</template>
|
||||
<template v-else-if="state === 'saved'">
|
||||
<Check class="text-primary size-3" />
|
||||
<span class="text-primary">Saved</span>
|
||||
</template>
|
||||
<template v-else-if="state === 'error' || error">
|
||||
<CircleAlert class="text-destructive size-3" />
|
||||
<span class="text-destructive">{{
|
||||
error || 'Could not save.'
|
||||
}}</span>
|
||||
</template>
|
||||
<CircleAlert class="text-destructive size-3" />
|
||||
<span class="text-destructive">{{ error || 'Could not save.' }}</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { Check, CircleAlert, LoaderCircle, Pencil } from 'lucide-vue-next';
|
||||
import { CircleAlert, Pencil } from 'lucide-vue-next';
|
||||
import { nextTick, ref } from 'vue';
|
||||
import type {
|
||||
InlineTextEditorEmits,
|
||||
@@ -181,25 +181,13 @@ const displayValue = (): string => {
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="saveState !== 'idle' || error"
|
||||
v-if="saveState === 'error' || error"
|
||||
class="mt-1 flex items-center gap-1.5 text-[11px]"
|
||||
>
|
||||
<template v-if="saveState === 'saving'">
|
||||
<LoaderCircle
|
||||
class="text-muted-foreground size-3 animate-spin"
|
||||
/>
|
||||
<span class="text-muted-foreground">Saving</span>
|
||||
</template>
|
||||
<template v-else-if="saveState === 'saved'">
|
||||
<Check class="text-primary size-3" />
|
||||
<span class="text-primary">Saved</span>
|
||||
</template>
|
||||
<template v-else-if="saveState === 'error' || error">
|
||||
<CircleAlert class="text-destructive size-3" />
|
||||
<span class="text-destructive">{{
|
||||
error || 'Could not save.'
|
||||
}}</span>
|
||||
</template>
|
||||
<CircleAlert class="text-destructive size-3" />
|
||||
<span class="text-destructive">{{
|
||||
error || 'Could not save.'
|
||||
}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
<script setup lang="ts">
|
||||
import { Check, ChevronDown } from 'lucide-vue-next';
|
||||
import { computed } from 'vue';
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from '@/components/ui/dropdown-menu';
|
||||
import type {
|
||||
PropertySelectDropdownEmits,
|
||||
PropertySelectDropdownProps,
|
||||
} from '@/types';
|
||||
|
||||
const props = withDefaults(defineProps<PropertySelectDropdownProps>(), {
|
||||
disabled: false,
|
||||
});
|
||||
|
||||
const emit = defineEmits<PropertySelectDropdownEmits>();
|
||||
|
||||
const normalizedValue = computed(() => String(props.modelValue ?? ''));
|
||||
const selectedOption = computed(
|
||||
() =>
|
||||
props.options.find(
|
||||
(option) => String(option.value) === normalizedValue.value,
|
||||
) ?? null,
|
||||
);
|
||||
|
||||
const selectOption = (value: number | string): void => {
|
||||
if (
|
||||
!props.editable ||
|
||||
props.disabled ||
|
||||
String(value) === normalizedValue.value
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
emit('update:modelValue', value);
|
||||
emit('commit', value);
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DropdownMenu v-if="editable">
|
||||
<DropdownMenuTrigger as-child>
|
||||
<button
|
||||
type="button"
|
||||
class="focus-visible:ring-ring/50 data-[state=open]:bg-primary/[0.08] data-[state=open]:text-primary hover:bg-primary/[0.055] hover:text-primary group -mx-1 flex min-h-7 w-[calc(100%+0.5rem)] items-center gap-2 rounded px-1 text-left text-sm font-medium outline-none transition-colors focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-60"
|
||||
:aria-label="`Select ${label}`"
|
||||
:disabled="disabled"
|
||||
>
|
||||
<span class="min-w-0 flex-1 truncate">
|
||||
{{ selectedOption?.label ?? 'Not set' }}
|
||||
</span>
|
||||
<ChevronDown
|
||||
class="text-muted-foreground size-3.5 shrink-0 transition-transform group-data-[state=open]:rotate-180"
|
||||
/>
|
||||
</button>
|
||||
</DropdownMenuTrigger>
|
||||
|
||||
<DropdownMenuContent
|
||||
align="end"
|
||||
:side-offset="6"
|
||||
class="w-(--reka-dropdown-menu-trigger-width) min-w-52 rounded p-1"
|
||||
>
|
||||
<DropdownMenuItem
|
||||
v-for="option in options"
|
||||
:key="String(option.value)"
|
||||
class="hover:bg-primary/[0.04] focus:bg-primary/[0.08] cursor-pointer gap-2.5 rounded px-2 py-2"
|
||||
:class="
|
||||
normalizedValue === String(option.value)
|
||||
? 'bg-primary/[0.055] text-primary'
|
||||
: ''
|
||||
"
|
||||
@select="selectOption(option.value)"
|
||||
>
|
||||
<span class="min-w-0 flex-1">
|
||||
<span class="block truncate font-medium">
|
||||
{{ option.label }}
|
||||
</span>
|
||||
<span
|
||||
v-if="option.description"
|
||||
class="text-muted-foreground block truncate text-[11px]"
|
||||
>
|
||||
{{ option.description }}
|
||||
</span>
|
||||
</span>
|
||||
<Check
|
||||
v-if="normalizedValue === String(option.value)"
|
||||
class="text-primary size-4 shrink-0"
|
||||
/>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
|
||||
<div
|
||||
v-else
|
||||
class="flex min-h-7 min-w-0 items-center gap-2 text-sm font-medium"
|
||||
>
|
||||
<span class="truncate">{{ selectedOption?.label ?? 'Not set' }}</span>
|
||||
</div>
|
||||
</template>
|
||||
Reference in New Issue
Block a user