Kalenders maken met toegankelijkheid plusteken internationalisering ter ie gedachten | CSS-trucs


Ene snelle zoekopdracht hier op CSS-Tricks laat zien hoeveel verschillende wegen er zijn wegens zakagenda’s te toenaderen. Sommigen permitteren zien hoe CSS Grid kan de lay-out efficiënt maken. Sommigen trachten ie breng actuele gegevens in de mix. Sommige vertrouwen op een raamwerk wegens te promoten met ie zeggenschap van den staat.

Er zijn veel overwegingen tijdens ie opbouwen van ene kalendercomponent – veel meer dan watten wordt behandeld ter den waar diegene ik heb gekoppeld. Mits jou erover nadenkt, zijn kalenders laden met nuances, van ie omgaan met tijdzones plusteken datumnotaties totdat lokalisatie plusteken zelfs ervoor zorgen dat datums van den ene maand zoals den andere oversteken… wordt weergegeven plusteken zo.

Veel ontwikkelaars vrezen den Date() voorwerp plusteken blijf tijdens oudere bibliotheken zoals moment.js. Maar hoewel er veel “valkuilen” zijn mits ie gaat wegens datums plusteken opmaak, heeft JavaScript veel coole API’s plusteken waar wegens te promoten!

Kalenderraster januari 2023.

Ik wil ie wiel hier noch weer maken, maar ik zou jou permitteren zien hoe wij ene verdomd goede kalender kunnen krijgen met vanille JavaScript. Wij zullen ernaar loeren toegankelijkheidmet behulp van semantische opmaak plusteken schermlezervriendelijk <time> -tags — evenals internationalisering Plus opmaakden … tradities Intl.Locale, Intl.DateTimeFormat Plus Intl.NumberFormat-API’s.

Met andere woorden, wij maken ene kalender… eenzaam zonder den reserve afhankelijkheden diegene jou normaal gesproken ter ene zelfstudie mits dit ziet, plusteken met enkele nuances diegene jou normaal gesproken noch ziet. Plus tijdens ie veranderingsproces stapel ik dat jou ene nieuwe waardering krijgt voordat nieuwere waar diegene JavaScript vermag uitrichten, terwijl jou ene idee krijgt van ie soort waar waar ik aan denken mits ik zulk ter mekaar zet.

Ten eerste, naamgeving

Hoe willen wij onze kalendercomponent vermeld? Ter mijn moedertaal zullen ie ‘kalenderelement’ heten, dus permitteren wij dat tradities plusteken dat verminderen totdat ‘Kal-El’ — zowel beroemd mits Superman’s naam op de planeet Krypton.

Permitteren wij ene functie maken wegens waar op tempo te krijgen:

function kalEl(settings = {}) { ... }

Dit trant zou renderen ene enkele maand. Zometeen vermeld wij dit trant from (...Array(12).keys()) wegens ene ​​intact schooljaar weer te geven.

Eerste gegevens plusteken internationalisering

Ene van den gebruikelijke waar diegene ene typische online kalender doet, is den huidige datum markeren. Dus permitteren wij daarginds ene toespeling voordat maken:

const today = new Date();

Vervolgens maken wij ene “configuratie-object” dat wij samenvoegen met ie optionele settings object van den primaire trant:

const config = Object.assign(
  {
    locale: (document.documentElement.getAttribute('lang') || 'en-US'), 
    today: { 
      day: today.getDate(),
      month: today.getMonth(),
      year: today.getFullYear() 
    } 
  }, settings
);

Wij nakijken of ie wortelelement (<html>) bevatten ene lang-kenmerk met positie informatie; verschillend vallen wij terug op gewoonte en-US. Dit is den eerste stap zoals internationalisering van de kalender.

