Erişilebilirlik ve Uluslararasılaşma Akılda Tutularak Takvimler Oluşturma

Erişilebilirlik ve Uluslararasılaşma Akılda Tutularak Takvimler Oluşturma

Burada CSS-Tricks'te hızlı bir arama yapmak, takvimlere yaklaşmanın kaç farklı yolu olduğunu gösterir. Bazıları nasıl olduğunu gösterir CSS Grid, düzeni verimli bir şekilde oluşturabilir. bazıları teşebbüs gerçek verileri karışıma dahil edin. Biraz bir çerçeveye güvenmek devlet yönetimine yardımcı olmak.

Bir takvim bileşeni oluştururken göz önünde bulundurulması gereken pek çok şey vardır - bağlantısını verdiğim makalelerde ele alınanlardan çok daha fazlası. Düşünürseniz, takvimler, saat dilimlerini ve tarih biçimlerini işlemekten yerelleştirmeye ve hatta tarihlerin bir aydan diğerine akmasını sağlamaya kadar nüanslarla doludur… ve bu, takvimin nerede olduğuna bağlı olarak erişilebilirlik ve ek düzen konularına girmeden öncedir. görüntülenir ve ne olursa olsun.

Birçok geliştirici, Date() nesne ve gibi eski kütüphanelere sadık kalın moment.js. Ancak tarihler ve biçimlendirme söz konusu olduğunda pek çok "yakalama" olsa da, JavaScript'in yardımcı olacak pek çok harika API'si ve malzemesi vardır!

Ocak 2023 takvim kılavuzu.
Erişilebilirlik ve Uluslararasılaşma Akılda Tutularak Takvimler Oluşturma

Burada çarkı yeniden yaratmak istemiyorum ama size normal JavaScript ile çok iyi bir takvimi nasıl elde edebileceğimizi göstereceğim. inceleyeceğiz ulaşabilme, semantik biçimlendirme ve ekran okuyucu dostu kullanma <time> -etiketler - yanı sıra uluslararası ve biçimlendirmeKullanılarak Intl.Locale, Intl.DateTimeFormat ve Intl.NumberFormat-API'ler.

Başka bir deyişle, bir takvim yapıyoruz… yalnızca bunun gibi bir eğitimde kullanıldığını görebileceğiniz ekstra bağımlılıklar olmadan ve normalde göremeyebileceğiniz bazı nüanslarla. Ve bu süreçte, JavaScript'in yapabileceği daha yeni şeyler için yeni bir takdir kazanırken, bunun gibi bir şeyi bir araya getirdiğimde aklımdan geçen türden şeyler hakkında bir fikir edineceğinizi umuyorum.

Öncelikle adlandırma

Takvim bileşenimizi ne olarak adlandırmalıyız? Ana dilimde buna “kalender elementi” denirdi, öyleyse onu kullanalım ve “Kal-El” olarak da bilinen kısaltalım. Superman'in Kripton gezegenindeki adı.

İşlerin yürümesi için bir fonksiyon oluşturalım:

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

Bu yöntem işleyecek tek bir ay. Daha sonra bu yöntemi şu adresten arayacağız: [...Array(12).keys()] bütün bir yılı işlemek için.

İlk veriler ve uluslararasılaştırma

Tipik bir çevrimiçi takvimin yaptığı yaygın şeylerden biri, geçerli tarihi vurgulamaktır. Bunun için bir referans oluşturalım:

const today = new Date();

Ardından, isteğe bağlı olanla birleştireceğimiz bir "yapılandırma nesnesi" oluşturacağız. settings birincil yöntemin nesnesi:

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

Kök elemanın (<html>) içerir lang- öznitelik ile yerel bilgi; aksi takdirde, kullanmaya geri döneceğiz en-US. Bu doğru ilk adım takvimi uluslararası hale getirmek.

Ayrıca, takvim işlendiğinde başlangıçta hangi ayın görüntüleneceğini de belirlememiz gerekiyor. O yüzden uzattık config birincil olan nesne date. Bu şekilde, tarih belirtilmemişse settings nesne, kullanacağız today bunun yerine referans:

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

