@foxford/navigation-ui 1.0.2 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/README.mdx +58 -0
  2. package/footer/atoms/logo-sk/index.js +1 -1
  3. package/footer/atoms/social-link/index.d.ts +3 -1
  4. package/footer/atoms/social-link/index.d.ts.map +1 -1
  5. package/footer/atoms/social-link/index.js +9 -0
  6. package/footer/contacts-column.d.ts.map +1 -1
  7. package/footer/contacts-column.js +10 -1
  8. package/footer/footer.d.ts.map +1 -1
  9. package/footer/footer.js +15 -6
  10. package/footer/molecules/footer-menu/index.d.ts +3 -1
  11. package/footer/molecules/footer-menu/index.d.ts.map +1 -1
  12. package/footer/molecules/footer-menu/index.js +3 -0
  13. package/footer/molecules/geoposition/index.js +1 -0
  14. package/footer/types.d.ts +16 -0
  15. package/footer/types.d.ts.map +1 -1
  16. package/header/atoms/action-button/index.d.ts +8 -1
  17. package/header/atoms/action-button/index.d.ts.map +1 -1
  18. package/header/atoms/action-button/index.js +65 -16
  19. package/header/atoms/back-button/index.d.ts.map +1 -1
  20. package/header/atoms/back-button/index.js +24 -3
  21. package/header/atoms/bonus-button/index.d.ts.map +1 -1
  22. package/header/atoms/bonus-button/index.js +1 -0
  23. package/header/atoms/close-button/index.d.ts.map +1 -1
  24. package/header/atoms/close-button/index.js +1 -0
  25. package/header/atoms/list-title/index.d.ts.map +1 -1
  26. package/header/atoms/list-title/index.js +2 -0
  27. package/header/atoms/logout-button/index.d.ts.map +1 -1
  28. package/header/atoms/logout-button/index.js +1 -0
  29. package/header/atoms/user-profile/index.d.ts.map +1 -1
  30. package/header/atoms/user-profile/index.js +54 -29
  31. package/header/molecules/cart-button/index.d.ts +2 -0
  32. package/header/molecules/cart-button/index.d.ts.map +1 -1
  33. package/header/molecules/cart-button/index.js +6 -1
  34. package/header/molecules/container/index.d.ts +3 -1
  35. package/header/molecules/container/index.d.ts.map +1 -1
  36. package/header/molecules/container/index.js +2 -0
  37. package/header/molecules/externat-buttons/index.d.ts.map +1 -1
  38. package/header/molecules/externat-buttons/index.js +28 -6
  39. package/header/molecules/menu/index.d.ts.map +1 -1
  40. package/header/molecules/menu/index.js +84 -64
  41. package/header/molecules/menu-button/index.d.ts.map +1 -1
  42. package/header/molecules/menu-button/index.js +3 -0
  43. package/header/molecules/oneclick-menu/index.js +3 -0
  44. package/header/molecules/profile-menu/index.d.ts.map +1 -1
  45. package/header/molecules/profile-menu/index.js +28 -21
  46. package/header/molecules/segment-list/index.d.ts.map +1 -1
  47. package/header/molecules/segment-list/index.js +26 -3
  48. package/header/molecules/support-button/index.d.ts.map +1 -1
  49. package/header/molecules/support-button/index.js +5 -0
  50. package/header/molecules/user-panel/index.d.ts.map +1 -1
  51. package/header/molecules/user-panel/index.js +3 -1
  52. package/header/navigation.d.ts.map +1 -1
  53. package/header/navigation.js +2 -0
  54. package/header/types.d.ts +6 -0
  55. package/header/types.d.ts.map +1 -1
  56. package/package.json +3 -3
  57. package/shared/a11y.d.ts +39 -0
  58. package/shared/a11y.d.ts.map +1 -0
  59. package/shared/a11y.js +96 -0
  60. package/shared/constants.d.ts +2 -0
  61. package/shared/constants.d.ts.map +1 -1
  62. package/shared/constants.js +2 -0
  63. package/shared/json-ld.d.ts +6 -0
  64. package/shared/json-ld.d.ts.map +1 -0
  65. package/shared/json-ld.js +18 -0
  66. package/shared/theme.d.ts.map +1 -1
package/README.mdx CHANGED
@@ -104,6 +104,64 @@
104
104
  - Потребителю нужно объявить `*.svg` (см. `src/shared/assets.d.ts`): шаблонные имена
105
105
  модулей живут только в глобальном `.d.ts`, а он не переезжает вместе с исходниками пакета.
106
106
 