Wij willen zowel defini welke maand ter eerste instantie moeten wordt weergegeven wanneer den kalender wordt weergegeven. Daarom hebben wij den config verweerschrift maken tegen den primaire date. Op dit methode, mits er geen datum is opgegeven ter den settings object, tradities wij den today toespeling ter positie daarvan:

const date = config.date ? new Date(config.date) : today;

Wij hebben watten meer verwittiging nodig wegens den kalender op den juiste methode op te maken op fundament van den landinstelling. Wij weten bijvoorbeeld noch of den eerste dag van den week zondag of maandag is, onderschikkend van den landinstelling. Mits wij den verwittiging hebben, massaal! Maar zo noch, dan werken wij ie tijdens met behulp van den Intl.Locale API. Den API heeft ene weekInfo voorwerp dat geven ene terug firstDay kenmerk diegene onzerzijds precies geven watten wij zoeken, zonder enige moeite. Wij kunnen zowel zien welke dagen van den week zijn toegewezen aan den weekend:

if (!config.informatie) config.informatie = new Intl.Locale(config.locale).weekInfo || { 
  firstDay: 7,
  weekend: (6, 7) 
};

Nogmaals, wij creëren fallbacks. Den “eerste dag” van den week voordat en-US is zondag, dus ie is standaard ene waarde van 7. Dit is ene ietsje verwarrend, vermits den getDay methode ter JavaScript retourneert den dagen mits (0-6)waar 0 is zondag… vraag mij noch waarom. Den weekenden zijn dus zaterdag plusteken zondag (6, 7).

Voordat wij den Intl.Locale API plusteken zijn weekInfo trant wasgoed ie behoorlijk moeilijk wegens ene ​​internationale kalender te maken zonder veel **objecten plusteken arrays met verwittiging overheen elke locale of regio. Tegenwoordig is ie easy peasy. Mits wij binnentreden en-GBretourneert den trant:

// en-GB
{
  firstDay: 1,
  weekend: (6, 7),
  minimalDays: 4
}

Ter ene land mits Brunei (ms-BN), ie weekend is vrijdag plusteken zondag:

// ms-BN
{
  firstDay: 7,
  weekend: (5, 7),
  minimalDays: 1
}

Jij vraagt ​​jou misschien uit ​​watten dat is minimalDays eigendom is. Dat is den het minste aantal dagen dat in de eerste week van een maand nodig is om als een volledige week te worden geteld. Ter sommige regio’s vermag ie slechts één dag zijn. Voordat anderen vermag ie ene volledige zeven dagen zijn.

Vervolgens maken wij ene render trant hierbinnen onze kalEl-methode:

const render = (date, locale) => { ... }

Wij hebben nog watten meer gegevens nodig wegens mee te werken voordat wij iets kunnen renderen:

const month = date.getMonth();
const year = date.getFullYear();
const numOfDays = new Date(year, month + 1, 0).getDate();
const renderToday = (year === config.today.year) && (month === config.today.month);

Den laatste is ene Boolean dat controleert of today bestaat ter den maand diegene wij gaan renderen.

Semantische opmaak

Wij gaan zo dadelijk dieper ter op weergave. Maar voorafgaand wil ik ervoor zorgen dat den details diegene wij klaarmaken gekoppeld zijn aan semantische HTML-tags. Vanwege dat ongezouten uit den opbergdoos op te zetwerk, krijgen wij vanaf ie start toegankelijkheidsvoordelen.

Kalenderomslag

Ten eerste hebben wij den niet-semantische wrapper: <kal-el>. Dat is prima, want er is geen semantiek <calendar> tag of iets dergelijks. Mits wij geen aanpassen factor zullen maken, <article> zullen ie uiterst geschikte factor kunnen zijn, vermits den kalender op zijn inherent pagina vermag staan.

Maand namen

Den <time> factor wordt ene volwassene voordat onzerzijds omdat ie helpt tijdens ie vertalen van datums zoals ene rangschikking diegene schermlezers plusteken zoekmachines nauwkeuriger plusteken consistenter kunnen analyseren. Zo kunnen wij bijvoorbeeld “januari 2023” overbrengen ter onze opmaak:

<time datetime="2023-01">January <i>2023</i></time>

Dag namen

Den rij bovenin den datums van den kalender met den namen van den dagen van den week vermag lastig zijn. Ie is ideaal mits wij den volledige namen voordat elke dag kunnen registreren — bijvoorbeeld zondag, maandag, dinsdag, enzovoorts. — maar dat vermag veel ruimte ter deeg nemen. Dus permitteren wij den namen voordat nu afkorten ter ene <ol> waar elke dag ene is <li>:

<ol>
  <li><abbr title="Sunday">Zon</abbr></li>
  <li><abbr title="Monday">Mon</abbr></li>
  <!-- etc. -->
</ol>

Wij kunnen lastig wordt met CSS wegens ie liefste van twee werelden te krijgen. Mits wij den opmaak bijvoorbeeld ene ietsje mits volgt hebben aanpassen:

<ol>
  <li>
    <abbr title="S">Sunday</abbr>
  </li>
</ol>

…wij krijgen standaard den volledige namen. Wij kunnen dan den volledige naam “verhullen” wanneer den ruimte opraakt plusteken den title attribuut ter positie daarvan:

@media all and (max-width: 800px) {
  li abbr::after {
    content: attr(title);
  }
}

Maar wij gaan noch diegene kantkloswerk op, want den Intl.DateTimeFormat API vermag hier zowel promoten. Daarheen komen wij ter den volgende vakgroep op terug mits wij ie overheen renderen hebben.

Dag nummers

Elke datum ter ie kalenderraster krijgt ene nummer. Elk nummer is ene lijstitem (<li>) ter ene geordende opsomming (<ol>), plusteken den inline <time> tag omsluit ie werkelijke veel.

<li>
  <time datetime="2023-01-01">1</time>
</li>

Plus hoewel ik nog noch van project ben wegens aan styling te uitrichten, weet ik dat ik ene methode wil wegens den datumnummers te stylen. Dat is mogelijk zoals ie is, maar ik wil zowel doordeweekse nummers verschillend kunnen stylen dan weekendnummers mits dat nodig is. Dus ik ga tapen data-* attributen speciaal daarvoor: data-weekend Plus data-today.

Week nummers

Er zijn 52 weken ter ene schooljaar, somwijlen 53. Hoewel ie noch intact gewoon is, vermag ie wieg zijn wegens ie nummer voordat ene bepalend week ter den kalender weer te geven voordat reserve setting. Ik vind ie wieg wegens ie nu te hebben, zelfs mits ik noch raadsbesluit ie noch te tradities. Maar wij zullen ie volledig tradities ter dit zelfstudie.

Wij tradities ene data-weeknumber attribuut mits stijlhaak plusteken neem ie op ter den opmaak voordat elke datum diegene den eerste datum van den week is.

<li data-day="7" data-weeknumber="1" data-weekend="">
  <time datetime="2023-01-08">8</time>
</li>

Weergave

Permitteren wij den kalender op ene pagina krijgen! Dat weten wij reeds <kal-el> is den naam van onzerzijds aangepaste factor. Ie eerste dat wij willen configureren, is ie klaarmaken van ie firstDay kenmerk erop, zodat den kalender weet of zondag of ene andere dag den eerste dag van den week is.

<kal-el data-firstday="${ config.informatie.firstDay }">

Wij zullen tradities letterlijke sjablonen wegens den opmaak weer te geven. Wegens den datums op te maken voordat ene internationaal publiek, tradities wij den Intl.DateTimeFormat API, weer met behulp van den locale wij vroeger specificeerden.

Den maand plusteken ie schooljaar

Mits wij opbellen met den monthkunnen wij klaarmaken of wij den long naam (bijvoorbeeld sprokkelmaand) of den short naam (bijv. feb.). Permitteren wij den tradities long naam vermits ie den waardigheidstitel bovenin den kalender is:

<time datetime="${year}-${(padachtige(month))}">
  ${new Intl.DateTimeFormat(
    locale,
    { month:'long'}).format(date)} <i>${year}</i>
</time>

Namen van weekdagen

Voordat weekdagen diegene bovenin ie datumraster wordt weergegeven, hebben wij zowel den long (bijv. “Zondag”) plusteken short (afgekort, d.w.z. “Zon”) namen. Op dit methode kunnen wij den “korte” naam tradities wanneer den kalender weinig ruimte heeft:

Intl.DateTimeFormat((locale), { weekday: 'long' })
Intl.DateTimeFormat((locale), { weekday: 'short' })

Permitteren wij ene kleine hulpmethode maken diegene ie iets gemakkelijker maakt wegens zij allen te opbellen:

const weekdays = (firstDay, locale) => {
  const date = new Date(0);
  const arr = (...Array(7).keys()).opbergmap(i => {
    date.setDate(5 + i)
    terugwedstrijd {
      long: new Intl.DateTimeFormat((locale), { weekday: 'long'}).format(date),
      short: new Intl.DateTimeFormat((locale), { weekday: 'short'}).format(date)
    }
  })
  for (let i = 0; i < 8 - firstDay; i++) arr.splice(0, 0, arr.speelpop());
  terugwedstrijd arr;
}

Hier is hoe wij dat toeroepen ter den sjabloon:

<ol>
  ${weekdays(config.informatie.firstDay,locale).opbergmap(name => `
    <li>
      <abbr title="${name.long}">${name.short}</abbr>
    </li>`).join('')
  }
</ol>

Dag nummers

Plus totdat slot, den dagen, verpakt ter ene <ol> factor:

${(...Array(numOfDays).keys()).opbergmap(i => {
  const cur = new Date(year, month, i + 1);
  let day = cur.getDay(); if (day === 0) day = 7;
  const today = renderToday && (config.today.day === i + 1) ? ' data-today':'';
  terugwedstrijd `
    <li data-day="${day}"${today}${i === 0 || day === config.informatie.firstDay ? ` data-weeknumber="${new Intl.NumberFormat(locale).format(getWeek(cur))}"`:''}${config.informatie.weekend.includes(day) ? ` data-weekend`:''}>
      <time datetime="${year}-${(padachtige(month))}-${padachtige(i)}" tabindex="0">
        ${new Intl.NumberFormat(locale).format(i + 1)}
      </time>
    </li>`
}).join('')}

Permitteren wij dat opsplitsen:

  1. Wij maken ene “dummy”-array, gebaseerd op den variabele “veel dagen”, diegene wij zullen tradities wegens te oefenen.
  2. Wij creëren ene day variabele voordat den huidige dag ter den iteratie.
  3. Wij repareren den discrepantie tussen den Intl.Locale API-en getDay().
  4. Mits den day is identiek aan todayvoegen wij ene toe data-* attribuut.
  5. Mits laatste omdraaien wij den <li> factor mits ene tekenreeks met samengevoegde gegevens.
  6. tabindex="0" maakt ie factor focusbaar, tijdens gewoonte van toetsenbordnavigatie, na positieve tabindexwaarden (Opmerking: u zullen nooit toevoegen positief tabindex-waarden)

Zoals “pad” de cijfers ter den datetime attribuut, tradities wij ene kleine helpermethode:

const padachtige = (val) => (val + 1).toString().padStart(2, '0');

Weeknummer

Nogmaals, ie “weeknummer” is waar ene week valt ter ene kalender met 52 weken. Zowel daarvoor tradities wij ene kleine helpermethode:

function getWeek(cur) {
  const date = new Date(cur.getTime());
  date.setHours(0, 0, 0, 0);
  date.setDate(date.getDate() + 3 - (date.getDay() + 6) % 7);
  const week = new Date(date.getFullYear(), 0, 4);
  terugwedstrijd 1 + Math.round(((date.getTime() - week.getTime()) / 86400000 - 3 + (week.getDay() + 6) % 7) / 7);
}

Ik heb dit noch schrijven getWeek-methode. Ie is ene opgeschoonde versie van dit schrift.

Plus dat is ie! Dankzij den Intl.Locale, Intl.DateTimeFormat Plus Intl.NumberFormat API’s kunnen wij nu eenvoudig den lang-attribuut van den <html> factor wegens den setting van den kalender te wijzigen op fundament van den huidige regio:

Kalenderraster januari 2023.
de-DE
Kalenderraster januari 2023.
fa-IR
Kalenderraster januari 2023.
zh-Hans-CN-u-nu-hanidec

Ie stylen van den kalender

Jij herinnert jou misschien hoe alle dagen slechts één zijn <ol> met lijstitems. Wegens dit ter ene leesbare kalender te stylen, vooroverduiken wij ter den wondere wereld van CSS Grid. Ter feite kunnen wij idem raster weer tradities een sjabloon voor een starterskalender hier op CSS-Tricksmaar ene smidge bijgewerkt met den :is() relationele pseudo wegens den code te optimaliseren.

Merknaam op dat ik onderweg configureerbare CSS-variabelen definieer (plusteken zij voorvoegsel met ---kalel- wegens conflicten te voorkomen).

kal-el :is(ol, ul) {
  display: grid;
  font-size: var(--kalel-fz, small);
  grid-row-gap: var(--kalel-row-gap, .33em);
  grid-template-columns: var(--kalel-gtc, repeat(7, 1fr));
  list-style: none;
  margin: unset;
  padding: unset;
  position: relative;
}
Kalenderraster met zeven kolommen met weergegeven rasterlijnen.

Permitteren wij randen rond den datumnummers tekenen wegens zij visueel te scheiden:

kal-el :is(ol, ul) li {
  border-color: var(--kalel-li-bdc, hsl(0, 0%, 80%));
  border-style: var(--kalel-li-bds, solid);
  border-width: var(--kalel-li-bdw, 0 0 1px 0);
  grid-column: var(--kalel-li-gc, initial);
  text-align: var(--kalel-li-tal, end); 
}

Ie raster met zeven kolommen werkt prima mits ie den eerste dag van den maand is Zowel den eerste dag van den week voordat den geselecteerde locale). Maar dat is vroeger uitzondering dan wet. Veelal willen wij den eerste dag van den maand verschuiven zoals ene andere weekdag.

Toont de eerste dag van de maand die op een donderdag valt.

Onthoud reeds ie reserve data-* attributen diegene wij hebben gedefinieerd tijdens ie schrijven van onze markup? Wij kunnen daarop inhaken wegens tijdens te werken welke rasterkolom (--kalel-li-gc) ie eerste datumnummer van den maand wordt situeren op:

(data-firstday="1") (data-day="3"):first-child {
  --kalel-li-gc: 1 / 4;
}

Ter dit geval gaan wij van den eerste rasterkolom zoals den vierde rasterkolom, waardoor ie volgende voorwerp (dag 2) vanzelf zoals den vijfde rasterkolom wordt “gestoten”, enzovoorts.

Permitteren wij ene ietsje stijl toevoegen aan den “huidige” datum, zodat dit opvalt. Dit zijn gewoon mijn stijlen. Jij kunt hier helemaal uitrichten watten jou wilt.

(data-today) {
  --kalel-day-bdrs: 50%;
  --kalel-day-bg: hsl(0, 86%, 40%);
  --kalel-day-hover-bgc: hsl(0, 86%, 70%);
  --kalel-day-c: #fff;
}