Takvimi yerel ayarlara göre düzgün bir şekilde biçimlendirmek için biraz daha bilgiye ihtiyacımız var. Örneğin, yerel ayarlara bağlı olarak haftanın ilk gününün Pazar mı yoksa Pazartesi mi olduğunu bilemeyebiliriz. Bilgimiz varsa, harika! Ancak değilse, kullanarak güncelleyeceğiz Intl.Locale API. API'nin bir weekInfo nesne bu bir döndürür firstDay bize tam olarak aradığımız şeyi sorunsuz bir şekilde veren özellik. Haftanın hangi günlerinin atandığını da öğrenebiliriz. weekend:

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

Yine yedekler yaratıyoruz. Haftanın “ilk günü” için en-US Pazar, bu nedenle varsayılan olarak bir değere sahiptir 7. Bu biraz kafa karıştırıcı, çünkü getDay yöntem JavaScript'te günleri şu şekilde döndürür: [0-6], Burada 0 Pazar… bana nedenini sorma. Hafta sonları cumartesi ve pazardır, dolayısıyla [6, 7].

sahip olmadan önce Intl.Locale API ve onun weekInfo yöntemiyle, her yerel ayar veya bölge hakkında bilgi içeren çok sayıda nesne ve dizi olmadan uluslararası bir takvim oluşturmak oldukça zordu. Günümüzde, kolay-peasy. eğer geçersek en-GB, yöntem şunu döndürür:

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

Brunei gibi bir ülkede (ms-BN), hafta sonu Cuma ve Pazar:

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

Bunun ne olduğunu merak edebilirsin minimalDays özelliktir. İşte bir ayın ilk haftasında tam bir hafta olarak sayılması için gereken en az gün. Bazı bölgelerde, sadece bir gün olabilir. Diğerleri için tam yedi gün olabilir.

Sonra, bir oluşturacağız render yöntem içimizdeki kalEl-yöntem:

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

Herhangi bir şey oluşturmadan önce çalışmak için biraz daha veriye ihtiyacımız var:

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

sonuncusu bir Boolean olup olmadığını kontrol eder today işlemek üzere olduğumuz ayda var.

Anlamsal biçimlendirme

Birazdan işlemede daha derine ineceğiz. Ama önce, kurduğumuz ayrıntıların kendileriyle ilişkilendirilmiş semantik HTML etiketlerine sahip olduğundan emin olmak istiyorum. Bunu kutudan çıkarır çıkarmaz ayarlamak, bize en baştan erişilebilirlik avantajları sağlıyor.

Takvim sarıcı

İlk olarak, anlamsal olmayan sarmalayıcımız var: <kal-el>. Bu iyi çünkü bir anlam bilgisi yok <calendar> etiket veya bunun gibi bir şey. Özel bir öğe yapmıyor olsaydık, <article> takvim kendi sayfasında durabileceği için en uygun öğe olabilir.

Ay isimleri

The <time> element, tarihleri ​​ekran okuyucuların ve arama motorlarının daha doğru ve tutarlı bir şekilde ayrıştırabileceği bir biçime çevirmeye yardımcı olduğu için bizim için büyük bir öğe olacak. Örneğin “Ocak 2023”ü işaretlememizde şu şekilde iletebiliriz:

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

gün isimleri

Haftanın günlerinin adlarını içeren takvim tarihlerinin üzerindeki satır yanıltıcı olabilir. Her gün için tam adları yazabilmemiz idealdir - örneğin Pazar, Pazartesi, Salı, vb. - ama bu çok yer kaplayabilir. Şimdilik isimleri bir dosyanın içinde kısaltalım. <ol> nerede her gün bir <li>:

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

Her iki dünyanın da en iyisini elde etmek için CSS ile zorlaşabiliriz. Örneğin, işaretlemeyi biraz şu şekilde değiştirirsek:

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

…varsayılan olarak tam adları alırız. Ardından, alan bittiğinde tam adı "gizleyebilir" ve title bunun yerine öznitelik:

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

Ama biz o yöne gitmiyoruz çünkü Intl.DateTimeFormat API burada da yardımcı olabilir. Bir sonraki bölümde işlemeyi ele aldığımızda buna geleceğiz.

gün numaraları

Takvim kılavuzundaki her tarih bir sayı alır. Her sayı bir liste öğesidir (<li>) sıralı bir listede (<ol>) ve satır içi <time> etiketi gerçek sayıyı sarar.

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

Ve henüz herhangi bir stil yapmayı planlamasam da, tarih numaralarını biçimlendirmek için bir yol isteyeceğimi biliyorum. Bu, olduğu gibi mümkün, ancak gerekirse hafta içi sayılarını hafta sonu sayılarından farklı şekilde biçimlendirebilmek istiyorum. Yani, dahil edeceğim data-* öznitelikleri özellikle bunun için: data-weekend ve data-today.

