@gravity-ui/page-constructor 4.5.3 → 4.6.1

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 (46) hide show
  1. package/build/cjs/blocks/Slider/Arrow/Arrow.js +2 -1
  2. package/build/cjs/blocks/Slider/Slider.css +4 -1
  3. package/build/cjs/blocks/Slider/Slider.js +32 -6
  4. package/build/cjs/blocks/Slider/i18n/en.json +4 -0
  5. package/build/cjs/blocks/Slider/i18n/index.d.ts +2 -0
  6. package/build/cjs/blocks/Slider/i18n/index.js +8 -0
  7. package/build/cjs/blocks/Slider/i18n/ru.json +4 -0
  8. package/build/cjs/components/HTML/HTML.d.ts +3 -1
  9. package/build/cjs/components/HTML/HTML.js +2 -1
  10. package/build/cjs/components/Link/Link.d.ts +2 -1
  11. package/build/cjs/components/Link/Link.js +4 -2
  12. package/build/cjs/components/VideoBlock/VideoBlock.js +6 -5
  13. package/build/cjs/components/YFMWrapper/YFMWrapper.d.ts +3 -2
  14. package/build/cjs/components/YFMWrapper/YFMWrapper.js +1 -1
  15. package/build/cjs/constructor-items.d.ts +1 -1
  16. package/build/cjs/sub-blocks/Content/Content.d.ts +3 -1
  17. package/build/cjs/sub-blocks/Content/Content.js +7 -5
  18. package/build/cjs/sub-blocks/Content/ContentList/ContentList.d.ts +2 -1
  19. package/build/cjs/sub-blocks/Content/ContentList/ContentList.js +7 -5
  20. package/build/cjs/utils/blocks.d.ts +1 -0
  21. package/build/cjs/utils/blocks.js +14 -1
  22. package/build/esm/blocks/Slider/Arrow/Arrow.js +2 -1
  23. package/build/esm/blocks/Slider/Slider.css +4 -1
  24. package/build/esm/blocks/Slider/Slider.js +33 -7
  25. package/build/esm/blocks/Slider/i18n/en.json +4 -0
  26. package/build/esm/blocks/Slider/i18n/index.d.ts +2 -0
  27. package/build/esm/blocks/Slider/i18n/index.js +5 -0
  28. package/build/esm/blocks/Slider/i18n/ru.json +4 -0
  29. package/build/esm/components/HTML/HTML.d.ts +3 -1
  30. package/build/esm/components/HTML/HTML.js +2 -1
  31. package/build/esm/components/Link/Link.d.ts +2 -1
  32. package/build/esm/components/Link/Link.js +4 -2
  33. package/build/esm/components/VideoBlock/VideoBlock.js +6 -5
  34. package/build/esm/components/YFMWrapper/YFMWrapper.d.ts +3 -2
  35. package/build/esm/components/YFMWrapper/YFMWrapper.js +1 -1
  36. package/build/esm/constructor-items.d.ts +1 -1
  37. package/build/esm/sub-blocks/Content/Content.d.ts +3 -1
  38. package/build/esm/sub-blocks/Content/Content.js +7 -5
  39. package/build/esm/sub-blocks/Content/ContentList/ContentList.d.ts +2 -1
  40. package/build/esm/sub-blocks/Content/ContentList/ContentList.js +7 -5
  41. package/build/esm/utils/blocks.d.ts +1 -0
  42. package/build/esm/utils/blocks.js +12 -0
  43. package/package.json +1 -1
  44. package/server/utils/blocks.d.ts +1 -0
  45. package/server/utils/blocks.js +14 -1
  46. package/widget/index.js +1 -1
@@ -4,9 +4,10 @@ const tslib_1 = require("tslib");
4
4
  const react_1 = tslib_1.__importDefault(require("react"));
5
5
  const ToggleArrow_1 = tslib_1.__importDefault(require("../../../components/ToggleArrow/ToggleArrow"));
6
6
  const utils_1 = require("../../../utils");
7
+ const i18n_1 = tslib_1.__importDefault(require("../i18n"));
7
8
  const b = (0, utils_1.block)('slider-block-arrow');
8
9
  const Arrow = ({ type, handleClick, className, size = 16 }) => (react_1.default.createElement("div", { className: b({ type }, className) },
9
- react_1.default.createElement("button", { className: b('button'), onClick: () => handleClick && handleClick(type) },
10
+ react_1.default.createElement("button", { className: b('button'), onClick: () => handleClick && handleClick(type), "aria-label": (0, i18n_1.default)(`arrow-${type}`) },
10
11
  react_1.default.createElement("span", { className: b('icon-wrapper') },
11
12
  react_1.default.createElement(ToggleArrow_1.default, { size: size, type: 'horizontal', iconType: "navigation", className: b('icon') })))));
12
13
  exports.default = Arrow;
@@ -134,6 +134,7 @@ unpredictable css rules order in build */
134
134
  justify-content: center;
135
135
  }
136
136
  .pc-SliderBlock__dots-list li.pc-SliderBlock__bar,
