Skip to content
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

KDateComponents #384

Merged

Conversation

LianaHarris360
Copy link
Member

@LianaHarris360 LianaHarris360 commented Nov 16, 2022

Description

This PR introduces the KDateRange modal component that contains two input components and a calendar component to select a date range. For translation flexibility, all strings used within the KDateRange can be provided as properties, and months are set using the dateLocale property.

Issue addressed

Closes #360

Before/after screenshots

View if there are no pre-selected start and end dates. The date input fields will be set to the placeholder.

HasNotExportedLogs

View if there is a pre-selected start date given. The end date is set to the current day.

HasExportedLogs

Steps to test

(optional) Implementation notes

  • The dateLocale property is a locale string used to set the language of the date strings and formatting. The month names shown on the calendar are automatically updated to reflect the language provided by this property.

  • The date objects are zero-indexed. January is month zero and December is month eleven. For example, if the firstAllowedDate is set to January 1, 2022, using JavaScript's date constructor, it should be created like this:
    new Date(2022, 0, 1)

At a high level, how did you implement this?

The KDateRange uses the KModal component and also contains two KDateInput and KDateCalendar components that allow for a start and end date selection. The dates can be set by typing into the inputs or selecting a date from the calendar. The date inputs are validated with the use of a state machine that validates that the entered date is:

  • In the correct format
  • A valid date
  • After the first allowed date
  • Before the last allowed date
  • An end date that cannot be before the start date

On submission, the returned values are date objects.

Does this introduce any tech-debt items?

Testing checklist

  • Contributor has fully tested the PR manually
  • If there are any front-end changes, before/after screenshots are included
  • Critical and brittle code paths are covered by unit tests
  • The change has been added to the changelog

Reviewer guidance

  • Is the code clean and well-commented?
  • Are there tests for this change?
  • Are all UI components LTR and RTL compliant (if applicable)?

Post-merge updates and tracking

  • Learning Platform update PR is submitted
  • Learning Platform update PR is merged
  • Studio update PR is submitted
  • Studio update PR is merged
  • Data Portal update PR is submitted
  • Data Portal update PR is merged

Comments

commit f85ddfb
Merge: 29122c4 9d29fe1
Author: akolson <[email protected]>
Date:   Thu Nov 3 22:42:28 2022 +0300

    Merge pull request learningequality#380 from akolson/standardize-kradiobutton-labels

    Wraps KRadioButton label text

commit 9d29fe1
Author: Samson Akol <[email protected]>
Date:   Wed Oct 26 23:34:47 2022 +0300

    updates changelog

commit ba2006c
Author: Samson Akol <[email protected]>
Date:   Wed Oct 26 22:23:03 2022 +0300

    updates documentation

commit f083b25
Author: Samson Akol <[email protected]>
Date:   Wed Oct 26 22:04:02 2022 +0300

    aligns kradiobutton api with kcheckbox's. Changes label from truncate to wrap. Adds truncateLabel prop to truncate text if necessary. updates documenation to reflect changes

commit 29122c4
Merge: 115a19d 84ef450
Author: akolson <[email protected]>
Date:   Tue Oct 18 20:48:16 2022 +0300

    Merge pull request learningequality#377 from akolson/implement-kResponsiveWindow-composable

    Implement k responsive window composable

commit 84ef450
Author: Samson Akol <[email protected]>
Date:   Mon Oct 17 10:51:27 2022 +0300

    Adds more review feedback

commit 94abe9e
Author: Samson Akol <[email protected]>
Date:   Fri Oct 14 19:16:35 2022 +0300

    Improves test descriptions

commit 6dc1d50
Author: Samson Akol <[email protected]>
Date:   Fri Oct 14 18:18:08 2022 +0300

    Adds tests to useKWindowDimensions and useKResponsiveWindow

commit 51d80a3
Author: Samson Akol <[email protected]>
Date:   Thu Oct 13 13:25:31 2022 +0300

    minor string refactors

commit 794ac2a
Author: Samson Akol <[email protected]>
Date:   Thu Oct 13 12:55:00 2022 +0300

    Fixes responsiveness bug on IE

