Skip to content

Commit e488355

Browse files
authored
Merge pull request #1182 from monarch-initiative/issue-1110-node-page-linkouts
Make Node Page Linkouts More Prominent
2 parents 399fd23 + 10929ec commit e488355

40 files changed

+2274
-536
lines changed

frontend/src/App.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
Disabling the banner is as simple as commenting out this section.
3131
</TheBanner> -->
3232

33-
<TheHeaderV2 />
33+
<TheHeaderV2 :key="route.path" />
3434
<main>
3535
<router-view />
3636
<TheFloatButtons />
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
<template>
2+
<div class="association-tabs">
3+
<div class="tab-item" v-if="hasDirectAssociations">
4+
<AppButton
5+
:info="true"
6+
:info-tooltip="directTooltip"
7+
:class="['tab-button', { active: directActive }]"
8+
:disabled="!hasDirectAssociations"
9+
:text="directLabel"
10+
color="none"
11+
@click="$emit('select', 'direct')"
12+
/>
13+
</div>
14+
15+
<div class="tab-item" v-if="showAllTab">
16+
<AppButton
17+
:info="true"
18+
:info-tooltip="inferredTooltip"
19+
:class="['tab-button', { active: allActive }]"
20+
:text="inferredLabel"
21+
color="none"
22+
@click="$emit('select', 'all')"
23+
/>
24+
</div>
25+
</div>
26+
</template>
27+
28+
<script setup lang="ts">
29+
import AppButton from "@/components/AppButton.vue";
30+
31+
const props = withDefaults(
32+
defineProps<{
33+
hasDirectAssociations: boolean;
34+
showAllTab: boolean;
35+
directActive: boolean;
36+
allActive: boolean;
37+
directLabel: string;
38+
inferredLabel: string;
39+
directTooltip?: string;
40+
inferredTooltip?: string;
41+
}>(),
42+
{
43+
hasDirectAssociations: true,
44+
showAllTab: true,
45+
directActive: true,
46+
allActive: false,
47+
directLabel: "Direct",
48+
inferredLabel: "All",
49+
directTooltip: undefined,
50+
inferredTooltip: undefined,
51+
},
52+
);
53+
defineEmits<{
54+
(e: "select", which: "direct" | "all"): void;
55+
}>();
56+
</script>
57+
58+
<style scoped lang="scss">
59+
$wrap: 900px;
60+
.association-tabs {
61+
display: flex;
62+
flex-wrap: wrap;
63+
gap: 0.25em;
64+
.tab-button {
65+
z-index: 0;
66+
position: relative;
67+
min-width: 22em;
68+
padding: 0.8rem 1.5rem;
69+
border: none;
70+
border-radius: 8px 8px 0 0;
71+
background-color: $light-gray;
72+
&.active {
73+
z-index: 1;
74+
background-color: $theme;
75+
box-shadow: 0 3px 0 0 $theme;
76+
color: white;
77+
}
78+
}
79+
:deep(.tab-button) {
80+
border: none;
81+
outline: none;
82+
box-shadow: none;
83+
&:focus,
84+
&:hover {
85+
outline: none;
86+
box-shadow: none;
87+
}
88+
}
89+
}
90+
.tab-item {
91+
display: flex;
92+
flex-direction: column;
93+
align-items: center;
94+
gap: 0.5rem;
95+
}
96+
</style>
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
<template>
2+
<div class="toc-top" v-show="show">
3+
<AppButton
4+
design="link"
5+
color="none"
6+
:text="label"
7+
icon="angle-up"
8+
icon-position="left"
9+
:aria-label="ariaLabel"
10+
@click="scrollTop"
11+
/>
12+
</div>
13+
</template>
14+
15+
<script setup lang="ts">
16+
import { computed } from "vue";
17+
import { useWindowScroll, useWindowSize } from "@vueuse/core";
18+
import AppButton from "@/components/AppButton.vue";
19+
20+
// Props
21+
const props = withDefaults(
22+
defineProps<{
23+
targetId?: string; // anchor to scroll to
24+
thresholdVH?: number; // show after this % of viewport
25+
label?: string;
26+
ariaLabel?: string;
27+
}>(),
28+
{
29+
targetId: "top",
30+
thresholdVH: 0.1,
31+
label: "Back to top",
32+
ariaLabel: "Back to top",
33+
},
34+
);
35+
36+
// Reactive scroll/viewport
37+
const { y } = useWindowScroll();
38+
const { height } = useWindowSize();
39+
40+
// Show control after threshold
41+
const show = computed(() => y.value > height.value * props.thresholdVH);
42+
43+
// Smooth scroll to anchor or page top
44+
const scrollTop = () => {
45+
const el = document.getElementById(props.targetId);
46+
el
47+
? el.scrollIntoView({ behavior: "smooth", block: "start" })
48+
: window.scrollTo({ top: 0, behavior: "smooth" });
49+
};
50+
</script>
51+
52+
<style scoped lang="scss">
53+
.toc-top {
54+
display: flex;
55+
z-index: 1;
56+
justify-content: center;
57+
padding: 1em;
58+
border-bottom: 1px solid #e9eef0;
59+
background: #fff;
60+
}
61+
</style>