107
+ ## Доступность и микроразметка
108
+
109
+ **Семантика задаётся атрибутами на существующих узлах, теги не меняются.** `<button>`, `<ul>`,
110
+ `<h2>` несут стили браузера по умолчанию (рамки, отступы, маркеры, шрифт), и замена `div` на
111
+ них — правка внешнего вида у всех хостов разом. Роль атрибутом (`role="button"`,
112
+ `role="heading"`, `role="navigation"`) даёт ту же семантику без единого пикселя. Узлам с
113
+ `role="button"` клавиши Enter/Space даёт `buttonProps()` из `src/shared/a11y.ts`. Id `foxNav*`
114
+ не трогаются: это контракт с хостами; добавлены только новые (`foxNavMenu`,
115
+ `foxNavSegment-<id>`).
116
+
117
+ - **Навигация — паттерн disclosure из APG, а не `role="menu"`.** Кнопка «Меню» и пункты сегментов
118
+ — кнопки с `aria-expanded`/`aria-controls`, панель сегмента — `region`. Роль `menu` — для меню
119
+ приложения: она обязала бы к роуминг-фокусу стрелками и сломала бы обход Tab-ом.
120
+ - **Ориентиры `banner`/`contentinfo` — только флагом `landmarks`.** Ориентир на странице один: на
121
+ портале шапка и подвал его и есть, а на чужом сайте у хоста свои, и второй сбил бы скринридер.
122
+ `navigation` (большое меню, меню в один клик, колонки подвала) ставится всегда: таких
123
+ ориентиров может быть несколько, у каждого своё имя.
124
+ - **Закрытое профиль-меню — `aria-hidden` + `inert`.** Спрятано оно только `opacity: 0` (ради
125
+ анимации), и без атрибутов его ссылки читались бы скринридером и ловили Tab невидимыми.
126
+ Одного `aria-hidden` мало: фокус оно не снимает.
127
+ - **`aria-controls` — только на уникальные узлы.** Мобильная и десктопная раскладки обе в DOM,
128
+ панель пользователя с профиль-меню в документе дважды, и их id дублируются. Поэтому у кнопок
129
+ профиля — только `aria-expanded`.
130
+ - **Закрыли меню с фокусом внутри — фокус возвращается на кнопку «Меню»** (`useReturnFocus`).
131
+ Иначе он терялся бы на `body` вместе со спрятанной простынёй. Был снаружи — не отбирается.
132
+ Где фокус, запоминается по `focusin`, а не читается при закрытии: к этому моменту браузер
133
+ мог уже снять его со спрятанного узла.
134
+ - **Открыли меню с клавиатуры — фокус на первом сегменте** (`useFocusOnKeyboardOpen`). В
135
+ документе меню стоит после правой части шапки и одноклик-меню, и без этого до него было бы с
136
+ десяток нажатий Tab. Клавиатура узнаётся по `click` с `detail === 0`; мышью открыли — фокус
137
+ не трогается. Ставится он попытками: меню проявляется анимацией `visibility` с задержкой, и
138
+ на скрытый узел `focus()` молча не срабатывает.
139
+ - **Маркеры «есть новое» и корзина сообщаются именем кнопки**: точку скринридер не видит.
140
+ - **Аккордеон подвала не трогается** — ни разметка, ни поведение. Имя колонке даёт ориентир
141
+ `navigation` на её списке (`FooterMenu`, проп `label`).
142
+
143
+ **Микроразметка — JSON-LD, одним узлом в конце подвала** (`FooterProps.jsonLd`,
144
+ `src/shared/json-ld.tsx`). Что в ней, решает адаптер: вёрстка не знает, на своём ли она сайте.
145
+ Не передана — узла нет. `<` в данных экранируется, иначе строка `</script>` закрыла бы тег.
146
+
147
+ - **Кнопкам, чья подпись прячется на узком экране, имя задаётся явно**: «Меню» (подпись скрыта
148
+ ниже S), кнопки с `hideTextInMobile` (`ActionButton` берёт имя из `text`), иконки без подписи
149
+ вовсе — «Выйти из аккаунта», «Бонусы за друга».
150
+
151
+ Сторожей три:
152
+ - **снапшоты разметки** — при правке доступности их диф обязан состоять только из добавленных
153
+ атрибутов;
154
+ - **`tests/a11y.spec.tsx`** — атрибуты живые: меняются с состоянием, клавиши работают, фокус
155
+ ходит куда надо;
156
+ - **`tests/axe.spec.tsx`** — axe (`axe-core`) по шапке и подвалу во всех состояниях снапшотов.
157
+ Правила — WCAG 2.x A/AA и «лучшие практики» про ориентиры; страничные правила (`region`) и
158
+ те, что требуют раскладки (`color-contrast`, `target-size`), выключены — набор и причины в
159
+ `tests/axe.ts`. jsdom не считает медиа-запросы, и обе раскладки видны разом: для axe это
160
+ строже жизни. Обратная сторона — спрятанное медиа-запросом (подпись «Меню» на телефоне) axe
161
+ здесь не увидит, такие случаи проверяются глазами в браузере. axe в jsdom небыстрый — около полутора
162
+ секунд на сценарий, — поэтому у спеков свой таймаут, а прогоны выстроены в очередь:
163
+ `axe.run` один на процесс и на параллельный вызов бросает исключение синхронно.
164
+
107
165
  ## Что не перенесено
108
166
 
109
167
  Отличия от легаси — каждое помечено комментарием в коде:
