Izdelava koledarjev z mislijo na dostopnost in internacionalizacijo

Izdelava koledarjev z mislijo na dostopnost in internacionalizacijo

Izvorno vozlišče: 2009236

Hitro iskanje tukaj na CSS-Tricks pokaže, na koliko različnih načinov lahko pristopite k koledarjem. Nekateri kažejo, kako CSS Grid lahko učinkovito ustvari postavitev. Nekateri poskusi vključite dejanske podatke v mešanico. Nekatere zanašati se na okvir pomagati pri upravljanju države.

Pri izdelavi komponente koledarja je veliko premislekov - veliko več kot je zajeto v člankih, ki sem jih povezal. Če dobro pomislite, so koledarji polni nians, od upravljanja časovnih pasov in formatov datumov do lokalizacije in celo zagotavljanja, da datumi tečejo iz enega meseca v drugega ... in to še preden se sploh lotimo dostopnosti in dodatnih pomislekov o postavitvi, odvisno od tega, kje je koledar se prikaže in še kaj.

Mnogi razvijalci se bojijo Date() predmet in se držite starejših knjižnic, kot je moment.js. Toda čeprav je veliko "gotcha", ko gre za datume in oblikovanje, ima JavaScript veliko kul API-jev in stvari, ki vam lahko pomagajo!

Koledarska mreža za januar 2023.

Tukaj ne želim ponovno ustvariti kolesa, vendar vam bom pokazal, kako lahko dobimo prekleto dober koledar z vanilijevim JavaScriptom. Preučili bomo dostopnost, ki uporablja pomensko oznako in je prijazen bralniku zaslona <time> -oznake — kot tudi internacionalizacija in Oblikovanje, uporabljati Intl.Locale, Intl.DateTimeFormat in Intl.NumberFormat-API.

Z drugimi besedami, izdelujemo koledar ... samo brez dodatnih odvisnosti, ki jih običajno vidite v vadnici, kot je ta, in z nekaterimi niansami, ki jih običajno ne vidite. In upam, da boste med tem procesom pridobili novo vrednost za novejše stvari, ki jih lahko naredi JavaScript, hkrati pa boste dobili idejo o vrstah stvari, ki mi pridejo na misel, ko sestavljam nekaj takega.

Najprej poimenovanje

Kako naj imenujemo našo komponento koledarja? V mojem maternem jeziku bi se temu reklo »kalender element«, zato ga uporabimo in skrajšajmo na »Kal-El« — znan tudi kot Supermanovo ime na planetu Krypton.

Ustvarimo funkcijo, ki bo zagnala stvari:

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

Ta metoda bo upodobila en sam mesec. Kasneje bomo to metodo poklicali iz [...Array(12).keys()] upodabljati celo leto.

Začetni podatki in internacionalizacija

Ena od pogostih stvari, ki jih običajni spletni koledar počne, je, da poudari trenutni datum. Torej ustvarimo referenco za to:

const today = new Date();

Nato bomo ustvarili »konfiguracijski objekt«, ki ga bomo združili z neobveznim settings predmet primarne metode:

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

Preverimo, ali je korenski element (<html>) vsebuje a lang-atribut z locale informacije; sicer se bomo vrnili k uporabi en-US. To je prvi korak k internacionalizacijo koledarja.

Prav tako moramo določiti, kateri mesec naj bo prvotno prikazan, ko je koledar upodobljen. Zato smo podaljšali config objekt s primarnim date. Na ta način, če datum ni naveden v settings predmet, bomo uporabili today namesto tega referenca:

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

Potrebujemo malo več informacij za pravilno oblikovanje koledarja glede na lokalne nastavitve. Na primer, morda ne vemo, ali je prvi dan v tednu nedelja ali ponedeljek, odvisno od kraja. Če imamo informacije, super! Če pa ne, ga bomo posodobili z uporabo Intl.Locale API. API ima a weekInfo predmet ki vrne a firstDay lastnina, ki nam brez kakršnih koli težav nudi točno to, kar iščemo. Prav tako lahko ugotovimo, kateri dnevi v tednu so dodeljeni weekend:

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