137
+ .pc-SliderBlock__dots-list li.pc-SliderBlock__accessible-bar,
137
138
  .pc-SliderBlock__dots-list li.pc-SliderBlock__dot {
138
139
  margin: calc(12px / 2) 8px;
139
140
  top: 0;
@@ -155,13 +156,15 @@ unpredictable css rules order in build */
155
156
  .pc-SliderBlock__dot_active {
156
157
  background-color: var(--g-color-line-generic-active);
157
158
  }
158
- .pc-SliderBlock__bar {
159
+ .pc-SliderBlock__bar, .pc-SliderBlock__accessible-bar {
159
160
  position: absolute;
160
161
  top: 12px;
161
162
  left: 0;
162
163
  width: 24px;
163
164
  height: 8px;
164
165
  border-radius: var(--pc-border-radius);
166
+ }
167
+ .pc-SliderBlock__bar {
165
168
  transition: left 0.3s;
166
169
  background-color: var(--pc-color-line-generic-active-solid);
167
170
  }
@@ -121,27 +121,53 @@ const SliderBlock = (props) => {
121
121
  slider.slickGoTo(nextIndex);
122
122
  }
123
123
  }, [slider, currentIndex, slidesCountByBreakpoint]);
124
+ const barSlidesCount = childrenCount - slidesToShowCount + 1;
125
+ const barPosition = (DOT_GAP + DOT_WIDTH) * currentIndex;
126
+ const barWidth = DOT_WIDTH + (DOT_GAP + DOT_WIDTH) * (slidesCountByBreakpoint - 1);
124
127
  const renderBar = () => {
125
- const barPosition = (DOT_GAP + DOT_WIDTH) * currentIndex;
126
- const barWidth = DOT_WIDTH + (DOT_GAP + DOT_WIDTH) * (slidesCountByBreakpoint - 1);
127
128
  return (slidesCountByBreakpoint > 1 && (react_1.default.createElement("li", { className: b('bar'), style: {
128
129
  left: barPosition,
129
130
  width: barWidth,
130
131
  } })));
131
132
  };
133
+ // renders additional bar, not visible in the layout but visible for screenreaders
134
+ const renderAccessibleBar = (index) => {
135
+ return (
136
+ // To have this key differ from keys used in renderDot function, added `-accessible-bar` part
137
+ react_1.default.createElement(react_1.Fragment, { key: `${index}-accessible-bar` }, slidesCountByBreakpoint > 1 && (react_1.default.createElement("li", { className: b('accessible-bar'), "aria-current": true, "aria-label": `Slide ${currentIndex + 1} of ${barSlidesCount}`, style: {
138
+ left: barPosition,
139
+ width: barWidth,
140
+ } }))));
141
+ };
132
142
  const renderDot = (index) => {
133
- return (react_1.default.createElement("li", { key: index, className: b('dot', { active: index === currentIndex }), onClick: () => handleDotClick(index) }));
143
+ const currentIndexDiff = index - currentIndex;
144
+ let currentSlideNumber;
145
+ if (0 <= currentIndexDiff && currentIndexDiff < slidesToShowCount) {
146
+ currentSlideNumber = currentIndex + 1;
147
+ }
148
+ else if (currentIndexDiff >= slidesToShowCount) {
149
+ currentSlideNumber = index - slidesToShowCount + 2;
150
+ }
151
+ else {
152
+ currentSlideNumber = index + 1;
153
+ }
154
+ return (react_1.default.createElement("li", { key: index, className: b('dot', { active: index === currentIndex }), onClick: () => handleDotClick(index), "aria-hidden": (slidesCountByBreakpoint > 1 &&
155
+ 0 <= currentIndexDiff &&
156
+ currentIndexDiff < slidesToShowCount) ||
157
+ undefined, "aria-label": `Slide ${currentSlideNumber} of ${barSlidesCount}` }));
134
158
  };
135
159
  const renderNavigation = () => {
136
160
  if (childrenCount <= slidesCountByBreakpoint || !dots || childrenCount === 1) {
137
161
  return null;
138
162
  }
163
+ const dotsList = Array(childrenCount)
164
+ .fill(null)
165
+ .map((_item, index) => renderDot(index));
166
+ dotsList.splice(currentIndex, 0, renderAccessibleBar(currentIndex));
139
167
  return (react_1.default.createElement("div", { className: b('dots', dotsClassName) },
140
168
  react_1.default.createElement("ul", { className: b('dots-list') },
141
169
  renderBar(),
142
- Array(childrenCount)
143
- .fill(null)
144
- .map((_item, index) => renderDot(index)))));
170
+ dotsList)));
145
171
  };