Ik hou van ie idee wegens den datumnummers voordat weekenden verschillend te stylen dan weekdagen. Ik ga ene roodachtige kleur tradities wegens diegene te stylen. Merknaam op dat wij kunnen reiken zoals den :not() pseudo-class wegens zij te uitpikken terwijl den huidige datum eenzaam wordt lijdelijk:

(data-weekend):not((data-today)) { 
  --kalel-day-c: var(--kalel-weekend-c, hsl(0, 86%, 46%));
}

Oh, plusteken permitteren wij den weeknummers noch vergeten diegene voordat ie eerste datumnummer van elke week gaan. Wij gebruikten ene data-weeknumber attribuut ter den opmaak daarvoor, maar den getallen wordt stap feitelijk weergegeven mits wij zij ontmaskeren met CSS, watten wij kunnen uitrichten op den ::before pseudo-element:

(data-weeknumber)::before {
  display: var(--kalel-weeknumber-d, inline-block);
  content: attr(data-weeknumber);
  position: absolute;
  inset-inline-start: 0;
  /* additional styles */
}

Wij zijn op dit punt technisch klaar! Wij kunnen ene kalenderraster maken dat den datums voordat den huidige maand weergeeft, compleet met overwegingen voordat ie lokaliseren van den gegevens vanaf landinstelling plusteken ervoor te zorgen dat den kalender den juiste semantiek tweedehands. Plus alles watten wij gebruikten wasgoed vanille JavaScript plusteken CSS!

Maar permitteren wij dit nemen nog ene stap

Ene intact schooljaar renderen

Misschien moeten u ene volledig schooljaar met datums weergeven! Dus ter positie van den huidige maand weer te geven, wilt u misschien alle maandrasters voordat ie huidige schooljaar weergeven.

Welnu, ie leuke van den werkwijze diegene wij tradities, is dat wij den render trant zo vaak mits wij willen plusteken verander eenzaam ie gehele numero dat den maand identificeert tijdens elke instantie. Permitteren wij ie 12 keer vermeld op fundament van ie huidige schooljaar.

televisiekanaal zo eenvoudig mits ie opbellen van den render-methode 12 keer, plusteken verander gewoon ie gehele numero voordat monthi:

(...Array(12).keys()).opbergmap(i =>
  render(
    new Date(date.getFullYear(),
    i,
    date.getDate()),
    config.locale,
    date.getMonth()
  )
).join('')

Ie is waarschijnlijk ene ja idee wegens ene ​​nieuwe bovenliggende wrapper te maken voordat ie weergegeven schooljaar. Elk agendaraster is ene <kal-el> factor. Permitteren wij den nieuwe bovenliggende wrapper vermeld <jor-el>waar Jor-El is de naam van de vader van Kal-El.

<jor-el id="app" data-year="true">
  <kal-el data-firstday="7">
    <!-- etc. -->
  </kal-el>

  <!-- other months -->
</jor-el>

Wij kunnen tradities <jor-el> wegens ene ​​raster voordat onze rasters te maken. Meta dus!

jor-el {
  background: var(--jorel-bg, none);
  display: var(--jorel-d, grid);
  gap: var(--jorel-gap, 2.5rem);
  grid-template-columns: var(--jorel-gtc, repeat(auto-fill, minmax(320px, 1fr)));
  padding: var(--jorel-p, 0);
}

Laatste demo

Verzekeringspremie: Confetti-kalender

Ik samenvoeging ene uitstekend proza genaamd Het maken en breken van het raster onlangs plusteken kwam dit prachtige “Nieuwjaarsposter” tegen:

Put: Het raster maken en breken (2e editie) doorheen Timothy Samara

Ik denk dat wij iets soortgelijks konden uitrichten zonder iets ter den HTML of JavaScript te veranderen. Ik heb den vrijheid genomen wegens maanden volledige namen op te nemen, plusteken nummers ter positie van dagnamen, wegens ie leesbaarder te maken. Smullen!