fix: tabhead and scrolling issue (#2966)
Co-authored-by: Andrew Bastin <andrewbastin.k@gmail.com>
This commit is contained in:
@@ -16,7 +16,6 @@ declare module '@vue/runtime-core' {
|
||||
AppHeader: typeof import('./components/app/Header.vue')['default']
|
||||
AppInterceptor: typeof import('./components/app/Interceptor.vue')['default']
|
||||
AppLogo: typeof import('./components/app/Logo.vue')['default']
|
||||
AppNavigation: typeof import('./components/app/Navigation.vue')['default']
|
||||
AppOptions: typeof import('./components/app/Options.vue')['default']
|
||||
AppPaneLayout: typeof import('./components/app/PaneLayout.vue')['default']
|
||||
AppPowerSearch: typeof import('./components/app/PowerSearch.vue')['default']
|
||||
@@ -117,6 +116,7 @@ declare module '@vue/runtime-core' {
|
||||
HttpTests: typeof import('./components/http/Tests.vue')['default']
|
||||
HttpURLEncodedParams: typeof import('./components/http/URLEncodedParams.vue')['default']
|
||||
IconLucideArrowLeft: typeof import('~icons/lucide/arrow-left')['default']
|
||||
IconLucideBrush: typeof import('~icons/lucide/brush')['default']
|
||||
IconLucideCheckCircle: typeof import('~icons/lucide/check-circle')['default']
|
||||
IconLucideChevronRight: typeof import('~icons/lucide/chevron-right')['default']
|
||||
IconLucideGlobe: typeof import('~icons/lucide/globe')['default']
|
||||
@@ -125,6 +125,7 @@ declare module '@vue/runtime-core' {
|
||||
IconLucideInfo: typeof import('~icons/lucide/info')['default']
|
||||
IconLucideLayers: typeof import('~icons/lucide/layers')['default']
|
||||
IconLucideMinus: typeof import('~icons/lucide/minus')['default']
|
||||
IconLucideRss: typeof import('~icons/lucide/rss')['default']
|
||||
IconLucideSearch: typeof import('~icons/lucide/search')['default']
|
||||
IconLucideUser: typeof import('~icons/lucide/user')['default']
|
||||
IconLucideUsers: typeof import('~icons/lucide/users')['default']
|
||||
|
||||
@@ -16,19 +16,33 @@
|
||||
:key="tab.id"
|
||||
:label="tab.document.request.name"
|
||||
:is-removable="tabs.length > 1"
|
||||
:close-visibility="'hover'"
|
||||
>
|
||||
<template #tabhead>
|
||||
<span
|
||||
class="font-semibold truncate text-tiny w-10"
|
||||
:class="getMethodLabelColorClassOf(tab.document.request)"
|
||||
<div
|
||||
v-tippy="{ theme: 'tooltip', delay: [500, 20] }"
|
||||
:title="tab.document.request.name"
|
||||
class="truncate px-2"
|
||||
>
|
||||
{{ tab.document.request.method }}
|
||||
</span>
|
||||
<span class="text-green-600 mr-1" v-if="tab.document.isDirty">
|
||||
•
|
||||
</span>
|
||||
<span class="truncate flex-1">
|
||||
{{ tab.document.request.name }}
|
||||
<span
|
||||
class="font-semibold text-tiny"
|
||||
:class="getMethodLabelColorClassOf(tab.document.request)"
|
||||
>
|
||||
{{ tab.document.request.method }}
|
||||
</span>
|
||||
<span class="leading-8 px-2">
|
||||
{{ tab.document.request.name }}
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
<template #suffix>
|
||||
<span
|
||||
class="text-green-600 text-[8px] group-hover:hidden w-4"
|
||||
v-if="tab.document.isDirty"
|
||||
>
|
||||
<svg viewBox="0 0 24 24" width="1.2em" height="1.2em">
|
||||
<circle cx="12" cy="12" r="10" fill="currentColor"></circle>
|
||||
</svg>
|
||||
</span>
|
||||
</template>
|
||||
<HttpRequestTab
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
"version": "0.0.1",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"watch": "vite build --watch",
|
||||
"build": "vite build",
|
||||
"story:dev": "histoire dev",
|
||||
"story:build": "histoire build",
|
||||
|
||||
@@ -21,23 +21,32 @@ import { TabMeta, TabProvider } from "./Windows.vue"
|
||||
|
||||
const slots = useSlots()
|
||||
|
||||
const props = defineProps({
|
||||
label: { type: String, default: null },
|
||||
info: { type: String, default: null },
|
||||
id: { type: String, default: null, required: true },
|
||||
isRemovable: { type: Boolean, default: true },
|
||||
selected: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
})
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
label: string | null
|
||||
info: string | null
|
||||
id: string
|
||||
isRemovable: boolean
|
||||
closeVisibility: "hover" | "always" | "never"
|
||||
selected: boolean
|
||||
}>(),
|
||||
{
|
||||
label: null,
|
||||
info: null,
|
||||
isRemovable: true,
|
||||
closeVisibility: "always",
|
||||
selected: false,
|
||||
}
|
||||
)
|
||||
|
||||
const tabMeta = computed<TabMeta>(() => ({
|
||||
info: props.info,
|
||||
label: props.label,
|
||||
isRemovable: props.isRemovable,
|
||||
icon: slots.icon,
|
||||
tabhead: slots.tabhead
|
||||
suffix: slots.suffix,
|
||||
tabhead: slots.tabhead,
|
||||
closeVisibility: props.closeVisibility,
|
||||
}))
|
||||
|
||||
const {
|
||||
|
||||
@@ -1,43 +1,95 @@
|
||||
<template>
|
||||
<div class="flex flex-col flex-1 h-auto overflow-y-hidden flex-nowrap">
|
||||
<div class="relative sticky top-0 z-10 flex-shrink-0 overflow-x-auto tabs bg-primaryLight">
|
||||
<div class="flex flex-1 flex-shrink-0 w-0 overflow-x-auto" ref="scrollContainer">
|
||||
<div class="flex justify-between divide-x divide-dividerLight" @wheel="scrollOnWindows">
|
||||
<div
|
||||
class="relative sticky top-0 z-10 flex-shrink-0 overflow-x-auto tabs bg-primaryLight"
|
||||
>
|
||||
<div
|
||||
class="flex flex-1 flex-shrink-0 w-0 overflow-x-auto"
|
||||
ref="scrollContainer"
|
||||
>
|
||||
<div
|
||||
class="flex justify-between divide-x divide-dividerLight"
|
||||
@wheel.prevent="scroll"
|
||||
>
|
||||
<div class="flex">
|
||||
<draggable v-bind="dragOptions" :list="tabEntries" :style="tabStyles" :item-key="'window-'"
|
||||
class="flex flex-shrink-0 overflow-x-auto transition divide-x divide-dividerLight" @sort="sortTabs">
|
||||
<draggable
|
||||
v-bind="dragOptions"
|
||||
:list="tabEntries"
|
||||
:style="tabStyles"
|
||||
:item-key="'window-'"
|
||||
class="flex flex-shrink-0 overflow-x-auto transition divide-x divide-dividerLight"
|
||||
@sort="sortTabs"
|
||||
>
|
||||
<template #item="{ element: [tabID, tabMeta] }">
|
||||
<button :key="`removable-tab-${tabID}`" class="tab" :class="[{ active: modelValue === tabID }]"
|
||||
:aria-label="tabMeta.label || ''" role="button" @keyup.enter="selectTab(tabID)"
|
||||
@click="selectTab(tabID)">
|
||||
<button
|
||||
:key="`removable-tab-${tabID}`"
|
||||
class="tab group px-2"
|
||||
:class="[{ active: modelValue === tabID }]"
|
||||
:aria-label="tabMeta.label || ''"
|
||||
role="button"
|
||||
@keyup.enter="selectTab(tabID)"
|
||||
@click="selectTab(tabID)"
|
||||
>
|
||||
<span
|
||||
v-if="tabMeta.icon"
|
||||
class="flex items-center justify-center cursor-pointer"
|
||||
>
|
||||
<component :is="tabMeta.icon" class="w-4 h-4 svg-icons" />
|
||||
</span>
|
||||
|
||||
<div v-if="!tabMeta.tabhead" class="flex items-stretch truncate">
|
||||
<span v-if="tabMeta.icon" class="flex items-center justify-center mx-4 cursor-pointer">
|
||||
<component :is="tabMeta.icon" class="w-4 h-4 svg-icons" />
|
||||
</span>
|
||||
<span class="truncate pl-4">
|
||||
<div
|
||||
v-if="!tabMeta.tabhead"
|
||||
class="truncate w-full text-left px-2"
|
||||
>
|
||||
<span class="truncate">
|
||||
{{ tabMeta.label }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div v-else class="truncate flex items-center justify-start mx-4">
|
||||
<div v-else class="truncate w-full text-left">
|
||||
<component :is="tabMeta.tabhead" />
|
||||
</div>
|
||||
|
||||
<HoppButtonSecondary v-tippy="{ theme: 'tooltip', delay: [500, 20] }" :icon="IconX" :style="{
|
||||
display: tabMeta.isRemovable ? 'flex' : 'none',
|
||||
}" :title="closeText ?? t?.('action.close') ?? 'Close'"
|
||||
:class="[{ active: modelValue === tabID }, 'close']" class="mx-2 !p-0.5"
|
||||
@click.stop="emit('removeTab', tabID)" />
|
||||
<component v-if="tabMeta.suffix" :is="tabMeta.suffix" />
|
||||
|
||||
<HoppButtonSecondary
|
||||
v-if="tabMeta.isRemovable"
|
||||
v-tippy="{ theme: 'tooltip', delay: [500, 20] }"
|
||||
:icon="IconX"
|
||||
:title="closeText ?? t?.('action.close') ?? 'Close'"
|
||||
:class="[
|
||||
{ active: modelValue === tabID },
|
||||
{
|
||||
flex: tabMeta.closeVisibility === 'always',
|
||||
'group-hover:flex hidden':
|
||||
tabMeta.closeVisibility === 'hover',
|
||||
hidden: tabMeta.closeVisibility === 'never',
|
||||
},
|
||||
'close',
|
||||
]"
|
||||
class="!p-0.5"
|
||||
@click.stop="emit('removeTab', tabID)"
|
||||
/>
|
||||
</button>
|
||||
</template>
|
||||
</draggable>
|
||||
</div>
|
||||
<div class="sticky right-0 flex items-center justify-center flex-shrink-0 overflow-x-auto z-8">
|
||||
<div
|
||||
class="sticky right-0 flex items-center justify-center flex-shrink-0 overflow-x-auto z-8"
|
||||
>
|
||||
<slot name="actions">
|
||||
<span v-if="canAddNewTab" class="flex items-center justify-center px-2 py-1.5 bg-primaryLight z-8">
|
||||
<HoppButtonSecondary v-tippy="{ theme: 'tooltip' }" :title="newText ?? t?.('action.new') ?? 'New'"
|
||||
:icon="IconPlus" class="rounded !p-1" filled @click="addTab" />
|
||||
<span
|
||||
v-if="canAddNewTab"
|
||||
class="flex items-center justify-center px-2 py-1.5 bg-primaryLight z-8 h-full"
|
||||
>
|
||||
<HoppButtonSecondary
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="newText ?? t?.('action.new') ?? 'New'"
|
||||
:icon="IconPlus"
|
||||
class="rounded !p-1"
|
||||
filled
|
||||
@click="addTab"
|
||||
/>
|
||||
</span>
|
||||
</slot>
|
||||
</div>
|
||||
@@ -66,9 +118,11 @@ import { HoppUIPluginOptions, HOPP_UI_OPTIONS } from "./../../index"
|
||||
export type TabMeta = {
|
||||
label: string | null
|
||||
icon: Slot | undefined
|
||||
suffix: Slot | undefined
|
||||
tabhead: Slot | undefined
|
||||
info: string | null
|
||||
isRemovable: boolean
|
||||
closeVisibility: "hover" | "always" | "never"
|
||||
}
|
||||
export type TabProvider = {
|
||||
// Whether inactive tabs should remain rendered
|
||||
@@ -187,12 +241,11 @@ const addTab = () => {
|
||||
emit("addTab")
|
||||
}
|
||||
|
||||
const scrollContainer = ref<HTMLElement|null>(null)
|
||||
const scrollContainer = ref<HTMLElement | null>(null)
|
||||
|
||||
const scrollOnWindows = (event: WheelEvent) => {
|
||||
event.preventDefault()
|
||||
if(scrollContainer.value)
|
||||
scrollContainer.value.scrollLeft += event.deltaY
|
||||
const scroll = (e: WheelEvent) => {
|
||||
scrollContainer.value!.scrollLeft += e.deltaY
|
||||
scrollContainer.value!.scrollLeft += e.deltaX
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user