frontend/src/components/AppButton.vue

Lines changed: 80 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,21 @@
77
:type="type"
88
@click="copy ? copyToClipboard(text) : click"
99
>
10+
<AppIcon v-if="icon && iconPosition === 'left'" :icon="icon" class="icon" />
1011
<span v-if="text" class="truncate">{{ text }}</span>
11-
<AppIcon v-if="icon" :icon="icon" />
12+
<!-- icon on the RIGHT (default) -->
13+
<AppIcon
14+
v-if="icon && iconPosition === 'right'"
15+
:icon="icon"
16+
class="icon"
17+
/>
18+
<!-- optional info icon -->
19+
<AppIcon
20+
v-if="info"
21+
icon="info-circle"
22+
v-tooltip="infoTooltip"
23+
class="info-icon"
24+
/>
1225
</component>
1326
</template>
1427

@@ -21,29 +34,38 @@ type Props = {
2134
text?: string;
2235
/** icon to show */
2336
icon?: string;
37+
/** where to place the icon relative to text */
38+
iconPosition?: "left" | "right";
2439
/** location to link to */
2540
to?: string;
2641
/** on click action */
2742
click?: () => unknown;
2843
/** visual design */
29-
design?: "normal" | "circle" | "small" | "tile";
44+
design?: "normal" | "circle" | "small" | "tile" | "big" | "link";
3045
/** color */
3146
color?: "primary" | "secondary" | "none";
3247
/** whether to copy text prop to clipboard on click */
3348
copy?: boolean;
3449
/** html button type attribute */
3550
type?: string;
51+
/** show an “i” info icon on the right */
52+
info?: boolean;
53+
/** tooltip text for the info icon */
54+
infoTooltip?: string;
3655
};
3756
3857
const props = withDefaults(defineProps<Props>(), {
3958
text: "",
4059
icon: "",
60+
iconPosition: "right",
4161
to: "",
4262
click: undefined,
4363
design: "normal",
4464
color: "primary",
4565
copy: false,
4666
type: "button",
67+
info: false,
68+
infoTooltip: "",
4769
});
4870
4971
/** element ref */
@@ -84,7 +106,6 @@ defineExpose({ button });
84106
&.primary {
85107
background: $theme-light;
86108
}
87-
88109
&.secondary {
89110
background: $light-gray;
90111
}
@@ -106,6 +127,12 @@ defineExpose({ button });
106127
width: fit-content;
107128
}
108129
130+
&.big {
131+
min-width: min(300px, 100% - 40px);
132+
padding: 0px 30px;
133+
font-size: 1.75rem;
134+
}
135+
109136
&.circle {
110137
border-radius: 999px;
111138
color: $off-black;
@@ -115,7 +142,6 @@ defineExpose({ button });
115142
min-height: 2em;
116143
padding: 0.25em 0.75em;
117144
}
118-
119145
&:not(.text) {
120146
width: 2.5em;
121147
height: 2.5em;
@@ -124,7 +150,6 @@ defineExpose({ button });
124150
&.primary {
125151
background: $theme-light;
126152
}
127-
128153
&.secondary {
129154
background: $light-gray;
130155
}
@@ -143,11 +168,9 @@ defineExpose({ button });
143168
&.primary {
144169
color: $theme;
145170
}
146-
147171
&.secondary {
148172
color: $off-black;
149173
}
150-
151174
&:hover,
152175
&:focus {
153176
color: $black;
@@ -166,7 +189,6 @@ defineExpose({ button });
166189
&.primary {
167190
background: $theme-light;
168191
}
169-
170192
&.secondary {
171193
background: $light-gray;
172194
}
@@ -177,13 +199,61 @@ defineExpose({ button });
177199
box-shadow: $outline;
178200
}
179201
}
202+
203+
/* link-style button*/
204+
&.link {
205+
min-height: unset;
206+
padding: 0;
207+
gap: 6px;
208+
background: transparent;
209+
color: $theme;
210+
text-decoration: none;
211+
212+
&:hover .truncate,
213+
&:focus .truncate {
214+
text-decoration: underline;
215+
text-decoration-thickness: 2px;
216+
text-underline-offset: 3px;
217+
}
218+
219+
.truncate {
220+
text-decoration: underline;
221+
text-decoration-thickness: 1px;
222+
text-underline-offset: 3px;
223+
}
224+
225+
&:focus {
226+
border-radius: 6px;
227+
outline: none;
228+
box-shadow: 0 0 0 3px rgba($theme, 0.25);
229+
}
230+
}
180231
}
181-
</style>
182232
183-
<style lang="scss">
184233
.fill {
185234
.button.circle.primary {
186235
background: $theme-mid;
187236
}
188237
}
238+
239+
.icon {
240+
margin-left: 0.5em;
241+
}
242+
243+
/* When icon is on the LEFT, remove the left margin and add a right one */
244+
.button.link .icon,
245+
.button.normal .icon,
246+
.button.small .icon,
247+
.button.tile .icon,
248+
.button.big .icon {
249+
margin-left: 0.5em;
250+
}
251+
.button.link.icon-left .icon {
252+
margin-right: 0.25em;
253+
margin-left: 0;
254+
}
255+
256+
.info-icon {
257+
margin-left: 0.5em;
258+
}
189259
</style>

