From 09f0a0a1e92bbe37d51ad4c2d7f360ca4804fe66 Mon Sep 17 00:00:00 2001 From: Lauren Hitchon Date: Wed, 28 Feb 2024 16:45:57 +1100 Subject: [PATCH] feature/date-picker (#385) * Create base files * Add base guidance content * Create date picker base functionality * Add multiple input setDate functionality * Add accessibility features. Cancel and accept buttons and keyboard navigation styles * Add disable dates functionality * Add min date and max date functionality * Remove comments and refactor disabled dates functionality * Add autocomplete attributes * Add theming and inverted examples * Add card carousel theming and inverted examples * Add links from date input to date picker * Add date picker and input inverted styles --- src/components/_all.scss | 1 + src/components/card-carousel/_carousel.scss | 45 +- src/components/card-carousel/blank.hbs | 11 + src/components/card-carousel/index.hbs | 1 + src/components/card-carousel/theme.hbs | 33 + src/components/date-input/_date-input.hbs | 6 +- src/components/date-input/_guidance.hbs | 4 + src/components/date-input/blank.hbs | 41 +- src/components/date-input/index.hbs | 4 +- src/components/date-input/theme.hbs | 81 +++ src/components/date-picker/_date-picker.hbs | 138 +++++ src/components/date-picker/_date-picker.scss | 324 ++++++++++ src/components/date-picker/_guidance.hbs | 226 +++++++ src/components/date-picker/blank.hbs | 37 ++ src/components/date-picker/date-picker.js | 578 ++++++++++++++++++ src/components/date-picker/index.hbs | 30 + .../date-picker/json/date-picker.json | 1 + src/components/date-picker/theme.hbs | 39 ++ src/components/form/_form.scss | 68 ++- src/components/form/blank.hbs | 126 ++-- src/components/form/theme.hbs | 124 ++-- src/docs/content/design/theming.hbs | 5 +- src/global/scss/helpers/_visibility.scss | 2 - src/main.js | 10 +- src/main.scss | 1 + 25 files changed, 1772 insertions(+), 164 deletions(-) create mode 100644 src/components/card-carousel/theme.hbs create mode 100644 src/components/date-input/theme.hbs create mode 100644 src/components/date-picker/_date-picker.hbs create mode 100644 src/components/date-picker/_date-picker.scss create mode 100644 src/components/date-picker/_guidance.hbs create mode 100644 src/components/date-picker/blank.hbs create mode 100644 src/components/date-picker/date-picker.js create mode 100644 src/components/date-picker/index.hbs create mode 100644 src/components/date-picker/json/date-picker.json create mode 100644 src/components/date-picker/theme.hbs diff --git a/src/components/_all.scss b/src/components/_all.scss index dd9d58087..6c3f362fd 100644 --- a/src/components/_all.scss +++ b/src/components/_all.scss @@ -7,6 +7,7 @@ @import 'card-carousel/carousel'; @import 'content-block/content-block'; @import 'date-input/date-input'; +@import 'date-picker/date-picker'; @import 'dialog/dialog'; @import 'file-upload/file-upload'; @import 'filters/filters'; diff --git a/src/components/card-carousel/_carousel.scss b/src/components/card-carousel/_carousel.scss index f279fc830..a55fa5402 100644 --- a/src/components/card-carousel/_carousel.scss +++ b/src/components/card-carousel/_carousel.scss @@ -1,3 +1,5 @@ +/* stylelint-disable max-nesting-depth */ + .nsw-carousel { --carousel-item-auto-size: 300px; position: relative; @@ -174,6 +176,28 @@ display: block; margin: auto; } + + .nsw-section--invert & { + border: 2px solid var(--nsw-white); + + &:hover { + @include nsw-hover-light(); + + .nsw-icon { + color: var(--nsw-white); + } + } + + &:focus { + @include nsw-focus($color: var(--nsw-focus-light)); + } + + .nsw-icon { + &:hover { + color: var(--nsw-white); + } + } + } } &__navigation { @@ -206,8 +230,8 @@ .nsw-carousel__nav-item button { background-color: transparent; - width: 1.5rem; - height: 1.5rem; + width: 1.6rem; + height: 1.6rem; color: var(--nsw-brand-dark); font-size: 12px; border-radius: var(--nsw-border-radius); @@ -218,12 +242,27 @@ @include nsw-focus(false); outline-offset: 2px; } + + .nsw-section--invert & { + background-color: var(--nsw-white); + border: 1px solid var(--nsw-white); + + &:focus { + @include nsw-focus($color: var(--nsw-focus-light)); + outline-offset: 2px; + } + } } .nsw-carousel__nav-item--selected { button { background-color: var(--nsw-brand-dark); color: var(--nsw-white); + + .nsw-section--invert & { + background-color: transparent; + border: 2px solid var(--nsw-white); + } } } } @@ -236,7 +275,7 @@ height: 1.2em; padding: 0; border-radius: 50%; - border: 1px solid var(--nsw-brand-dark); + border: 2px solid var(--nsw-brand-dark); transition: all 300ms ease-in-out; cursor: pointer; line-height: 0; diff --git a/src/components/card-carousel/blank.hbs b/src/components/card-carousel/blank.hbs index 48ad7159d..016b65874 100644 --- a/src/components/card-carousel/blank.hbs +++ b/src/components/card-carousel/blank.hbs @@ -19,3 +19,14 @@ page: true {{/_layout-container}} +{{#>_layout-container brand-dark="true" invert="true"}} +

5 cards

+{{>_carousel model.carousel-five default=true loop=true}} + +

9 cards

+{{>_carousel model.carousel-nine default=true loop=true}} + +

Pagination

+{{>_carousel model.carousel-nine pagination=true default=true}} +{{/_layout-container}} + diff --git a/src/components/card-carousel/index.hbs b/src/components/card-carousel/index.hbs index 49bbc7056..18c6f79b0 100644 --- a/src/components/card-carousel/index.hbs +++ b/src/components/card-carousel/index.hbs @@ -4,6 +4,7 @@ width: wide tabs: true directory: card-carousel intro: A card carousel displays multiple related cards horizontally, allowing users to swipe or navigate through the content. +theme: true model: carousel-five: ../../components/card-carousel/json/carousel-five.json carousel-nine: ../../components/card-carousel/json/carousel-nine.json diff --git a/src/components/card-carousel/theme.hbs b/src/components/card-carousel/theme.hbs new file mode 100644 index 000000000..99a6757a3 --- /dev/null +++ b/src/components/card-carousel/theme.hbs @@ -0,0 +1,33 @@ +--- +title: Card carousel +width: wide +model: + carousel-five: ../../components/card-carousel/json/carousel-five.json + carousel-nine: ../../components/card-carousel/json/carousel-nine.json +page: true +--- + +{{#>_theme}} +{{#>_layout-container}} +

5 cards

+{{>_carousel model.carousel-five default=true loop=true}} + +

9 cards

+{{>_carousel model.carousel-nine default=true loop=true}} + +

Pagination

+{{>_carousel model.carousel-nine pagination=true default=true}} + +{{/_layout-container}} + +{{#>_layout-container brand-dark="true" invert="true"}} +

5 cards

+{{>_carousel model.carousel-five default=true loop=true}} + +

9 cards

+{{>_carousel model.carousel-nine default=true loop=true}} + +

Pagination

+{{>_carousel model.carousel-nine pagination=true default=true}} +{{/_layout-container}} +{{/_theme}} diff --git a/src/components/date-input/_date-input.hbs b/src/components/date-input/_date-input.hbs index 96aa834bb..39719759a 100644 --- a/src/components/date-input/_date-input.hbs +++ b/src/components/date-input/_date-input.hbs @@ -6,15 +6,15 @@
- +
- +
- +
diff --git a/src/components/date-input/_guidance.hbs b/src/components/date-input/_guidance.hbs index 6a251b14f..632ebd1c3 100644 --- a/src/components/date-input/_guidance.hbs +++ b/src/components/date-input/_guidance.hbs @@ -31,6 +31,10 @@ layout: blank-layout.hbs
  • use in instances where only part of a date is needed, like a month or a year without a specific day.
  • +

    Helping users pick a date

    + +

    Users might need to pick a date from a selection, for example, to book an appointment. To do this, you can present dates in a calendar format using a date picker.

    +

    How this component works

    The three fields in the date input component are grouped together in a fieldset with a legend that describes them, as shown in the examples on this page. The legend is usually a question, such as 'What is your date of birth?'.

    diff --git a/src/components/date-input/blank.hbs b/src/components/date-input/blank.hbs index ea0e7f56e..3a60ab773 100644 --- a/src/components/date-input/blank.hbs +++ b/src/components/date-input/blank.hbs @@ -1,8 +1,10 @@ --- title: Date input -width: wide +width: narrow page: true --- + + {{#>_layout-container}}
    @@ -39,3 +41,40 @@ page: true
    {{/_layout-container}} + +{{#>_layout-container brand-dark="true" invert="true"}} + +
    +
    + {{>_date-input + id="date" + label="Date of birth" + helper-text="For example 08 12 1990" + }} +
    +
    + +
    +
    + {{>_date-input + id="date-error" + label="Date of birth" + required=true + error-text="This field is required" + helper-text="For example 08 12 1990" + }} +
    +
    + +
    +
    + {{>_date-input + id="date-success" + label="Date of birth" + valid-text="This field has been validated" + helper-text="For example 08 12 1990" + }} +
    +
    + +{{/_layout-container}} \ No newline at end of file diff --git a/src/components/date-input/index.hbs b/src/components/date-input/index.hbs index 9d2e44f26..a46e8d4c7 100644 --- a/src/components/date-input/index.hbs +++ b/src/components/date-input/index.hbs @@ -1,10 +1,10 @@ --- title: Date input -width: wide +width: narrow tabs: true directory: date-input intro: 'A date input allows users to enter a date.' -figma: 'URL' +theme: true meta-description: 'A date input allows users to enter a date.' meta-index: true --- diff --git a/src/components/date-input/theme.hbs b/src/components/date-input/theme.hbs new file mode 100644 index 000000000..06dbca029 --- /dev/null +++ b/src/components/date-input/theme.hbs @@ -0,0 +1,81 @@ +--- +title: Date input +width: narrow +page: true +--- + +{{#>_theme}} +{{#>_layout-container}} + +
    +
    + {{>_date-input + id="date" + label="Date of birth" + helper-text="For example 08 12 1990" + }} +
    +
    + +
    +
    + {{>_date-input + id="date-error" + label="Date of birth" + required=true + error-text="This field is required" + helper-text="For example 08 12 1990" + }} +
    +
    + +
    +
    + {{>_date-input + id="date-success" + label="Date of birth" + valid-text="This field has been validated" + helper-text="For example 08 12 1990" + }} +
    +
    + +{{/_layout-container}} + +{{#>_layout-container brand-dark="true" invert="true"}} + +
    +
    + {{>_date-input + id="date" + label="Date of birth" + helper-text="For example 08 12 1990" + }} +
    +
    + +
    +
    + {{>_date-input + id="date-error" + label="Date of birth" + required=true + error-text="This field is required" + helper-text="For example 08 12 1990" + }} +
    +
    + +
    +
    + {{>_date-input + id="date-success" + label="Date of birth" + valid-text="This field has been validated" + helper-text="For example 08 12 1990" + }} +
    +
    + +{{/_layout-container}} +{{/_theme}} diff --git a/src/components/date-picker/_date-picker.hbs b/src/components/date-picker/_date-picker.hbs new file mode 100644 index 000000000..64a551649 --- /dev/null +++ b/src/components/date-picker/_date-picker.hbs @@ -0,0 +1,138 @@ +{{#if single}} +
    + + + Use dd/mm/yyyy format. For example 28 10 2024 + + +
    + + + +
    + + +
    {{/if}}{{#if multiple}} +
    + + Start date + For example 28 10 2024 + +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    + +
    +
    + + +
    +{{/if}} \ No newline at end of file diff --git a/src/components/date-picker/_date-picker.scss b/src/components/date-picker/_date-picker.scss new file mode 100644 index 000000000..0574d2dff --- /dev/null +++ b/src/components/date-picker/_date-picker.scss @@ -0,0 +1,324 @@ +:root { + --date-picker-calendar-gap: 4px; + --date-picker-calendar-item-size: 2.6em; +} + +@media (min-width: 48rem) { + :root { + --date-picker-calendar-item-size: 3em; + } +} + +.nsw-date-input { + position: relative; + + &__button { + display: flex; + + button { + height: rem(48px); + align-self: flex-end; + } + } + + &__wrapper { + position: relative; + + button { + .nsw-material-icons { + font-size: 1.25rem; + } + } + } +} + +.nsw-date-picker { + display: inline-block; + position: absolute; + left: 0; + top: calc(var(--date-picker-calendar-gap) + 100%); + background-color: var(--nsw-white); + border-radius: var(--nsw-border-radius); + box-shadow: var(--nsw-box-shadow); + padding: 0.375rem; + z-index: 5; + user-select: none; + overflow: hidden; + visibility: hidden; + opacity: 0; + transition: visibility 0s 0.2s, opacity 0.2s; + + @include breakpoint('lg') { + padding: 0.5625rem; + } + + * { + margin: 0; + padding: 0; + border: 0; + } + + ol, + ul { + list-style: none; + } + + .nsw-section--invert & { + .nsw-icon-button { + color: rgba(var(--nsw-palette-grey-01-rgb), 0.6); + + &:hover { + @include nsw-hover; + outline-width: 0; + } + + &:focus { + @include nsw-focus(); + } + } + + .nsw-button--dark-outline-solid { + background-color: var(--nsw-white); + border-color: var(--nsw-brand-dark); + color: var(--nsw-brand-dark); + + &:focus { + @include nsw-focus(); + } + + &:hover { + background-color: var(--nsw-brand-dark); + border-color: transparent; + color: var(--nsw-text-light); + } + } + + .nsw-button--dark { + background-color: var(--nsw-brand-dark); + color: var(--nsw-text-light); + + &:focus { + @include nsw-focus(); + } + + &:hover { + color: var(--nsw-text-light); + background-color: var(--nsw-brand-dark); + background-image: linear-gradient(rgba(var(--nsw-white-rgb), 0.15), rgba(var(--nsw-white-rgb), 0.15)); + border-color: transparent; + } + + &:active { + background-color: var(--nsw-brand-dark); + background-image: linear-gradient(rgba(var(--nsw-white-rgb), 0.075), rgba(var(--nsw-white-rgb), 0.075)); + border-color: transparent; + } + } + } + + &--is-visible { + visibility: visible; + opacity: 1; + transition: opacity 0.2s; + } + + &__title { + position: relative; + + &-label { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + display: flex; + justify-content: center; + align-items: center; + font-weight: 600; + color: var(--nsw-text-dark); + } + + &-nav { + display: flex; + flex-wrap: wrap; + position: relative; + z-index: 1; + justify-content: space-between; + + li { + display: flex; + } + + &-btn { + width: var(--date-picker-calendar-item-size); + height: var(--date-picker-calendar-item-size); + border-radius: var(--nsw-border-radius); + color: rgba(var(--nsw-palette-grey-01-rgb), 0.6); + transition: transform 0.2s; + + &:hover { + background-color: rgba(var(--nsw-palette-grey-01-rgb), 0.075); + color: var(--nsw-text-dark); + } + } + } + } + + &__week, + &__dates { + display: flex; + flex-wrap: wrap; + + li { + width: var(--date-picker-calendar-item-size); + height: var(--date-picker-calendar-item-size); + } + } + + &__day { + width: 100%; + height: 100%; + display: flex; + justify-content: center; + align-items: center; + font-size: 0.833rem; + color: rgba(var(--nsw-palette-grey-01-rgb), 0.6); + } + + &__dates { + width: calc(var(--date-picker-calendar-item-size) * 7); + } + + &__date { + background-color: transparent; + padding: 0; + border: 0; + border-radius: 0; + color: var(--nsw-text-dark); + line-height: inherit; + appearance: none; + position: relative; + z-index: 1; + width: 100%; + height: 100%; + text-align: center; + font-size: 1rem; + + &:focus { + @include nsw-focus(false); + } + + &:focus, + &:hover { + border-radius: var(--nsw-border-radius); + } + + &:hover { + box-shadow: inset 0 0 0 2px var(--nsw-focus); + } + + &:focus:not(:hover) { + box-shadow: 0 0 0 2px rgba(var(--nsw-palette-blue-01-rgb), 0.2), 0 2px 4px rgba(var(--nsw-palette-blue-01-rgb), 0.3); + } + + &--today { + color: var(--nsw-brand-dark); + border-radius: var(--nsw-border-radius); + + @include nsw-hover(); + + &::after { + content: ''; + background-color: var(--nsw-brand-dark); + border-radius: 4px; + bottom: 6px; + height: 4px; + left: 50%; + margin-left: -2px; + position: absolute; + width: 4px; + } + } + + &--keyboard-focus { + background-color: rgba(var(--nsw-palette-grey-01-rgb), 0.2); + border-radius: var(--nsw-border-radius); + } + + &::-moz-focus-inner { + border: 0; + } + + &--selected { + border-radius: var(--nsw-border-radius); + background-color: var(--nsw-brand-dark); + box-shadow: 0 2px 4px rgba(var(--nsw-palette-blue-01-rgb), 0.3); + color: var(--nsw-white); + z-index: 2; + } + + &[disabled='true'], + &[aria-disabled='true'] { + background-color: rgba(var(--nsw-palette-grey-03-rgb), 0.5); + color: rgba(var(--nsw-palette-grey-01-rgb), 0.7); + border-radius: var(--nsw-border-radius); + pointer-events: none; + } + + &.nsw-date-picker__date--range { + background-color: rgba(var(--nsw-palette-blue-01-rgb), 0.2); + color: var(--nsw-text-dark); + + &:focus, + &:hover { + border-radius: 0; + } + + &:focus { + background-color: var(--nsw-focus); + } + + &-start, + &-end { + background-color: var(--nsw-brand-dark); + box-shadow: 0 2px 4px rgba(var(--nsw-palette-blue-01-rgb), 0.3); + color: var(--nsw-white); + z-index: 2; + + &:focus:not(:hover) { + box-shadow: 0 0 0 2px rgba(var(--nsw-palette-blue-01-rgb), 0.2), 0 2px 4px rgba(var(--nsw-palette-blue-01-rgb), 0.3); + } + } + + &-start { + border-top-right-radius: 0; + border-bottom-right-radius: 0; + } + + &-end { + border-top-left-radius: 0; + border-bottom-left-radius: 0; + } + } + } + + &__buttongroup { + display: flex; + justify-content: space-between; + align-items: center; + margin: 0.375rem 0; + gap: 0.5rem; + + @include breakpoint('lg') { + margin: 0.5625rem 0; + } + + button { + height: var(--date-picker-calendar-item-size); + line-height: 1; + padding: 0; + flex-basis: 100%; + flex: 1; + } + } +} diff --git a/src/components/date-picker/_guidance.hbs b/src/components/date-picker/_guidance.hbs new file mode 100644 index 000000000..d13cfc335 --- /dev/null +++ b/src/components/date-picker/_guidance.hbs @@ -0,0 +1,226 @@ +--- +title: Date picker +layout: blank-layout.hbs +--- + +

    Usage

    + +

    A date picker simplifies the process of entering dates by providing a visual calendar interface helping users to understand a date’s relationship to other days, such as the day of the week or how far away a date is from today.

    + +

    The date picker is a progressive enhancement to text inputs that lets users choose a date from a calendar interface or enter the date as text. To help with accessibility, it can be used with a keyboard, as well as mouse or touchscreen.

    + +

    Do:

    + + +

    When to use

    +

    Use a date picker:

    + + +

    When to avoid

    +

    Don't use a date picker:

    + + +

    How this component works

    + +

    The date picker component relies on JavaScript so should be treated as an enhancement. Users should always be able to enter the date into a text field as well as use the control.

    + +

    Users select dates from a visual representation of the month and can skip through months and years. This allows them to easily see what day of the week and week of the month a particular date is in, which is particularly useful for tasks like appointment booking.

    + +

    The Date Picker component comes with the following customizations:

    + +

    Month labels

    + +

    By default, the calendar widget shows the full English name of the months; if you wish to change this default (e.g., passing a short version of the label or using a different language), add a data-months attribute to the .nsw-date-input element with the comma-separated list of the labels you want to use:

    + +{{#>_docs-code open=true}} +
    + +
    +{{/_docs-code}} + +

    Date format

    + +

    By default, the date format of the text input field is 'dd/mm/yyyy'; if you want to change this order (e.g., yyyy/mm/dd), add a data-date-format attribute to the .nsw-date-input element with the new order you want to use:

    + +{{#>_docs-code open=true}} +
    + +
    ​​ +{{/_docs-code}} + +

    Date separator

    + +

    By default, the slash ('/') is used as date separator; if you want to use a different character (e.g., '-'), add a data-date-separator attribute to the .nsw-date-input element:

    + +{{#>_docs-code open=true}} +
    + +
    +{{/_docs-code}} + +

    Preselected date

    + +

    If you want to prefill your Date Picker component with a date, use the .js-date-input__text input element and set its value equal to the date you want to use. Make sure to use the date format you specify in data-date-format or the default, d-m-y.

    + +

    Date ranges and disabled dates

    + +

    Allowed date ranges for a date picker can be set by specifying earliest and latest allowed dates. Individual dates can also be disabled.

    + +

    If you want to specify an earliest possible date for the calendar, add a data-min-date attribute to the .nsw-date-input element:

    + +{{#>_docs-code open=true}} +
    + +
    +{{/_docs-code}} + +

    If you want to specify a latest possible date for the calendar, add a data-max-date attribute to the .nsw-date-input element:

    +

    + +{{#>_docs-code open=true}} +
    + +
    +{{/_docs-code}} + +

    Use an attribute of data-dates-disabled to specify a list of dates that the user will not be able to select.

    + +

    The value of this attribute should be a space-separated list of dates in the format you specify in data-date-format or the default, d-m-y.

    + +{{#>_docs-code open=true}} +
    + +
    +{{/_docs-code}} + +

    Accessibility

    +

    All components are responsive and meet WCAG 2.1 AA accessibility guidelines.

    + +

    The dialog contains a calendar that uses the grid pattern to present buttons that enable the user to choose a day from the calendar. Choosing a date from the calendar closes the dialog and populates the date input field. When the dialog is opened, if the input field is empty, or does not contain a valid date, then the current date is focused in the calendar. Otherwise, the focus is placed on the day in the calendar that matches the value of the date input field.

    + +

    Keyboard support

    + +

    Users can navigate the calendar by using the cursor keys to move around the calendar, and can use the enter key or spacebar to select a date.

    + +

    The following table lists the keyboard commands that the date picker supports.

    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    ElementKeyAction
    Calendar buttonSpace,
    + Enter +
    Opens the date picker. If there is a current date set in the text input, that date is focussed in the date picker. If not, today's date is focussed.
    Date pickerTabMoves focus to the next element in the tab order. If tabbing away from the last focusable element in the tab order, moves focus to the first focusable element in the date picker.
    Date pickerShift + TabMoves focus to the previous element in the tab order. If tabbing away from the first focusable element in the tab order, moves focus to the last focusable element in the date picker.
    Month and year buttonsSpace,
    + Enter +
    Change the current month or year displayed in the date picker.
    DatesSpace,
    + Enter +
    Selects the focussed date, closes the date picker and moves focus back to the calendar button. Updates the accessible name of the calendar button to indicate the selected date.
    DatesUpMoves focus to the same day of the previous week, changing the displayed month if necessary.
    DatesDownMoves focus to the same day of the next week, changing the displayed month if necessary.
    DatesLeftMoves focus to the previous day, changing the displayed month if necessary.
    DatesRightMoves focus to the next day, changing the displayed month if necessary.
    DatesHomeMoves focus to the first day of the current week.
    DatesEndMoves focus to the last day of the current week.
    DatesPage UpShows the previous month and focuses on the same day of the month.
    DatesShift + Page UpShows same month in the previous year and focuses on the same day of the month.
    DatesPage DownShows the next month and focuses on the same day of the month.
    DatesShift + Page DownShows same month in the next year and focuses on the same day of the month.
    Cancel buttonSpace,
    + Enter +
    Closes the date picker and makes no change to the date in the text input. Focus is returned to the calendar button.
    OK buttonSpace,
    + Enter +
    Closes the date picker and updates the date in the text input with the chosen date in the date picker. Focus is returned to the calendar button.
    +
    \ No newline at end of file diff --git a/src/components/date-picker/blank.hbs b/src/components/date-picker/blank.hbs new file mode 100644 index 000000000..862fac7a9 --- /dev/null +++ b/src/components/date-picker/blank.hbs @@ -0,0 +1,37 @@ +--- +title: Date picker +width: narrow +page: true +--- + +{{#>_layout-container}} + +{{>_date-picker single=true id="1"}} + +
    +
    + +{{>_date-picker single=true disabled=true id="2"}} + +
    +
    + +{{>_date-picker multiple=true id="3"}} + +{{/_layout-container}} + +{{#>_layout-container brand-dark="true" invert="true"}} + +{{>_date-picker single=true id="1"}} + +
    +
    + +{{>_date-picker single=true disabled=true id="2"}} + +
    +
    + +{{>_date-picker multiple=true id="3"}} + +{{/_layout-container}} diff --git a/src/components/date-picker/date-picker.js b/src/components/date-picker/date-picker.js new file mode 100644 index 000000000..d0b4698cd --- /dev/null +++ b/src/components/date-picker/date-picker.js @@ -0,0 +1,578 @@ +/* eslint-disable max-len */ +class DatePicker { + constructor(element) { + this.element = element + this.prefix = 'nsw-' + this.class = 'date-picker' + this.dateClass = `${this.prefix}${this.class}__date` + this.todayClass = `${this.dateClass}--today` + this.selectedClass = `${this.dateClass}--selected` + this.keyboardFocusClass = `${this.dateClass}--keyboard-focus` + this.visibleClass = `${this.prefix}${this.class}--is-visible` + this.months = this.element.getAttribute('data-months') ? this.element.getAttribute('data-months') : ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'] + this.dateFormat = this.element.getAttribute('data-date-format') ? this.element.getAttribute('data-date-format') : 'd-m-y' + this.dateSeparator = this.element.getAttribute('data-date-separator') ? this.element.getAttribute('data-date-separator') : '/' + this.datesDisabled = this.element.getAttribute('data-dates-disabled') ? this.element.getAttribute('data-dates-disabled') : '' + this.minDate = this.element.getAttribute('data-min-date') ? this.element.getAttribute('data-min-date') : '' + this.maxDate = this.element.getAttribute('data-max-date') ? this.element.getAttribute('data-max-date') : '' + this.input = this.element.querySelector('.js-date-input__text') + this.trigger = this.element.querySelector('.js-date-input__trigger') + this.triggerLabel = this.trigger.getAttribute('aria-label') + this.datePicker = this.element.querySelector('.js-date-picker') + this.body = this.datePicker.querySelector('.js-date-picker__dates') + this.navigation = this.datePicker.querySelector('.js-date-picker__title-nav') + this.heading = this.datePicker.querySelector('.js-date-picker__title-label') + this.close = this.datePicker.querySelector('.js-date-picker__close') + this.accept = this.datePicker.querySelector('.js-date-picker__accept') + this.multipleInput = this.element.querySelector('.js-date-input-multiple') + this.dateInput = this.multipleInput && this.multipleInput.querySelector('.js-date-picker-date') + this.monthInput = this.multipleInput && this.multipleInput.querySelector('.js-date-picker-month') + this.yearInput = this.multipleInput && this.multipleInput.querySelector('.js-date-picker-year') + this.multiDateArray = [this.dateInput, this.monthInput, this.yearInput] + this.dateIndexes = this.getDateIndexes() + this.pickerVisible = false + this.dateSelected = false + this.selectedDay = false + this.selectedMonth = false + this.selectedYear = false + this.firstFocusable = false + this.lastFocusable = false + this.disabledArray = false + } + + init() { + this.disabledDates() + this.resetCalendar() + this.initCalendarAria() + this.initCalendarEvents() + this.placeCalendar() + } + + initCalendarAria() { + this.resetLabelCalendarTrigger() + + const srLiveReagion = document.createElement('div') + srLiveReagion.setAttribute('aria-live', 'polite') + srLiveReagion.classList.add('sr-only', 'js-date-input__sr-live') + this.element.appendChild(srLiveReagion) + this.srLiveReagion = this.element.querySelector('.js-date-input__sr-live') + } + + initCalendarEvents() { + if (this.input) { + this.input.addEventListener('focus', () => { + this.toggleCalendar(true) + }) + } + + if (this.multipleInput) { + this.multiDateArray.forEach((element) => { + element.addEventListener('focus', () => { + this.hideCalendar() + }) + }) + } + + if (this.trigger) { + this.trigger.addEventListener('click', (event) => { + event.preventDefault() + this.pickerVisible = false + this.toggleCalendar() + this.trigger.setAttribute('aria-expanded', 'true') + }) + } + + if (this.close) { + this.close.addEventListener('click', (event) => { + event.preventDefault() + this.hideCalendar() + }) + } + + if (this.accept) { + this.accept.addEventListener('click', (event) => { + event.preventDefault() + const day = this.body.querySelector('button[tabindex="0"]') + if (day) { + this.dateSelected = true + this.selectedDay = day.innerText + this.selectedMonth = this.currentMonth + this.selectedYear = this.currentYear + this.setInputValue() + if (this.input) { + this.input.focus() + } else if (this.multipleInput) { + this.trigger.focus() + this.hideCalendar() + } + + this.resetLabelCalendarTrigger() + } + }) + } + + this.body.addEventListener('click', (event) => { + event.preventDefault() + const day = event.target.closest('button') + if (day) { + this.dateSelected = true + this.selectedDay = day.innerText + this.selectedMonth = this.currentMonth + this.selectedYear = this.currentYear + this.setInputValue() + if (this.input) { + this.input.focus() + } else if (this.multipleInput) { + this.trigger.focus() + this.hideCalendar() + } + + this.resetLabelCalendarTrigger() + } + }) + + this.navigation.addEventListener('click', (event) => { + event.preventDefault() + const monthBtn = event.target.closest('.js-date-picker__month-nav-btn') + const yearBtn = event.target.closest('.js-date-picker__year-nav-btn') + + if (monthBtn && monthBtn.classList.contains('js-date-picker__month-nav-btn--prev')) { + this.showPrevMonth(true) + } else if (monthBtn && monthBtn.classList.contains('js-date-picker__month-nav-btn--next')) { + this.showNextMonth(true) + } else if (yearBtn && yearBtn.classList.contains('js-date-picker__year-nav-btn--prev')) { + this.showPrevYear(true) + } else if (yearBtn && yearBtn.classList.contains('js-date-picker__year-nav-btn--next')) { + this.showNextYear(true) + } + }) + + window.addEventListener('keydown', (event) => { + if ((event.code && event.code === 27) || (event.key && event.key.toLowerCase() === 'escape')) { + if (document.activeElement.closest('.js-date-picker')) { + const activeInput = document.activeElement.closest('.js-date-input').querySelector('input') + activeInput.focus() + } else { + this.hideCalendar() + } + } + }) + + window.addEventListener('click', (event) => { + if (!event.target.closest('.js-date-picker') && !event.target.closest('.js-date-input') && this.pickerVisible) { + this.hideCalendar() + } + }) + + this.body.addEventListener('keydown', (event) => { + let day = this.currentDay + if ((event.code && event.code === 40) || (event.key && event.key.toLowerCase() === 'arrowdown')) { + day += 7 + this.resetDayValue(day) + } else if ((event.code && event.code === 39) || (event.key && event.key.toLowerCase() === 'arrowright')) { + day += 1 + this.resetDayValue(day) + } else if ((event.code && event.code === 37) || (event.key && event.key.toLowerCase() === 'arrowleft')) { + day -= 1 + this.resetDayValue(day) + } else if ((event.code && event.code === 38) || (event.key && event.key.toLowerCase() === 'arrowup')) { + day -= 7 + this.resetDayValue(day) + } else if ((event.code && event.code === 35) || (event.key && event.key.toLowerCase() === 'end')) { + event.preventDefault() + day = day + 6 - this.getDayOfWeek(this.currentYear, this.currentMonth, day) + this.resetDayValue(day) + } else if ((event.code && event.code === 36) || (event.key && event.key.toLowerCase() === 'home')) { + event.preventDefault() + day -= this.getDayOfWeek(this.currentYear, this.currentMonth, day) + this.resetDayValue(day) + } else if ((event.code && event.code === 34) || (event.key && event.key.toLowerCase() === 'pagedown')) { + event.preventDefault() + this.showNextMonth() + } else if ((event.code && event.code === 33) || (event.key && event.key.toLowerCase() === 'pageup')) { + event.preventDefault() + this.showPrevMonth() + } + }) + + this.datePicker.addEventListener('keydown', (event) => { + if ((event.code && event.code === 9) || (event.key && event.key === 'Tab')) { + this.trapFocus(event) + } + }) + + if (this.input) { + this.input.addEventListener('keydown', (event) => { + if ((event.code && event.code === 13) || (event.key && event.key.toLowerCase() === 'enter')) { + this.resetCalendar() + this.resetLabelCalendarTrigger() + this.hideCalendar() + } else if ((event.code && event.code === 40) || (event.key && event.key.toLowerCase() === 'arrowdown' && this.pickerVisible)) { + this.body.querySelector('button[tabindex="0"]').focus() + } + }) + } + + if (this.multipleInput) { + this.multiDateArray.forEach((element) => { + element.addEventListener('keydown', (event) => { + if ((event.code && event.code === 13) || (event.key && event.key.toLowerCase() === 'enter')) { + this.resetCalendar() + this.resetLabelCalendarTrigger() + this.hideCalendar() + } else if ((event.code && event.code === 40) || (event.key && event.key.toLowerCase() === 'arrowdown' && this.pickerVisible)) { + this.body.querySelector('button[tabindex="0"]').focus() + } + }) + }) + } + } + + getCurrentDay(date) { + return (date) + ? this.getDayFromDate(date) + : new Date().getDate() + } + + getCurrentMonth(date) { + return (date) + ? this.getMonthFromDate(date) + : new Date().getMonth() + } + + getCurrentYear(date) { + return (date) + ? this.getYearFromDate(date) + : new Date().getFullYear() + } + + getDayFromDate(date) { + const day = parseInt(date.split('-')[2], 10) + return Number.isNaN(day) ? this.getCurrentDay(false) : day + } + + getMonthFromDate(date) { + const month = parseInt(date.split('-')[1], 10) - 1 + return Number.isNaN(month) ? this.getCurrentMonth(false) : month + } + + getYearFromDate(date) { + const year = parseInt(date.split('-')[0], 10) + return Number.isNaN(year) ? this.getCurrentYear(false) : year + } + + showNextMonth(bool) { + this.currentYear = (this.currentMonth === 11) ? this.currentYear + 1 : this.currentYear + this.currentMonth = (this.currentMonth + 1) % 12 + this.currentDay = this.checkDayInMonth() + this.showCalendar(bool) + this.srLiveReagion.textContent = `${this.months[this.currentMonth]} ${this.currentYear}` + } + + showPrevMonth(bool) { + this.currentYear = (this.currentMonth === 0) ? this.currentYear - 1 : this.currentYear + this.currentMonth = (this.currentMonth === 0) ? 11 : this.currentMonth - 1 + this.currentDay = this.checkDayInMonth() + this.showCalendar(bool) + this.srLiveReagion.textContent = `${this.months[this.currentMonth]} ${this.currentYear}` + } + + showNextYear(bool) { + this.currentYear += 1 + this.currentMonth %= 12 + this.currentDay = this.checkDayInMonth() + this.showCalendar(bool) + this.srLiveReagion.textContent = `${this.months[this.currentMonth]} ${this.currentYear}` + } + + showPrevYear(bool) { + this.currentYear -= 1 + this.currentMonth %= 12 + this.currentDay = this.checkDayInMonth() + this.showCalendar(bool) + this.srLiveReagion.textContent = `${this.months[this.currentMonth]} ${this.currentYear}` + } + + checkDayInMonth() { + return (this.currentDay > this.constructor.daysInMonth(this.currentYear, this.currentMonth)) ? 1 : this.currentDay + } + + static daysInMonth(year, month) { + return 32 - new Date(year, month, 32).getDate() + } + + resetCalendar() { + let currentDate = false + let selectedDate + + if (this.input) { + selectedDate = this.input.value + } else if (this.multipleInput) { + if (this.dateInput.value !== '' && this.monthInput.value !== '' && this.yearInput.value !== '') { + selectedDate = `${this.dateInput.value}/${this.monthInput.value}/${this.yearInput.value}` + } else { + selectedDate = '' + } + } + + this.dateSelected = false + if (selectedDate !== '') { + const date = this.getDateFromInput() + this.dateSelected = true + currentDate = date + } + this.currentDay = this.getCurrentDay(currentDate) + this.currentMonth = this.getCurrentMonth(currentDate) + this.currentYear = this.getCurrentYear(currentDate) + + this.selectedDay = this.dateSelected ? this.currentDay : false + this.selectedMonth = this.dateSelected ? this.currentMonth : false + this.selectedYear = this.dateSelected ? this.currentYear : false + } + + disabledDates() { + this.disabledArray = [] + + if (this.datesDisabled) { + const disabledDates = this.datesDisabled.split(' ') + + disabledDates.forEach((element) => { + this.disabledArray.push(element) + }) + } + } + + convertDateToParse(date) { + const dateArray = date.split(this.dateSeparator) + return `${dateArray[this.dateIndexes[2]]}, ${dateArray[this.dateIndexes[1]]}, ${dateArray[this.dateIndexes[0]]}` + } + + isDisabledDate(day, month, year) { + let disabled = false + + const dateParse = new Date(year, month, day) + const minDate = new Date(this.convertDateToParse(this.minDate)) + const maxDate = new Date(this.convertDateToParse(this.maxDate)) + + if (this.minDate && minDate > dateParse) { + disabled = true + } + + if (this.maxDate && maxDate < dateParse) { + disabled = true + } + + if (this.disabledArray.length > 0) { + this.disabledArray.forEach((element) => { + const disabledDate = new Date(this.convertDateToParse(element)) + if (dateParse.getTime() === disabledDate.getTime()) { + disabled = true + } + }) + } + + return disabled + } + + showCalendar(bool) { + const firstDay = this.constructor.getDayOfWeek(this.currentYear, this.currentMonth, '01') + this.body.innerHTML = '' + this.heading.innerHTML = `${this.months[this.currentMonth]} ${this.currentYear}` + + let date = 1 + let calendar = '' + for (let i = 0; i < 6; i += 1) { + for (let j = 0; j < 7; j += 1) { + if (i === 0 && j < firstDay) { + calendar += '
  • ' + } else if (date > this.constructor.daysInMonth(this.currentYear, this.currentMonth)) { + break + } else { + let classListDate = '' + let tabindexValue = '-1' + let disabled + if (date === this.currentDay) { + tabindexValue = '0' + } + if (this.getCurrentMonth() === this.currentMonth && this.getCurrentYear() === this.currentYear && date === this.getCurrentDay()) { + classListDate += ` ${this.todayClass}` + } + + if (this.isDisabledDate(date, this.currentMonth, this.currentYear)) { + classListDate += ` ${this.dateClass}--disabled` + disabled = 'aria-disabled="true"' + } + if (this.dateSelected && date === this.selectedDay && this.currentYear === this.selectedYear && this.currentMonth === this.selectedMonth) { + classListDate += ` ${this.selectedClass}` + } + calendar = `${calendar}
  • ` + date += 1 + } + } + } + this.body.innerHTML = calendar + + if (!this.pickerVisible) this.datePicker.classList.add(this.visibleClass) + this.pickerVisible = true + + if (!bool) this.body.querySelector('button[tabindex="0"]').focus() + + this.getFocusableElements() + + this.placeCalendar() + } + + hideCalendar() { + this.datePicker.classList.remove(this.visibleClass) + this.pickerVisible = false + + this.firstFocusable = false + this.lastFocusable = false + + if (this.trigger) this.trigger.setAttribute('aria-expanded', 'false') + } + + toggleCalendar(bool) { + if (!this.pickerVisible) { + this.resetCalendar() + this.showCalendar(bool) + } else { + this.hideCalendar() + } + } + + static getDayOfWeek(year, month, day) { + let weekDay = (new Date(year, month, day)).getDay() - 1 + if (weekDay < 0) weekDay = 6 + return weekDay + } + + getDateIndexes() { + const dateFormat = this.dateFormat.toLowerCase().replace(/-/g, '') + return [dateFormat.indexOf('d'), dateFormat.indexOf('m'), dateFormat.indexOf('y')] + } + + setInputValue() { + if (this.input) { + this.input.value = this.getDateForInput(this.selectedDay, this.selectedMonth, this.selectedYear) + } else if (this.multipleInput) { + this.dateInput.value = this.constructor.getReadableDate(this.selectedDay) + this.monthInput.value = this.constructor.getReadableDate(this.selectedMonth + 1) + this.yearInput.value = this.selectedYear + } + } + + getDateForInput(day, month, year) { + const dateArray = [] + dateArray[this.dateIndexes[0]] = this.constructor.getReadableDate(day) + dateArray[this.dateIndexes[1]] = this.constructor.getReadableDate(month + 1) + dateArray[this.dateIndexes[2]] = year + return dateArray[0] + this.dateSeparator + dateArray[1] + this.dateSeparator + dateArray[2] + } + + getDateFromInput() { + let dateArray + + if (this.input) { + dateArray = this.input.value.split(this.dateSeparator) + } else if (this.multipleInput) { + dateArray = [this.dateInput.value, this.monthInput.value, this.yearInput.value] + } + return `${dateArray[this.dateIndexes[2]]}-${dateArray[this.dateIndexes[1]]}-${dateArray[this.dateIndexes[0]]}` + } + + static getReadableDate(date) { + return (date < 10) ? `0${date}` : date + } + + resetDayValue(day) { + const totDays = this.constructor.daysInMonth(this.currentYear, this.currentMonth) + if (day > totDays) { + this.currentDay = day - totDays + this.showNextMonth(false) + } else if (day < 1) { + const newMonth = this.currentMonth === 0 ? 11 : this.currentMonth - 1 + this.currentDay = this.constructor.daysInMonth(this.currentYear, newMonth) + day + this.showPrevMonth(false) + } else { + this.currentDay = day + const focusItem = this.body.querySelector('button[tabindex="0"]') + focusItem.setAttribute('tabindex', '-1') + focusItem.classList.remove(this.keyboardFocusClass) + + const buttons = this.body.getElementsByTagName('button') + for (let i = 0; i < buttons.length; i += 1) { + if (parseInt(buttons[i].textContent, 10) === this.currentDay) { + buttons[i].setAttribute('tabindex', '0') + buttons[i].classList.add(this.keyboardFocusClass) + buttons[i].focus() + break + } + } + this.getFocusableElements() + } + } + + resetLabelCalendarTrigger() { + if (!this.trigger) return + + if (this.selectedYear && this.selectedMonth !== false && this.selectedDay) { + this.trigger.setAttribute('aria-label', `${this.triggerLabel}, selected date is ${new Date(this.selectedYear, this.selectedMonth, this.selectedDay).toDateString()}`) + } else { + this.trigger.setAttribute('aria-label', this.triggerLabel) + } + } + + getFocusableElements() { + const allFocusable = this.datePicker.querySelectorAll('[href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), iframe, object, embed, [tabindex]:not([tabindex="-1"]), [contenteditable], audio[controls], video[controls], summary') + this.getFirstFocusable(allFocusable) + this.getLastFocusable(allFocusable) + } + + getFirstFocusable(elements) { + for (let i = 0; i < elements.length; i += 1) { + if ((elements[i].offsetWidth || elements[i].offsetHeight || elements[i].getClientRects().length) && elements[i].getAttribute('tabindex') !== '-1') { + this.firstFocusable = elements[i] + return true + } + } + + return false + } + + getLastFocusable(elements) { + for (let i = elements.length - 1; i >= 0; i -= 1) { + if ((elements[i].offsetWidth || elements[i].offsetHeight || elements[i].getClientRects().length) && elements[i].getAttribute('tabindex') !== '-1') { + this.lastFocusable = elements[i] + return true + } + } + + return false + } + + trapFocus(event) { + if (this.firstFocusable === document.activeElement && event.shiftKey) { + event.preventDefault() + this.lastFocusable.focus() + } + if (this.lastFocusable === document.activeElement && !event.shiftKey) { + event.preventDefault() + this.firstFocusable.focus() + } + } + + placeCalendar() { + this.datePicker.style.left = '0px' + this.datePicker.style.right = 'auto' + + const pickerBoundingRect = this.datePicker.getBoundingClientRect() + + if (pickerBoundingRect.right > window.innerWidth) { + this.datePicker.style.left = 'auto' + this.datePicker.style.right = '0px' + } + } +} + +export default DatePicker diff --git a/src/components/date-picker/index.hbs b/src/components/date-picker/index.hbs new file mode 100644 index 000000000..11d5b199c --- /dev/null +++ b/src/components/date-picker/index.hbs @@ -0,0 +1,30 @@ +--- +title: Date picker +width: narrow +tabs: true +directory: date-picker +theme: true +intro: 'Use a date picker to let users pick a date from a calendar' +meta-description: 'Use a date picker to let users pick a date from a calendar' +meta-index: true +--- + +{{#>_docs-example}} +{{>_date-picker single=true id="1"}} +{{/_docs-example}} + +

    Date ranges and disabled dates

    + +

    Allowed date ranges for a date picker can be set by specifying earliest and latest allowed dates. Individual dates can also be disabled.

    + +

    In this example, a max date of 28 January 2024, a min date of 02 January 2024 has been set and three days in January 2024 have been disabled.

    + +{{#>_docs-example separated=true}} +{{>_date-picker single=true disabled=true id="2"}} +{{/_docs-example}} + +

    Individual date inputs for day, month and year

    + +{{#>_docs-example}} +{{>_date-picker multiple=true id="3"}} +{{/_docs-example}} \ No newline at end of file diff --git a/src/components/date-picker/json/date-picker.json b/src/components/date-picker/json/date-picker.json new file mode 100644 index 000000000..9e26dfeeb --- /dev/null +++ b/src/components/date-picker/json/date-picker.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/src/components/date-picker/theme.hbs b/src/components/date-picker/theme.hbs new file mode 100644 index 000000000..3490b4208 --- /dev/null +++ b/src/components/date-picker/theme.hbs @@ -0,0 +1,39 @@ +--- +title: Date picker +width: narrow +page: true +--- + +{{#>_theme}} +{{#>_layout-container}} + +{{>_date-picker single=true id="1"}} + +
    +
    + +{{>_date-picker single=true disabled=true id="2"}} + +
    +
    + +{{>_date-picker multiple=true id="3"}} + +{{/_layout-container}} + +{{#>_layout-container brand-dark="true" invert="true"}} + +{{>_date-picker single=true id="1"}} + +
    +
    + +{{>_date-picker single=true disabled=true id="2"}} + +
    +
    + +{{>_date-picker multiple=true id="3"}} + +{{/_layout-container}} +{{/_theme}} \ No newline at end of file diff --git a/src/components/form/_form.scss b/src/components/form/_form.scss index 0c2f68ea0..cb00f5625 100644 --- a/src/components/form/_form.scss +++ b/src/components/form/_form.scss @@ -1,3 +1,5 @@ +/* stylelint-disable max-nesting-depth */ + $nsw-form-tick: ''; .nsw-form { @@ -93,6 +95,10 @@ $nsw-form-tick: '
    {{>_text-input - id="form-text-1" + id="form-text-11" label="Name" }}
    {{>_textarea - id="form-textarea-1" + id="form-textarea-11" label="Comment" }}
    {{>_input-group - id="form-input-group-1" + id="form-input-group-11" label="Search" }}
    {{>_input-group - id="form-input-group-icon-1" + id="form-input-group-icon-11" label="Search" icon="search" }}
    {{>_input-group - id="form-input-group-icon-2" + id="form-input-group-icon-12" label="Search" icon="search" white=true @@ -418,9 +419,9 @@ model:
    - +
    - +
    • Hornsby
    • Hornsby Heights
    • @@ -432,7 +433,7 @@ model:
      {{>_input-group - id="form-predictive-group-1" + id="form-predictive-group-11" label="Search" value="Tran" predictive=true @@ -448,7 +449,7 @@ model:
      {{>_input-group - id="form-predictive-group-icon-1" + id="form-predictive-group-icon-11" label="Search" value="Govern" icon="search" @@ -465,21 +466,21 @@ model:
      {{>_date - id="form-date-1" + id="form-date-11" label="Date of birth" }}
      {{>_select model.select - id="form-select-1" + id="form-select-11" }}
      {{>_checkbox - id="form-checkbox-1" - name="form-checkbox-1" + id="form-checkbox-11" + name="form-checkbox-11" label="I agree to terms and conditions" }}
      @@ -489,13 +490,13 @@ model: Category {{>_checkbox - id="form-checkbox-multi-1-a" - name="form-checkbox-multi-1" + id="form-checkbox-multi-11-a" + name="form-checkbox-multi-11" label="Customer Service" }} {{>_checkbox - id="form-checkbox-multi-1-b" - name="form-checkbox-multi-1" + id="form-checkbox-multi-11-b" + name="form-checkbox-multi-11" label="Communities and Justice" checked=true }} @@ -508,14 +509,14 @@ model: Topic {{>_radio - id="form-radio-1-a" - name="form-radio-1" + id="form-radio-11-a" + name="form-radio-11" label="NSW Government" checked=true }} {{>_radio - id="form-radio-1-b" - name="form-radio-1" + id="form-radio-11-b" + name="form-radio-11" label="Business and Economy" }} @@ -525,14 +526,14 @@ model:
      {{>_text-input - id="form-text-2" + id="form-text-12" label="Name" helper-text="As it appears on your identity document" }}
      {{>_textarea - id="form-textarea-2" + id="form-textarea-12" label="Comment" helper-text="Maximum 500 characters" }} @@ -540,15 +541,15 @@ model:
      {{>_date - id="form-date-2" + id="form-date-12" label="Date of birth" - helper-text="For example 08 12 1990" + helper-text="For example 08 1112 11990" }}
      {{>_select model.select - id="form-select-2" + id="form-select-12" helper-text="Your region of residence" }}
      @@ -560,13 +561,13 @@ model: Select all that applies to you {{>_checkbox - id="form-checkbox-multi-2-a" - name="form-checkbox-multi-2" + id="form-checkbox-multi-12-a" + name="form-checkbox-multi-12" label="Customer Service" }} {{>_checkbox - id="form-checkbox-multi-2-b" - name="form-checkbox-multi-2" + id="form-checkbox-multi-12-b" + name="form-checkbox-multi-12" label="Communities and Justice" }} @@ -579,13 +580,13 @@ model: Select closest match {{>_radio - id="form-radio-2-a" - name="form-radio-2" + id="form-radio-12-a" + name="form-radio-12" label="NSW Government" }} {{>_radio - id="form-radio-2-b" - name="form-radio-2" + id="form-radio-12-b" + name="form-radio-12" label="Business and Economy" }} @@ -595,7 +596,7 @@ model:
      {{>_text-input - id="form-text-3" + id="form-text-13" label="Name" required=true error-text="This field is required" @@ -603,7 +604,7 @@ model:
      {{>_textarea - id="form-textarea-3" + id="form-textarea-13" label="Comment" required=true error-text="This field is required" @@ -612,7 +613,7 @@ model:
      {{>_date - id="form-date-3" + id="form-date-13" label="Date of birth" required=true error-text="This field is required" @@ -621,7 +622,7 @@ model:
      {{>_select model.select - id="form-select-3" + id="form-select-13" required=true error-text="This field is required" }} @@ -629,8 +630,8 @@ model:
      {{>_checkbox - id="form-checkbox-3" - name="form-checkbox-3" + id="form-checkbox-13" + name="form-checkbox-13" label="I agree to terms and conditions" required=true error-text="This field is required" @@ -643,13 +644,13 @@ model: This field is required {{>_checkbox - id="form-checkbox-multi-3-a" - name="form-checkbox-multi-3" + id="form-checkbox-multi-13-a" + name="form-checkbox-multi-13" label="Customer Service" }} {{>_checkbox - id="form-checkbox-multi-3-b" - name="form-checkbox-multi-3" + id="form-checkbox-multi-13-b" + name="form-checkbox-multi-13" label="Communities and Justice" }} @@ -662,13 +663,13 @@ model: This field is required {{>_radio - id="form-radio-3-a" - name="form-radio-3" + id="form-radio-13-a" + name="form-radio-13" label="NSW Government" }} {{>_radio - id="form-radio-3-b" - name="form-radio-3" + id="form-radio-13-b" + name="form-radio-13" label="Business and Economy" }} @@ -678,14 +679,14 @@ model:
      {{>_text-input - id="form-text-4" + id="form-text-14" label="Name" valid-text="This field has been validated" }}
      {{>_textarea - id="form-textarea-4" + id="form-textarea-14" label="Comment" valid-text="This field has been validated" }} @@ -693,7 +694,7 @@ model:
      {{>_date - id="form-date-4" + id="form-date-14" label="Date of birth" valid-text="This field has been validated" }} @@ -701,15 +702,15 @@ model:
      {{>_select model.select - id="form-select-4" + id="form-select-14" valid-text="This field has been validated" }}
      {{>_checkbox - id="form-checkbox-4" - name="form-checkbox-4" + id="form-checkbox-14" + name="form-checkbox-14" label="I agree to terms and conditions" valid-text="This field has been validated" }} @@ -721,13 +722,13 @@ model: This field has been validated {{>_checkbox - id="form-checkbox-multi-4-a" - name="form-checkbox-multi-4" + id="form-checkbox-multi-14-a" + name="form-checkbox-multi-14" label="Customer Service" }} {{>_checkbox - id="form-checkbox-multi-4-b" - name="form-checkbox-multi-4" + id="form-checkbox-multi-14-b" + name="form-checkbox-multi-14" label="Communities and Justice" }} @@ -740,16 +741,17 @@ model: This field has been validated {{>_radio - id="form-radio-4-a" - name="form-radio-4" + id="form-radio-14-a" + name="form-radio-14" label="NSW Government" }} {{>_radio - id="form-radio-4-b" - name="form-radio-4" + id="form-radio-14-b" + name="form-radio-14" label="Business and Economy" }}
      + {{/_layout-container}} diff --git a/src/components/form/theme.hbs b/src/components/form/theme.hbs index fd002ba9d..c7e18eaf4 100644 --- a/src/components/form/theme.hbs +++ b/src/components/form/theme.hbs @@ -385,33 +385,33 @@ model:
      {{>_text-input - id="form-text-1" + id="form-text-11" label="Name" }}
      {{>_textarea - id="form-textarea-1" + id="form-textarea-11" label="Comment" }}
      {{>_input-group - id="form-input-group-1" + id="form-input-group-11" label="Search" }}
      {{>_input-group - id="form-input-group-icon-1" + id="form-input-group-icon-11" label="Search" icon="search" }}
      {{>_input-group - id="form-input-group-icon-2" + id="form-input-group-icon-12" label="Search" icon="search" white=true @@ -419,9 +419,9 @@ model:
      - +
      - +
      • Hornsby
      • Hornsby Heights
      • @@ -433,7 +433,7 @@ model:
        {{>_input-group - id="form-predictive-group-1" + id="form-predictive-group-11" label="Search" value="Tran" predictive=true @@ -449,7 +449,7 @@ model:
        {{>_input-group - id="form-predictive-group-icon-1" + id="form-predictive-group-icon-11" label="Search" value="Govern" icon="search" @@ -466,21 +466,21 @@ model:
        {{>_date - id="form-date-1" + id="form-date-11" label="Date of birth" }}
        {{>_select model.select - id="form-select-1" + id="form-select-11" }}
        {{>_checkbox - id="form-checkbox-1" - name="form-checkbox-1" + id="form-checkbox-11" + name="form-checkbox-11" label="I agree to terms and conditions" }}
        @@ -490,13 +490,13 @@ model: Category {{>_checkbox - id="form-checkbox-multi-1-a" - name="form-checkbox-multi-1" + id="form-checkbox-multi-11-a" + name="form-checkbox-multi-11" label="Customer Service" }} {{>_checkbox - id="form-checkbox-multi-1-b" - name="form-checkbox-multi-1" + id="form-checkbox-multi-11-b" + name="form-checkbox-multi-11" label="Communities and Justice" checked=true }} @@ -509,14 +509,14 @@ model: Topic {{>_radio - id="form-radio-1-a" - name="form-radio-1" + id="form-radio-11-a" + name="form-radio-11" label="NSW Government" checked=true }} {{>_radio - id="form-radio-1-b" - name="form-radio-1" + id="form-radio-11-b" + name="form-radio-11" label="Business and Economy" }} @@ -526,14 +526,14 @@ model:
        {{>_text-input - id="form-text-2" + id="form-text-12" label="Name" helper-text="As it appears on your identity document" }}
        {{>_textarea - id="form-textarea-2" + id="form-textarea-12" label="Comment" helper-text="Maximum 500 characters" }} @@ -541,15 +541,15 @@ model:
        {{>_date - id="form-date-2" + id="form-date-12" label="Date of birth" - helper-text="For example 08 12 1990" + helper-text="For example 08 1112 11990" }}
        {{>_select model.select - id="form-select-2" + id="form-select-12" helper-text="Your region of residence" }}
        @@ -561,13 +561,13 @@ model: Select all that applies to you {{>_checkbox - id="form-checkbox-multi-2-a" - name="form-checkbox-multi-2" + id="form-checkbox-multi-12-a" + name="form-checkbox-multi-12" label="Customer Service" }} {{>_checkbox - id="form-checkbox-multi-2-b" - name="form-checkbox-multi-2" + id="form-checkbox-multi-12-b" + name="form-checkbox-multi-12" label="Communities and Justice" }} @@ -580,13 +580,13 @@ model: Select closest match {{>_radio - id="form-radio-2-a" - name="form-radio-2" + id="form-radio-12-a" + name="form-radio-12" label="NSW Government" }} {{>_radio - id="form-radio-2-b" - name="form-radio-2" + id="form-radio-12-b" + name="form-radio-12" label="Business and Economy" }} @@ -596,7 +596,7 @@ model:
        {{>_text-input - id="form-text-3" + id="form-text-13" label="Name" required=true error-text="This field is required" @@ -604,7 +604,7 @@ model:
        {{>_textarea - id="form-textarea-3" + id="form-textarea-13" label="Comment" required=true error-text="This field is required" @@ -613,7 +613,7 @@ model:
        {{>_date - id="form-date-3" + id="form-date-13" label="Date of birth" required=true error-text="This field is required" @@ -622,7 +622,7 @@ model:
        {{>_select model.select - id="form-select-3" + id="form-select-13" required=true error-text="This field is required" }} @@ -630,8 +630,8 @@ model:
        {{>_checkbox - id="form-checkbox-3" - name="form-checkbox-3" + id="form-checkbox-13" + name="form-checkbox-13" label="I agree to terms and conditions" required=true error-text="This field is required" @@ -644,13 +644,13 @@ model: This field is required {{>_checkbox - id="form-checkbox-multi-3-a" - name="form-checkbox-multi-3" + id="form-checkbox-multi-13-a" + name="form-checkbox-multi-13" label="Customer Service" }} {{>_checkbox - id="form-checkbox-multi-3-b" - name="form-checkbox-multi-3" + id="form-checkbox-multi-13-b" + name="form-checkbox-multi-13" label="Communities and Justice" }} @@ -663,13 +663,13 @@ model: This field is required {{>_radio - id="form-radio-3-a" - name="form-radio-3" + id="form-radio-13-a" + name="form-radio-13" label="NSW Government" }} {{>_radio - id="form-radio-3-b" - name="form-radio-3" + id="form-radio-13-b" + name="form-radio-13" label="Business and Economy" }} @@ -679,14 +679,14 @@ model:
        {{>_text-input - id="form-text-4" + id="form-text-14" label="Name" valid-text="This field has been validated" }}
        {{>_textarea - id="form-textarea-4" + id="form-textarea-14" label="Comment" valid-text="This field has been validated" }} @@ -694,7 +694,7 @@ model:
        {{>_date - id="form-date-4" + id="form-date-14" label="Date of birth" valid-text="This field has been validated" }} @@ -702,15 +702,15 @@ model:
        {{>_select model.select - id="form-select-4" + id="form-select-14" valid-text="This field has been validated" }}
        {{>_checkbox - id="form-checkbox-4" - name="form-checkbox-4" + id="form-checkbox-14" + name="form-checkbox-14" label="I agree to terms and conditions" valid-text="This field has been validated" }} @@ -722,13 +722,13 @@ model: This field has been validated {{>_checkbox - id="form-checkbox-multi-4-a" - name="form-checkbox-multi-4" + id="form-checkbox-multi-14-a" + name="form-checkbox-multi-14" label="Customer Service" }} {{>_checkbox - id="form-checkbox-multi-4-b" - name="form-checkbox-multi-4" + id="form-checkbox-multi-14-b" + name="form-checkbox-multi-14" label="Communities and Justice" }} @@ -741,13 +741,13 @@ model: This field has been validated {{>_radio - id="form-radio-4-a" - name="form-radio-4" + id="form-radio-14-a" + name="form-radio-14" label="NSW Government" }} {{>_radio - id="form-radio-4-b" - name="form-radio-4" + id="form-radio-14-b" + name="form-radio-14" label="Business and Economy" }} diff --git a/src/docs/content/design/theming.hbs b/src/docs/content/design/theming.hbs index 0c2bd3e50..ba0567dfe 100644 --- a/src/docs/content/design/theming.hbs +++ b/src/docs/content/design/theming.hbs @@ -189,7 +189,7 @@ meta-index: true
        • Brand Dark - the primary interaction colour and is used for buttons, text links, icons, dark variants of components and backgrounds. To ensure the required 4.5:1 contrast ratio for accessibility, this colour must always be selected from row 01 of the NSW Government colour palette.
        • -
        • Brand Light - your contrasting colour to Brand Dark and is used for secondary buttons, light variants of components, backgrounds and content sections. To ensure the required 4.5:1 contrast ratio for accessibility, this colour must be a light colour.
        • +
        • Brand Light - your contrasting colour to Brand Dark and is used for secondary buttons, light variants of components, backgrounds and content sections. To ensure the required 4.5:1 contrast ratio for accessibility, this colour must be a light colour. Acceptable colour options for Brand Light include rows 03 or 04 of the NSW Government colour palette.
        • Brand Supplementary - an optional third colour which should be used sparingly in instances such as pictograms, dark variants of components and background. To ensure the required 4.5:1 contrast ratio for accessibility, this colour must be a dark colour.
        • Brand Accent - the highlight colour to capture attention and draw the users eye. You’ll find its use in highlight bars and pictograms. This colour has different selection rules depending on your brand classification.
        @@ -303,8 +303,7 @@ meta-index: true

        Colour selection rules:

        • Brand Dark must be selected from row 01.
        • -
        • Brand Light must be a light colour (accessible with Text Dark). -
        • +
        • Brand Light must be a light colour (accessible with Text Dark, row 03 and 04 recommended).
        • Brand Supplementary must be a dark colour (accessible with Text Light). This is an additional colour option only available if your Brand Light is not from row 02.
        • Brand Accent can be selected from any row from your chosen colour sets (row 02 and 03 recommended).
        diff --git a/src/global/scss/helpers/_visibility.scss b/src/global/scss/helpers/_visibility.scss index c2d23e784..91c0ff585 100644 --- a/src/global/scss/helpers/_visibility.scss +++ b/src/global/scss/helpers/_visibility.scss @@ -3,8 +3,6 @@ @include sr-only(); } - - .nsw-show { @each $breakpoint, $breakpoint-size in $nsw-breakpoints { &-#{$breakpoint} { diff --git a/src/main.js b/src/main.js index 7f97a61ef..2628ae819 100644 --- a/src/main.js +++ b/src/main.js @@ -1,6 +1,7 @@ /* eslint-disable max-len */ import Accordion from './components/accordion/accordion' import BackTop from './components/back-to-top/back-to-top' +import DatePicker from './components/date-picker/date-picker' import Carousel from './components/card-carousel/carousel' import Dialog from './components/dialog/dialog' import ExternalLink from './components/link/link' @@ -41,6 +42,7 @@ function initSite() { const backTop = document.querySelectorAll('.js-back-to-top') const carousel = document.querySelectorAll('.js-carousel') const closeSearchButton = document.querySelectorAll('.js-close-search') + const datePicker = document.querySelectorAll('.js-date-input') const dialogs = document.querySelectorAll('.js-dialog') const fileUpload = document.querySelectorAll('.js-file-upload') const filters = document.querySelectorAll('.js-filters') @@ -79,6 +81,12 @@ function initSite() { }) } + if (datePicker) { + datePicker.forEach((element) => { + new DatePicker(element).init() + }) + } + if (dialogs) { dialogs.forEach((element) => { new Dialog(element).init() @@ -158,5 +166,5 @@ function initSite() { } export { - initSite, Accordion, BackTop, Carousel, Dialog, ExternalLink, FileUpload, Filters, GlobalAlert, Navigation, Popover, Select, SiteSearch, Tabs, Toggletip, Tooltip, UtilityList, + initSite, Accordion, BackTop, DatePicker, Carousel, Dialog, ExternalLink, FileUpload, Filters, GlobalAlert, Navigation, Popover, Select, SiteSearch, Tabs, Toggletip, Tooltip, UtilityList, } diff --git a/src/main.scss b/src/main.scss index b8883cf5b..99cd6a1a3 100644 --- a/src/main.scss +++ b/src/main.scss @@ -8,6 +8,7 @@ @import 'components/accordion/accordion'; @import 'components/back-to-top/back-to-top'; @import 'components/date-input/date-input'; +@import 'components/date-picker/date-picker'; @import 'components/dialog/dialog'; @import 'components/breadcrumbs/breadcrumbs'; @import 'components/button/button';