Spet ustvarjamo rezerve. “Prvi dan” v tednu za en-US je nedelja, zato je privzeta vrednost 7. To je nekoliko zmedeno, saj getDay Metoda v JavaScript vrne dneve kot [0-6], Kjer 0 je nedelja… ne sprašuj me zakaj. Konci tedna so torej sobota in nedelja [6, 7].

Preden smo imeli Intl.Locale API in njegov weekInfo po metodi je bilo precej težko ustvariti mednarodni koledar brez številnih **predmetov in nizov z informacijami o vsakem jeziku ali regiji. Dandanes je enostavno. Če vstopimo en-GB, metoda vrne:

// en-GB
{ firstDay: 1, weekend: [6, 7], minimalDays: 4
}

V državi, kot je Brunej (ms-BN), vikend je petek in nedelja:

// ms-BN
{ firstDay: 7, weekend: [5, 7], minimalDays: 1
}

Morda se sprašujete, kaj to minimalDays lastnina je. To je tisto najmanj potrebnih dni v prvem tednu v mesecu, da se šteje kot polni teden. V nekaterih regijah je lahko samo en dan. Za druge lahko traja celih sedem dni.

Nato bomo ustvarili a render metoda znotraj našega kalEl-metoda:

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

Še vedno potrebujemo nekaj več podatkov, s katerimi lahko delamo, preden karkoli upodabljamo:

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);

Zadnji je a Boolean ki preverja, ali today obstaja v mesecu, ki ga bomo upodobili.

Semantična oznaka

Čez trenutek se bomo poglobili v upodabljanje. Najprej pa se želim prepričati, da imajo podrobnosti, ki jih nastavimo, povezane semantične oznake HTML. Nastavitev tega takoj po izdelavi nam že na začetku omogoča prednosti dostopnosti.

Ovoj koledarja

Najprej imamo nesemantični ovoj: <kal-el>. To je v redu, ker ni semantike <calendar> oznako ali kaj podobnega. Če ne bi izdelovali elementa po meri, <article> morda najbolj primeren element, saj bi koledar lahko stal na svoji strani.

Imena mesecev

O <time> element bo za nas velik, saj pomaga prevesti datume v obliko, ki jo lahko bralniki zaslona in iskalniki natančneje in dosledneje razčlenijo. Na primer, tukaj je opisano, kako lahko prenesemo »januar 2023« v našo oznako:

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

Imena dni

Vrstica nad datumi koledarja, ki vsebuje imena dni v tednu, je lahko težavna. Idealno je, če lahko napišemo polna imena za vsak dan – npr. nedelja, ponedeljek, torek itd. – vendar to lahko zavzame veliko prostora. Torej, skrajšajmo imena za zdaj znotraj <ol> kjer je vsak dan a <li>:

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

S CSS bi se lahko zapletli, da bi dobili najboljše iz obeh svetov. Na primer, če smo oznako spremenili nekoliko takole:

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

...privzeto dobimo polna imena. Nato lahko polno ime »skrijemo«, ko zmanjka prostora, in prikažemo title namesto tega atribut:

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

Vendar ne gremo tako, ker Intl.DateTimeFormat API lahko pomaga tudi tukaj. To bomo obravnavali v naslednjem razdelku, ko bomo obravnavali upodabljanje.

Številke dni

Vsak datum v mreži koledarja dobi številko. Vsaka številka je postavka seznama (<li>) na urejenem seznamu (<ol>), in inline <time> oznaka ovije dejansko številko.

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

In čeprav še ne nameravam oblikovati stilov, vem, da bom želela nekako stilizirati datumske številke. To je možno, kot je, vendar želim tudi, da bi lahko številke delovnih dni oblikoval drugače kot številke ob koncu tedna, če bi bilo treba. Torej, bom vključil data-* lastnosti posebej za to: data-weekend in data-today.

Številke tedna

V letu je 52 tednov, včasih 53. Čeprav ni zelo pogosto, je lahko lepo prikazati številko za določen teden v koledarju za dodaten kontekst. Všeč mi je, da ga imam zdaj, čeprav ga ne preneham uporabljati. Vendar ga bomo popolnoma uporabili v tej vadnici.