commit 445c740
Author: Samson Akol <[email protected]>
Date:   Wed Oct 12 23:01:34 2022 +0300

    Adds feedback from review

commit 68266f0
Author: Samson Akol <[email protected]>
Date:   Wed Oct 12 18:53:17 2022 +0300

    Improves jsdoc, fixes IE issue

commit b16c125
Author: Samson Akol <[email protected]>
Date:   Mon Oct 10 17:34:57 2022 +0300

    Adds use composition-api to composable due to @vue/composition-api shortcomings in vue2

commit 4075c57
Author: Samson Akol <[email protected]>
Date:   Mon Oct 10 13:30:48 2022 +0300

    Updates kresponsivewindow documentation

commit 5940c76
Author: Samson Akol <[email protected]>
Date:   Mon Oct 10 12:38:45 2022 +0300

    Adds feedback from code review

commit b0fc205
Author: Samson Akol <[email protected]>
Date:   Thu Oct 6 18:27:24 2022 +0300

    Updates changelog

commit e504a70
Author: Samson Akol <[email protected]>
Date:   Thu Oct 6 15:52:29 2022 +0300

    Code refactor

commit 76c13a8
Author: Samson Akol <[email protected]>
Date:   Thu Oct 6 15:12:43 2022 +0300

    Moves composition api initialization to useKResponsiveWindow

commit 9dd5975
Author: Samson Akol <[email protected]>
Date:   Wed Oct 5 17:52:57 2022 +0300

    Updates indentation

commit 078c000
Author: Samson Akol <[email protected]>
Date:   Wed Oct 5 16:34:14 2022 +0300

    uses anonymous function in documentation due to failures in character escaping

commit ad681dd
Author: Samson Akol <[email protected]>
Date:   Wed Oct 5 15:41:46 2022 +0300

    Configures composition api, and documents the useKResponsiveWindow

commit 2d1351e
Author: Samson Akol <[email protected]>
Date:   Tue Oct 4 17:45:50 2022 +0300

    Refactors code, adds documentation

commit 8fe1519
Author: Samson Akol <[email protected]>
Date:   Mon Oct 3 18:59:22 2022 +0300

    fixes bug in breakpoint media query listener

commit f6afdf6
Author: Samson Akol <[email protected]>
Date:   Mon Oct 3 18:56:47 2022 +0300

    Renames responsive window compodable to recommended composable file names

commit c4278c2
Author: Samson Akol <[email protected]>
Date:   Fri Sep 30 23:15:15 2022 +0300

    updates composable  name

commit fca8437
Author: Samson Akol <[email protected]>
Date:   Fri Sep 30 15:57:07 2022 +0300

    Fixes bugs in composable

commit e43b5a0
Author: Samson Akol <[email protected]>
Date:   Thu Sep 29 19:45:59 2022 +0300

    Port of KResponsiveWindowMixin to KResponsiveWindow composable
@LianaHarris360 LianaHarris360 marked this pull request as ready for review December 1, 2022 17:16
Copy link
Member

@bjester bjester left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left some code review comments. Overall, this looks really awesome!

An overview of my comments:

  • making sure all user facing strings will be translated
  • cross browser support and intricacies
  • ensuring HTML id's will be unique on a page