@@ -4,7 +4,7 @@ function LogoSk({
4
4
  onClick
5
5
  }) {
6
6
  return jsx(Root, {
7
- "aria-label": "Skolkovo",
7
+ "aria-label": "Сколково",
8
8
  href: "http://sk.ru",
9
9
  onClick,
10
10
  target: "_blank",
@@ -2,7 +2,9 @@ import type { IconName } from '@foxford/ui';
2
2
  export interface SocialLinkProps {
3
3
  href: string;
4
4
  iconName: IconName;
5
+ /** Имя для скринридера; по умолчанию — название сети. */
6
+ label?: string;
5
7
  onClick?: () => void;
6
8
  }
7
- export declare function SocialLink({ href, iconName, onClick }: SocialLinkProps): import("preact").JSX.Element;
9
+ export declare function SocialLink({ href, iconName, label, onClick }: SocialLinkProps): import("preact").JSX.Element;
8
10
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/footer/atoms/social-link/index.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAA;AAU3C,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,EAAE,QAAQ,CAAA;IAClB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAA;CACrB;AAED,wBAAgB,UAAU,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,EAAE,eAAe,gCAgBtE"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/footer/atoms/social-link/index.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAA;AAmB3C,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,EAAE,QAAQ,CAAA;IAClB,yDAAyD;IACzD,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,OAAO,CAAC,EAAE,MAAM,IAAI,CAAA;CACrB;AAED,wBAAgB,UAAU,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,eAAe,gCAiB7E"}
@@ -8,12 +8,21 @@ const Root = styled(Button).withConfig({
8
8
  const palette = {
9
9
  color: "content-oncolor-secondary"
10
10
  };
11
+ const NETWORK_NAMES = {
12
+ maxSocial: "MAX",
13
+ okSocial: "Одноклассники",
14
+ telegramSocial: "Telegram",
15
+ vkSocial: "ВКонтакте",
16
+ youtubeAltSocial: "YouTube"
17
+ };
11
18
  function SocialLink({
12
19
  href,
13
20
  iconName,
21
+ label,
14
22
  onClick
15
23
  }) {
16
24
  return jsx(Root, {
25
+ "aria-label": label != null ? label : NETWORK_NAMES[iconName],
17
26
  href,
18
27
  icon: iconName,
19
28
  iconProps: {
@@ -1 +1 @@
1
- {"version":3,"file":"contacts-column.d.ts","sourceRoot":"","sources":["../../src/footer/contacts-column.tsx"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EAAE,WAAW,EAAoB,MAAM,SAAS,CAAA;AAE5D,KAAK,mBAAmB,GAAG,IAAI,CAAC,WAAW,EAAE,UAAU,GAAG,kBAAkB,GAAG,YAAY,GAAG,aAAa,CAAC,CAAA;AA0B5G,wBAAgB,cAAc,CAAC,EAC7B,QAAa,EACb,gBAAwB,EACxB,UAAe,EACf,WAAgB,GACjB,EAAE,mBAAmB,gCAoErB"}
1
+ {"version":3,"file":"contacts-column.d.ts","sourceRoot":"","sources":["../../src/footer/contacts-column.tsx"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EAAE,WAAW,EAAoB,MAAM,SAAS,CAAA;AAE5D,KAAK,mBAAmB,GAAG,IAAI,CAAC,WAAW,EAAE,UAAU,GAAG,kBAAkB,GAAG,YAAY,GAAG,aAAa,CAAC,CAAA;AA0B5G,wBAAgB,cAAc,CAAC,EAC7B,QAAa,EACb,gBAAwB,EACxB,UAAe,EACf,WAAgB,GACjB,EAAE,mBAAmB,gCAwErB"}
@@ -32,7 +32,8 @@ function Links({
32
32
  return jsx(SocialLinks, {
33
33
  children: links.map((link) => jsx(SocialLink, {
34
34
  href: link.href,
35
- iconName: link.iconName
35
+ iconName: link.iconName,
36
+ label: link.label
36
37
  }, link.id))
37
38
  });
38
39
  }
@@ -81,6 +82,8 @@ function ContactsColumn({
81
82
  children: jsxs(Spacer, {
82
83
  bottomXL: 24,
83
84
  children: [jsxs(ContactsTitle, {
85
+ "aria-level": 2,
86
+ role: "heading",
84
87
  children: ["Подбор ", jsx("br", {}), "и покупка занятий"]
85
88
  }), salesPhone !== void 0 && jsx(ContactPhone, {
86
89
  number: salesPhone
@@ -92,6 +95,8 @@ function ContactsColumn({
92
95
  children: jsxs(Spacer, {
93
96
  bottomXL: 24,
94
97
  children: [jsxs(ContactsTitle, {
98
+ "aria-level": 2,
99
+ role: "heading",
95
100
  children: ["Поддержка учеников ", jsx("br", {}), "и родителей Фоксфорда"]
96
101
  }), phone !== void 0 && jsx(ContactPhone, {
97
102
  number: phone
@@ -106,6 +111,8 @@ function ContactsColumn({
106
111
  bottomS: 0,
107
112
  children: jsxs(Contacts, {
108
113
  children: [jsx(ContactsTitle, {
114
+ "aria-level": 2,
115
+ role: "heading",
109
116
  children: "Почта"
110
117
  }), jsx(Email, {
111
118
  href: `mailto:${contacts.email}`,
@@ -124,6 +131,8 @@ function ContactsColumn({
124
131
  children: jsxs(Spacer, {
125
132
  topXL: 24,
126
133
  children: [jsx(ContactsTitle, {
134
+ "aria-level": 2,
135
+ role: "heading",
127
136
  children: "Социальные сети"
128
137
  }), jsx(Links, {
129
138
  links: socialLinks
@@ -1 +1 @@
1
- {"version":3,"file":"footer.d.ts","sourceRoot":"","sources":["../../src/footer/footer.tsx"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAA;AAE1C,wBAAgB,MAAM,CAAC,KAAK,EAAE,WAAW,gCA6CxC"}
1
+ {"version":3,"file":"footer.d.ts","sourceRoot":"","sources":["../../src/footer/footer.tsx"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAA;AAE1C,wBAAgB,MAAM,CAAC,KAAK,EAAE,WAAW,gCA8CxC"}
package/footer/footer.js CHANGED
@@ -22,6 +22,7 @@ import { OnboardingBanner } from "./molecules/onboarding-banner/index.js";
22
22
  import { Root, Container, Logo, MainInner, Column, Banners } from "./styled.js";
23
23
  import { SubFooter } from "./sub-footer.js";
24
24
  import { SELECTOR_IDS } from "../shared/constants.js";
25
+ import { JsonLd } from "../shared/json-ld.js";
25
26
  import { NavigationTheme } from "../shared/theme-provider.js";
26
27
  import { jsx, jsxs } from "preact/jsx-runtime";
27
28
  function Footer(props) {
@@ -30,13 +31,16 @@ function Footer(props) {
30
31
  appLinks,
31
32
  cobranding = false,
32
33
  freeItems,
34
+ jsonLd,
35
+ landmarks = false,
33
36
  slots,
34
37
  studyItems
35
38
  } = props;
36
39
  return jsx(NavigationTheme, {
37
- children: jsx(Root, {
40
+ children: jsxs(Root, {
38
41
  id: SELECTOR_IDS.FOOTER,
39
- children: jsxs(Container, {
42
+ role: landmarks ? "contentinfo" : void 0,
43
+ children: [jsxs(Container, {
40
44
  children: [slots == null ? void 0 : slots.aboveLinks, jsx(Logo, {
41
45
  cobranding
42
46
  }), jsxs(MainInner, {
@@ -44,21 +48,24 @@ function Footer(props) {
44
48
  children: jsx(Accordeon, {
45
49
  heading: "О нас",
46
50
  children: jsx(FooterMenu, {
47
- items: aboutItems
51
+ items: aboutItems,
52
+ label: "О нас"
48
53
  })
49
54
  })
50
55
  }), jsx(Column, {
51
56
  children: jsx(Accordeon, {
52
57
  heading: "Учёба в Фоксфорде",
53
58
  children: jsx(FooterMenu, {
54
- items: studyItems
59
+ items: studyItems,
60
+ label: "Учёба в Фоксфорде"
55
61
  })
56
62
  })
57
63
  }), jsx(Column, {
58
64
  children: jsx(Accordeon, {
59
65
  heading: "Бесплатно",
60
66
  children: jsx(FooterMenu, {
61
- items: freeItems
67
+ items: freeItems,
68
+ label: "Бесплатно"
62
69
  })
63
70
  })
64
71
  }), jsxs(Banners, {
@@ -68,7 +75,9 @@ function Footer(props) {
68
75
  }), slots == null ? void 0 : slots.banners]
69
76
  }), jsx(ContactsColumn, __spreadValues({}, props))]
70
77
  }), slots == null ? void 0 : slots.belowLinks, jsx(SubFooter, __spreadValues({}, props)), slots == null ? void 0 : slots.belowSubFooter]
71
- })
78
+ }), jsonLd && jsx(JsonLd, {
79
+ data: jsonLd
80
+ })]
72
81
  })
73
82
  });
74
83
  }
@@ -6,7 +6,9 @@ export interface FooterMenuItem {
6
6
  }
7
7
  export interface FooterMenuProps {
8
8
  items: FooterMenuItem[];
9
+ /** Имя колонки для ориентира `navigation` — тот же текст, что в заголовке аккордеона. */
10
+ label?: string;
9
11
  onItemClick?: (id: string) => void;
10
12
  }
11
- export declare function FooterMenu({ items, onItemClick }: FooterMenuProps): import("preact").JSX.Element;
13
+ export declare function FooterMenu({ items, label, onItemClick }: FooterMenuProps): import("preact").JSX.Element;
12
14
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/footer/molecules/footer-menu/index.tsx"],"names":[],"mappings":"AA2BA,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAA;IACV,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,IAAI,EAAE,MAAM,CAAA;IACZ,GAAG,EAAE,MAAM,CAAA;CACZ;AAED,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,cAAc,EAAE,CAAA;IACvB,WAAW,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAA;CACnC;AAED,wBAAgB,UAAU,CAAC,EAAE,KAAK,EAAE,WAAW,EAAE,EAAE,eAAe,gCAUjE"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/footer/molecules/footer-menu/index.tsx"],"names":[],"mappings":"AA2BA,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAA;IACV,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,IAAI,EAAE,MAAM,CAAA;IACZ,GAAG,EAAE,MAAM,CAAA;CACZ;AAED,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,cAAc,EAAE,CAAA;IACvB,yFAAyF;IACzF,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,WAAW,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAA;CACnC;AAID,wBAAgB,UAAU,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,WAAW,EAAE,EAAE,eAAe,gCAUxE"}
@@ -13,9 +13,12 @@ const Item = styled(Link).withConfig({
13
13
  }) => theme.colors["content-oncolor-primary"]);
14
14
  function FooterMenu({
15
15
  items,
16
+ label,
16
17
  onItemClick
17
18
  }) {
18
19
  return jsx("div", {
20
+ "aria-label": label,
21
+ role: label === void 0 ? void 0 : "navigation",
19
22
  children: items.map((item) => jsx(Item, {
20
23
  $isSpacer: item.isSpacer,
21
24
  href: item.url,
@@ -22,6 +22,7 @@ function Geoposition({
22
22
  name: "navigation",
23
23
  size: 20
24
24
  }), jsx(CityButton, {
25
+ "aria-haspopup": "dialog",
25
26
  onClick: handleOpen,
26
27
  type: "button",
27
28
  children: currentCity == null ? void 0 : currentCity.name
package/footer/types.d.ts CHANGED
@@ -6,6 +6,11 @@ export interface FooterSocialLink {
6
6
  href: string;
7
7
  iconName: IconName;
8
8
  id: string;
9
+ /**
10
+ * Имя ссылки для скринридера: у кнопки только пиктограмма. Не передано — берётся название
11
+ * сети по пиктограмме (`atoms/social-link`).
12
+ */
13
+ label?: string;
9
14
  }
10
15
  export interface FooterContacts {
11
16
  email?: string;
@@ -36,6 +41,17 @@ export interface FooterProps {
36
41
  currentYear: number;
37
42
  freeItems: FooterMenuItem[];
38
43
  hideDataOnMirror?: boolean;
44
+ /**
45
+ * Микроразметка schema.org одним узлом JSON-LD в конце подвала. Что в ней, решает адаптер:
46
+ * вёрстка не знает, на своём ли она сайте, а организация Фоксфорда на странице партнёра
47
+ * объявила бы поисковику, что страница принадлежит Фоксфорду. Не передана — узла нет.
48
+ */
49
+ jsonLd?: Record<string, unknown>;
50
+ /**
51
+ * Объявить подвал ориентиром `contentinfo`. Он должен быть на странице один — см.
52
+ * `NavigationProps.landmarks`: по умолчанию выключено.
53
+ */
54
+ landmarks?: boolean;
39
55
  onSelectCity?: (value: string) => void;
40
56
  salesLinks?: FooterSocialLink[];
41
57
  slots?: FooterSlots;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/footer/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAA;AAC7D,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,yBAAyB,CAAA;AACnD,OAAO,KAAK,EAAE,kBAAkB,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAA;AACpE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAA;AAE3C,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,EAAE,QAAQ,CAAA;IAClB,EAAE,EAAE,MAAM,CAAA;CACX;AAED,MAAM,WAAW,cAAc;IAC7B,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,6DAA6D;IAC7D,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,0FAA0F;IAC1F,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED;;GAEG;AACH,MAAM,MAAM,WAAW,GAAG,SAAS,CAAC,OAAO,kBAAkB,CAAC,CAAA;AAE9D,MAAM,WAAW,WAAW;IAC1B,UAAU,EAAE,cAAc,EAAE,CAAA;IAC5B,6FAA6F;IAC7F,QAAQ,CAAC,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAA;IAC9C,MAAM,CAAC,EAAE,IAAI,EAAE,CAAA;IACf,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,QAAQ,CAAC,EAAE,cAAc,CAAA;IACzB,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,8FAA8F;IAC9F,WAAW,EAAE,MAAM,CAAA;IACnB,SAAS,EAAE,cAAc,EAAE,CAAA;IAC3B,gBAAgB,CAAC,EAAE,OAAO,CAAA;IAC1B,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IACtC,UAAU,CAAC,EAAE,gBAAgB,EAAE,CAAA;IAC/B,KAAK,CAAC,EAAE,WAAW,CAAA;IACnB,WAAW,CAAC,EAAE,gBAAgB,EAAE,CAAA;IAChC,UAAU,EAAE,cAAc,EAAE,CAAA;CAC7B"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/footer/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAA;AAC7D,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,yBAAyB,CAAA;AACnD,OAAO,KAAK,EAAE,kBAAkB,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAA;AACpE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAA;AAE3C,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,EAAE,QAAQ,CAAA;IAClB,EAAE,EAAE,MAAM,CAAA;IACV;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED,MAAM,WAAW,cAAc;IAC7B,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,6DAA6D;IAC7D,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,0FAA0F;IAC1F,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED;;GAEG;AACH,MAAM,MAAM,WAAW,GAAG,SAAS,CAAC,OAAO,kBAAkB,CAAC,CAAA;AAE9D,MAAM,WAAW,WAAW;IAC1B,UAAU,EAAE,cAAc,EAAE,CAAA;IAC5B,6FAA6F;IAC7F,QAAQ,CAAC,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAA;IAC9C,MAAM,CAAC,EAAE,IAAI,EAAE,CAAA;IACf,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,QAAQ,CAAC,EAAE,cAAc,CAAA;IACzB,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,8FAA8F;IAC9F,WAAW,EAAE,MAAM,CAAA;IACnB,SAAS,EAAE,cAAc,EAAE,CAAA;IAC3B,gBAAgB,CAAC,EAAE,OAAO,CAAA;IAC1B;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAChC;;;OAGG;IACH,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IACtC,UAAU,CAAC,EAAE,gBAAgB,EAAE,CAAA;IAC/B,KAAK,CAAC,EAAE,WAAW,CAAA;IACnB,WAAW,CAAC,EAAE,gBAAgB,EAAE,CAAA;IAChC,UAAU,EAAE,cAAc,EAAE,CAAA;CAC7B"}
@@ -2,6 +2,13 @@ import type { NavigationIconName } from './elementary-icons';
2
2
  import type { ReactNode } from 'react';
3
3
  export interface ActionButtonProps {
4
4
  active?: boolean;
5
+ /**
6
+ * Доступное имя, когда видимого текста нет или его мало: кнопка-иконка, текст, спрятанный на
7
+ * узком экране. Не передано — имя складывается из текста и маркера.
8
+ */
9
+ 'aria-label'?: string;
10
+ /** Кнопка раскрывает меню (профиль): его состояние для скринридера. */
11
+ expanded?: boolean;
5
12
  avatar?: ReactNode;
6
13
  className?: string;
7
14
  hideTextInMobile?: boolean;
@@ -14,5 +21,5 @@ export interface ActionButtonProps {
14
21
  text?: ReactNode;
15
22
  url?: string;
16
23
  }
17
- export declare function ActionButton({ active, avatar, className, hideTextInMobile, icon, iconSize, id, marker, onClick, text, url, }: ActionButtonProps): import("preact").JSX.Element;
24
+ export declare function ActionButton({ active, 'aria-label': ariaLabel, avatar, className, expanded, hideTextInMobile, icon, iconSize, id, marker, onClick, text, url, }: ActionButtonProps): import("preact").JSX.Element;
18
25
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/header/atoms/action-button/index.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAA;AAC5D,OAAO,KAAK,EAAwB,SAAS,EAAE,MAAM,OAAO,CAAA;AAE5D,MAAM,WAAW,iBAAiB;IAChC,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,MAAM,CAAC,EAAE,SAAS,CAAA;IAClB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,gBAAgB,CAAC,EAAE,OAAO,CAAA;IAC1B,IAAI,CAAC,EAAE,kBAAkB,CAAA;IACzB,sEAAsE;IACtE,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAA;IACpB,IAAI,CAAC,EAAE,SAAS,CAAA;IAChB,GAAG,CAAC,EAAE,MAAM,CAAA;CACb;AAWD,wBAAgB,YAAY,CAAC,EAC3B,MAAc,EACd,MAAM,EACN,SAAS,EACT,gBAAwB,EACxB,IAAI,EACJ,QAAa,EACb,EAAE,EACF,MAAc,EACd,OAAO,EACP,IAAI,EACJ,GAAG,GACJ,EAAE,iBAAiB,gCA+BnB"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/header/atoms/action-button/index.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAA;AAC5D,OAAO,KAAK,EAAwB,SAAS,EAAE,MAAM,OAAO,CAAA;AAE5D,MAAM,WAAW,iBAAiB;IAChC,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,uEAAuE;IACvE,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,MAAM,CAAC,EAAE,SAAS,CAAA;IAClB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,gBAAgB,CAAC,EAAE,OAAO,CAAA;IAC1B,IAAI,CAAC,EAAE,kBAAkB,CAAA;IACzB,sEAAsE;IACtE,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAA;IACpB,IAAI,CAAC,EAAE,SAAS,CAAA;IAChB,GAAG,CAAC,EAAE,MAAM,CAAA;CACb;AAiED,wBAAgB,YAAY,CAAC,EAC3B,MAAc,EACd,YAAY,EAAE,SAAS,EACvB,MAAM,EACN,SAAS,EACT,QAAQ,EACR,gBAAwB,EACxB,IAAI,EACJ,QAAQ,EACR,EAAE,EACF,MAAc,EACd,OAAO,EACP,IAAI,EACJ,GAAG,GACJ,EAAE,iBAAiB,gCA+BnB"}
@@ -2,7 +2,7 @@ import { Icon } from "@foxford/ui";
2
2
  import { isElementaryIcon, ELEMENTARY_ICONS } from "./elementary-icons.js";
3
3
  import { Inner, Marker, Text } from "./styled.js";
4
4
  import { Link } from "../../../shared/link.js";
5
- import { jsxs, jsx } from "preact/jsx-runtime";
5
+ import { jsx, jsxs } from "preact/jsx-runtime";
6
6
  function ButtonIcon({
7
7
  icon,
8
8
  size
@@ -21,21 +21,33 @@ function ButtonIcon({
21
21
  size
22
22
  });
23
23
  }
24
- function ActionButton({
25
- active = false,
24
+ function accessibleName(label, text, {
25
+ hideTextInMobile,
26
+ marker
27
+ }) {
28
+ if (label !== void 0) {
29
+ return label;
30
+ }
31
+ if (typeof text !== "string" || text === "") {
32
+ return void 0;
33
+ }
34
+ if (marker) {
35
+ return `${text}, есть новое`;
36
+ }
37
+ return hideTextInMobile ? text : void 0;
38
+ }
39
+ function renderContent({
40
+ active,
26
41
  avatar,
27
42
  className,
28
- hideTextInMobile = false,
43
+ hideTextInMobile,
29
44
  icon,
30
45
  iconSize = 20,
31
- id,
32
- marker = false,
33
- onClick,
34
- text,
35
- url
46
+ marker,
47
+ text
36
48
  }) {
37
49
  const hasText = text !== void 0 && text !== "";
38
- const content = jsxs(Inner, {
50
+ return jsxs(Inner, {
39
51
  $active: active,
40
52
  className,
41
53
  children: [(avatar !== void 0 || icon !== void 0) && jsx(Marker, {
@@ -49,13 +61,48 @@ function ActionButton({
49
61
  children: text
50
62
  })]
51
63
  });
64
+ }
65
+ function ActionButton({
66
+ active = false,
67
+ "aria-label": ariaLabel,
68
+ avatar,
69
+ className,
70
+ expanded,
71
+ hideTextInMobile = false,
72
+ icon,
73
+ iconSize,
74
+ id,
75
+ marker = false,
76
+ onClick,
77
+ text,
78
+ url
79
+ }) {
80
+ const label = accessibleName(ariaLabel, text, {
81
+ hideTextInMobile,
82
+ marker
83
+ });
84
+ const content = renderContent({
85
+ active,
86
+ avatar,
87
+ className,
88
+ hideTextInMobile,
89
+ icon,
90
+ iconSize,
91
+ marker,
92
+ text
93
+ });
52
94
  if (url) {
53
- return jsx(Link, {
54
- href: url,
55
- id,
56
- onClick,
57
- children: content
58
- });
95
+ return (
96
+ // `active` у ссылки ряда — это текущая страница (`highlightedItemId` адаптера).
97
+ jsx(Link, {
98
+ "aria-current": active ? "page" : void 0,
99
+ "aria-label": label,
100
+ href: url,
101
+ id,
102
+ onClick,
103
+ children: content
104
+ })
105
+ );
59
106
  }
60
107
  const onKeyDown = (event) => {
61
108
  if (event.key === "Enter" || event.key === " ") {
@@ -63,6 +110,8 @@ function ActionButton({
63
110
  }
64
111
  };
65
112
  return jsx("div", {
113
+ "aria-expanded": expanded,
114
+ "aria-label": label,
66
115
  id,
67
116
  onClick,
68
117
  onKeyDown,
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/header/atoms/back-button/index.tsx"],"names":[],"mappings":"AAIA,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,MAAM,IAAI,CAAA;CACpB;AAED,wBAAgB,UAAU,CAAC,EAAE,OAAO,EAAE,EAAE,eAAe,gCAOtD"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/header/atoms/back-button/index.tsx"],"names":[],"mappings":"AAKA,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,MAAM,IAAI,CAAA;CACpB;AAED,wBAAgB,UAAU,CAAC,EAAE,OAAO,EAAE,EAAE,eAAe,gCAOtD"}
@@ -1,18 +1,39 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __defProps = Object.defineProperties;
3
+ var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
4
+ var __getOwnPropSymbols = Object.getOwnPropertySymbols;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __propIsEnum = Object.prototype.propertyIsEnumerable;
7
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
8
+ var __spreadValues = (a, b) => {
9
+ for (var prop in b || (b = {}))
10
+ if (__hasOwnProp.call(b, prop))
11
+ __defNormalProp(a, prop, b[prop]);
12
+ if (__getOwnPropSymbols)
13
+ for (var prop of __getOwnPropSymbols(b)) {
14
+ if (__propIsEnum.call(b, prop))
15
+ __defNormalProp(a, prop, b[prop]);
16
+ }
17
+ return a;
18
+ };
19
+ var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
1
20
  import { Icon } from "@foxford/ui";
2
21
  import { Root } from "./styled.js";
22
+ import { buttonProps } from "../../../shared/a11y.js";
3
23
  import { jsxs, jsx } from "preact/jsx-runtime";
4
24
  function BackButton({
5
25
  onClick
6
26
  }) {
7
- return jsxs(Root, {
8
- onClick,
27
+ return jsxs(Root, __spreadProps(__spreadValues({
28
+ onClick
29
+ }, buttonProps(onClick)), {
9
30
  children: [jsx(Icon, {
10
31
  name: "arrowUp",
11
32
  preset: "brand",
12
33
  rotate: "-90deg",
13
34
  size: 20
14
35
  }), "  Назад"]
15
- });
36
+ }));
16
37
  }
17
38
  export {
18
39
  BackButton
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/header/atoms/bonus-button/index.tsx"],"names":[],"mappings":"AAGA,wBAAgB,WAAW,iCAE1B"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/header/atoms/bonus-button/index.tsx"],"names":[],"mappings":"AAGA,wBAAgB,WAAW,iCAK1B"}
@@ -3,6 +3,7 @@ import { ActionButton } from "../action-button/index.js";
3
3
  import { jsx } from "preact/jsx-runtime";
4
4
  function BonusButton() {
5
5
  return jsx(ActionButton, {
6
+ "aria-label": "Бонусы за друга",
6
7
  icon: "giftBox",
7
8
  id: SELECTOR_IDS.TOP_MENU_REFERRALS,
8
9
  url: "/referrals"
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/header/atoms/close-button/index.tsx"],"names":[],"mappings":"AAEA,MAAM,WAAW,gBAAgB;IAC/B,OAAO,CAAC,EAAE,MAAM,IAAI,CAAA;CACrB;AAED,wBAAgB,WAAW,CAAC,EAAE,OAAO,EAAE,EAAE,gBAAgB,gCAExD"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/header/atoms/close-button/index.tsx"],"names":[],"mappings":"AAEA,MAAM,WAAW,gBAAgB;IAC/B,OAAO,CAAC,EAAE,MAAM,IAAI,CAAA;CACrB;AAED,wBAAgB,WAAW,CAAC,EAAE,OAAO,EAAE,EAAE,gBAAgB,gCAMxD"}
@@ -5,6 +5,7 @@ function CloseButton({
5
5
  }) {
6
6
  return jsx(Root, {
7
7
  onClick,
8
+ type: "button",
8
9
  children: "Закрыть меню"
9
10
  });
10
11
  }
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/header/atoms/list-title/index.tsx"],"names":[],"mappings":"AAEA,MAAM,WAAW,cAAc;IAC7B,OAAO,CAAC,EAAE,MAAM,IAAI,CAAA;IACpB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,GAAG,CAAC,EAAE,MAAM,CAAA;CACb;AAED,wBAAgB,SAAS,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,GAAG,EAAE,EAAE,cAAc,uCAc/D"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/header/atoms/list-title/index.tsx"],"names":[],"mappings":"AAEA,MAAM,WAAW,cAAc;IAC7B,OAAO,CAAC,EAAE,MAAM,IAAI,CAAA;IACpB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,GAAG,CAAC,EAAE,MAAM,CAAA;CACb;AAED,wBAAgB,SAAS,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,GAAG,EAAE,EAAE,cAAc,uCAmB/D"}
@@ -16,6 +16,8 @@ function ListTitle({
16
16
  });
17
17
  }
18
18
  return jsx(Title, {
19
+ "aria-level": 3,
20
+ role: "heading",
19
21
  children: text
20
22
  });
21
23
  }
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/header/atoms/logout-button/index.tsx"],"names":[],"mappings":"AAGA,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,MAAM,IAAI,CAAA;CACpB;AAED,wBAAgB,YAAY,CAAC,EAAE,OAAO,EAAE,EAAE,iBAAiB,gCAE1D"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/header/atoms/logout-button/index.tsx"],"names":[],"mappings":"AAGA,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,MAAM,IAAI,CAAA;CACpB;AAED,wBAAgB,YAAY,CAAC,EAAE,OAAO,EAAE,EAAE,iBAAiB,gCAW1D"}
@@ -5,6 +5,7 @@ function LogoutButton({
5
5
  onClick
6
6
  }) {
7
7
  return jsx(ActionButton, {
8
+ "aria-label": "Выйти из аккаунта",
8
9
  hideTextInMobile: true,
9
10
  icon: "logOut",
10
11
  id: SELECTOR_IDS.LOGOUT_BUTTON_MOBILE,
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/header/atoms/user-profile/index.tsx"],"names":[],"mappings":"AAKA,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,OAAO,CAAA;IACf,SAAS,EAAE,MAAM,CAAA;IACjB,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,MAAM,IAAI,CAAA;IACnB,IAAI,EAAE,MAAM,CAAA;CACb;AAED,wBAAgB,WAAW,CAAC,EAAE,MAAM,EAAE,SAAS,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE,gBAAgB,gCAiBvF"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/header/atoms/user-profile/index.tsx"],"names":[],"mappings":"AAMA,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,OAAO,CAAA;IACf,SAAS,EAAE,MAAM,CAAA;IACjB,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,MAAM,IAAI,CAAA;IACnB,IAAI,EAAE,MAAM,CAAA;CACb;AAED,wBAAgB,WAAW,CAAC,EAAE,MAAM,EAAE,SAAS,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE,gBAAgB,gCAkBvF"}