Hafta sayıları

Bir yılda 52, bazen 53 hafta vardır. Çok yaygın olmamakla birlikte, ek bağlam için takvimde belirli bir haftanın sayısını görüntülemek güzel olabilir. Kullanmamaya karar vermesem bile, şimdi sahip olmayı seviyorum. Ancak bu eğitimde tamamen kullanacağız.

kullanacağız data-weeknumber özelliğini bir stil kancası olarak kullanın ve haftanın ilk tarihi olan her tarih için işaretlemeye ekleyin.

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

rendering

Takvimi bir sayfaya alalım! bunu zaten biliyoruz <kal-el> özel öğemizin adıdır. Yapılandırmamız gereken ilk şey, firstDay Böylece takvim Pazar gününün mü yoksa başka bir günün mü haftanın ilk günü olduğunu bilir.

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

Kullanacağız şablon değişmez değerleri işaretlemeyi yapmak için. Tarihleri ​​uluslararası bir hedef kitle için biçimlendirmek üzere Intl.DateTimeFormat API, yine kullanarak locale daha önce belirttik.

ay ve yıl

aradığımızda monthkullanmak isteyip istemediğimizi ayarlayabiliriz. long adı (örneğin Şubat) veya short isim (örn. Şubat). kullanalım long isim çünkü takvimin üstündeki başlık:

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

hafta içi isimleri

Tarih ızgarasının üzerinde görüntülenen hafta içi günler için, her ikisine de ihtiyacımız var. long (örneğin “Pazar”) ve short (kısaltılmış, yani “Güneş”) adları. Bu şekilde, takvimde yer olmadığında “kısa” adını kullanabiliriz:

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

Her birini çağırmayı biraz daha kolaylaştıran küçük bir yardımcı yöntem yapalım:

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

Bunu şablonda şu şekilde çağırıyoruz:

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

gün numaraları

Ve nihayet, günler, bir sarılı <ol> eleman:

${[...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('')}

Bunu parçalayalım:

  1. Yinelemek için kullanacağımız "gün sayısı" değişkenine dayalı olarak "kukla" bir dizi oluşturuyoruz.
  2. Bir yaratıyoruz day yinelemedeki geçerli gün için değişken.
  3. arasındaki farkı düzeltiyoruz. Intl.Locale API ve getDay().
  4. Eğer day eşittir today, bir ekliyoruz data-* özniteliği.
  5. Son olarak, iade ediyoruz <li> birleştirilmiş verilerle bir dize olarak öğe.
  6. tabindex="0" herhangi bir pozitif tabindex değerinden sonra, klavye gezintisi kullanılırken öğeyi odaklanabilir hale getirir (Not: asla eklemek pozitif tabindex değerleri)

için sayıları “doldurun” içinde datetime öznitelik, küçük bir yardımcı yöntem kullanıyoruz:

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

Hafta numarası

Yine “hafta sayısı”, 52 haftalık bir takvimde haftanın denk geldiği yerdir. Bunun için de küçük bir yardımcı yöntem kullanıyoruz:

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

bunu ben yazmadım getWeek-yöntem. temizlenmiş versiyonudur bu senaryo.

Ve bu kadar! sayesinde Intl.Locale, Intl.DateTimeFormat ve Intl.NumberFormat API'ler, artık basitçe değiştirebiliriz lang-özelliği <html> geçerli bölgeye göre takvimin içeriğini değiştirmek için öğe:

Ocak 2023 takvim kılavuzu.
de-DE
Ocak 2023 takvim kılavuzu.
fa-IR
Ocak 2023 takvim kılavuzu.
zh-Hans-CN-u-nu-hanidec

Takvimi şekillendirmek

Bütün günlerin nasıl sadece bir olduğunu hatırlayabilirsin. <ol> liste öğeleri ile. Bunları okunabilir bir takvime dönüştürmek için CSS Grid'in harika dünyasına dalıyoruz. Aslında, aynı ızgarayı tam burada, CSS-Tricks'te bir başlangıç ​​takvimi şablonu, ancak bir parça güncellendi :is() kodu optimize etmek için ilişkisel sözde.

Yol boyunca yapılandırılabilir CSS değişkenleri tanımladığıma (ve bunların önüne ---kalel- çatışmalardan kaçınmak için).

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;
}
Izgara çizgilerinin gösterildiği yedi sütunlu takvim ızgarası.
Erişilebilirlik ve Uluslararasılaşma Akılda Tutularak Takvimler Oluşturma