146
172
  const renderDisclaimer = () => {
147
173
  return disclaimer ? (react_1.default.createElement("div", { className: b('disclaimer', { size: disclaimer.size || 'm' }) }, disclaimer.text)) : null;
@@ -0,0 +1,4 @@
1
+ {
2
+ "arrow-right": "Next",
3
+ "arrow-left": "Previous"
4
+ }
@@ -0,0 +1,2 @@
1
+ declare const _default: (key: string, params?: import("@gravity-ui/i18n").Params | undefined) => string;
2
+ export default _default;
@@ -0,0 +1,8 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const tslib_1 = require("tslib");
4
+ const registerKeyset_1 = require("../../../utils/registerKeyset");
5
+ const en_json_1 = tslib_1.__importDefault(require("./en.json"));
6
+ const ru_json_1 = tslib_1.__importDefault(require("./ru.json"));
7
+ const COMPONENT = 'SliderBlock';
8
+ exports.default = (0, registerKeyset_1.registerKeyset)({ en: en_json_1.default, ru: ru_json_1.default }, COMPONENT);
@@ -0,0 +1,4 @@
1
+ {
2
+ "arrow-right": "Дальше",
3
+ "arrow-left": "Назад"
4
+ }
@@ -1,5 +1,6 @@
1
1
  import React from 'react';
2
2
  import { WithChildren } from '../../models';
3
+ import { QAProps } from '../../models/common';
3
4
  export interface HTMLProps {
4
5
  children?: string;
5
6
  block?: boolean;
@@ -7,12 +8,13 @@ export interface HTMLProps {
7
8
  itemProp?: string;
8
9
  id?: string;
9
10
  }
10
- declare const HTML: ({ children, block, className, itemProp, id }: WithChildren<HTMLProps>) => React.DetailedReactHTMLElement<{
11
+ declare const HTML: ({ children, block, className, itemProp, id, qa, }: WithChildren<HTMLProps & QAProps>) => React.DetailedReactHTMLElement<{
11
12
  dangerouslySetInnerHTML: {
12
13
  __html: string | (string & React.ReactElement<any, string | React.JSXElementConstructor<any>>) | (string & React.ReactFragment) | (string & React.ReactPortal);
13
14
  };
14
15
  className: string | undefined;
15
16
  itemProp: string | undefined;
16
17
  id: string | undefined;
18
+ 'data-qa': string | undefined;
17
19
  }, HTMLElement> | null;
18
20
  export default HTML;
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const tslib_1 = require("tslib");
4
4
  const react_1 = tslib_1.__importDefault(require("react"));
5
5
  const utils_1 = require("../../utils");
6
- const HTML = ({ children, block = false, className, itemProp, id }) => {
6
+ const HTML = ({ children, block = false, className, itemProp, id, qa, }) => {
7
7
  if (!children) {
8
8
  return null;
9
9
  }
@@ -12,6 +12,7 @@ const HTML = ({ children, block = false, className, itemProp, id }) => {
12
12
  className,
13
13
  itemProp,
14
14
  id,
15
+ 'data-qa': qa,
15
16
  });
16
17
  };
17
18
  exports.default = HTML;
@@ -1,4 +1,5 @@
1
1
  import { ClassNameProps, LinkProps, Tabbable, WithChildren } from '../../models';
2
- export type LinkFullProps = LinkProps & ClassNameProps & Tabbable;
2
+ import { QAProps } from '../../models/common';
3
+ export type LinkFullProps = LinkProps & ClassNameProps & Tabbable & QAProps;
3
4
  declare const LinkBlock: (props: WithChildren<LinkFullProps>) => JSX.Element;
4
5
  export default LinkBlock;
@@ -10,6 +10,7 @@ const useMetrika_1 = require("../../hooks/useMetrika");
10
10
  const icons_1 = require("../../icons");
11
11
  const models_1 = require("../../models");
12
12
  const utils_1 = require("../../utils");
13
+ const index_1 = require("../../utils/index");
13
14
  const BackLink_1 = tslib_1.__importDefault(require("../BackLink/BackLink"));
14
15
  const FileLink_1 = tslib_1.__importDefault(require("../FileLink/FileLink"));
15
16
  const b = (0, utils_1.block)('link-block');
@@ -27,7 +28,8 @@ function getArrowSize(size) {
27
28
  }
28
29
  }
29
30
  const LinkBlock = (props) => {
30
- const { text, url, arrow, metrikaGoals, pixelEvents, analyticsEvents, theme = 'file-link', colorTheme = 'light', textSize = 'm', className, target, children, tabIndex, } = props;
31
+ const { text, url, arrow, metrikaGoals, pixelEvents, analyticsEvents, theme = 'file-link', colorTheme = 'light', textSize = 'm', className, target, children, tabIndex, qa, } = props;
32
+ const qaAttributes = (0, index_1.getQaAttrubutes)(qa, ['normal']);
31
33
  const handleMetrika = (0, useMetrika_1.useMetrika)();
32
34
  const handleAnalytics = (0, hooks_1.useAnalytics)(models_1.DefaultEventNames.Link, url);
33
35
  const { hostname } = (0, react_1.useContext)(locationContext_1.LocationContext);
@@ -48,7 +50,7 @@ const LinkBlock = (props) => {
48
50
  case 'normal': {
49
51
  const linkProps = (0, utils_1.getLinkProps)(url, hostname, target);
50
52
  const content = children || text;
51
- return (react_1.default.createElement("a", Object.assign({ className: b('link', { theme: colorTheme, 'has-arrow': arrow }), href: href, onClick: onClick, tabIndex: tabIndex }, linkProps), arrow ? (react_1.default.createElement(react_1.Fragment, null,
53
+ return (react_1.default.createElement("a", Object.assign({ className: b('link', { theme: colorTheme, 'has-arrow': arrow }), href: href, onClick: onClick, tabIndex: tabIndex }, linkProps, { "data-qa": qaAttributes.normal }), arrow ? (react_1.default.createElement(react_1.Fragment, null,
52
54
  react_1.default.createElement("span", { className: b('content') }, content),
53
55
  WORD_JOINER_SYM,
54
56
  react_1.default.createElement(uikit_1.Icon, { className: b('arrow'), data: icons_1.Chevron, size: getArrowSize(textSize) }))) : (content)));
@@ -4,7 +4,7 @@ exports.getHeight = exports.AUTOPLAY_ATTRIBUTES = void 0;
4
4
  const tslib_1 = require("tslib");
5
5
  const react_1 = tslib_1.__importStar(require("react"));
6
6
  const uikit_1 = require("@gravity-ui/uikit");
7
- const lodash_1 = tslib_1.__importDefault(require("lodash"));
7
+ const debounce_1 = tslib_1.__importDefault(require("lodash/debounce"));
8
8
  const uuid_1 = require("uuid");
9
9
  const useAnalytics_1 = require("../../hooks/useAnalytics");
10
10
  const icons_1 = require("../../icons");
@@ -58,7 +58,7 @@ const VideoBlock = (props) => {
58
58
  setTimeout(() => setHidePreview(true), AUTOPLAY_DELAY);
59
59
  }, [handleAnalytics, analyticsEvents, src, attributes]);
60
60
  (0, react_1.useEffect)(() => {
61
- const updateSize = lodash_1.default.debounce(() => {
61
+ const updateSize = (0, debounce_1.default)(() => {
62
62
  setCurrentHeight(ref.current ? Math.round(getHeight(ref.current.offsetWidth)) : undefined);
63
63
  }, 100);
64
64
  updateSize();
@@ -71,11 +71,12 @@ const VideoBlock = (props) => {
71
71
  if (norender) {
72
72
  return;
73
73
  }
74
- const fullSrc = `${src}?${(0, utils_1.getPageSearchParams)(attributes || {})}`;
75
74
  if (ref.current && !iframeRef.current) {
76
75
  const iframe = document.createElement('iframe');
77
76
  iframe.id = fullId;
78
- iframe.src = fullSrc;
77
+ if (!previewImg) {
78
+ iframe.src = `${src}?${(0, utils_1.getPageSearchParams)(attributes || {})}`;
79
+ }
79
80
  iframe.width = '100%';
80
81
  iframe.height = '100%';
81
82
  iframe.title = (0, i18n_1.default)('iframe-title');
@@ -86,7 +87,7 @@ const VideoBlock = (props) => {
86
87
  ref.current.appendChild(iframe);
87
88
  iframeRef.current = iframe;
88
89
  }
89
- }, [stream, record, norender, src, fullId, attributes, iframeRef, fullscreen]);
90
+ }, [stream, record, norender, src, fullId, attributes, iframeRef, previewImg]);
90
91
  (0, react_1.useEffect)(() => {
91
92
  setHidePreview(false);
92
93
  }, [src, setHidePreview]);
@@ -1,9 +1,10 @@
1
1
  import { ClassNameProps, Modifiers } from '../../models';
2
- export interface YFMWrapperProps extends ClassNameProps {
2
+ import { QAProps } from '../../models/common';
3
+ export interface YFMWrapperProps extends ClassNameProps, QAProps {
3
4
  content: string;
4
5
  modifiers?: Modifiers;
5
6
  itemProp?: string;
6
7
  id?: string;
7
8
  }
8
- declare const YFMWrapper: ({ content, modifiers, className, itemProp, id }: YFMWrapperProps) => JSX.Element;
9
+ declare const YFMWrapper: ({ content, modifiers, className, itemProp, id, qa }: YFMWrapperProps) => JSX.Element;
9
10
  export default YFMWrapper;
@@ -8,5 +8,5 @@ const bem_cn_lite_1 = tslib_1.__importDefault(require("bem-cn-lite"));
8
8
  const snakecase_keys_1 = tslib_1.__importDefault(require("snakecase-keys"));
9
9
  const components_1 = require("../../components");
10
10
  const yfm = (0, bem_cn_lite_1.default)('yfm');
11
- const YFMWrapper = ({ content, modifiers, className, itemProp, id }) => (react_1.default.createElement(components_1.HTML, { className: yfm(modifiers ? (0, snakecase_keys_1.default)(modifiers) : {}, className), itemProp: itemProp, id: id }, content));
11
+ const YFMWrapper = ({ content, modifiers, className, itemProp, id, qa }) => (react_1.default.createElement(components_1.HTML, { className: yfm(modifiers ? (0, snakecase_keys_1.default)(modifiers) : {}, className), itemProp: itemProp, id: id, qa: qa }, content));
12
12
  exports.default = YFMWrapper;
@@ -26,7 +26,7 @@ export declare const subBlockMap: {
26
26
  "layout-item": ({ content: { links, ...content }, metaInfo, media, border, fullscreen, className, }: import("./models").LayoutItemProps) => JSX.Element;
27
27
  "background-card": (props: import("./models").BackgroundCardProps) => JSX.Element;
28
28
  "basic-card": (props: import("./models").BasicCardProps) => JSX.Element;
29
- content: (props: import("./models").ContentBlockProps & import("./models").ClassNameProps) => JSX.Element;
29
+ content: (props: import("./sub-blocks/Content/Content").ContentProps) => JSX.Element;
30
30
  quote: (props: import("./models").QuoteProps) => JSX.Element;
31
31
  };
32
32
  export declare const navItemMap: {
@@ -1,3 +1,5 @@
1
1
  import { ClassNameProps, ContentBlockProps } from '../../models';
2
- declare const Content: (props: ContentBlockProps & ClassNameProps) => JSX.Element;
2
+ import { QAProps } from '../../models/common';
3
+ export type ContentProps = ContentBlockProps & ClassNameProps & QAProps;
4
+ declare const Content: (props: ContentProps) => JSX.Element;
3
5
  export default Content;
@@ -6,6 +6,7 @@ const components_1 = require("../../components");
6
6
  const Link_1 = tslib_1.__importDefault(require("../../components/Link/Link"));
7
7
  const grid_1 = require("../../grid");
8
8
  const utils_1 = require("../../utils");
9
+ const blocks_1 = require("../../utils/blocks");
9
10
  const ContentList_1 = tslib_1.__importDefault(require("./ContentList/ContentList"));
10
11
  const b = (0, utils_1.block)('content');
11
12
  function getTextSize(size) {
@@ -36,23 +37,24 @@ function getButtonSize(size) {
36
37
  }
37
38
  }
38
39
  const Content = (props) => {
39
- const { title, text, additionalInfo, size = 'l', links, buttons, colSizes = { all: 12, sm: 8 }, centered, theme, className, list, } = props;
40
+ const { title, text, additionalInfo, size = 'l', links, buttons, colSizes = { all: 12, sm: 8 }, centered, theme, className, list, qa, } = props;
41
+ const qaAttributes = (0, blocks_1.getQaAttrubutes)(qa, ['links', 'link', 'buttons', 'button', 'list']);
40
42
  const titleProps = !title || typeof title === 'string'
41
43
  ? { text: title, textSize: getTextSize(size) }
42
44
  : title;
43
45
  const hasTitle = Boolean(title);
44
- return (react_1.default.createElement(grid_1.Col, { className: b({ size, centered, theme }, className), reset: true, sizes: colSizes },
46
+ return (react_1.default.createElement(grid_1.Col, { className: b({ size, centered, theme }, className), reset: true, sizes: colSizes, qa: qaAttributes.container },
45
47
  title && react_1.default.createElement(components_1.Title, { className: b('title'), title: titleProps, colSizes: { all: 12 } }),
46
48
  text && (react_1.default.createElement("div", { className: b('text', { ['without-title']: !hasTitle }) },
47
49
  react_1.default.createElement(components_1.YFMWrapper, { content: text, modifiers: { constructor: true, [`constructor-size-${size}`]: true } }))),
48
- (list === null || list === void 0 ? void 0 : list.length) ? react_1.default.createElement(ContentList_1.default, { list: list, size: size }) : null,
50
+ (list === null || list === void 0 ? void 0 : list.length) ? react_1.default.createElement(ContentList_1.default, { list: list, size: size, qa: qaAttributes.list }) : null,
49
51
  additionalInfo && (react_1.default.createElement("div", { className: b('notice') },
50
52
  react_1.default.createElement(components_1.YFMWrapper, { content: additionalInfo, modifiers: {
51
53
  constructor: true,
52
54
  'constructor-notice': true,
53
55
  [`constructor-size-${size}`]: true,
54
56
  } }))),
55
- links && (react_1.default.createElement("div", { className: b('links') }, links.map((link) => (react_1.default.createElement(Link_1.default, Object.assign({ className: b('link') }, link, { textSize: getLinkSize(size), key: link.url })))))),
56
- buttons && (react_1.default.createElement("div", { className: b('buttons') }, buttons.map((item) => (react_1.default.createElement(components_1.Button, Object.assign({ className: b('button') }, item, { key: item.url, size: getButtonSize(size) }))))))));
57
+ links && (react_1.default.createElement("div", { className: b('links'), "data-qa": qaAttributes.links }, links.map((link) => (react_1.default.createElement(Link_1.default, Object.assign({ className: b('link') }, link, { textSize: getLinkSize(size), key: link.url, qa: qaAttributes.link })))))),
58
+ buttons && (react_1.default.createElement("div", { className: b('buttons'), "data-qa": qaAttributes.buttons }, buttons.map((item) => (react_1.default.createElement(components_1.Button, Object.assign({ className: b('button') }, item, { key: item.url, size: getButtonSize(size), qa: qaAttributes.button }))))))));
57
59
  };
58
60
  exports.default = Content;
@@ -1,7 +1,8 @@
1
1
  import { ContentItemProps, ContentSize } from '../../../models';
2
+ import { QAProps } from '../../../models/common';
2
3
  interface ContentListProps {
3
4
  list: ContentItemProps[];
4
5
  size: ContentSize;
5
6
  }
6
- declare const ContentList: ({ list, size }: ContentListProps) => JSX.Element;
7
+ declare const ContentList: ({ list, size, qa }: ContentListProps & QAProps) => JSX.Element;
7
8
  export default ContentList;
@@ -8,6 +8,7 @@ const Image_1 = tslib_1.__importDefault(require("../../../components/Image/Image
8
8
  const utils_1 = require("../../../components/Media/Image/utils");
9
9
  const theme_1 = require("../../../context/theme");
10
10
  const utils_2 = require("../../../utils");
11
+ const blocks_1 = require("../../../utils/blocks");
11
12
  const b = (0, utils_2.block)('content-list');
12
13
  function getHeadingLevel(size) {
13
14
  switch (size) {
@@ -18,18 +19,19 @@ function getHeadingLevel(size) {
18
19
  return 'h3';
19
20
  }
20
21
  }
21
- const ContentList = ({ list, size }) => {
22
+ const ContentList = ({ list, size, qa }) => {
22
23
  const theme = (0, theme_1.useTheme)();
23
- return (react_1.default.createElement("div", { className: b({ size }) }, list === null || list === void 0 ? void 0 : list.map((item) => {
24
+ const qaAttributes = (0, blocks_1.getQaAttrubutes)(qa, ['image', 'title', 'text']);
25
+ return (react_1.default.createElement("div", { className: b({ size }), "data-qa": qa }, list === null || list === void 0 ? void 0 : list.map((item) => {
24
26
  const { icon, title, text } = item;
25
27
  const iconThemed = (0, utils_2.getThemedValue)(icon, theme);
26
28
  const iconData = (0, utils_1.getMediaImage)(iconThemed);
27
29
  return (react_1.default.createElement("div", { className: b('item'), key: (0, uuid_1.v4)() },
28
- react_1.default.createElement(Image_1.default, Object.assign({}, iconData, { className: b('icon') })),
30
+ react_1.default.createElement(Image_1.default, Object.assign({}, iconData, { className: b('icon'), qa: qaAttributes.image })),
29
31
  react_1.default.createElement("div", null,
30
32
  title &&
31
- react_1.default.createElement(getHeadingLevel(size), { className: b('title') }, title),
32
- text && (react_1.default.createElement(components_1.YFMWrapper, { className: b('text'), content: text, modifiers: { constructor: true } })))));
33
+ react_1.default.createElement(getHeadingLevel(size), { className: b('title'), 'data-qa': qaAttributes.title }, title),
34
+ text && (react_1.default.createElement(components_1.YFMWrapper, { className: b('text'), content: text, modifiers: { constructor: true }, qa: qaAttributes.text })))));
33
35
  })));
34
36
  };
35
37
  exports.default = ContentList;
@@ -7,3 +7,4 @@ export declare const getCustomTypes: (types: (keyof CustomConfig)[], customBlock
7
7
  export declare const getOrderedBlocks: (blocks: ConstructorBlock[], headerBlockTypes?: string[]) => ConstructorBlock[];
8
8
  export declare const getHeaderBlock: (blocks: ConstructorBlock[], headerBlockTypes?: string[]) => ConstructorBlock | undefined;
9
9
  export declare const getShareLink: (url: string, type: PCShareSocialNetwork, title?: string, text?: string) => string | undefined;
10
+ export declare const getQaAttrubutes: (qa?: string, customKeys?: Array<string>) => Record<string, string>;
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getShareLink = exports.getHeaderBlock = exports.getOrderedBlocks = exports.getCustomTypes = exports.getCustomItems = exports.getBlockKey = exports.hasBlockTag = exports.getHeaderTag = void 0;
3
+ exports.getQaAttrubutes = exports.getShareLink = exports.getHeaderBlock = exports.getOrderedBlocks = exports.getCustomTypes = exports.getCustomItems = exports.getBlockKey = exports.hasBlockTag = exports.getHeaderTag = void 0;
4
+ const lodash_1 = require("lodash");
4
5
  const models_1 = require("../models");
5
6
  const BLOCK_ELEMENTS = [
6
7
  'div',
@@ -39,6 +40,7 @@ const BLOCK_ELEMENTS = [
39
40
  'td',
40
41
  ];
41
42
  const BLOCK_ELEMENTS_REGEX = `<(${BLOCK_ELEMENTS.join('|')})[^>]*>`;
43
+ const QA_ATTRIBUTES_KEYS = ['container', 'content', 'wrapper', 'image', 'button'];
42
44
  function getHeaderTag(size) {
43
45
  switch (size) {
44
46
  case 'l':
@@ -120,3 +122,14 @@ const getShareLink = (url, type, title, text) => {
120
122
  }
121
123
  };
122
124
  exports.getShareLink = getShareLink;
125
+ const getQaAttrubutes = (qa, customKeys = []) => {
126
+ const attributes = {};
127
+ if (qa) {
128
+ const keys = QA_ATTRIBUTES_KEYS.concat(customKeys);
129
+ keys.forEach((key) => {
130
+ attributes[(0, lodash_1.camelCase)(key)] = `${qa}-${key}`;
131
+ });
132
+ }
133
+ return attributes;
134
+ };
135
+ exports.getQaAttrubutes = getQaAttrubutes;
@@ -1,10 +1,11 @@
1
1
  import React from 'react';
2
2
  import ToggleArrow from '../../../components/ToggleArrow/ToggleArrow';
3
3
  import { block } from '../../../utils';
4
+ import i18n from '../i18n';
4
5
  import './Arrow.css';
5
6
  const b = block('slider-block-arrow');
6
7
  const Arrow = ({ type, handleClick, className, size = 16 }) => (React.createElement("div", { className: b({ type }, className) },
7
- React.createElement("button", { className: b('button'), onClick: () => handleClick && handleClick(type) },
8
+ React.createElement("button", { className: b('button'), onClick: () => handleClick && handleClick(type), "aria-label": i18n(`arrow-${type}`) },
8
9
  React.createElement("span", { className: b('icon-wrapper') },
9
10
  React.createElement(ToggleArrow, { size: size, type: 'horizontal', iconType: "navigation", className: b('icon') })))));
10
11
  export default Arrow;
@@ -134,6 +134,7 @@ unpredictable css rules order in build */
134
134
  justify-content: center;
135
135
  }
136
136
  .pc-SliderBlock__dots-list li.pc-SliderBlock__bar,
137
+ .pc-SliderBlock__dots-list li.pc-SliderBlock__accessible-bar,
137
138
  .pc-SliderBlock__dots-list li.pc-SliderBlock__dot {
138
139
  margin: calc(12px / 2) 8px;
139
140
  top: 0;
@@ -155,13 +156,15 @@ unpredictable css rules order in build */
155
156
  .pc-SliderBlock__dot_active {
156
157
  background-color: var(--g-color-line-generic-active);
157
158
  }
158
- .pc-SliderBlock__bar {
159
+ .pc-SliderBlock__bar, .pc-SliderBlock__accessible-bar {
159
160
  position: absolute;
160
161
  top: 12px;
161
162
  left: 0;
162
163
  width: 24px;
163
164
  height: 8px;
164
165
  border-radius: var(--pc-border-radius);
166
+ }
167
+ .pc-SliderBlock__bar {
165
168
  transition: left 0.3s;
166
169
  background-color: var(--pc-color-line-generic-active-solid);
167
170
  }
@@ -1,4 +1,4 @@
1
- import React, { useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react';
1
+ import React, { Fragment, useCallback, useContext, useEffect, useMemo, useRef, useState, } from 'react';
2
2
  import _ from 'lodash';
3
3
  import SlickSlider from 'react-slick';
4
4
  import Anchor from '../../components/Anchor/Anchor';
@@ -118,27 +118,53 @@ export const SliderBlock = (props) => {
118
118
  slider.slickGoTo(nextIndex);
119
119
  }
120
120
  }, [slider, currentIndex, slidesCountByBreakpoint]);
121
+ const barSlidesCount = childrenCount - slidesToShowCount + 1;
122
+ const barPosition = (DOT_GAP + DOT_WIDTH) * currentIndex;
123
+ const barWidth = DOT_WIDTH + (DOT_GAP + DOT_WIDTH) * (slidesCountByBreakpoint - 1);
121
124
  const renderBar = () => {
122
- const barPosition = (DOT_GAP + DOT_WIDTH) * currentIndex;
123
- const barWidth = DOT_WIDTH + (DOT_GAP + DOT_WIDTH) * (slidesCountByBreakpoint - 1);
124
125
  return (slidesCountByBreakpoint > 1 && (React.createElement("li", { className: b('bar'), style: {
125
126
  left: barPosition,
126
127
  width: barWidth,
127
128
  } })));
128
129
  };
130
+ // renders additional bar, not visible in the layout but visible for screenreaders
131
+ const renderAccessibleBar = (index) => {
132
+ return (
133
+ // To have this key differ from keys used in renderDot function, added `-accessible-bar` part
134
+ React.createElement(Fragment, { key: `${index}-accessible-bar` }, slidesCountByBreakpoint > 1 && (React.createElement("li", { className: b('accessible-bar'), "aria-current": true, "aria-label": `Slide ${currentIndex + 1} of ${barSlidesCount}`, style: {
135
+ left: barPosition,
136
+ width: barWidth,
137
+ } }))));
138
+ };
129
139
  const renderDot = (index) => {
130
- return (React.createElement("li", { key: index, className: b('dot', { active: index === currentIndex }), onClick: () => handleDotClick(index) }));
140
+ const currentIndexDiff = index - currentIndex;
141
+ let currentSlideNumber;
142
+ if (0 <= currentIndexDiff && currentIndexDiff < slidesToShowCount) {
143
+ currentSlideNumber = currentIndex + 1;
144
+ }
145
+ else if (currentIndexDiff >= slidesToShowCount) {
146
+ currentSlideNumber = index - slidesToShowCount + 2;
147
+ }
148
+ else {
149
+ currentSlideNumber = index + 1;
150
+ }
151
+ return (React.createElement("li", { key: index, className: b('dot', { active: index === currentIndex }), onClick: () => handleDotClick(index), "aria-hidden": (slidesCountByBreakpoint > 1 &&
152
+ 0 <= currentIndexDiff &&
153
+ currentIndexDiff < slidesToShowCount) ||
154
+ undefined, "aria-label": `Slide ${currentSlideNumber} of ${barSlidesCount}` }));
131
155
  };
132
156
  const renderNavigation = () => {
133
157
  if (childrenCount <= slidesCountByBreakpoint || !dots || childrenCount === 1) {
134
158
  return null;
135
159
  }
160
+ const dotsList = Array(childrenCount)
161
+ .fill(null)
162
+ .map((_item, index) => renderDot(index));
163
+ dotsList.splice(currentIndex, 0, renderAccessibleBar(currentIndex));
136
164
  return (React.createElement("div", { className: b('dots', dotsClassName) },
137
165
  React.createElement("ul", { className: b('dots-list') },
138
166
  renderBar(),
139
- Array(childrenCount)
140
- .fill(null)
141
- .map((_item, index) => renderDot(index)))));
167
+ dotsList)));
142
168
  };
143
169
  const renderDisclaimer = () => {
144
170
  return disclaimer ? (React.createElement("div", { className: b('disclaimer', { size: disclaimer.size || 'm' }) }, disclaimer.text)) : null;
@@ -0,0 +1,4 @@
1
+ {
2
+ "arrow-right": "Next",
3
+ "arrow-left": "Previous"
4
+ }
@@ -0,0 +1,2 @@
1
+ declare const _default: (key: string, params?: import("@gravity-ui/i18n").Params | undefined) => string;
2
+ export default _default;
@@ -0,0 +1,5 @@
1
+ import { registerKeyset } from '../../../utils/registerKeyset';
2
+ import en from './en.json';
3
+ import ru from './ru.json';
4
+ const COMPONENT = 'SliderBlock';
5
+ export default registerKeyset({ en, ru }, COMPONENT);
@@ -0,0 +1,4 @@
1
+ {
2
+ "arrow-right": "Дальше",
3
+ "arrow-left": "Назад"
4
+ }
@@ -1,5 +1,6 @@
1
1
  import React from 'react';
2
2
  import { WithChildren } from '../../models';
3
+ import { QAProps } from '../../models/common';
3
4
  export interface HTMLProps {
4
5
  children?: string;
5
6
  block?: boolean;
@@ -7,12 +8,13 @@ export interface HTMLProps {
7
8
  itemProp?: string;
8
9
  id?: string;
9
10
  }
10
- declare const HTML: ({ children, block, className, itemProp, id }: WithChildren<HTMLProps>) => React.DetailedReactHTMLElement<{
11
+ declare const HTML: ({ children, block, className, itemProp, id, qa, }: WithChildren<HTMLProps & QAProps>) => React.DetailedReactHTMLElement<{
11
12
  dangerouslySetInnerHTML: {
12
13
  __html: string | (string & React.ReactElement<any, string | React.JSXElementConstructor<any>>) | (string & React.ReactFragment) | (string & React.ReactPortal);
13
14
  };
14
15
  className: string | undefined;
15
16
  itemProp: string | undefined;
16
17
  id: string | undefined;
18
+ 'data-qa': string | undefined;
17
19
  }, HTMLElement> | null;
18
20
  export default HTML;
@@ -1,6 +1,6 @@
1
1
  import React from 'react';
2
2
  import { hasBlockTag } from '../../utils';
3
- const HTML = ({ children, block = false, className, itemProp, id }) => {
3
+ const HTML = ({ children, block = false, className, itemProp, id, qa, }) => {
4
4
  if (!children) {
5
5
  return null;
6
6
  }
@@ -9,6 +9,7 @@ const HTML = ({ children, block = false, className, itemProp, id }) => {
9
9
  className,
10
10
  itemProp,
11
11
  id,
12
+ 'data-qa': qa,
12
13
  });
13
14
  };
14
15
  export default HTML;
@@ -1,5 +1,6 @@
1
1
  import { ClassNameProps, LinkProps, Tabbable, WithChildren } from '../../models';
2
+ import { QAProps } from '../../models/common';
2
3
  import './Link.css';
3
- export type LinkFullProps = LinkProps & ClassNameProps & Tabbable;
4
+ export type LinkFullProps = LinkProps & ClassNameProps & Tabbable & QAProps;
4
5
  declare const LinkBlock: (props: WithChildren<LinkFullProps>) => JSX.Element;
5
6
  export default LinkBlock;
@@ -7,6 +7,7 @@ import { useMetrika } from '../../hooks/useMetrika';
7
7
  import { Chevron } from '../../icons';
8
8
  import { DefaultEventNames, } from '../../models';
9
9
  import { block, getLinkProps, setUrlTld } from '../../utils';
10
+ import { getQaAttrubutes } from '../../utils/index';
10
11
  import BackLink from '../BackLink/BackLink';
11
12
  import FileLink from '../FileLink/FileLink';
12
13
  import './Link.css';
@@ -25,7 +26,8 @@ function getArrowSize(size) {
25
26
  }
26
27
  }
27
28
  const LinkBlock = (props) => {
28
- const { text, url, arrow, metrikaGoals, pixelEvents, analyticsEvents, theme = 'file-link', colorTheme = 'light', textSize = 'm', className, target, children, tabIndex, } = props;
29
+ const { text, url, arrow, metrikaGoals, pixelEvents, analyticsEvents, theme = 'file-link', colorTheme = 'light', textSize = 'm', className, target, children, tabIndex, qa, } = props;
30
+ const qaAttributes = getQaAttrubutes(qa, ['normal']);
29
31
  const handleMetrika = useMetrika();
30
32
  const handleAnalytics = useAnalytics(DefaultEventNames.Link, url);
31
33
  const { hostname } = useContext(LocationContext);
@@ -46,7 +48,7 @@ const LinkBlock = (props) => {
46
48
  case 'normal': {
47
49
  const linkProps = getLinkProps(url, hostname, target);
48
50
  const content = children || text;
49
- return (React.createElement("a", Object.assign({ className: b('link', { theme: colorTheme, 'has-arrow': arrow }), href: href, onClick: onClick, tabIndex: tabIndex }, linkProps), arrow ? (React.createElement(Fragment, null,
51
+ return (React.createElement("a", Object.assign({ className: b('link', { theme: colorTheme, 'has-arrow': arrow }), href: href, onClick: onClick, tabIndex: tabIndex }, linkProps, { "data-qa": qaAttributes.normal }), arrow ? (React.createElement(Fragment, null,
50
52
  React.createElement("span", { className: b('content') }, content),
51
53
  WORD_JOINER_SYM,
52
54
  React.createElement(Icon, { className: b('arrow'), data: Chevron, size: getArrowSize(textSize) }))) : (content)));