@gravity-ui/blog-constructor 5.0.1 → 5.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/build/cjs/blocks/Author/Author.js +2 -2
  2. package/build/cjs/blocks/Banner/Banner.js +7 -5
  3. package/build/cjs/blocks/ColoredText/ColoredText.js +5 -4
  4. package/build/cjs/blocks/YFM/YFM.js +6 -4
  5. package/build/cjs/components/FeedHeader/components/Controls/Controls.css +5 -23
  6. package/build/cjs/components/FeedHeader/components/Controls/Controls.js +1 -1
  7. package/build/cjs/components/FeedHeader/components/Controls/customRenders.js +1 -2
  8. package/build/cjs/components/FeedHeader/components/CustomSwitcher/CustomSwitcher.css +1 -1
  9. package/build/cjs/components/Paginator/types.d.ts +4 -4
  10. package/build/cjs/components/PostCard/PostCard.d.ts +3 -2
  11. package/build/cjs/components/PostCard/PostCard.js +5 -4
  12. package/build/cjs/components/PostInfo/SuggestPostInfo.d.ts +2 -2
  13. package/build/cjs/components/PostInfo/SuggestPostInfo.js +2 -1
  14. package/build/cjs/components/PostInfo/components/Date.d.ts +2 -1
  15. package/build/cjs/components/PostInfo/components/Date.js +2 -1
  16. package/build/cjs/components/Posts/Posts.js +5 -2
  17. package/build/cjs/components/Search/Search.css +13 -15
  18. package/build/cjs/components/Search/Search.d.ts +7 -1
  19. package/build/cjs/components/Search/Search.js +26 -14
  20. package/build/cjs/models/blocks.d.ts +5 -5
  21. package/build/cjs/models/common.d.ts +8 -0
  22. package/build/cjs/models/common.js +12 -2
  23. package/build/cjs/utils/cn.d.ts +2 -3
  24. package/build/cjs/utils/cn.js +4 -7
  25. package/build/cjs/utils/common.d.ts +1 -0
  26. package/build/cjs/utils/common.js +15 -2
  27. package/build/esm/blocks/Author/Author.js +2 -2
  28. package/build/esm/blocks/Banner/Banner.js +8 -6
  29. package/build/esm/blocks/ColoredText/ColoredText.js +6 -5
  30. package/build/esm/blocks/YFM/YFM.js +6 -4
  31. package/build/esm/components/FeedHeader/components/Controls/Controls.css +5 -23
  32. package/build/esm/components/FeedHeader/components/Controls/Controls.js +1 -1
  33. package/build/esm/components/FeedHeader/components/Controls/customRenders.js +1 -2
  34. package/build/esm/components/FeedHeader/components/CustomSwitcher/CustomSwitcher.css +1 -1
  35. package/build/esm/components/Paginator/types.d.ts +4 -4
  36. package/build/esm/components/PostCard/PostCard.d.ts +3 -2
  37. package/build/esm/components/PostCard/PostCard.js +5 -4
  38. package/build/esm/components/PostInfo/SuggestPostInfo.d.ts +2 -2
  39. package/build/esm/components/PostInfo/SuggestPostInfo.js +2 -1
  40. package/build/esm/components/PostInfo/components/Date.d.ts +2 -1
  41. package/build/esm/components/PostInfo/components/Date.js +2 -1
  42. package/build/esm/components/Posts/Posts.js +5 -2
  43. package/build/esm/components/Search/Search.css +13 -15
  44. package/build/esm/components/Search/Search.d.ts +7 -1
  45. package/build/esm/components/Search/Search.js +28 -16
  46. package/build/esm/models/blocks.d.ts +5 -5
  47. package/build/esm/models/common.d.ts +8 -0
  48. package/build/esm/models/common.js +11 -1
  49. package/build/esm/utils/cn.d.ts +2 -3
  50. package/build/esm/utils/cn.js +3 -4
  51. package/build/esm/utils/common.d.ts +1 -0
  52. package/build/esm/utils/common.js +13 -1
  53. package/package.json +5 -6
  54. package/server/models/blocks.d.ts +5 -5
  55. package/server/models/common.d.ts +8 -0
  56. package/server/models/common.js +12 -2
  57. package/build/cjs/components/ButtonWithIcon/ButtonWithIcon.css +0 -76
  58. package/build/cjs/components/ButtonWithIcon/ButtonWithIcon.d.ts +0 -12
  59. package/build/cjs/components/ButtonWithIcon/ButtonWithIcon.js +0 -11
  60. package/build/cjs/demo/DocsDecorator/DocsDecorator.css +0 -14
  61. package/build/cjs/internal-typings/bem-cn-lite.d.ts +0 -17
  62. package/build/esm/components/ButtonWithIcon/ButtonWithIcon.css +0 -76
  63. package/build/esm/components/ButtonWithIcon/ButtonWithIcon.d.ts +0 -13
  64. package/build/esm/components/ButtonWithIcon/ButtonWithIcon.js +0 -7
  65. package/build/esm/demo/DocsDecorator/DocsDecorator.css +0 -14
  66. package/build/esm/internal-typings/bem-cn-lite.d.ts +0 -17
@@ -5,13 +5,15 @@ import { Wrapper } from '../../components/Wrapper/Wrapper';
5
5
  import { BlogMetrikaGoalIds } from '../../constants';
