Skip to content

Commit 1a8dfb3

Browse files
committed
Svelte drop downs for Version and Message Docs
1 parent 046ba2d commit 1a8dfb3

8 files changed

Lines changed: 539 additions & 50 deletions

File tree

components.d.ts

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,6 @@ declare module 'vue' {
2222
ElCollapse: typeof import('element-plus/es')['ElCollapse']
2323
ElCollapseItem: typeof import('element-plus/es')['ElCollapseItem']
2424
ElContainer: typeof import('element-plus/es')['ElContainer']
25-
ElContainter: typeof import('element-plus/es')['ElContainter']
26-
ElDescriptions: typeof import('element-plus/es')['ElDescriptions']
27-
ElDescriptionsItem: typeof import('element-plus/es')['ElDescriptionsItem']
2825
ElDivider: typeof import('element-plus/es')['ElDivider']
2926
ElDropdown: typeof import('element-plus/es')['ElDropdown']
3027
ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem']
@@ -36,21 +33,19 @@ declare module 'vue' {
3633
ElIcon: typeof import('element-plus/es')['ElIcon']
3734
ElInput: typeof import('element-plus/es')['ElInput']
3835
ElMain: typeof import('element-plus/es')['ElMain']
39-
ElMenu: typeof import('element-plus/es')['ElMenu']
40-
ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
4136
ElRow: typeof import('element-plus/es')['ElRow']
4237
ElScrollbar: typeof import('element-plus/es')['ElScrollbar']
4338
ElTag: typeof import('element-plus/es')['ElTag']
4439
ElTooltip: typeof import('element-plus/es')['ElTooltip']
4540
GlossarySearchNav: typeof import('./src/components/GlossarySearchNav.vue')['default']
4641
HeaderNav: typeof import('./src/components/HeaderNav.vue')['default']
4742
Menu: typeof import('./src/components/Menu.vue')['default']
48-
MessageDocsContent: typeof import('./src/components/MessageDocsContent.vue')['default']
4943
MessageDocsSearchNav: typeof import('./src/components/MessageDocsSearchNav.vue')['default']
5044
Preview: typeof import('./src/components/Preview.vue')['default']
5145
RouterLink: typeof import('vue-router')['RouterLink']
5246
RouterView: typeof import('vue-router')['RouterView']
5347
SearchNav: typeof import('./src/components/SearchNav.vue')['default']
48+
SvelteDropdown: typeof import('./src/components/SvelteDropdown.vue')['default']
5449
ToolCall: typeof import('./src/components/ToolCall.vue')['default']
5550
}
5651
}

src-svelte/Dropdown.svelte