Uporabili bomo a data-weeknumber kot slogovni kavelj in ga vključite v oznako za vsak datum, ki je prvi datum v tednu.

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

Rendering

Dajmo koledar na stran! To že vemo <kal-el> je ime našega elementa po meri. Prva stvar, ki jo moramo konfigurirati, je nastaviti firstDay lastnino na njem, tako da koledar ve, ali je prvi dan v tednu nedelja ali kateri drug dan.

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

Uporabili bomo predlogi literal predloge za upodobitev oznake. Za oblikovanje datumov za mednarodno občinstvo bomo uporabili Intl.DateTimeFormat API, spet z uporabo locale smo določili prej.

Mesec in leto

Ko pokličemo month, lahko nastavimo, ali želimo uporabiti long ime (npr. februar) ali short ime (npr. feb.). Uporabimo long ime, ker je to naslov nad koledarjem:

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

Imena delovnih dni

Za dneve v tednu, prikazane nad mrežo datumov, potrebujemo oba long (npr. »nedelja«) in short (skrajšana, tj. »Sonce«) imena. Tako lahko uporabimo »kratko« ime, ko koledarju primanjkuje prostora:

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

Ustvarimo majhno pomožno metodo, ki olajša klic vsakega od njih:

const weekdays = (firstDay, locale) => { const date = new Date(0); const arr = [...Array(7).keys()].map(i => { date.setDate(5 + i) return { 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.pop()); return arr;
}

Tukaj je opisano, kako to prikličemo v predlogi:

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

Številke dni

In končno dnevi, zaviti v <ol> Element:

${[...Array(numOfDays).keys()].map(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':''; return ` <li data-day="${day}"${today}${i === 0 || day === config.info.firstDay ? ` data-weeknumber="${new Intl.NumberFormat(locale).format(getWeek(cur))}"`:''}${config.info.weekend.includes(day) ? ` data-weekend`:''}> <time datetime="${year}-${(pad(month))}-${pad(i)}" tabindex="0"> ${new Intl.NumberFormat(locale).format(i + 1)} </time> </li>`
}).join('')}

Razčlenimo to:

  1. Ustvarimo "navidezno" matriko, ki temelji na spremenljivki "število dni", ki jo bomo uporabili za ponavljanje.
  2. Ustvarimo a day spremenljivka za trenutni dan v ponovitvi.
  3. Odpravimo neskladje med Intl.Locale API in getDay().
  4. Če day je enako today, dodamo a data-* atribut.
  5. Na koncu vrnemo <li> element kot niz z združenimi podatki.
  6. tabindex="0" pri uporabi navigacije s tipkovnico naredi element osredotočen po vseh pozitivnih vrednostih tabindex (Opomba: morali bi nikoli dodajte pozitiven tabindex-values)

Da »podbijajte« številke v datetime atribut, uporabljamo majhno pomožno metodo:

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

Številka tedna

Še enkrat, "številka tedna" je mesto, kjer teden pade v 52-tedenskem koledarju. Tudi za to uporabljamo majhno pomožno metodo:

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); return 1 + Math.round(((date.getTime() - week.getTime()) / 86400000 - 3 + (week.getDay() + 6) % 7) / 7);
}

Tega nisem jaz napisal getWeek-metoda. Gre za prečiščeno različico ta scenarij.

In to je to! Hvala za Intl.Locale, Intl.DateTimeFormat in Intl.NumberFormat API-jev, lahko zdaj preprosto spremenimo lang-atribut <html> element za spreminjanje konteksta koledarja glede na trenutno regijo:

Koledarska mreža za januar 2023.
de-DE
Koledarska mreža za januar 2023.
fa-IR
Koledarska mreža za januar 2023.
zh-Hans-CN-u-nu-hanidec

Oblikovanje koledarja

Morda se spomnite, kako so vsi dnevi samo en <ol> z elementi seznama. Da bi jih oblikovali v berljiv koledar, se potopimo v čudoviti svet mreže CSS. Pravzaprav lahko isto mrežo preoblikujemo iz začetno predlogo koledarja tukaj na CSS-Tricks, vendar je nekoliko posodobil z :is() relacijski psevdo za optimizacijo kode.