6
6
  import { PaddingsDirections } from '../../models/paddings';
7
7
  import { block } from '../../utils/cn';
8
- import { getBlogElementMetrika, updateContentSizes } from '../../utils/common';
8
+ import { getBlogElementMetrika, getQaAttributes, updateContentSizes } from '../../utils/common';
9
9
  import './Banner.css';
10
10
  const b = block('banner');
11
+ const BANNER_CUSTOM_QA_ATTRIBUTES = ['image-container'];
11
12
  export const Banner = (_a) => {
12
13
  var _b;
13
- var { color, imageSize = 's', image, paddingTop, paddingBottom } = _a, content = __rest(_a, ["color", "imageSize", "image", "paddingTop", "paddingBottom"]);
14
+ var { color, imageSize = 's', image, paddingTop, paddingBottom, qa } = _a, content = __rest(_a, ["color", "imageSize", "image", "paddingTop", "paddingBottom", "qa"]);
14
15
  const contentStyle = {};
16
+ const qaAttributes = getQaAttributes(qa, BANNER_CUSTOM_QA_ATTRIBUTES);
15
17
  if (color) {
16
18
  contentStyle.backgroundColor = color;
17
19
  }
@@ -30,10 +32,10 @@ export const Banner = (_a) => {
30
32
  return (React.createElement(Wrapper, { paddings: {
31
33
  [PaddingsDirections.top]: paddingTop,
32
34
  [PaddingsDirections.bottom]: paddingBottom,
33
- }, className: b('container') },
34
- React.createElement("div", { className: b('content'), style: contentStyle, "data-qa": "blog-banner-content" },
35
+ }, qa: qaAttributes.wrapper, className: b('container') },
36
+ React.createElement("div", { className: b('content'), style: contentStyle, "data-qa": qaAttributes.content },
35
37
  React.createElement("div", { className: b('info') },
36
- React.createElement(Content, Object.assign({}, contentData))),
37
- image && (React.createElement("div", { className: b('image-container', { ['image-size']: imageSize }) },
38
+ React.createElement(Content, Object.assign({}, contentData, { qa: qaAttributes.content }))),
39
+ image && (React.createElement("div", { className: b('image-container', { ['image-size']: imageSize }), "data-qa": qaAttributes.imageContainer },
38
40
  React.createElement(Image, { className: b('image'), src: image }))))));
39
41
  };
@@ -4,18 +4,19 @@ import { BackgroundImage, Content } from '@gravity-ui/page-constructor';
4
4
  import { Wrapper } from '../../components/Wrapper/Wrapper';
5
5
  import { PaddingsDirections } from '../../models/paddings';
6
6
  import { block } from '../../utils/cn';
7
- import { updateContentSizes } from '../../utils/common';
7
+ import { getQaAttributes, updateContentSizes } from '../../utils/common';
8
8
  import './ColoredText.css';
9
9
  const b = block('colored-text');
10
10
  export const ColoredText = (_a) => {
11
- var { background, paddingTop, paddingBottom } = _a, content = __rest(_a, ["background", "paddingTop", "paddingBottom"]);
11
+ var { background, paddingTop, paddingBottom, qa } = _a, content = __rest(_a, ["background", "paddingTop", "paddingBottom", "qa"]);
12
12
  const contentData = updateContentSizes(content);
13
+ const qaAttributes = getQaAttributes(qa);
13
14
  return (React.createElement(Wrapper, { paddings: {
14
15
  [PaddingsDirections.top]: paddingTop,
15
16
  [PaddingsDirections.bottom]: paddingBottom,
16
- } },
17
- React.createElement("div", { className: b('container'), style: { backgroundColor: (background === null || background === void 0 ? void 0 : background.color) || 'none' }, "data-qa": "blog-colored-text-content" },
17
+ }, qa: qaAttributes.wrapper },
18
+ React.createElement("div", { className: b('container'), style: { backgroundColor: (background === null || background === void 0 ? void 0 : background.color) || 'none' }, "data-qa": qaAttributes.container },
18
19
  React.createElement("div", { className: b('picture-container') }, (background === null || background === void 0 ? void 0 : background.image) && (React.createElement(BackgroundImage, { className: b('picture'), alt: background === null || background === void 0 ? void 0 : background.altText, src: background === null || background === void 0 ? void 0 : background.image }))),
19
20
  React.createElement("div", { className: b('text-content') },
20
- React.createElement(Content, Object.assign({}, contentData))))));
21
+ React.createElement(Content, Object.assign({}, contentData, { qa: qaAttributes.content }))))));
21
22
  };
@@ -1,15 +1,17 @@
1
1
  import React from 'react';
2
2
  import { YFMWrapper } from '@gravity-ui/page-constructor';
3
- import block from 'bem-cn-lite';
4
3
  import { Wrapper } from '../../components/Wrapper/Wrapper';
5
4
  import { PaddingsDirections } from '../../models/paddings';