Görsel olarak ayrılmalarına yardımcı olmak için tarih numaralarının çevresine kenarlıklar çizelim:

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

Yedi sütunlu ızgara, ayın ilk günü olduğunda iyi çalışır. Ayrıca seçilen yerel ayar için haftanın ilk günü). Ancak bu, kuraldan ziyade istisnadır. Çoğu zaman, ayın ilk gününü hafta içi farklı bir güne kaydırmamız gerekir.

Perşembe gününe denk gelen ayın ilk günü gösteriliyor.
Erişilebilirlik ve Uluslararasılaşma Akılda Tutularak Takvimler Oluşturma

Tüm ekstraları hatırla data-* işaretlememizi yazarken tanımladığımız nitelikler? Hangi ızgara sütununu (--kalel-li-gc) ayın ilk tarih numarası şuraya yerleştirilir:

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

Bu durumda, birinci ızgara sütunundan dördüncü ızgara sütununa yayıyoruz — bu, sonraki öğeyi (2. Gün) otomatik olarak beşinci ızgara sütununa "itecek" ve bu böyle devam edecek.

"Geçerli" tarihe biraz stil ekleyelim ki öne çıksın. Bunlar sadece benim stillerim. Burada istediğinizi tamamen yapabilirsiniz.

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

Hafta sonları için tarih numaralarını hafta içi günlerden farklı bir şekilde tasarlama fikrini seviyorum. Bunları şekillendirmek için kırmızımsı bir renk kullanacağım. için ulaşabileceğimizi unutmayın. :not() geçerli tarihi tek başına bırakırken bunları seçmek için sözde sınıf:

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

Oh, ve her haftanın ilk tarih sayısından önce gelen hafta numaralarını da unutmayalım. biz kullandık data-weeknumber bunun için işaretlemedeki özniteliği, ancak sayıları CSS ile göstermediğimiz sürece gerçekte görüntülenmeyecekler; ::before sözde eleman:

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

Teknik olarak bu noktada işimiz bitti! Verileri yerel ayarlara göre yerelleştirme ve takvimin uygun semantiği kullanmasını sağlama hususlarıyla birlikte, geçerli ayın tarihlerini gösteren bir takvim ızgarası oluşturabiliriz. Ve tek kullandığımız vanilya JavaScript ve CSS'di!

Ama şunu alalım bir adım daha...

Bütün bir yıl render

Belki de tam bir yılı görüntülemeniz gerekir! Bu nedenle, geçerli ayı oluşturmak yerine, geçerli yıl için tüm ay tablolarını görüntülemek isteyebilirsiniz.

Kullandığımız yaklaşımın güzel yanı, render yöntemini istediğimiz kadar çok kullanırız ve yalnızca her örnekte ayı tanımlayan tamsayıyı değiştiririz. İçinde bulunduğumuz yıla göre 12 kez diyelim.

çağırmak kadar basit render-yöntem 12 kez ve sadece için tamsayıyı değiştirin month - i:

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

Oluşturulan yıl için yeni bir ana sarmalayıcı oluşturmak muhtemelen iyi bir fikirdir. Her takvim ızgarası bir <kal-el> eleman. Yeni üst paketleyiciyi arayalım <jor-el>, Burada Jor-El, Kal-El'in babasının adıdır..

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

Biz kullanabilirsiniz <jor-el> ızgaralarımız için bir ızgara oluşturmak için. Yani 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);
}

Son demo

Bonus: Konfeti Takvimi

adlı mükemmel bir kitap okudum. Şebekeyi Yapmak ve Kırmak geçen gün ve bu güzel "Yeni Yıl afişi" ile karşılaştım:

Erişilebilirlik ve Uluslararasılaştırmayı Göz Önünde Bulundurarak Takvimler Oluşturmak PlatoBlockchain Veri Zekası. Dikey Arama. Ai.
Kaynak: Izgarayı Oluşturma ve Kırma (2. Baskı) kaydeden Timothy Samara

HTML veya JavaScript'te hiçbir şeyi değiştirmeden benzer bir şey yapabileceğimizi düşündüm. Daha okunaklı olması için ayların tam adlarını ve gün adları yerine sayıları dahil etme cüretinde bulundum. Eğlence!

Zaman Damgası:

Den fazla CSS Püf Noktaları