Esikatselu
<DateRangePickerV2 labelText="Lorem ipsum" />
Käyttötarkoitus
Ajanjakson valitsin (DateRangePickerV2) tarjoaa käyttäjälle alku- ja loppupäivämäärien syöttökentät ja kalenterinäkymän päivämäärävälin valitsemiseen.
Päivämäärän voi syöttää muodoissa pp.kk.vvvv
, p.k.vvvv
, ppkkvvvv
sekä vvvv-kk-pp
.
Jos käytät päivämäärävalitsinta tilanteessa, jossa on tarve syöttää päivämäärä usean vuoden päähän, aseta
rajoitus päivämääräväliin. Tällöin käyttäjälle tulee kuukausi- ja vuosivalikot näkyviin. Myös erillisten
päivämääräkenttien käyttöä kannattaa harkita.
Yksittäisen päivämäärän valitsemiseen kannattaa käyttää
DatePickerV2-komponenttia.
Harkitse käytätkö päivämäärävalitsinta, jos on tarve syöttää päivämäärää usean vuoden päähän. Tällöin
kannattaa useimmiten käyttää tavallista tekstikenttää (Input).
Jos käyttäjän tulee syöttää hänelle tuttu päivämäärä, esimerkiksi oma syntymäaika, kannattaa käyttää
tavallista tekstikenttää (Input).
Saavutettavuus
DateRangePickerV2:n sisältyvä kalenteri tukee näppäimistönavigaatiota kohdistuksen ollessa kalenterinäkymän päivämäärävalinnoissa. Huom. ruudunlukijan käyttö saattaa vaikuttaa näppäimistönavigaation toimintatapaan.
|
Nuoli vasemmalle | Siirry edelliseen päivään. |
Nuoli oikealle | Siirry seuraavaan päivään. |
Nuoli ylös | Siirry edelliseen viikkoon. |
Nuoli alas | Siirry seuraavaan viikkoon. |
Page Up | Siirry edelliseen kuukauteen. |
Page Down | Siirry seuraavaan kuukauteen. |
Shift + Page Up | Siirry edelliseen vuoteen. |
Shift + Page Down | Siirry seuraavaan vuoteen. |
Home | Siirry viikon alkuun. |
End | Siirry viikon loppuun. |
Ruudunlukijalle tarkoitettuja tekstejä voi muokata komponentin localization
-propin avulla. Saman propin avulla voi toteuttaa kalenterinäkymän lokalisointia mikäli käyttöliittymä näytetään jollain muulla kielellä kuin suomeksi.
Tarkemmat ohjeet localization
-propin käytöstä löydät kalenterin lokalisointi osiosta.
Ajanjakson valinta
Ajanjakson valitseminen onnistuu joko syöttämällä alkamis- ja päättymispäivät tekstikenttiin tai valitsemalla ajanjakson kalenterista.
<DateRangePickerV2
labelText="Ajanjakson valinta"
helpText="Ilmoita päivämäärät muodossa pp.kk.vvvv"
onChange={(dateRangeValue, validity) => console.log("onChange", dateRangeValue, validity)}
onSelect={(dateRangeValue, validity) => console.log("onSelect", dateRangeValue, validity)}
onError={(dateRangeValue, validity) => console.log("onError", dateRangeValue, validity)}
onBlur={(e, validity) => console.log("onBlur", e.target.value, validity)}
/>
Oletuspäivän asettaminen ajanjaksolle
Oletusarvon voi antaa sekä alkamis- että päättymispäivälle, tai vain toiselle niistä. Oletusarvon voi antaa Date
-objektina tai vvvv-kk-pp
-muotoisena tekstiarvona.
<DateRangePickerV2
labelText="Ajanjakson valinta"
helpText="Ilmoita päivämäärät muodossa pp.kk.vvvv"
defaultValue={{ from: new Date(2023, 9, 2), to: "2023-10-27" }}
/>
Valinnan estäminen tietyiltä päiviltä tai aikaväleiltä
Valinta voidaan estää tietyiltä päiviltä tai aikaväleiltä. Estettyä päivää ei pysty valitsemaan kalenterista ja jos sen syöttää käsin, tulee käyttäjälle esittää virheilmoitus.
Ajanjaksoa ei pysty valitsemaan siten, että ajanjakson sisällä olisi estettyjä päiviä. Kalenteri estää ne vaihtoehdot, joita ei voi valita, kun käyttäjä valitsee ensimmäisen päivämäärän.
Valinta voidaan estää:
- yksittäisiltä päiviltä,
- tietyltä aikaväliltä,
- ennen tiettyä päivämäärää,
- tietyn päivämäärän jälkeen, tai
- tietyiltä viikonpäiviltä.
<DateRangePickerV2
calendarDefaultMonth="2023-11"
onChange={(dateValue, validity) => console.log("onChange", dateValue, validity)}
disabledDates={[
"2023-11-01",
"2023-11-02",
{ from: "2023-11-06", to: "2023-11-10" },
{ from: "2023-11-22", to: "2023-11-25" },
{ from: "2023-12-08", to: "2023-12-15" },
{ after: "2023-12-31" },
{ before: "2023-01-01" },
//{ dayOfWeek: [0, 6] }, // 0 (sunday) ... 6 (saturday)
]}
labelText="Ajanjakson valinta"
helpText="Ilmoita päivämäärät muodossa pp.kk.vvvv"
/>
Päivämäärien validointi ja virheviestien asettaminen
Virhetilan asettaminen
Komponentin voi asettaa virhetilaan errorText
ja invalid
-proppien avulla.
<DateRangePickerV2
required
labelText="Ajanjakson valinta"
helpText="Ilmoita päivämäärät muodossa pp.kk.vvvv"
errorText="Virheellinen ajanjakso. Ilmoita päivämäärät muodossa pp.kk.vvvv."
invalid={{ from: true, to: true }}
/>
Komponentille voi asettaa useamman virheviestin antamalla errorText
-propille listan virheviesteistä.
<DateRangePickerV2
labelText="Ajanjakson valinta"
helpText="Ilmoita päivämäärät muodossa pp.kk.vvvv"
required
errorText={[
{ type: "from", message: "Alkamispäivä on virheellinen." },
{ type: "to", message: "Päättymispäivä on virheellinen." },
{ type: "range", message: "Ajanjakso on virheellinen." },
]}
invalid={{ from: true, to: true }}
/>
Esimerkki virheenkäsittelyn toteuttamisesta
Komponentti palauttaa kaikkien tapahtumien (onChange
, onSelect
, onError
, onBlur
ja onCalendarClose
) mukana validity
-objektin,
josta löytyy validoinnin tila, virheen tyyppi ja virheviesti, sekä päivämäärä ISO ja Date-muotoisena tietona
(silloin kun input-kentän arvosta voidaan muodostaa päivämäärä). Lisäksi palautetaan tieto, kumpaa kentistä käyttäjä on viimeksi käsitellyt.
Validoinnin mukana palautetaan aina tulokset molempien kenttien validoinnista. Sisäinen validointi kattaa komponentin tarjoamien proppien validoinnin.
Tarvittaessa sisäisen validoinnin voi jättää huomiotta, koska komponentti ei itse aseta itseään virhetilaan, vaan se tehdään
errorText
ja invalid
-proppien avulla. Sovelluksen vastuulle jää virheilmoitusten tarkempi käsittely, esimerkiksi milloin
virheilmoitukset näytetään ja missä järjestyksessä.
Alta löytyy karkea esimerkki dynaamisen virheentarkistuksen toteuttamisesta:
function DateRangePickerV2Example() {
const [error, setError] = useState("");
const [invalid, setInvalid] = useState({ from: false, to: false });
const [touched, setTouched] = useState({ from: false, to: false });
const handleError = (touched, validity) => {
const isFromValidatable = touched.from && !validity?.from.isValid;
const isToValidatable = touched.to && !validity?.to.isValid;
const isFromInvalid = isFromValidatable && (validity?.field === "from" || invalid.from || !validity?.field);
const isToInvalid = isToValidatable && (validity?.field === "to" || invalid.to || !validity?.field);
setTouched(touched);
setError((isFromInvalid && validity?.from.error.message) || (isToInvalid && validity?.to.error.message) || "");
setInvalid({ from: isFromInvalid, to: isToInvalid });
};
return (
<DateRangePickerV2
required
onChange={(dateRangeValue, validity) => {
console.log("onChange", dateRangeValue, validity);
handleError(touched, validity);
}}
onBlur={(e, validity) => {
console.log("onBlur", e.target.value, validity);
handleError(
{
from: validity?.field === "from" || touched.from,
to: validity?.field === "to" || touched.to,
},
validity
);
}}
onCalendarClose={(dateRangeValue, validity) => {
console.log("onCalendarClose", dateRangeValue, validity);
handleError({ from: true, to: true }, validity);
}}
disablePast
invalid={invalid}
errorText={(invalid.from || invalid.to) && error ? error : undefined}
labelText="Ajanjakson valinta"
helpText="Syötä kenttään mennyt päivämäärä, tai siirry pois tyhjästä kentästä, niin saat virheilmoituksen esiin."
/>
);
}
Päivämäärien validointi getDateRangeValidator-funktiolla
Myös komponentin käyttämää getDateRangeValidator
-funktiota voi hyödyntää validoinnin toteuttamiseen.
Funktiolle annetaan samat validointisäännöt (required
, fromRequired
, fromDate
, toDate
, disabledDates
, disablePast
), kuin komponentille.
Tätä voi hyödyntää esimerkiksi React Hook Formin validate
-funktiossa. Oletusvirheilmoituksia voi päivittää
toErrorTexts
, fromErrorTexts
ja rangeErrorTexts
-objektien kautta.
function DateRangePickerV2Example() {
const defaultValue = { from: new Date("2023-10-10"), to: "" };
const validationProps = {
fromDate: new Date("2023-01-01"),
toDate: new Date("2023-12-31"),
required: true,
};
const validationResult = getDateRangeValidator(validationProps).validate(defaultValue);
return (
<DateRangePickerV2
calendarDefaultMonth="2023-10"
defaultValue={defaultValue}
labelText="Ajanjakson valinta"
helpText="Ilmoita päivämäärät muodossa pp.kk.vvvv"
errorText={validationResult.from.error.message || validationResult.to.error.message}
invalid={{ from: !validationResult.from.isValid, to: !validationResult.to.isValid }}
{...validationProps}
/>
);
}
Validoinnin palauttamat arvot
|
field | Kertoo kumpaa input-kenttää käyttäjä on viimeksi käsitellyt (onChange , onBlur , onError ). Arvoina from , to tai undefined . Voidaan käyttää esimerkiksi validointiviestien kohdistamiseen. |
from.isValid
to.isValid | Kertoo onko päivämäärä hyväksytty arvo (oikea päivämäärä ja läpäisee validoinnit). |
from.valueAsDate
to.valueAsDate | Päivämäärä Date -objektina, silloin kun input-kentän syötteestä pystytään muodostamaan päivämäärä. |
from.valueAsISO
to.valueAsISO | Päivämäärä ISO-muotoisena tekstinä (esim. 2023-10-30), silloin kun input-kentän syötteestä pystytään muodostamaan päivämäärä. |
from.error.type
to.error.type | Virheen tyyppi: invalidFormat , invalidRelation , notInRangeTo , notInRangeFrom , required |
from.error.message
to.error.message | Kenttäkohtainen oletusvirheviesti, jota voi päivittää localization -propin kautta. |
range.isValid | Kertoo ovatko molemmat arvot hyväksyttyjä (oikeat päivämäärät ja läpäisevät validoinnit). |
range.error.type | Virheen tyyppi: invalidRange |
range.error.message | Oletusvirheviesti, jota voi päivittää localization -propin kautta. |
Vuosi- ja kuukausivalikon näyttäminen
Vuosi- ja kuukausivalikon saa näkyviin, kun valinnan rajoittaa tietylle aikavälille. Valittavia päivämääriä voidaan rajoittaa tietylle välille fromDate
- ja toDate
-proppien avulla.
Vuosi- ja kuukausivalikkojen näyttämisen voi tarvittaessa estää asettamalla showDropdownNavigation
-propin arvoksi false
.
<DateRangePickerV2
labelText="Ajanjakson valinta"
helpText="Ilmoita päivämäärät muodossa pp.kk.vvvv"
fromDate="2020-01"
toDate="2024-12"
// showDropdownNavigation={false}
/>
Päivämääräarvojen näyttäminen etunollien kanssa
Input-kentässä näytettävä päivämääräarvo voidaan tarvittaessa esittää etunollien kanssa asettamalla showLeadingZeros
-propin arvoksi true
. Oletuksena etunollia ei näytetä.
<DateRangePickerV2 labelText="Ajanjakson valinta" defaultValue="2022-01-01" showLeadingZeros={true} />
Estetty kenttä
Kentät voi tarvittaessa asettaa estetyksi, eli disabloiduksi. Tällöin päivämäärää ei pääse syöttämään tekstinä, eikä kalenterin kautta.
<DateRangePickerV2 disabled labelText="Ajanjakson valinta" />
Kokovaihtoehdot
<>
<DateRangePickerV2 labelText="Ajanjakson valinta xs-koossa" size="xs" />
<DateRangePickerV2 labelText="Ajanjakson valinta sm-koossa" size="sm" />
<DateRangePickerV2 labelText="Ajanjakson valinta md-koossa (oletus)" />
</>
Ajanjakson päivittäminen komponentin ulkopuolelta
Tarvittaessa päivämääräarvon voi päivittää tai tyhjentää komponentin ulkopuolelta antamalla value
-arvon.
Proppien fromRef
ja toRef
kautta pääsee käsiksi input-elementteihin, mikä mahdollistaa esimerkiksi kohdistuksen siirtämisen kenttään.
function DateRangePickerV2Example() {
const fromRef = React.useRef();
const toRef = React.useRef();
const [value, setValue] = useState({ from: "", to: "" });
return (
<>
<DateRangePickerV2
fromRef={fromRef}
toRef={toRef}
onChange={(dateRangeValue, validity) => {
console.log("onChange", dateRangeValue, validity);
setValue(dateRangeValue);
}}
labelText="Ajanjakson valinta"
helpText="Ilmoita päivämäärät muodossa pp.kk.vvvv"
value={value}
/>
<ButtonGroup horizontal>
<Button onClick={() => setValue({ from: "11.12.2023", to: "15.12.2023" })}>Aseta arvo</Button>
<Button onClick={() => setValue({ from: "", to: "" })} appearance="outline">
Tyhjennä
</Button>
<Button onClick={() => fromRef.current.focus()} appearance="outline">
Kohdista alkamispäivään
</Button>
<Button onClick={() => toRef.current.focus()} appearance="outline">
Kohdista päättymispäivään
</Button>
</ButtonGroup>
</>
);
}
Aputoimintojen näyttäminen
Komponentti mahdollistaa räätälöityjen aputoimintojen näyttämisen input-kentän yläpuolella. Toiminnot välitetään komponentille helpers
-propin avulla.
function HelpersExample() {
const [value, setValue] = useState();
const today = new Date();
const firstDayOfMonth = new Date(today.getFullYear(), today.getMonth(), 1);
const lastDayOfMonth = new Date(today.getFullYear(), today.getMonth() + 1, 0);
const firstDayOfPreviousMonth = new Date(today.getFullYear(), today.getMonth() - 1, 1);
const lastDayOfPreviousMonth = new Date(today.getFullYear(), today.getMonth(), 0);
const firstDayOfMonthBeforePreviousMonth = new Date(today.getFullYear(), today.getMonth() - 2, 1);
function formatDate(date) {
const day = date.getDate();
const month = date.getMonth() + 1;
const year = date.getFullYear();
return `${day}.${month}.${year}`;
}
const separator = <span className="kds-mx-2 kds-text-muted">|</span>;
const helpers = (
<>
<Button
appearance="link"
onClick={() => setValue({ from: formatDate(firstDayOfMonth), to: formatDate(lastDayOfMonth) })}
>
Kuluva kuukausi
</Button>
{separator}
<Button
appearance="link"
onClick={() => setValue({ from: formatDate(firstDayOfPreviousMonth), to: formatDate(lastDayOfPreviousMonth) })}
>
Edellinen kuukausi
</Button>
{separator}
<Button
appearance="link"
onClick={() =>
setValue({ from: formatDate(firstDayOfMonthBeforePreviousMonth), to: formatDate(lastDayOfPreviousMonth) })
}
>
Edeltävät 2 kuukautta
</Button>
</>
);
return (
<DateRangePickerV2
helpers={helpers}
labelText="Aikaväli"
onChange={(dateRangeValue) => {
setValue(dateRangeValue);
}}
required
value={value}
/>
);
}
Kalenterin lokalisointi
Lokalisointia tulee tehdä, jos käyttöliittymä näytetään jollain muulla kielellä kuin suomeksi tai jos suomenkielisiä ruudunlukijalle luettavia tekstejä halutaan muuttaa.
Kalenterinäkymän lokalisointi onnistuu localization
-propin avulla. Propin sallima lokalisaatio-objekti sisältää locale
-arvon, joka on tyypiltään date-fns
-kirjaston Locale
.
Lisäksi localization
-objektin kautta pääsee muokkaamaan kalenteriin liittyvien toimintojen ruudunlukijoille annettavia label-tekstejä.
Funktio-muotoisten lokalisaatio-objektin labelien saama parametri riippuu käyttötapauksesta. Tarkempia tietoja
labeleista ja niiden parametreista selviää tutkimalla omassa koodieditorissa komponentin saamia tyypityksiä.
<DateRangePickerV2
labelText="Date range"
helpText="Enter the dates as dd.mm.yyyy"
placeholder="dd.mm.yyyy"
fromDate="2020-01-01"
toDate="2040-12-31"
localization={{
ariaLabelRangeFrom: "Start date",
ariaLabelRangeTo: "End date",
ariaStatusClearDate: "Selected time interval removed",
labelClearDate: "Clear date",
labelWeekNumber: (week) => `Week ${week}`,
labelMonthDropdown: () => "Month",
labelYearDropdown: () => "Year",
labelNext: () => "Go to next month",
labelPrevious: () => "Go to previous month",
labelToggleCalendar: "Choose a date",
labelCloseCalendar: "Close the calendar",
labelSelected: "selected",
labelGoToCurrentDay: "Go to current day",
locale: enGB, // import { enGB } from "date-fns/locale/en-GB";
rangeErrorTexts: {
invalidRange: "Invalid time interval. Check the given dates.",
},
fromErrorTexts: {
dateDisabled: "The start date is not in the allowed date interval. Choose another date.",
invalidRelation: "The start date is incorrect. Check that the start date precedes the end date.",
invalidFormat: "The start date is incorrect. Enter the date as dd.mm.yyyy.",
notInRangeTo: "The start date is incorrect. Check that the date is within the allowed range.",
notInRangeFrom: "The start date is incorrect. Check that the date is within the allowed range.",
required: "The start date is mandatory information. Enter the date as dd.mm.yyyy.",
},
toErrorTexts: {
dateDisabled: "The end date is not in the allowed date interval. Choose another date.",
invalidRelation: "The end date is incorrect. Check that the start date precedes the end date.",
invalidFormat: "The end date is incorrect. Enter the date as dd.mm.yyyy.",
notInRangeTo: "The end date is incorrect. Check that the date is within the allowed range.",
notInRangeFrom: "The end date is incorrect. Check that the date is within the allowed range.",
required: "The end date is mandatory information. Enter the date as dd.mm.yyyy.",
},
}}
/>
Input-elementtien tulostukseen voi vaikuttaa hyödyntämällä renderInputs
-proppia, jolloin voidaan mm. muuttaa kenttien esitystapaa ja
välittää input-elementeille haluttuja attribuutteja. Input-kentät eivät saa maksimileveyttä renderInputs
-propin ollessa käytössä, joten
kenttien leveyttä voi tarvittaessa rajoittaa omilla CSS-määrityksillä, tai hyödyntämällä palstakomponentteja.
Toteuttajan vastuulle jää responsiivisen toiminnan ja saavutettavuuden varmistaminen.
Input-kentille voi renderInputs
-propin avulla tulostaa myös kenttäkohtaiset nimilaput ja virheviestit.
Huom! Alla oleva esimerkki ei ole malli valmiista ulkoasusta, vaan tekninen esimerkki renderInputs
-funktion käyttämisestä.
function DateRangePickerV2Example() {
const fromRef = useRef(null);
const toRef = useRef(null);
const [error, setError] = useState({ from: "", to: "" });
const [invalid, setInvalid] = useState({ from: false, to: false });
const [touched, setTouched] = useState({ from: false, to: false });
const handleError = (touched, validity) => {
const isFromValidatable = touched.from && !validity.from.isValid;
const isToValidatable = touched.to && !validity.to.isValid;
const isFromInvalid = isFromValidatable && (validity.field === "from" || invalid.from || !validity.field);
const isToInvalid = isToValidatable && (validity.field === "to" || invalid.to || !validity.field);
setTouched(touched);
setError({
from: isFromInvalid && validity.from.error.message,
to: isToInvalid && validity.to.error.message,
});
setInvalid({ from: isFromInvalid, to: isToInvalid });
};
const renderInputs = ({ FromInput, ToInput, fromInputProps, toInputProps, calendarButton }) => {
return (
<>
<div className="kds-flex kds-self-end" style={{ gap: "0.5rem" }}>
<span>
<InputLabel htmlFor={fromInputProps.id}>Alkamispäivä</InputLabel>
<FromInput
{...fromInputProps}
ref={fromRef}
aria-label={undefined}
aria-describedby={clsx([fromInputProps["aria-describedby"]], {
[`${fromInputProps.id}-error-text`]: invalid.from,
})}
/>
</span>
<span className="kds-flex kds-self-end kds-mb-2"> - </span>
<span>
<InputLabel htmlFor={toInputProps.id}>Päättymispäivä</InputLabel>
<ToInput
{...toInputProps}
ref={toRef}
aria-label={undefined}
aria-describedby={clsx([toInputProps["aria-describedby"]], {
[`${toInputProps.id}-error-text`]: invalid.to && !error.from,
})}
/>
</span>
<span className="kds-flex kds-self-end">{calendarButton}</span>
</div>
{invalid.from && error.from && (
<InputText error id={`${fromInputProps.id}-error-text`}>
{error.from}
</InputText>
)}
{invalid.to && error.to && !error.from && (
<InputText error id={`${toInputProps.id}-error-text`}>
{error.to}
</InputText>
)}
</>
);
};
return (
<DateRangePickerV2
required
labelText="Ajanjakso"
helpText="Ilmoita päivämäärät muodossa pp.kk.vvvv"
renderInputs={renderInputs}
invalid={invalid}
onChange={(dateRangeValue, validity) => {
console.log("onChange", dateRangeValue, validity);
handleError(touched, validity);
}}
onBlur={(e, validity) => {
console.log("onBlur", e.target.value, validity);
handleError(
{
from: validity?.field === "from" || touched?.from,
to: validity?.field === "to" || touched?.to,
},
validity
);
}}
onCalendarClose={(dateRangeValue, validity) => {
console.log("onCalendarClose", dateRangeValue, validity);
handleError({ from: true, to: true }, validity);
}}
/>
);
}
Tekniset rajoitteet
- Kontrolloituna komponenttina käytettäessä päivämääräarvo tulee antaa
value
-propille pp.kk.vvvv
-muodossa (samassa muodossa kuin input-kentässä).