6
- const b = block('yfm');
5
+ import { cn } from '../../utils/cn';
6
+ import { getQaAttributes } from '../../utils/common';
7
+ const b = cn('yfm');
7
8
  export const YFM = (props) => {
8
- const { text, paddingTop, paddingBottom } = props;
9
+ const { text, paddingTop, paddingBottom, qa } = props;
10
+ const qaAttributes = getQaAttributes(qa);
9
11
  return (React.createElement(Wrapper, { paddings: {
10
12
  [PaddingsDirections.top]: paddingTop,
11
13
  [PaddingsDirections.bottom]: paddingBottom,
12
- } },
14
+ }, qa: qaAttributes.wrapper },
13
15
  React.createElement(YFMWrapper, { content: text, modifiers: {
14
16
  blog: true,
15
17
  resetPaddings: true,
@@ -23,9 +23,6 @@ unpredictable css rules order in build */
23
23
  width: 100%;
24
24
  max-width: 100%;
25
25
  }
26
- .bc-feed-controls__search .yc-text-input_view_normal .yc-text-input__control {
27
- border-color: var(--g-color-base-background);
28
- }
29
26
  .bc-feed-controls__select {
30
27
  width: 100%;
31
28
  }
@@ -33,9 +30,6 @@ unpredictable css rules order in build */
33
30
  max-height: 500px;
34
31
  border-radius: 12px;
35
32
  }
36
- .bc-feed-controls__popup.bc-feed-controls__popup .g-select-filter + .g-select-list {
37
- margin-top: 0;
38
- }
39
33
  .bc-feed-controls__popup.bc-feed-controls__popup .g-select-list {
40
34
  overflow: scroll;
41
35
  }
@@ -49,27 +43,15 @@ unpredictable css rules order in build */
49
43
  margin: 4px;
50
44
  border-radius: 8px;
51
45
  }
52
- .bc-feed-controls__popup.bc-feed-controls__popup .yc-text-input__content {
53
- padding: 4px;
54
- border: none;
55
- border-bottom: 1px solid var(--yc-color-line-generic);
56
- border-bottom-right-radius: 0;
57
- border-bottom-left-radius: 0;
58
- }
59
- .bc-feed-controls__popup.bc-feed-controls__popup .yc-text-input__content .yc-text-input__control.yc-text-input__control_type_input {
60
- border: none;
61
- }
62
- .bc-feed-controls__popup.bc-feed-controls__popup .g-select-list_size_xl .g-select-list__option {
63
- padding: 0 16px;
64
- }
65
46
 
66
- .bc-feed-controls__popup-filter .yc-text-input__control {
47
+ .bc-feed-controls__popup-filter {
67
48
  font-size: var(--g-text-body-2-font-size);
68
49
  line-height: var(--g-text-body-2-line-height);
50
+ padding: 4px 12px;
69
51
  border: none;
70
- }
71
- .bc-feed-controls__popup-filter .yc-text-input__control:hover, .bc-feed-controls__popup-filter .yc-text-input__control:focus {
72
- border: none;
52
+ border-bottom: 1px solid var(--yc-color-line-generic);
53
+ border-bottom-right-radius: 0;
54
+ border-bottom-left-radius: 0;
73
55
  }
74
56
  .bc-feed-controls__select:hover {
75
57
  width: 100%;
@@ -101,7 +101,7 @@ export const Controls = ({ handleLoadData, tags = [], services = [], queryParams
101
101
  defaultLabel: i18(Keyset.AllTags),
102
102
  }), disablePortal: true, virtualizationThreshold: VIRTUALIZATION_THRESHOLD, renderOption: renderOption })),
