Skip to content

Commit cb264cd

Browse files
authored
added opening hours voice over logic and format time (#4080)
2 parents 1074dfd + 178e6de commit cb264cd

14 files changed

Lines changed: 130 additions & 43 deletions

File tree

‎storefront/components/Basic/Flag/Flag.tsx‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,10 @@ type FlagProps = {
66
href?: string;
77
type?: 'blog' | 'discount';
88
rgbBgColor?: string;
9+
ariaLabel?: string;
910
};
1011

11-
export const Flag: FC<FlagProps> = ({ children, href, className, type, rgbBgColor }) => {
12+
export const Flag: FC<FlagProps> = ({ children, href, className, type, rgbBgColor, ariaLabel }) => {
1213
const router = useRouter();
1314

1415
const textColor = rgbBgColor && getYIQContrastTextColor(rgbBgColor);
@@ -40,6 +41,7 @@ export const Flag: FC<FlagProps> = ({ children, href, className, type, rgbBgColo
4041
if (href) {
4142
return (
4243
<button
44+
aria-label={ariaLabel}
4345
className={flagTwClass}
4446
style={{ backgroundColor: rgbBgColor }}
4547
tabIndex={0}
@@ -52,7 +54,7 @@ export const Flag: FC<FlagProps> = ({ children, href, className, type, rgbBgColo
5254
}
5355

5456
return (
55-
<div className={flagTwClass} style={{ backgroundColor: rgbBgColor }}>
57+
<div aria-label={ariaLabel} className={flagTwClass} style={{ backgroundColor: rgbBgColor }}>
5658
{children}
5759
</div>
5860
);

‎storefront/components/Basic/Infobox/Infobox.tsx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { ChatIcon } from 'components/Basic/Icon/ChatIcon';
33
export const Infobox: FC<{ message: string }> = ({ message }) => {
44
return (
55
<div className="bg-background-accent-less text-text-accent inline-flex w-full items-center justify-start gap-1 rounded-md px-4 py-2.5 text-sm font-semibold">
6-
<ChatIcon className="text-2x size-6" />
6+
<ChatIcon aria-hidden="true" className="text-2x size-6" />
77
{message}
88
</div>
99
);

‎storefront/components/Blocks/OpeningHours/OpeningHours.tsx‎

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { TypeOpeningHours } from 'graphql/types';
44
import useTranslation from 'next-translate/useTranslation';
55
import { Fragment } from 'react';
66
import { twJoin } from 'tailwind-merge';
7+
import { formatAccessibleTime } from 'utils/accessibility/formatAccessibleTime';
78
import { useFormatDate } from 'utils/formatting/useFormatDate';
89
import { StoreOrPacketeryPoint } from 'utils/packetery/types';
910
import { twMergeCustom } from 'utils/twMerge';
@@ -12,7 +13,7 @@ export const OpeningHours: FC<{ openingHours: StoreOrPacketeryPoint['openingHour
1213
openingHours,
1314
className,
1415
}) => {
15-
const { t } = useTranslation();
16+
const { t, lang } = useTranslation();
1617
const { formatDate } = useFormatDate();
1718

1819
const getDayName = (currentDayOfWeek: number, requestedDayOfWeek: number): string => {
@@ -72,34 +73,55 @@ export const OpeningHours: FC<{ openingHours: StoreOrPacketeryPoint['openingHour
7273
})}
7374

7475
<div
76+
aria-label={t('Opening hours')}
7577
className={twMergeCustom('text-text-default flex flex-col gap-1 self-baseline text-xs', className)}
7678
data-tid={TIDs.opening_hours}
79+
role="list"
7780
>
7881
{openingHours.openingHoursOfDays.map(({ date, dayOfWeek, openingHoursRanges }) => {
7982
const isToday = openingHours.dayOfWeek === dayOfWeek;
8083
const isClosedWholeDay = openingHoursRanges.length === 0;
8184

85+
const ariaClosedText = `${getDayName(openingHours.dayOfWeek, dayOfWeek)} ${formatDate(date)}, ${t('Closed')}`;
86+
const ariaOpenText = `${getDayName(openingHours.dayOfWeek, dayOfWeek)} ${formatDate(date)}, ${t('Open')} ${openingHoursRanges
87+
.map(({ openingTime, closingTime }) => {
88+
const openingFormatted = formatAccessibleTime(openingTime, lang);
89+
const closingFormatted = formatAccessibleTime(closingTime, lang);
90+
91+
return `${openingFormatted} ${t('to')} ${closingFormatted}`;
92+
})
93+
.join(', ')}`;
94+
95+
const dayAriaText = isClosedWholeDay ? ariaClosedText : ariaOpenText;
96+
8297
return (
8398
<div
8499
key={dayOfWeek}
100+
aria-current={isToday ? 'date' : undefined}
101+
aria-label={dayAriaText}
85102
className={twJoin(
86103
'flex flex-col flex-wrap gap-x-5 gap-y-2 rounded-lg p-2 sm:flex-row sm:items-center',
87104
isToday ? 'bg-background-accent-less' : 'hover:bg-background-more',
88105
)}
89106
>
90-
<span className="h6 w-44">
107+
<span aria-hidden="true" className="h6 w-44">
91108
{getDayName(openingHours.dayOfWeek, dayOfWeek)} <span>{formatDate(date)}</span>
92109
</span>
93110

94-
<span>
111+
<span aria-hidden="true">
95112
{isClosedWholeDay ? (
96113
<>{t('Closed')}</>
97114
) : (
98-
openingHoursRanges.map(({ openingTime, closingTime }, index) => (
99-
<Fragment key={index}>
100-
{index > 0 && ','} {openingTime}&nbsp;&#8209;&nbsp;{closingTime}
101-
</Fragment>
102-
))
115+
openingHoursRanges.map(({ openingTime, closingTime }, index) => {
116+
const openingFormatted = formatAccessibleTime(openingTime, lang);
117+
const closingFormatted = formatAccessibleTime(closingTime, lang);
118+
119+
return (
120+
<Fragment key={index}>
121+
{index > 0 && ','} {openingFormatted} - {closingFormatted}
122+
</Fragment>
123+
);
124+
})
103125
)}
104126
</span>
105127

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { TIDs } from 'cypress/tids';
2+
import { TypeOpeningHours } from 'graphql/types';
3+
import useTranslation from 'next-translate/useTranslation';
4+
import { formatAccessibleTime } from 'utils/accessibility/formatAccessibleTime';
5+
6+
export default function OpeningHoursToday({ openingHours }: { openingHours: TypeOpeningHours }) {
7+
const { t, lang } = useTranslation();
8+
9+
const todayOpeningDayRanges = openingHours.openingHoursOfDays[0].openingHoursRanges;
10+
11+
if (todayOpeningDayRanges.length === 0) {
12+
return null;
13+
}
14+
15+
const ariaLabel = `${t('Today opening hours')} ${todayOpeningDayRanges
16+
.map(({ openingTime, closingTime }) => {
17+
const openingFormatted = formatAccessibleTime(openingTime, lang);
18+
const closingFormatted = formatAccessibleTime(closingTime, lang);
19+
20+
return `${openingFormatted} ${t('to')} ${closingFormatted}`;
21+
})
22+
.join(', ')}`;
23+
24+
return (
25+
<div aria-current="date" aria-label={ariaLabel} className="ml-2.5 text-xs" data-tid={TIDs.store_opening_hours}>
26+
{todayOpeningDayRanges.map(({ openingTime, closingTime }, index) => {
27+
const openingFormatted = formatAccessibleTime(openingTime, lang);
28+
const closingFormatted = formatAccessibleTime(closingTime, lang);
29+
30+
return (
31+
<span key={index} aria-hidden="true">
32+
{index > 0 && ','} {openingFormatted} - {closingFormatted}
33+
</span>
34+
);
35+
})}
36+
</div>
37+
);
38+
}

‎storefront/components/Blocks/OpeningHours/OpeningStatus.tsx‎

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ export const OpeningStatus: FC<OpeningStatusProps> = ({ status, className }) =>
2626
}
2727
};
2828

29+
const ariaLabel = t('Currently {{status}}', { status: statusText(status) });
30+
2931
const statusClasses = {
3032
[TypeStoreOpeningStatusEnum.Open]: 'bg-opening-status-open',
3133
[TypeStoreOpeningStatusEnum.Closed]: 'bg-opening-status-closed',
@@ -34,8 +36,11 @@ export const OpeningStatus: FC<OpeningStatusProps> = ({ status, className }) =>
3436
};
3537

3638
return (
37-
<Flag className={twMergeCustom('text-text-inverted text-nowrap', className, statusClasses[status])}>
38-
{statusText(status)}
39+
<Flag
40+
ariaLabel={ariaLabel}
41+
className={twMergeCustom('text-text-inverted text-nowrap', className, statusClasses[status])}
42+
>
43+
<div aria-hidden="true">{statusText(status)}</div>
3944
</Flag>
4045
);
4146
};

‎storefront/components/Blocks/StoreList/StoreListItem.tsx‎

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,13 @@ import { AnimateCollapseDiv } from 'components/Basic/Animations/AnimateCollapseD
33
import { ArrowIcon } from 'components/Basic/Icon/ArrowIcon';
44
import { Infobox } from 'components/Basic/Infobox/Infobox';
55
import { OpeningHours } from 'components/Blocks/OpeningHours/OpeningHours';
6+
import OpeningHoursToday from 'components/Blocks/OpeningHours/OpeningHoursToday';
67
import { OpeningStatus } from 'components/Blocks/OpeningHours/OpeningStatus';
78
import { LinkButton } from 'components/Forms/Button/LinkButton';
89
import { TIDs } from 'cypress/tids';
910
import { AnimatePresence } from 'framer-motion';
1011
import useTranslation from 'next-translate/useTranslation';
1112
import { useEffect, useRef, useState } from 'react';
12-
import { getTodayOpeningHours } from 'utils/openingHours/openingHoursHelper';
1313
import { StoreOrPacketeryPoint } from 'utils/packetery/types';
1414
import { twMergeCustom } from 'utils/twMerge';
1515

@@ -71,31 +71,37 @@ export const StoreListItem: FC<StoreListItemProps> = ({ store, isSelected }) =>
7171
setIsExpanded((isExpanded) => !isExpanded);
7272
}}
7373
>
74-
<div className="flex items-center justify-between gap-3.5">
74+
<div aria-label={t('Store info')} className="flex items-center justify-between gap-3.5">
7575
<div className="w-full items-center justify-between xl:flex">
7676
<div className="max-xl:mb-2.5 xl:w-[215px]">
7777
<span className="h5">{store.name}</span>
78-
<p className="mt-1.5 text-xs">
78+
79+
<p aria-label={t('Address')} className="mt-1.5 text-xs">
7980
{store.street}, {store.postcode} {store.city}
8081
</p>
8182
</div>
83+
8284
{store.distance && (
8385
<p className="text-input-placeholder-default text-xs max-xl:hidden">
8486
{t('{{ distance }} km from you', {
8587
distance: (store.distance / 1000).toFixed(0),
8688
})}
8789
</p>
8890
)}
89-
<div className="flex w-44 items-center xl:block xl:text-right" data-tid={TIDs.store_opening_status}>
91+
<div
92+
className="flex items-center xl:block xl:w-44 xl:text-right"
93+
data-tid={TIDs.store_opening_status}
94+
>
9095
<OpeningStatus className="xl:mb-1.5" status={store.openingHours.status} />
91-
<p className="ml-2.5 text-xs" data-tid={TIDs.store_opening_hours}>
92-
{getTodayOpeningHours(store.openingHours)}
93-
</p>
96+
97+
<OpeningHoursToday openingHours={store.openingHours} />
9498
</div>
9599
</div>
96-
<div>
97-
<ArrowIcon className={`size-5 motion-safe:transform ${isExpanded ? 'rotate-180' : ''}`} />
98-
</div>
100+
101+
<ArrowIcon
102+
aria-hidden="true"
103+
className={`size-5 motion-safe:transform ${isExpanded ? 'rotate-180' : ''}`}
104+
/>
99105
</div>
100106

101107
<div id={storeInfoId}>
Loading
-3 Bytes
Loading
-159 Bytes
Loading

‎storefront/public/locales/cs/common.json‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
"Add to cart {{ productName }}, quantity {{ quantity }} {{ unit }} for {{ price }}": "Přidat do košíku {{ productName }}, množství {{ quantity }} {{ unit }} za {{ price }}",
1818
"Add to wishlist": "Přidat do oblíbených produktů",
1919
"Add user": "Přidat uživatele",
20+
"Address": "Adresa",
2021
"adminLoggedInAsCustomerWarning": "Pozor! Jste přihlášen za uživatele s emailem {{ email }} <button>Odhlásit</button>",
2122
"All articles": "Všechny články",
2223
"An error occurred while adding user": "Při přidávání uživatele nastala chyba",
@@ -145,6 +146,7 @@
145146
"Create complaint manually": "Vytvořit reklamaci ručně",
146147
"Creation date": "Datum objednávky",
147148
"Current password": "Současné heslo",
149+
"Currently {{status}}": "Aktuálně {{status}}",
148150
"Currently, it is not possible to purchase any variant of this product.": "Aktuálně není možné koupit ani jednu variantu produktu.",
149151
"Customer information": "Informace o zákazníkovi",
150152
"Customer is already registered.": "Zákazník je již registrován.",
@@ -638,6 +640,7 @@
638640
"Store detail": "Detail prodejny",
639641
"Store detail for {{storeName}}": "Detail prodejny {{storeName}}",
640642
"Store image of": "Obrázek prodejny",
643+
"Store info": "Informace o prodejně",
641644
"Store not found.": "Prodejna nenalezena.",
642645
"Stores": "Prodejny",
643646
"Stores and collection points throughout the Czech Republic": "Prodejny a odběrná místa po celé ČR",
@@ -725,6 +728,7 @@
725728
"To prevent the possibility of account theft, it is necessary to deal with the change of e-mail individually. If your e-mail address has changed, please contact us.": "Abychom předešli možnosti odcizení účtu, je třeba změnu e-mailu řešit individuálně. Pokud se Vám e-mailová adresa změnila, tak nás prosím kontaktujte.",
726729
"To sign up for newsletter, enter": "Pro přihlášení k newsletteru zadejte",
727730
"Today": "Dnes",
731+
"Today opening hours": "Dnešní otevírací doba",
728732
"Toggle bestseller list": "Přepnout seznam bestsellerů",
729733
"Toggle blog category": "Zobrazit/skrýt kategorii blogu",
730734
"Toggle marketing consent": "Přepnout marketingové souhlas",

0 commit comments

Comments
 (0)