class="right"
size="mini"
@click="goNextMonth"
/>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like there are more user-facing strings in the button components above-- 'Previous Month' and 'Next Month'

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, I missed these somehow! They should also be provided as properties for translation like the rest of the user-facing strings

},
watch: {
nextActiveMonth(value) {
if (value === 0) this.activeYearEnd = this.activeYearStart + 1;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm wondering if it's possible to be consistent and use another computed property for activeYear/nextActiveYear? I may not be understanding the reasons for this.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this case it is because a computed value should not be mutated, referenced here:

a computed return value should be treated as read-only and never be mutated - instead, update the source state it depends on to trigger new computations

So when the Previous/Next Month buttons are clicked, the activeYearStart and activeYearEnd values will be updated directly and that will trigger the computed properties that depend on these values to update as well.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, you shouldn't modify data in a computed prop, but that wasn't quite what I was referring to. So why did you choose a watcher instead of adding this code to the handlers for when the Previous/Next Month buttons are clicked? I do see code in those handlers that are modifying activeYearEnd, so it isn't clear why both are needed?

With regards to this specific code-- what happens if the picker is on the month of December, and I click Next, then Previous, then Next, then Previous, and so on? Does this.activeYearEnd just keep on incrementing? I suspect the code in the button handlers is possibly covering up a potential issue?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, I misunderstood your question at first. The watcher was chosen as a convenient way of keeping tabs on when the nextActiveMonth becomes 0 (January) because that is when activeYearEnd should be updated to the next year.

When goNextMonth is pressed, the activeMonth is what is actually being updated, not activeYearEnd, so you are right, changing activeYearEnd inside of the goNextMonth method is redundant.

activeYearEnd only needs to update when nextActiveMonth becomes 0 or when goPrevMonth is pressed and activeYearStart isn't the same value as activeYearEnd, which indicates that the right half of the calendar is in a different year. So pressing goPrevMonth would put both sides of the calendar in the same year when it updates the month, this is what decrements the activeYearEnd.

I thought the watcher was appropriate because nextActiveMonth is only changed after activeMonth is updated (which happens inside goNextMonth), so the watcher allows activeYearEnd to be updated only when the computed property nextActiveMonth is a certain number.

But, based on what you're suggesting, I should be able to make activeYearEnd and activeYearStart computed properties where the original values are set using activeMonth, so they should both update if activeMonth is updated and I can still check the value of nextActiveMonth when updating activeYearEnd. I can implement it this way and update you if I have any issues!

if (!this.isFirstChoice && resultDate < this.dateRange.start) {
this.isFirstChoice = false;
return { start: resultDate };
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this if block returns early, and doesn't depend on the code above (that I can see), would be good to move it to the beginning of the function.

if (!this.isFirstChoice) {
key = 'end';
} else {
newData['end'] = null;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

newData.end is okay here

lastDay = new Date(this.activeYearStart, this.activeMonth + 1, 0).getDate();
} else {
lastDay = new Date(this.activeYearEnd, this.nextActiveMonth + 1, 0).getDate();
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can be a ternary statement:

const lastDay = (key === 'first')
  ? new Date(this.activeYearStart, this.activeMonth + 1, 0).getDate()
  : new Date(this.activeYearEnd, this.nextActiveMonth + 1, 0).getDate();

this.activeYearEnd = nextMonth.getFullYear();
},
isValidDate(date) {
return new Date(date) !== 'Invalid Date' && !isNaN(new Date(date));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is 'Invalid Date' browser specific? Also, what if they have their browser in a different language?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a good point, maybe the !isNaN(new Date(date)) should be enough to validate the date argument.

}

.calendar-days li.calendar-days--disabled {
color: var(--disabled-button-color);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we use CSS vars elsewhere in KDS? Seems there isn't support for them in IE 11

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we use them. That said, working with the theme tokens, plus active and disabled styles, can be a bit convoluted with our theming system and isn't always intuitive.

The KRadioButton code is an example of using a combination of computed styles and :style within the component to access the token values. @LianaHarris360 let me know if you have questions after reviewing this, or want to work together on how you could use this pattern in your component.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to affirm here that we can't use CSS vars in KDS, because of the lack of IE11 support. We've investigated a ponyfill, but not implemented it yet, due to the technical complexity (and because it is not a complete polyfill it comes with a bunch of caveats).

In general, using computed :style attributes is preferable, as it is a little simpler to reason about, but (especially for pseudo-selectors, which cannot be applied via style attributes) the $computedClass helper function lets you generate arbitrary CSS in JS: https://design-system.learningequality.org/colors/#usage

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you @marcellamaki and @rtibbles!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That definitely makes sense, the browser support is an important factor that I missed! Thank you for the references, I'm going to reimplement the styling with these suggestions and will reach out if I need more support on this.

/>
<input type="hidden" name="date" :value="valueAsDate" data-test="valueAsDate">
<span class="k-date-vhidden">
<span v-if="valueAsDate" id="date-desc">
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

id="date-desc" 👀 -- What happens if there are multiple KDateInput components on the same page?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, I remember now that we did talk about a solution that generates unique ids. I will go back and add that.

isNaN(this.valueAsDate)
) {
return ' ';
} else {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since the prior if block returns, you can safely extract this from the else, like you did with valueAsDate. Makes the code a little cleaner

return isAfter(newDate, lastAllowedDate);
};

/** Returns if the given date string is before the first allowed date */
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For these functions above, any reason not to also document the arguments, like you've done for validate?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no reason not to. I can see how documenting the arguments makes things clearer, so I'll update these functions.

@LianaHarris360
Copy link
Member Author

Left some code review comments. Overall, this looks really awesome!

An overview of my comments:

  • making sure all user facing strings will be translated
  • cross browser support and intricacies
  • ensuring HTML id's will be unique on a page

Thank you for the review, I will work on clarifying the code and making updates with these suggestions!

Copy link
Member

@marcellamaki marcellamaki left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @LianaHarris360 - I've added just a couple of comments here to follow up on some of what @bjester said regarding css variables. Let me know if you have any questions!

}

.calendar-days li.calendar-days--disabled {
color: var(--disabled-button-color);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we use them. That said, working with the theme tokens, plus active and disabled styles, can be a bit convoluted with our theming system and isn't always intuitive.

The KRadioButton code is an example of using a combination of computed styles and :style within the component to access the token values. @LianaHarris360 let me know if you have questions after reviewing this, or want to work together on how you could use this pattern in your component.

'--selected-button-text-color': this.$themePalette.white,
'--in-range-button-bg-color': this.$themeBrand.secondary.v_50,
'--in-range-button-text-color': this.$themePalette.grey.v_700,
'--in-range-button-hover-color': this.$themePalette.grey.v_200,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar to above for here. I actually think it's okay to use a computed prop to come up with your button styles, but I think using :style or :class within the component itself would be more consistent with our current patterns (and, as @bjester mentioned, prevent potential problems with IE11). Again, happy to help brainstorm how to do this -- I know there are a lot of different conditions that are being managed with the button and date range logic!

<template>

<div>
<fieldset :aria-labelledby="legendText" class="date-input-fieldset" aria-describedby="date-desc" aria-live="polite">
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Love to see accessibility in the code from the very first PRs! Awesome!! 🎉

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Taking another look here, I believe aria-labelledby is supposed to accept element ID list. Is this passing a text string?

The aria-labelledby property takes as value a space-separated id reference list

Copy link
Member Author

@LianaHarris360 LianaHarris360 Dec 7, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes this is correct, in this case, the aria-labelledby should be aria-label or should maybe point to the KTextBox element. There was previously a legend and the aria-labelledby was set to that, but when testing with the chrome extension screen reader, the label would be announced twice so I removed the legend and did not update aria-labelledby accordingly. Thank you for pointing this out!

@LianaHarris360
Copy link
Member Author

Hi @LianaHarris360 - I've added just a couple of comments here to follow up on some of what @bjester said regarding css variables. Let me know if you have any questions!

@marcellamaki - I will let you know if I have any questions for sure. I am currently looking into those potential solutions, thank you!

Copy link
Member

@marcellamaki marcellamaki left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks great, @LianaHarris360 - thank you so much for your hard work on this! I think all of the PR feedback has been addressed now, and I feel good about merging this into KDS develop and then you moving forward with your work using it within Kolibri. There's always a chance an edge case or bug might pop up, but that's completely fine. I think we're most likely to surface that as you and the rest of the team begin to use it, and we can continue to build on the component as needed.

🎉 Amazing, amazing work! 🎉

@marcellamaki marcellamaki merged commit d4226b5 into learningequality:develop Jan 5, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Create KDatePicker and KDateRange components
4 participants