Upoštevajte, da na poti definiram nastavljive spremenljivke CSS (in jim dodam predpono ---kalel- da bi se izognili konfliktom).

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;
}
Koledarska mreža s sedmimi stolpci s prikazanimi mrežnimi črtami.

Narišimo obrobe okrog datumskih številk, da jih vizualno ločimo:

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); }

Mreža s sedmimi stolpci deluje dobro, ko je prvi dan v mesecu Prav tako prvi dan v tednu za izbrano lokacijo). Ampak to je prej izjema kot pravilo. Največkrat bomo morali prvi dan v mesecu premakniti na drug dan v tednu.

Prikazuje prvi dan v mesecu, ki je četrtek.

Zapomnite si vse dodatke data-* atributov, ki smo jih definirali pri pisanju naše oznake? Te lahko zaskočimo, da posodobimo kateri stolpec mreže (--kalel-li-gc) prva številka meseca je postavljena na:

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

V tem primeru se raztezamo od prvega stolpca mreže do četrtega stolpca mreže – kar bo samodejno »potisnilo« naslednji element (2. dan) v peti stolpec mreže in tako naprej.

Dodajmo malo stila "trenutnemu" datumu, da bo izstopal. To so samo moji stili. Tukaj lahko počneš, kar hočeš.

[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;
}

Všeč mi je zamisel, da bi številke datumov za vikende oblikovali drugače kot za delavnike. Za oblikovanje teh bom uporabil rdečkasto barvo. Upoštevajte, da lahko dosežemo :not() psevdorazred, da jih izberete, trenutni datum pa pustite pri miru:

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

Oh, in ne pozabimo na številke tednov, ki so pred številko prvega datuma vsakega tedna. Uporabili smo a data-weeknumber v oznaki za to, vendar številke dejansko ne bodo prikazane, razen če jih razkrijemo s CSS, kar lahko storimo na ::before psevdoelement:

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

Na tej točki smo tehnično končali! Upodobimo lahko koledarsko mrežo, ki prikazuje datume za trenutni mesec, skupaj s premisleki o lokalizaciji podatkov po področnih nastavitvah in zagotavljanju, da koledar uporablja pravilno semantiko. In vse, kar smo uporabili, je bil JavaScript in CSS!

Ampak vzemimo to še en korak...

Upodabljanje celega leta

Morda morate prikazati celotno leto datumov! Torej, namesto upodabljanja trenutnega meseca, boste morda želeli prikazati vse mesečne mreže za tekoče leto.

No, dobra stvar pri pristopu, ki ga uporabljamo, je, da lahko pokličemo render metodo tolikokrat, kot želimo, in samo spremenimo celo število, ki identificira mesec na vsaki instanci. Pokličimo ga 12-krat glede na tekoče leto.

tako preprosto kot klicanje render-metoda 12-krat in samo spremenite celo število za month - i:

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

Verjetno je dobra ideja ustvariti nov nadrejeni ovoj za upodobljeno leto. Vsaka koledarska mreža je a <kal-el> element. Pokličimo nov nadrejeni ovoj <jor-el>, Kjer Jor-El je ime Kal-Elovega očeta.

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

Lahko uporabimo <jor-el> ustvariti mrežo za naše mreže. Torej meta!

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);
}

Končni demo

Bonus: Koledar konfetov

Prebral sem odlično knjigo z naslovom Izdelava in razbijanje mreže prejšnji dan in naletel na ta čudovit "novoletni plakat":

vir: Ustvarjanje in razbijanje mreže (2. izdaja) avtorja Timothy Samara

Mislil sem, da bi lahko naredili nekaj podobnega, ne da bi spremenili karkoli v HTML ali JavaScript. Vzel sem si svobodo vključiti polna imena mesecev in številke namesto imen dni, da bo bolj berljivo. Uživajte!

Časovni žig:

Več od Triki CSS

Astro

Izvorno vozlišče: 845518
Časovni žig: Maj 5, 2021