-
Notifications
You must be signed in to change notification settings - Fork 59
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add hover cards #1067
Add hover cards #1067
Conversation
346ee43
to
b84a874
Compare
b84a874
to
898aa14
Compare
I want to write a few more tests, especially of |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Explanatory comments to help with async review
'LIST_RENDERING', | ||
'CONDITIONALS', | ||
['CONDITIONALS', 'DEFINITION'], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is because I want to be able to specify v-if
before :is
. I feel like v-if
should generally be the very first thing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree.
@@ -75,7 +74,7 @@ dl:not(.dl-horizontal) { | |||
// down to the fact that .dl-horizontal uses a CSS grid, while other <dl> | |||
// elements use flexbox. | |||
.dl-horizontal { | |||
$padding-between: 10px; | |||
$padding-between: 12px; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will have a minor effect on EntityUploadHeaderErrors
, but I think that's OK.
const to = computed(() => datasetPath(props.projectId, props.name)); | ||
|
||
const link = ref(null); | ||
useHoverCard(computed(() => link.value?.$el), 'dataset', () => props); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need to pass the HTMLElement
of the <router-link>
to useHoverCard()
. It looks like that's accessible via $el
, though I don't see that documented in the Vue Router docs. Apparently $el
is generally accessible from components that use the Options API. Components that use the Composition API can opt into providing access to the root element by using defineExpose()
.
@@ -14,9 +14,8 @@ except according to the terms contained in the LICENSE file. | |||
<div class="row"> | |||
<div class="col-xs-6 dataset-name-wrap"> | |||
<div class="dataset-name text-overflow-ellipsis" v-tooltip.text> | |||
<router-link v-if="!dataset.isNew" :to="datasetPath(projectId, dataset.name)" v-tooltip.text> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The parent element specifies v-tooltip.text
, so I don't think this one needs to.
$border: 1px solid #fff; | ||
$border-radius: 12px; | ||
|
||
.popover:has(.hover-card) { border-radius: $border-radius; } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I had to fight to make the border radius visible on the hover card and spent a bit of time trying to get that to work. Adding border-radius
to .popover
seemed to be needed.
@@ -33,7 +33,7 @@ implementation of the Bootstrap plugins. We could also consider implementing our | |||
own popover functionality, perhaps using Popper. | |||
--> | |||
<template> | |||
<div class="popover-content-source"> | |||
<div class="popover-content-source" aria-hidden="true"> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because we are now displaying this off-screen rather than hiding it with display: none
, we tell screen readers to ignore it.
@@ -84,7 +83,7 @@ export default { | |||
const { tabPath, tabClass } = useTabs(datasetPath()); | |||
return { | |||
project, dataset, ...resourceStates([project, dataset]), | |||
projectPath, datasetPath, tabPath, tabClass, canRoute |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I searched for all uses of datasetPath()
to see where to add DatasetLink
. I noticed that this component doesn't use datasetPath()
in the template, so it doesn't need to return it from setup()
.
</template> | ||
<template v-else-if="canLinkToSubmissions"> | ||
<router-link :to="submissionsPath.all">{{ form.nameOrId }}</router-link> | ||
<form-link :form="form" :to="submissionsPath.all"/> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
canRoute()
and LinkIfCan
don't always work that well on the homepage, which is why <router-link>
+ v-if
was used here. For example, form pages will check the form
resource, but the form
resource isn't set on the homepage, so it will never fail validateData
when checked from the homepage.
Here, the default value of the to
prop is primaryFormPath()
. However, given the behavior of canRoute()
on the homepage, primaryFormPath()
won't ever return a path to the submissions page. That's why we specify submissionsPath.all
explicitly.
src/composables/event-listener.js
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've wanted to make this change on other occasions as well. There are times when it's convenient to pass a template ref to useEventListener()
.
? meta.instanceName | ||
: submission.__id; | ||
}) | ||
if (meta == null || typeof meta !== 'object') return null; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought I might as well check that meta
is an object as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am still reviewing this PR, wanted to share my observation up till now:
1- Hover card should be 3-4px above the viewport: "If there isn’t room onscreen (without needing to scroll) to fit the height of the card, I should see the bottom of the card 3-4px above the bottom of the browser viewport" getodk/central#797
2- What do you think of caching the response of hover requests until user navigates to another page/route? right now every time I hover on a link a request is sent.
3- If I create a new Submission in a separate tab and then hover over Form link on the project overview page, it shows the correct number of Submission count but count is not updated on the FormList table.
None of the above are super critical to get this PR merged.
timeoutId = null; | ||
} | ||
}; | ||
useEventListener(anchorRef, 'mouseleave', () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice to have: I wish if hover cards don't hide on mouse leaving the anchor link and entering into the hover card.
One way to achieve that is to append popover
inside the anchor instead of 'body'
(src/components/popover.vue:87
).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like this idea. If the user could enter the hover card, then we could show tooltips over truncated text. That said, the release criteria says the opposite:
I should not be able to hover on or interact with the infocard.
How about we discuss more in the meeting tomorrow?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We decided that we wouldn't make this change for v2024.3, but I'm going to file a follow-up issue about this.
I actually think this is sort of a nice thing. It's a signal that the page you're viewing is out of date. We could patch the one form, but it might not be the only stale data on the page. For what it's worth, I've noticed that GitHub does something similar with their hover cards. For example:
I like this idea! I don't think I'll have time to do it before release, but I've filed an issue for it and slotted it for the next release: #1093. Since I mentioned them already, I'll say that I also noticed that GitHub does cache their hover cards. For example:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is amazing 🎉
Liked the approach of calculating and positioning hover-card, it's not easy.
Test coverage is also good 👍
const idParts = computed(() => { | ||
const id = submission.__id; | ||
const match = id.match(/^((?:uuid:)?[0-9a-f]{8})[0-9a-f-]+([0-9a-f]{8})$/); | ||
return match != null ? [match[1], '…', match[2]] : [id]; | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some unit tests for this would be helpful. We can add them later
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When you say unit tests, are you thinking that we should move this logic into a utility function somewhere? I'd be happy to do that.
There are some tests of this behavior in test/components/hover-card/submission.spec.js. There are a couple of cases that I didn't test:
- The case where the instance ID doesn't start with
uuid:
, in which case it should still be truncated. - The case where the instance ID is not a UUID, in which case
idParts.value
falls back to[id]
.
If I added tests of those cases in test/components/hover-card/submission.spec.js, would that be enough? Or should I create a utility function and test that separately? I'd be happy with either approach.
Thinking of the future, I bet that there will be other cases in which we want to truncate instance IDs in the middle like this. When that day comes, I'll probably move some of this into its own small component.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I find regex harder to read, seeing unit (or other) tests assure me that it is doing what it suppose to do + tests are kind of requirement documentation as well.
|
||
const width = ref(''); | ||
const el = ref(null); | ||
const resize = () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd like this function to be cross-browser tested
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds good. 👍 I think it'd be difficult to explain some of this logic to the QA team, but I could test it out locally. I usually work in Chrome, but it'd be easy for me to test it in Firefox. I can leave a comment about the scenarios I try out: there are a few specific ones that I have in mind.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think QA team uses Firefox, they will report if they see something strange. And I try in Safari
I still need to finish these tests. I'll file a follow-up issue about that. |
This PR closes getodk/central#670.
What has been done to verify that this works as intended?
I tried things out locally. I've deployed this branch to the QA server, so you can check out the hover cards there.
I've also updated existing tests and written new tests.
Why is this the best possible solution? Were any other approaches considered?
There are a few moving parts:
hoverCard
object (accessible fromcontainer
) manages the reactive state of the hover card. It's similar in that way to thealert
object, which manages the reactive state of the alert. (TheAlert
component is what actually uses that state to render the alert.)useHoverCard()
composable sets up event listeners onmouseenter
andmouseleave
that will modify thehoverCard
object. It's also responsible for waiting the 350ms to show the hover card.hoverCard
object is modified, theHoverCards
component will react to it.HoverCards
sends one or more requests for therequestData
needed to render the hover card. Once the responses are received, it renders the correct hover card in a popover.requestData
resources thatHoverCards
uses to send requests are created inuseHoverCardResources()
. The resources transform the response data. The resources don't need to be reactive, because once a hover card is shown, it won't change. TheuseHoverCardResources()
composable makes it easier to test various hover card components, since it can be passed totestRequestData()
.HoverCardForm
,HoverCardSubmission
,HoverCardDataset
,HoverCardEntity
. They all use theHoverCard
component, which lays out the structure and styles of the hover card.useHoverCard()
are new*Link
components. There is one for each resource:FormLink
,SubmissionLink
,DatasetLink
,EntityLink
. These links are the elements that trigger the hover card to be shown.I think all these pieces are necessary. I also like that the
hoverCard
object has parallels to thealert
object, as well asmodalData()
.One goal I had was to keep
useHoverCard()
fairly flexible and not tightly couple it to the*Link
components. I think we're going to use hover cards in other ways in the future (e.g., I think with infonav buttons: getodk/central#827).How does this change affect users? Describe intentional changes to behavior and behavior that could have accidentally been affected by code changes. In other words, what are the regression risks?
This PR updates a lot existing links to use the new
*Link
components. I think regression testing will be useful as a check that each of those links is still working as expected. In many cases, there's also good test coverage of the links.The thing that I feel the most unsure about is whether the hover card is always hidden when it should be. I think so, but that would be a good area for the QA team to explore. (I've noted that in a comment on the release criteria.)
Once we add infonav buttons, we will probably need to hide hover cards in more cases: see getodk/central#800. However, I think we can look into that in the future.
Before submitting this PR, please make sure you have:
npm run test
andnpm run lint
and confirmed all checks still pass OR confirm CircleCI build passes