Lines changed: 258 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,258 @@
1+
<!--
2+
- Open Bank Project - API Explorer II
3+
- Copyright (C) 2023-2024, TESOBE GmbH
4+
-
5+
- This program is free software: you can redistribute it and/or modify
6+
- it under the terms of the GNU Affero General Public License as published by
7+
- the Free Software Foundation, either version 3 of the License, or
8+
- (at your option) any later version.
9+
-
10+
- This program is distributed in the hope that it will be useful,
11+
- but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
- GNU Affero General Public License for more details.
14+
-
15+
- You should have received a copy of the GNU Affero General Public License
16+
- along with this program. If not, see <http://www.gnu.org/licenses/>.
17+
-
18+
- Email: contact@tesobe.com
19+
- TESOBE GmbH
20+
- Osloerstrasse 16/17
21+
- Berlin 13359, Germany
22+
-
23+
- This product includes software developed at
24+
- TESOBE (http://www.tesobe.com/)
25+
-
26+
-->
27+
28+
<script lang="ts">
29+
interface Props {
30+
label?: string
31+
items?: string[]
32+
hoverColor?: string
33+
backgroundColor?: string
34+
}
35+
36+
let {
37+
label = 'Dropdown',
38+
items = [],
39+
hoverColor = '#32b9ce',
40+
backgroundColor = '#e8f4f8'
41+
}: Props = $props()
42+
43+
let isOpen = $state(false)
44+
let dropdownRef = $state<HTMLDivElement>()
45+
let timeoutId: number | null = null
46+
47+
function handleMouseEnter() {
48+
if (timeoutId) {
49+
clearTimeout(timeoutId)
50+
timeoutId = null
51+
}
52+
isOpen = true
53+
}
54+
55+
function handleMouseLeave() {
56+
timeoutId = window.setTimeout(() => {
57+
isOpen = false
58+
}, 1000)
59+
}
60+
61+
function handleSelect(item: string) {
62+
const event = new CustomEvent('select', {
63+
detail: item,
64+
bubbles: true,
65+
composed: true
66+
})
67+
dropdownRef?.dispatchEvent(event)
68+
isOpen = false
69+
}
70+
71+
function handleClickOutside(event: MouseEvent) {
72+
if (dropdownRef && !dropdownRef.contains(event.target as Node)) {
73+
isOpen = false
74+
}
75+
}
76+
77+
function handleEscape(event: KeyboardEvent) {
78+
if (event.key === 'Escape') {
79+
isOpen = false
80+
}
81+
}
82+
83+
$effect(() => {
84+
if (isOpen) {
85+
document.addEventListener('click', handleClickOutside)
86+
document.addEventListener('keydown', handleEscape)
87+
return () => {
88+
document.removeEventListener('click', handleClickOutside)
89+
document.removeEventListener('keydown', handleEscape)
90+
}
91+
}
92+
})
93+
</script>
94+
95+
<div
96+
bind:this={dropdownRef}
97+
class="dropdown-container"
98+
style:--hover-bg={backgroundColor}
99+
style:--hover-color={hoverColor}
100+
onmouseenter={handleMouseEnter}
101+
onmouseleave={handleMouseLeave}
102+
role="navigation"
103+
>
104+
<button
105+
class="dropdown-trigger"
106+
onclick={() => isOpen = !isOpen}
107+
aria-expanded={isOpen}
108+
aria-haspopup="true"
109+
>
110+
{label}
111+
<svg
112+
class="arrow-icon"
113+
class:rotated={isOpen}
114+
viewBox="0 0 1024 1024"
115+
xmlns="http://www.w3.org/2000/svg"
116+
>
117+
<path fill="currentColor" d="M831.872 340.864 512 652.672 192.128 340.864a30.592 30.592 0 0 0-42.752 0 29.12 29.12 0 0 0 0 41.6L489.664 714.24a32 32 0 0 0 44.672 0l340.288-331.712a29.12 29.12 0 0 0 0-41.728 30.592 30.592 0 0 0-42.752 0z"></path>
118+
</svg>
119+
</button>
120+
121+
{#if isOpen}
122+
<div class="dropdown-menu">
123+
<div class="dropdown-content">
124+
{#each items as item}
125+
<button
126+
class="dropdown-item"
127+
onclick={() => handleSelect(item)}
128+
>
129+
{item}
130+
</button>
131+
{/each}
132+
</div>
133+
</div>
134+
{/if}
135+
</div>
136+
137+
<style>
138+
.dropdown-container {
139+
position: relative;
140+
display: inline-block;
141+
}
142+
143+
.dropdown-trigger {
144+
padding: 9px;
145+
margin: 3px;
146+
color: #39455f;
147+
font-family: 'Roboto', sans-serif;
148+
font-size: 14px;
149+
text-decoration: none;
150+
border-radius: 8px;
151+
background: transparent;
152+
border: none;
153+
cursor: pointer;
154+
display: inline-flex;
155+
align-items: center;
156+
gap: 4px;
157+
transition: all 0.2s ease;
158+
}
159+
160+
.dropdown-trigger:hover {
161+
background-color: var(--hover-bg) !important;
162+
color: var(--hover-color) !important;
163+
}
164+
165+
.arrow-icon {
166+
width: 14px;
167+
height: 14px;
168+
transition: transform 0.3s ease;
169+
}
170+
171+
.arrow-icon.rotated {
172+
transform: rotate(180deg);
173+
}
174+
175+
.dropdown-menu {
176+
position: absolute;
177+
top: 100%;
178+
right: 0;
179+
margin-top: 4px;
180+
background: white;
181+
border: 1px solid #e4e7ed;
182+
border-radius: 8px;
183+
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
184+
z-index: 2000;
185+
min-width: 180px;
186+
max-width: 280px;
187+
animation: fadeIn 0.2s ease;
188+
}
189+
190+
@keyframes fadeIn {
191+
from {
192+
opacity: 0;
193+
transform: translateY(-8px);
194+
}
195+
to {
196+
opacity: 1;
197+
transform: translateY(0);
198+
}
199+
}
200+
201+
.dropdown-content {
202+
max-height: 400px;
203+
overflow-y: auto;
204+
padding: 6px 0;
205+
}
206+
207+
.dropdown-item {
208+
width: 100%;
209+
padding: 10px 20px;
210+
margin: 0;
211+
color: #606266;
212+
font-family: 'Roboto', sans-serif;
213+
font-size: 14px;
214+
text-align: left;
215+
background: transparent;
216+
border: none;
217+
cursor: pointer;
218+
transition: all 0.2s ease;
219+
white-space: nowrap;
220+
overflow: hidden;
221+
text-overflow: ellipsis;
222+
}
223+
224+
.dropdown-item:hover {
225+
background-color: var(--hover-bg);
226+
color: var(--hover-color);
227+
}
228+
229+
.dropdown-item:active {
230+
background-color: var(--hover-bg);
231+
opacity: 0.8;
232+
}
233+
234+
/* Custom scrollbar styling */
235+
.dropdown-content::-webkit-scrollbar {
236+
width: 6px;
237+
}
238+
239+
.dropdown-content::-webkit-scrollbar-track {
240+
background: #f5f5f5;
241+
border-radius: 3px;
242+
}
243+
244+
.dropdown-content::-webkit-scrollbar-thumb {
245+
background: #c1c1c1;
246+
border-radius: 3px;
247+
}
248+
249+
.dropdown-content::-webkit-scrollbar-thumb:hover {
250+
background: #a8a8a8;
251+
}
252+
253+
/* Firefox scrollbar */
254+
.dropdown-content {
255+
scrollbar-width: thin;
256+
scrollbar-color: #c1c1c1 #f5f5f5;
257+
}
258+
</style>

src/components/HeaderNav.vue

Lines changed: 29 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@
2727

2828
<script setup lang="ts">
2929
import { ref, inject, watchEffect, onMounted, computed } from 'vue'
30-
import { ArrowDown } from '@element-plus/icons-vue'
3130
import { useRoute, useRouter } from 'vue-router'
3231
import { OBP_API_DEFAULT_RESOURCE_DOC_VERSION, getCurrentUser } from '../obp'
3332
import { getOBPAPIVersions } from '../obp/api-version'
@@ -38,6 +37,7 @@ import {
3837
HEADER_LINKS_BACKGROUND_COLOR as headerLinksBackgroundColorSetting
3938
} from '../obp/style-setting'
4039
import { obpApiActiveVersionsKey, obpGroupedMessageDocsKey, obpMyCollectionsEndpointKey } from '@/obp/keys'
40+
import SvelteDropdown from './SvelteDropdown.vue'
4141
4242
const route = useRoute()
4343
const router = useRouter()
@@ -82,7 +82,11 @@ const handleMore = (command: string) => {
8282
if (element !== null) {
8383
element.textContent = command;
8484
}
85-
if (command.includes('_')) {
85+
if (command === '/message-docs') {
86+
// Navigate to message docs list
87+
console.log('Navigating to message docs list')
88+
router.push({ name: 'message-docs-list' })
89+
} else if (command.includes('_')) {
8690
console.log('Navigating to message docs:', command)
8791
router.push({ name: 'message-docs', params: { id: command } })
8892
} else {
@@ -145,31 +149,24 @@ const getCurrentPath = () => {
145149
<a v-if="showObpApiManagerButton && hasObpApiManagerHost" v-bind:href="obpApiManagerHost" class="router-link" id="header-nav-api-manager">
146150
{{ $t('header.api_manager') }}
147151
</a>
148-
<el-dropdown
149-
class="menu-right router-link"
150-
id="header-nav-more"
151-
@command="handleMore"
152-
trigger="hover"
153-
placement="bottom-end"
154-
:teleported="true"
155-
max-height="700px"
156-
>
157-
<span class="el-dropdown-link">
158-
{{ $t('header.more') }}
159-
<el-icon class="el-icon--right">
160-
<arrow-down />
161-
</el-icon>
162-
</span>
163-
<template #dropdown>
164-
<el-dropdown-menu>
165-
<el-dropdown-item v-for="value in obpApiVersions" :command="value" :key="value">{{
166-
value
167-
}}</el-dropdown-item>
168-
<el-dropdown-item v-for="value in obpMessageDocs" :command="value" :key="value">
169-
Message Docs for: {{ value }}</el-dropdown-item>
170-
</el-dropdown-menu>
171-
</template>
172-
</el-dropdown>
152+
<SvelteDropdown
153+
class="menu-right"
154+
id="header-nav-versions"
155+
label="Versions"
156+
:items="obpApiVersions"
157+
:hover-color="headerLinksHoverColor"
158+
:background-color="headerLinksBackgroundColor"
159+
@select="handleMore"
160+
/>
161+
<SvelteDropdown
162+
class="menu-right"
163+
id="header-nav-message-docs"
164+
label="Message Docs"
165+
:items="obpMessageDocs"
166+
:hover-color="headerLinksHoverColor"
167+
:background-color="headerLinksBackgroundColor"
168+
@select="handleMore"
169+
/>
173170
<!--<span class="el-dropdown-link">
174171
<RouterLink class="router-link" id="header-nav-spaces" to="/spaces">{{
175172
$t('header.spaces')
@@ -261,21 +258,10 @@ a.logoff-button {
261258
color: #39455f;
262259
}
263260
264-
/*override element plus*/
265-
.el-dropdown-menu__item:hover {
266-
color: v-bind(headerLinksHoverColor) !important;
267-
}
268-
269-
/* Fix dropdown menu overflow */
270-
.el-dropdown-menu {
271-
max-height: 400px;
272-
overflow-y: auto;
273-
}
274-
275-
/* Ensure dropdown trigger behaves correctly */
276-
#header-nav-more .el-dropdown-link {
277-
cursor: pointer;
278-
display: inline-flex;
279-
align-items: center;
261+
/* Custom dropdown containers */
262+
#header-nav-versions,
263+
#header-nav-message-docs {
264+
display: inline-block;
265+
vertical-align: middle;
280266
}
281267
</style>

0 commit comments

Comments
 (0)