frontend/src/components/AppCitation.vue

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,15 @@ type Props = {
3333
3434
const props = defineProps<Props>();
3535
36+
/** name/label */
37+
const title = computed(() => props.title || "");
38+
39+
/** link */
40+
const link = computed(() => props.link || "");
41+
42+
/** authors */
43+
const authors = computed(() => props.authors || "");
44+
3645
/** joined details as string */
3746
const detailsString = computed(() =>
3847
(props.details || []).filter((e) => e).join("&nbsp; · &nbsp;"),

frontend/src/components/AppDownloadTable.vue

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@
1717
import type { DownloadItem } from "@/data/downloads";
1818
import AppButton from "./AppButton.vue";
1919
20-
defineProps<{ items: DownloadItem[] }>();
20+
const { items } = withDefaults(defineProps<{ items?: DownloadItem[] }>(), {
21+
items: () => [],
22+
});
2123
</script>
2224

2325
<style scoped>

frontend/src/components/AppNodeBadge.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ type Props = {
6262
/** whether to show id. not shown by default, unless name/label empty. */
6363
showId?: boolean;
6464
/** boolen to use for highlighting */
65-
highlight: boolean;
65+
highlight?: boolean;
6666
};
6767
6868
const props = withDefaults(defineProps<Props>(), {

0 commit comments

Comments
 (0)