103
103
  services.length > 0 ? (React.createElement("div", { className: b('filter-item') },
104
- React.createElement(Select, { className: b('select'), size: "xl", multiple: true, filterable: true, disablePortal: true, options: services, defaultValue: servicesItems, popupClassName: b('popup'), onUpdate: handleServicesSelect, placeholder: i18(Keyset.AllServices), renderControl: renderSwitcher({
104
+ React.createElement(Select, { className: b('select'), size: "xl", multiple: true, filterable: true, hasClear: true, disablePortal: true, options: services, defaultValue: servicesItems, popupClassName: b('popup'), onUpdate: handleServicesSelect, placeholder: i18(Keyset.AllServices), renderControl: renderSwitcher({
105
105
  initial: servicesItems,
106
106
  list: services,
107
107
  defaultLabel: i18(Keyset.AllServices),
@@ -9,6 +9,5 @@ const b = block('feed-controls');
9
9
  export const renderSwitcher = ({ initial, list, defaultLabel }) =>
10
10
  // eslint-disable-next-line react/display-name
11
11
  ({ onClick, ref }) => (React.createElement(CustomSwitcher, { initial: initial, defaultLabel: defaultLabel, list: list, controlRef: ref, onClick: onClick }));
12
- export const renderFilter = ({ value, ref, onChange, onKeyDown }) => (React.createElement("div", { className: b('popup-filter') },
13
- React.createElement(TextInput, { controlRef: ref, controlProps: { size: 1 }, value: value, placeholder: i18(Keyset.Search), onUpdate: onChange, onKeyDown: onKeyDown })));
12
+ export const renderFilter = ({ value, ref, onChange, onKeyDown }) => (React.createElement(TextInput, { controlRef: ref, controlProps: { size: 1 }, value: value, view: "clear", placeholder: i18(Keyset.Search), onUpdate: onChange, onKeyDown: onKeyDown, className: b('popup-filter') }));
14
13
  export const renderOption = (option) => (React.createElement(CustomSelectOption, { data: option }));
@@ -9,7 +9,7 @@ unpredictable css rules order in build */
9
9
  align-items: center;
10
10
  background-color: var(--g-color-base-background);
11
11
  border: 1px solid var(--g-color-base-background);
12
- border-radius: 10px;
12
+ border-radius: var(--g-border-radius-xl);
13
13
  width: 100%;
14
14
  line-height: 42px;
15
15
  }
@@ -1,10 +1,10 @@
1
- import { ReactNode } from 'react';
2
- import { Modifications } from 'bem-cn-lite';
3
- import { ClassNameProps } from '../../models/common';
1
+ import type { ReactNode } from 'react';
2
+ import type { NoStrictEntityMods } from '@bem-react/classname';
3
+ import type { ClassNameProps } from '../../models/common';
4
4
  export interface PaginatorItemProps {
5
5
  key: string | ArrowType;
6
6
  dataKey: string | ArrowType;
7
- mods: Modifications;
7
+ mods: NoStrictEntityMods;
8
8
  content: ReactNode;
9
9
  onClick?: (key: number | ArrowType) => void;
10
10
  loading?: boolean;
@@ -1,12 +1,13 @@
1
1
  import React from 'react';
2
2
  import { MetrikaGoal } from '@gravity-ui/page-constructor';
3
- import { PostData } from '../../models/common';
3
+ import { PostCardSize, PostCardTitleHeadingLevel, PostData } from '../../models/common';
4
4
  import './PostCard.css';
5
5
  type PostCardProps = {
6
6
  post: PostData;
7
7
  fullWidth?: boolean;
8
8
  showTag?: boolean;
9
- size?: 's' | 'm';
9
+ size?: PostCardSize;
10
+ titleHeadingLevel?: PostCardTitleHeadingLevel;
10
11
  /**
11
12
  * @deprecated Metrika will be deleted after launch of analyticsEvents
12
13
  */
@@ -1,11 +1,12 @@
1
1
  import React, { useContext, useMemo } from 'react';
2
2
  import { CardBase, HTML, YFMWrapper } from '@gravity-ui/page-constructor';
3
3
  import { LikesContext } from '../../contexts/LikesContext';
4
+ import { PostCardSize, PostCardTitleHeadingLevel } from '../../models/common';
4
5
  import { block } from '../../utils/cn';
5
6
  import { SuggestPostInfo } from '../PostInfo/SuggestPostInfo';
6
7
  import './PostCard.css';
7
8
  const b = block('post-card');
8
- export const PostCard = ({ post, metrikaGoals, fullWidth = false, size = 's', showTag = false, }) => {
9
+ export const PostCard = ({ post, metrikaGoals, fullWidth = false, size = PostCardSize.SMALL, showTag = false, titleHeadingLevel = PostCardTitleHeadingLevel.H3, }) => {
9
10
  var _a;
10
11
  const { title: postTitle, htmlTitle, textTitle, blogPostId, id, date, readingTime, hasUserLike, likes, image, description, tags, url, } = post;
11
12
  const title = postTitle || textTitle || htmlTitle;
@@ -22,9 +23,9 @@ export const PostCard = ({ post, metrikaGoals, fullWidth = false, size = 's', sh
22
23
  React.createElement("div", { className: b('image-container'), "data-qa": "blog-suggest-header" })),
23
24
  React.createElement(CardBase.Content, null,
24
25
  showTag && ((_a = tags === null || tags === void 0 ? void 0 : tags[0]) === null || _a === void 0 ? void 0 : _a.name) && (React.createElement("div", { className: b('tag', { size }) }, tags[0].name)),
25
- title && (React.createElement("h4", { className: b('title', { size }) },
26
- React.createElement("span", null,
27
- React.createElement(HTML, null, title)))),
26
+ title &&
27
+ React.createElement(titleHeadingLevel, { className: b('title', { size }) }, React.createElement("span", null,
28
+ React.createElement(HTML, null, title))),
28
29
  description && (React.createElement(YFMWrapper, { className: b('description'), content: description, modifiers: {
29
30
  blog: size === 'm',
30
31
  blogCard: true,
@@ -1,9 +1,9 @@
1
1
  import React from 'react';
2
- import { PostData, QAProps, ToggleLikeCallbackType } from '../../models/common';
2
+ import { PostCardSize, PostData, QAProps, ToggleLikeCallbackType } from '../../models/common';
3
3
  import './PostInfo.css';
4
4
  export interface SuggestPostInfoProps extends Pick<PostData, 'date' | 'readingTime' | 'hasUserLike'>, QAProps {
5
5
  postId: PostData['blogPostId'];
6
- size?: 's' | 'm';
6
+ size?: PostCardSize;
7
7
  likes?: {
8
8
  likesCount?: number;
9
9
  hasUserLike?: boolean;
@@ -1,5 +1,6 @@
1
1
  import React from 'react';
2
2
  import { useLikes } from '../../hooks/useLikes';
3
+ import { PostCardSize } from '../../models/common';
3
4
  import { block } from '../../utils/cn';
4
5
  import { Date } from './components/Date';
5
6
  import { ReadingTime } from './components/ReadingTime';
@@ -20,7 +21,7 @@ const b = block('post-info');
20
21
  *
21
22
  * @returns jsx
22
23
  */
23
- export const SuggestPostInfo = ({ postId, date, readingTime, likes, size = 's', qa, }) => {
24
+ export const SuggestPostInfo = ({ postId, date, readingTime, likes, size = PostCardSize.SMALL, qa, }) => {
24
25
  const { hasUserLike, likesCount, handleLike } = useLikes({
25
26
  hasLike: likes === null || likes === void 0 ? void 0 : likes.hasUserLike,
26
27
  count: likes === null || likes === void 0 ? void 0 : likes.likesCount,
@@ -1,8 +1,9 @@
1
1
  import React from 'react';
2
+ import { PostCardSize } from '../../../models/common';
2
3
  import '../PostInfo.css';
3
4
  type DateProps = {
4
5
  date: string | number;
5
- size?: 's' | 'm';
6
+ size?: PostCardSize;
6
7
  };
7
8
  export declare const Date: React.FC<DateProps>;
8
9
  export {};
@@ -1,10 +1,11 @@
1
1
  import React, { useContext } from 'react';
2
2
  import { LocaleContext } from '../../../contexts/LocaleContext';
3
+ import { PostCardSize } from '../../../models/common';
3
4
  import { block } from '../../../utils/cn';
4
5
  import { format } from '../../../utils/date';
5
6
  import '../PostInfo.css';
6
7
  const b = block('post-info');
7
- export const Date = ({ date, size = 's' }) => {
8
+ export const Date = ({ date, size = PostCardSize.SMALL }) => {
8
9
  const { locale } = useContext(LocaleContext);
9
10
  return React.createElement("div", { className: b('item', { size }) }, format(date, 'longDate', locale === null || locale === void 0 ? void 0 : locale.code));
10
11
  };
@@ -2,6 +2,7 @@ import React from 'react';
2
2
  import { CardLayoutBlock } from '@gravity-ui/page-constructor';
3
3
  import { Button } from '@gravity-ui/uikit';
4
4
  import { Keyset, i18 } from '../../i18n';
5
+ import { PostCardSize, PostCardTitleHeadingLevel } from '../../models/common';
5
6
  import { block } from '../../utils/cn';
6
7
  import { Paginator } from '../Paginator/Paginator';
7
8
  import { PostCard } from '../PostCard/PostCard';
@@ -12,12 +13,14 @@ export const Posts = ({ containerId, pinnedPostOnPage, currentPage, postsOnPage,
12
13
  isFetching && React.createElement("div", { className: b('loaderContainer') }),
13
14
  React.createElement("div", { id: containerId, className: b('cards-container', { isLoading: isFetching }) },
14
15
  pinnedPostOnPage && currentPage === 1 && (React.createElement("div", { className: b('pinned-container') },
15
- React.createElement(PostCard, { post: pinnedPostOnPage, size: "m", fullWidth: true, showTag: true }))),
16
+ React.createElement(PostCard, { post: pinnedPostOnPage, size: PostCardSize.MEDIUM, fullWidth: true, showTag: true, titleHeadingLevel: PostCardTitleHeadingLevel.H2 }))),
16
17
  (postsOnPage === null || postsOnPage === void 0 ? void 0 : postsOnPage.length) ? (React.createElement(CardLayoutBlock, { title: '', colSizes: {
17
18
  all: 12,
18
19
  lg: 4,
19
20
  md: 6,
20
- } }, postsOnPage === null || postsOnPage === void 0 ? void 0 : postsOnPage.map((post) => (React.createElement(PostCard, { key: post.id, post: post, showTag: true }))))) : (React.createElement(PostsEmpty, null))),
21
+ } }, postsOnPage === null || postsOnPage === void 0 ? void 0 : postsOnPage.map((post) => (React.createElement(PostCard, { key: post.id, post: post, showTag: true, titleHeadingLevel: pinnedPostOnPage
22
+ ? PostCardTitleHeadingLevel.H3
23
+ : PostCardTitleHeadingLevel.H2 }))))) : (React.createElement(PostsEmpty, null))),
21
24
  React.createElement("div", { className: b('pagination') },
22
25
  Boolean(isShowMoreVisible && (postsOnPage === null || postsOnPage === void 0 ? void 0 : postsOnPage.length)) && (React.createElement(Button, { view: "outlined", size: "xl", className: b('more-button'), onClick: handleShowMore }, i18(Keyset.ActionLoadMore))),
23
26
  errorShowMore && (React.createElement("div", { className: b('error-show-more') },
@@ -7,30 +7,28 @@ unpredictable css rules order in build */
7
7
  display: flex;
8
8
  align-items: center;
9
9
  justify-content: flex-end;
10
- height: 44px;
11
10
  transition: width 0.3s;
12
11
  }
13
- .bc-search__close-button, .bc-search__search-button {
14
- position: absolute;
15
- z-index: 2;
16
- right: 10px;
12
+ .bc-search__input-icon {
13
+ display: flex;
14
+ padding-right: 7px;
17
15
  color: var(--g-color-text-hint);
16
+ cursor: pointer;
18
17
  }
19
- .bc-search__search-button:hover {
20
- color: var(--g-color-text-secondary);
18
+ .bc-search__search-suggest {
19
+ display: flex;
20
+ align-items: center;
21
+ height: 44px;
22
+ background-color: var(--g-color-base-background);
23
+ border-radius: var(--bc-text-input-border-radius);
24
+ border: 1px solid var(--g-color-base-background);
21
25
  }
22
- .bc-search__search-suggest-container {
23
- width: 100%;
26
+ .bc-search__search-suggest:hover, .bc-search__search-suggest:focus {
27
+ border: 1px solid var(--g-color-base-generic-hover);
24
28
  }
25
29
  .bc-search .bc-search__search-suggest-control {
26
- background-color: var(--g-color-base-background);
27
30
  padding-left: 12px;
28
31
  padding-right: 32px;
29
- border-radius: var(--bc-text-input-border-radius);
30
- border: 1px solid transparent;
31
- }
32
- .bc-search .bc-search__search-suggest-control:hover, .bc-search .bc-search__search-suggest-control:focus {
33
- border: 1px solid var(--g-color-base-generic-hover);
34
32
  }
35
33
  .bc-search_size_s {
36
34
  --bc-text-input-border-radius: var(--g-border-radius-l);
@@ -12,5 +12,11 @@ interface SearchProps extends ClassNameProps {
12
12
  autoFocus?: boolean;
13
13
  className?: string;
14
14
  }
15
- export declare const Search: React.FC<SearchProps>;
15
+ /**
16
+ * Search component, placed on blog main page,
17
+ * based on TextInput from uikit
18
+ *
19
+ * @returns {JSX|null}
20
+ */
21
+ export declare const Search: ({ className, initialValue, onSubmit, debounce, placeholder, size, autoFocus, value: externalValue, }: SearchProps) => React.JSX.Element;
16
22
  export {};
@@ -1,18 +1,23 @@
1
- import React, { useEffect, useRef, useState } from 'react';
2
- import { TextInput } from '@gravity-ui/uikit';
1
+ import React, { useEffect, useMemo, useRef, useState } from 'react';
2
+ import { Icon, TextInput } from '@gravity-ui/uikit';
3
3
  import { debounce as lodashDebounce } from 'lodash';
4
4
  import { useIsIPhone } from '../../hooks/useIsIPhone';
5
5
  import { Keyset, i18 } from '../../i18n';
6
6
  import { Close } from '../../icons/Close';
7
7
  import { SearchIcon } from '../../icons/SearchIcon';
8
8
  import { block } from '../../utils/cn';
9
- import { ButtonWithIcon } from '../ButtonWithIcon/ButtonWithIcon';
10
9
  import './Search.css';
11
10
  const b = block('search');
12
11
  const SEARCH_ICON_SIZE = 16;
13
12
  const CLOSE_ICON_SIZE = 12;
14
- export const Search = (props) => {
15
- const { className, initialValue, onSubmit, debounce = 300, placeholder = i18(Keyset.Search), size = 'm', autoFocus = false, value: externalValue, } = props;
13
+ const AUTOFOCUS_TIMEOUT = 0;
14
+ /**
15
+ * Search component, placed on blog main page,
16
+ * based on TextInput from uikit
17
+ *
18
+ * @returns {JSX|null}
19
+ */
20
+ export const Search = ({ className, initialValue, onSubmit, debounce = 300, placeholder = i18(Keyset.Search), size = 'm', autoFocus = false, value: externalValue, }) => {
16
21
  const handleChange = lodashDebounce(onSubmit, debounce);
17
22
  const [value, setValue] = useState(initialValue);
18
23
  const inputRef = useRef(null);
@@ -24,20 +29,27 @@ export const Search = (props) => {
24
29
  }, [externalValue]);
25
30
  useEffect(() => {
26
31
  if (autoFocus && !isIPhone) {
27
- setTimeout(() => { var _a; return (_a = inputRef === null || inputRef === void 0 ? void 0 : inputRef.current) === null || _a === void 0 ? void 0 : _a.focus({ preventScroll: true }); }, 0);
32
+ setTimeout(() => { var _a; return (_a = inputRef === null || inputRef === void 0 ? void 0 : inputRef.current) === null || _a === void 0 ? void 0 : _a.focus({ preventScroll: true }); }, AUTOFOCUS_TIMEOUT);
28
33
  }
29
34
  }, [autoFocus, inputRef, isIPhone]);
30
- return (React.createElement("div", { className: b({ size }, className) },
31
- React.createElement("div", { className: b('search-suggest-container') },
32
- React.createElement(TextInput, { className: b('search-suggest'), value: value, onUpdate: (query) => {
33
- setValue(query);
34
- handleChange(query);
35
- }, placeholder: placeholder, size: size === 'm' ? 'xl' : 'l', controlRef: inputRef, view: "clear", controlProps: {
36
- className: b('search-suggest-control'),
37
- } })),
38
- value ? (React.createElement(ButtonWithIcon, { className: b('close-button'), icon: Close, iconSize: CLOSE_ICON_SIZE, size: "xs", onClick: () => {
35
+ const rightContent = useMemo(() => {
36
+ const iconData = value ? Close : SearchIcon;
37
+ const iconSize = value ? CLOSE_ICON_SIZE : SEARCH_ICON_SIZE;
38
+ const handleClick = () => {
39
+ if (value) {
39
40
  handleChange.cancel();
40
41
  setValue('');
41
42
  onSubmit('');
42
- } })) : (React.createElement(ButtonWithIcon, { className: b('search-button'), icon: SearchIcon, iconSize: SEARCH_ICON_SIZE, size: "xs", disabled: true }))));
43
+ }
44
+ };
45
+ return (React.createElement("div", { className: b('input-icon'), onClick: handleClick },
46
+ React.createElement(Icon, { size: iconSize, data: iconData })));
47
+ }, [handleChange, onSubmit, value]);
48
+ return (React.createElement("div", { className: b({ size }, className) },
49
+ React.createElement(TextInput, { className: b('search-suggest'), value: value, onUpdate: (query) => {
50
+ setValue(query);
51
+ handleChange(query);
52
+ }, placeholder: placeholder, size: size === 'm' ? 'xl' : 'l', controlRef: inputRef, view: "clear", controlProps: {
53
+ className: b('search-suggest-control'),
54
+ }, rightContent: rightContent })));
43
55
  };
@@ -1,17 +1,17 @@
1
1
  import { ReactElement } from 'react';
2
2
  import { ContentBlockProps, HeaderBlockProps, MediaProps as PCMediaProps, TextTheme } from '@gravity-ui/page-constructor';
3
- import { BlockType, ClassNameProps, PostData } from './common';
3
+ import { BlockType, ClassNameProps, PostData, QAProps } from './common';
4
4
  import { PaddingsYFMProps } from './paddings';
5
5
  export type AuthorProps = ClassNameProps & {
6
6
  authorId: number;
7
7
  image: string;
8
- } & PaddingsYFMProps;
9
- export type BannerProps = ContentBlockProps & {
8
+ } & PaddingsYFMProps & QAProps;
9
+ export type BannerProps = ContentBlockProps & QAProps & {
10
10
  color?: string;
11
11
  image?: string;
12
12
  imageSize?: 's' | 'm';
13
13
  } & PaddingsYFMProps;
14
- export type ColoredTextProps = ContentBlockProps & {
14
+ export type ColoredTextProps = ContentBlockProps & QAProps & {
15
15
  background?: {
16
16
  color?: string;
17
17
  image?: string;
@@ -40,7 +40,7 @@ export type SuggestProps = ClassNameProps & {
40
40
  } & PaddingsYFMProps;
41
41
  export type YFMProps = {
42
42
  text: string;
43
- } & PaddingsYFMProps;
43
+ } & PaddingsYFMProps & QAProps;
44
44
  export type FeedProps = {
45
45
  image: string;
46
46
  };
@@ -168,3 +168,11 @@ export type FetchArgs = {
168
168
  export interface QAProps {
169
169
  qa?: string;
170
170
  }
171
+ export declare enum PostCardSize {
172
+ SMALL = "s",
173
+ MEDIUM = "m"
174
+ }
175
+ export declare enum PostCardTitleHeadingLevel {
176
+ H2 = "h2",
177
+ H3 = "h3"
178
+ }
@@ -28,4 +28,14 @@ export var DefaultEventNames;
28
28
  DefaultEventNames["Tag"] = "selector-tag-click";
29
29
  DefaultEventNames["Service"] = "selector-service-click";
30
30
  DefaultEventNames["SaveOnly"] = "save-only-button-click";
31
- })(DefaultEventNames || (DefaultEventNames = {}));
31
+ })(DefaultEventNames || (DefaultEventNames = {}));
32
+ export var PostCardSize;
33
+ (function (PostCardSize) {
34
+ PostCardSize["SMALL"] = "s";
35
+ PostCardSize["MEDIUM"] = "m";
36
+ })(PostCardSize || (PostCardSize = {}));
37
+ export var PostCardTitleHeadingLevel;
38
+ (function (PostCardTitleHeadingLevel) {
39
+ PostCardTitleHeadingLevel["H2"] = "h2";
40
+ PostCardTitleHeadingLevel["H3"] = "h3";
41
+ })(PostCardTitleHeadingLevel || (PostCardTitleHeadingLevel = {}));
@@ -1,4 +1,3 @@
1
- import blockOrigin from 'bem-cn-lite';
2
1
  export declare const NAMESPACE = "bc-";
3
- export type CnBlock = ReturnType<typeof blockOrigin>;
4
- export declare function block(name: string): CnBlock;
2
+ export declare const cn: import("@bem-react/classname").ClassNameInitilizer;
3
+ export declare const block: import("@bem-react/classname").ClassNameInitilizer;
@@ -1,5 +1,4 @@
1
- import blockOrigin from 'bem-cn-lite';
1
+ import { withNaming } from '@bem-react/classname';
2
2
  export const NAMESPACE = 'bc-';
3
- export function block(name) {
4
- return blockOrigin(`${NAMESPACE}${name}`);
5
- }
3
+ export const cn = withNaming({ e: '__', m: '_' });
4
+ export const block = withNaming({ n: NAMESPACE, e: '__', m: '_' });
@@ -41,4 +41,5 @@ export declare const isMetrikaExist: (goal: NewMetrikaGoal, existGoals: NewMetri
41
41
  export declare const getBlogElementMetrika: (blogCustomGoal: NewMetrikaGoal, existingGoals?: MetrikaGoal) => string | string[] | NewMetrikaGoal[];
42
42
  export declare const getFeedQueryParams: (queryString: Query, pageNumber?: number) => GetPostsRequest;
43
43
  export declare const scrollOnPageChange: (containerId: string) => void;
44
+ export declare const getQaAttributes: (qa?: string, ...customKeys: (string | Array<string>)[]) => Record<string, string>;
44
45
  export {};
@@ -1,9 +1,10 @@
1
1
  import { __rest } from "tslib";
2
2
  import { format, parse } from 'url';
3
3
  import { isNewMetrikaFormat, } from '@gravity-ui/page-constructor';
4
- import { debounce, memoize } from 'lodash';
4
+ import { camelCase, debounce, flatten, memoize } from 'lodash';
5
5
  import { CONTENT_DEFAULT_COL_SIZES, CONTENT_DEFAULT_SIZE, CONTENT_DEFAULT_THEME, DEFAULT_PAGE, DEFAULT_ROWS_PER_PAGE, } from '../blocks/constants';
6
6
  import { Keyset, i18 } from '../i18n';
7
+ const QA_ATTRIBUTES_KEYS = ['container', 'content', 'wrapper', 'image', 'button'];
7
8
  export function getAbsolutePath(router, url) {
8
9
  if (!router || !router.pathname) {
9
10
  return url !== null && url !== void 0 ? url : '';
@@ -98,4 +99,15 @@ export const scrollOnPageChange = (containerId) => {
98
99
  if (y < 0) {
99
100
  scrollToHash(containerId);
100
101
  }
102
+ };
103
+ export const getQaAttributes = (qa, ...customKeys) => {
104
+ const attributes = {};
105
+ if (qa) {
106
+ const keys = QA_ATTRIBUTES_KEYS.concat(flatten(customKeys));
107
+ keys.forEach((key) => {
108
+ attributes[camelCase(key)] = `${qa}-${key}`;
109
+ });
110
+ attributes.default = qa;
111
+ }
112
+ return attributes;
101
113
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gravity-ui/blog-constructor",
3
- "version": "5.0.1",
3
+ "version": "5.1.0",
4
4
  "description": "Gravity UI Blog Constructor",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -46,9 +46,9 @@
46
46
  "test:watch": "jest --watchAll --maxWorkers=25%"
47
47
  },
48
48
  "dependencies": {
49
- "@gravity-ui/components": "^2.0.0",
49
+ "@bem-react/classname": "^1.6.0",
50
+ "@gravity-ui/components": "^2.4.0",
50
51
  "@gravity-ui/i18n": "^1.0.0",
51
- "bem-cn-lite": "^4.0.0",
52
52
  "lodash": "^4.17.21",
53
53
  "react-helmet": "^6.1.0",
54
54
  "react-player": "^2.9.0",
@@ -76,11 +76,11 @@
76
76
  "@commitlint/config-conventional": "^17.4.3",
77
77
  "@doc-tools/transform": "^3.3.2",
78
78
  "@gravity-ui/eslint-config": "^2.0.0",
79
- "@gravity-ui/page-constructor": "^4.0.0",
79
+ "@gravity-ui/page-constructor": "^4.8.0",
80
80
  "@gravity-ui/prettier-config": "^1.0.1",
81
81
  "@gravity-ui/stylelint-config": "^1.0.0",
82
82
  "@gravity-ui/tsconfig": "^1.0.0",
83
- "@gravity-ui/uikit": "^5.1.0",
83
+ "@gravity-ui/uikit": "^5.11.0",
84
84
  "@storybook/addon-essentials": "^7.0.27",
85
85
  "@storybook/cli": "^7.0.27",
86
86
  "@storybook/preset-scss": "^1.0.3",
@@ -99,7 +99,6 @@
99
99
  "@types/sanitize-html": "^2.6.0",
100
100
  "@types/ua-parser-js": "^0.7.36",
101
101
  "eslint": "^8.34.0",
102
- "eslint-plugin-local": "./eslint-plugin-local",
103
102
  "eslint-plugin-no-not-accumulator-reassign": "^0.1.0",
104
103
  "eslint-plugin-testing-library": "^5.9.1",
105
104
  "gulp": "^4.0.2",
@@ -1,17 +1,17 @@
1
1
  import { ReactElement } from 'react';
2
2
  import { ContentBlockProps, HeaderBlockProps, MediaProps as PCMediaProps, TextTheme } from '@gravity-ui/page-constructor';
3
- import { BlockType, ClassNameProps, PostData } from './common';
3
+ import { BlockType, ClassNameProps, PostData, QAProps } from './common';
4
4
  import { PaddingsYFMProps } from './paddings';
5
5
  export type AuthorProps = ClassNameProps & {
6
6
  authorId: number;
7
7
  image: string;
8
- } & PaddingsYFMProps;
9
- export type BannerProps = ContentBlockProps & {
8
+ } & PaddingsYFMProps & QAProps;
9
+ export type BannerProps = ContentBlockProps & QAProps & {
10
10
  color?: string;
11
11
  image?: string;
12
12
  imageSize?: 's' | 'm';
13
13
  } & PaddingsYFMProps;
14
- export type ColoredTextProps = ContentBlockProps & {
14
+ export type ColoredTextProps = ContentBlockProps & QAProps & {
15
15
  background?: {
16
16
  color?: string;
17
17
  image?: string;
@@ -40,7 +40,7 @@ export type SuggestProps = ClassNameProps & {
40
40
  } & PaddingsYFMProps;
41
41
  export type YFMProps = {
42
42
  text: string;
43
- } & PaddingsYFMProps;
43
+ } & PaddingsYFMProps & QAProps;
44
44
  export type FeedProps = {
45
45
  image: string;
46
46
  };