@gravity-ui/blog-constructor 3.3.0 → 3.4.0-alpha.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 (33) hide show
  1. package/CHANGELOG.md +0 -7
  2. package/build/cjs/blocks/CTA/CTA.css +36 -14
  3. package/build/cjs/blocks/CTA/CTA.js +14 -3
  4. package/build/cjs/blocks/Feed/Feed.js +47 -37
  5. package/build/cjs/blocks/Feed/reducer.d.ts +0 -5
  6. package/build/cjs/blocks/Feed/reducer.js +1 -4
  7. package/build/cjs/components/FeedHeader/FeedHeader.js +2 -2
  8. package/build/cjs/components/FeedHeader/components/Controls/Controls.d.ts +2 -3
  9. package/build/cjs/components/FeedHeader/components/Controls/Controls.js +24 -16
  10. package/build/cjs/components/Paginator/Paginator.d.ts +1 -1
  11. package/build/cjs/components/Paginator/Paginator.js +2 -2
  12. package/build/cjs/components/Paginator/types.d.ts +0 -1
  13. package/build/cjs/components/Posts/Posts.css +25 -0
  14. package/build/cjs/components/Posts/Posts.d.ts +0 -1
  15. package/build/cjs/components/Posts/Posts.js +5 -4
  16. package/build/cjs/models/common.d.ts +4 -0
  17. package/build/esm/blocks/CTA/CTA.css +36 -14
  18. package/build/esm/blocks/CTA/CTA.js +14 -3
  19. package/build/esm/blocks/Feed/Feed.js +47 -37
  20. package/build/esm/blocks/Feed/reducer.d.ts +0 -5
  21. package/build/esm/blocks/Feed/reducer.js +1 -4
  22. package/build/esm/components/FeedHeader/FeedHeader.js +2 -2
  23. package/build/esm/components/FeedHeader/components/Controls/Controls.d.ts +2 -3
  24. package/build/esm/components/FeedHeader/components/Controls/Controls.js +25 -17
  25. package/build/esm/components/Paginator/Paginator.d.ts +1 -1
  26. package/build/esm/components/Paginator/Paginator.js +2 -2
  27. package/build/esm/components/Paginator/types.d.ts +0 -1
  28. package/build/esm/components/Posts/Posts.css +25 -0
  29. package/build/esm/components/Posts/Posts.d.ts +0 -1
  30. package/build/esm/components/Posts/Posts.js +5 -4
  31. package/build/esm/models/common.d.ts +4 -0
  32. package/package.json +4 -1
  33. package/server/models/common.d.ts +4 -0
package/CHANGELOG.md CHANGED
@@ -1,12 +1,5 @@
1
1
  # Changelog
2
2
 
3
- ## [3.3.0](https://github.com/gravity-ui/blog-constructor/compare/v3.2.1...v3.3.0) (2023-05-16)
4
-
5
-
6
- ### Features
7
-
8
- * add md breackpoint to cta-block ([#45](https://github.com/gravity-ui/blog-constructor/issues/45)) ([e859b0b](https://github.com/gravity-ui/blog-constructor/commit/e859b0b02e80b20eb0b4496e2c4834fb2de74922))
9
-
10
3
  ## [3.2.1](https://github.com/gravity-ui/blog-constructor/compare/v3.2.0...v3.2.1) (2023-05-16)
11
4
 
12
5
 
@@ -8,27 +8,49 @@
8
8
  flex-direction: column;
9
9
  flex-grow: 1;
10
10
  padding: 32px;
11
- width: calc(33.3333333333% - (32px / 2));
11
+ }
12
+ .bc-cta__button {
13
+ display: flex;
14
+ padding-bottom: 16px;
12
15
  }
13
16
  .bc-cta__content {
14
17
  display: flex;
15
- flex-flow: row wrap;
16
- gap: 16px;
18
+ flex-direction: column;
17
19
  }
18
- @media (max-width: 769px) {
20
+ @media (min-width: 577px) {
19
21
  .bc-cta__content {
20
- flex-wrap: wrap;
22
+ display: flex;
23
+ flex-direction: row;
21
24
  }
22
- .bc-cta__card {
23
- width: calc((100% / 2) - 16px);
24
- flex-grow: 1;
25
- }
26
- }
27
- @media (max-width: 577px) {
28
- .bc-cta__content {
29
- flex-direction: column;
25
+ .bc-cta__button {
26
+ padding-bottom: 0px;
30
27
  }
31
- .bc-cta__card {
28
+ .bc-cta__button_layout {
32
29
  width: 100%;
30
+ max-width: 100%;
31
+ margin-right: 0px;
32
+ }
33
+ .bc-cta__button_layout_2 {
34
+ width: calc(50% - (16px / 2));
35
+ max-width: 50%;
36
+ margin-right: 16px;
37
+ }
38
+ .bc-cta__button_layout_2:nth-child(2n) {
39
+ margin-right: 0px;
40
+ }
41
+ .bc-cta__button_layout_3 {
42
+ width: calc(33.3333333333% - (32px / 3));
43
+ margin-right: 16px;
44
+ }
45
+ .bc-cta__button_layout_3:nth-child(3n) {
46
+ margin-right: 0px;
47
+ }
48
+ .bc-cta__button_layout_4 {
49
+ width: calc(25% - (48px / 4));
50
+ max-width: 50%;
51
+ margin-right: 16px;
52
+ }
53
+ .bc-cta__button_layout_4:nth-child(4n) {
54
+ margin-right: 0px;
33
55
  }
34
56
  }
@@ -12,7 +12,15 @@ const paddings_1 = require("../../models/paddings");
12
12
  const cn_1 = require("../../utils/cn");
13
13
  const common_1 = require("../../utils/common");
14
14
  const b = (0, cn_1.block)('cta');
15
+ const MAX_COLUMN_COUNT = 4, MIN_COLUMN_COUNT = 2, DEFAULT_COLUMN_COUNT = 3;
15
16
  const CTA = ({ items, paddingTop, paddingBottom }) => {
17
+ let count = items ? items.length : DEFAULT_COLUMN_COUNT;
18
+ if (count < MIN_COLUMN_COUNT) {
19
+ count = MIN_COLUMN_COUNT;
20
+ }
21
+ else if (count > MAX_COLUMN_COUNT) {
22
+ count = MAX_COLUMN_COUNT;
23
+ }
16
24
  /**
17
25
  * @deprecated Metrika will be deleted after launch of analyticsEvents
18
26
  */
@@ -23,15 +31,18 @@ const CTA = ({ items, paddingTop, paddingBottom }) => {
23
31
  return (react_1.default.createElement(Wrapper_1.Wrapper, { paddings: {
24
32
  [paddings_1.PaddingsDirections.top]: paddingTop,
25
33
  [paddings_1.PaddingsDirections.bottom]: paddingBottom,
26
- }, className: b('content'), dataQa: "blog-cta-content" }, items.map((content, index) => {
34
+ }, className: b('content'), dataQa: "blog-cta-content" }, items.slice(0, count).map((content, index) => {
27
35
  var _a;
28
36
  const contentData = (0, common_1.updateContentSizes)(content);
29
37
  (_a = contentData.links) === null || _a === void 0 ? void 0 : _a.forEach((link) => {
30
38
  // eslint-disable-next-line no-not-accumulator-reassign/no-not-accumulator-reassign
31
39
  link.metrikaGoals = (0, common_1.getBlogElementMetrika)(metrikaGoal, link.metrikaGoals);
32
40
  });
33
- return (react_1.default.createElement("div", { key: index, className: b('card'), "data-qa": "blog-cta-card" },
34
- react_1.default.createElement(page_constructor_1.Content, Object.assign({}, contentData))));
41
+ return (react_1.default.createElement("div", { key: index, className: b('button', {
42
+ ['layout']: count,
43
+ }), "data-qa": "blog-cta-card" },
44
+ react_1.default.createElement("div", { className: b('card') },
45
+ react_1.default.createElement(page_constructor_1.Content, Object.assign({}, contentData)))));
35
46
  })));
36
47
  };
37
48
  exports.CTA = CTA;
@@ -56,11 +56,10 @@ const Feed = ({ image }) => {
56
56
  const { posts, totalCount, tags, services, pinnedPost, getPosts, pageCountForShowSupportButtons, } = (0, react_1.useContext)(FeedContext_1.FeedContext);
57
57
  const router = (0, react_1.useContext)(RouterContext_1.RouterContext);
58
58
  const handleAnalytics = (0, page_constructor_1.useAnalytics)(common_1.DefaultEventNames.ShowMore);
59
- const [{ errorLoad, errorShowMore, isFetching, isShowMoreFetching, isShowMoreVisible, lastLoadedCount, postCountOnPage, postsOnPage, pinnedPostOnPage, currentPage, queryParams, }, dispatch,] = (0, react_1.useReducer)(reducer_1.reducer, {
59
+ const [{ errorLoad, errorShowMore, isFetching, isShowMoreVisible, lastLoadedCount, postCountOnPage, postsOnPage, pinnedPostOnPage, currentPage, queryParams, }, dispatch,] = (0, react_1.useReducer)(reducer_1.reducer, {
60
60
  errorLoad: false,
61
61
  errorShowMore: false,
62
62
  isFetching: false,
63
- isShowMoreFetching: false,
64
63
  isShowMoreVisible: true,
65
64
  lastLoadedCount: (posts === null || posts === void 0 ? void 0 : posts.length) || 0,
66
65
  postCountOnPage: totalCount || 0,
@@ -75,36 +74,36 @@ const Feed = ({ image }) => {
75
74
  const pageChange = (value) => {
76
75
  dispatch({ type: reducer_1.ActionTypes.PageChange, payload: value });
77
76
  };
78
- const handleChangeQueryParams = (value) => {
79
- dispatch({ type: reducer_1.ActionTypes.QueryParamsChange, payload: value });
80
- const hasFirstPageQuery = Object.keys(value).some((queryKey) => queryKey === PAGE_QUERY && value[queryKey] === FIRST_PAGE);
81
- if (hasFirstPageQuery) {
82
- // eslint-disable-next-line no-not-accumulator-reassign/no-not-accumulator-reassign
83
- value[PAGE_QUERY] = null;
84
- }
85
- router.updateQueryCallback(value);
77
+ const setIsFetching = (value) => {
78
+ dispatch({ type: reducer_1.ActionTypes.SetIsFetching, payload: value });
86
79
  };
87
- const handlePageChange = async (value) => {
88
- pageChange(value);
89
- handleChangeQueryParams({ page: value });
80
+ const setErrorLoad = (value) => {
81
+ dispatch({ type: reducer_1.ActionTypes.SetErrorLoad, payload: value });
90
82
  };
91
- const fetchData = (0, react_1.useCallback)(async (pageNumber) => {
92
- if (queryParams && getPosts) {
93
- const query = (0, common_2.getFeedQueryParams)(queryParams, pageNumber);
94
- const data = await getPosts(query);
83
+ const handleChangeQueryParams = (0, react_1.useCallback)((value) => {
84
+ dispatch({ type: reducer_1.ActionTypes.QueryParamsChange, payload: value });
85
+ const hasFirstPageQuery = Object.keys(value).some((queryKey) => queryKey === PAGE_QUERY && value[queryKey] === FIRST_PAGE);
86
+ const result = hasFirstPageQuery
87
+ ? Object.assign(Object.assign({}, value), { [PAGE_QUERY]: null }) : Object.assign({}, value);
88
+ router.updateQueryCallback(result);
89
+ }, [router]);
90
+ const fetchData = (0, react_1.useCallback)(async ({ page, query }) => {
91
+ if (query && getPosts) {
92
+ const queryParamsForRequest = (0, common_2.getFeedQueryParams)(Object.assign(Object.assign({}, queryParams), query), page);
93
+ const data = await getPosts(queryParamsForRequest);
95
94
  return data;
96
95
  }
97
96
  else {
98
97
  throw new Error('cant get request');
99
98
  }
100
99
  }, [getPosts, queryParams]);
101
- const setIsFetching = (value) => {
102
- dispatch({ type: reducer_1.ActionTypes.SetIsFetching, payload: value });
103
- };
104
- const fetchAndReplaceData = (0, react_1.useCallback)(async (pageNumber) => {
100
+ const handleLoad = (0, react_1.useCallback)(async ({ page, query }) => {
101
+ const pageNumber = Number(page || queryParams.page || constants_2.DEFAULT_PAGE);
102
+ handleChangeQueryParams(query);
105
103
  try {
106
- dispatch({ type: reducer_1.ActionTypes.SetErrorLoad, payload: false });
107
- const fetchedData = await fetchData(pageNumber);
104
+ setErrorLoad(false);
105
+ setIsFetching(true);
106
+ const fetchedData = await fetchData({ page: pageNumber, query });
108
107
  if (fetchedData) {
109
108
  dispatch({
110
109
  type: reducer_1.ActionTypes.SetPosts,
@@ -118,22 +117,35 @@ const Feed = ({ image }) => {
118
117
  }
119
118
  }
120
119
  catch (err) {
121
- dispatch({ type: reducer_1.ActionTypes.SetErrorLoad, payload: true });
120
+ setErrorLoad(true);
122
121
  }
123
122
  (0, common_2.scrollOnPageChange)(CONTAINER_ID);
124
123
  setIsFetching(false);
125
- }, [fetchData]);
124
+ }, [fetchData, handleChangeQueryParams, queryParams]);
125
+ const handlePageChange = async (value) => {
126
+ pageChange(value);
127
+ handleLoad({
128
+ page: value,
129
+ query: Object.assign(Object.assign({}, queryParams), { page: value }),
130
+ });
131
+ };
126
132
  const handleShowMore = async () => {
127
- dispatch({ type: reducer_1.ActionTypes.SetIsShowMoreFetching, payload: true });
128
133
  /**
129
134
  * @deprecated Metrika will be deleted after launch of analyticsEvents
130
135
  */
131
136
  metrika_js_1.default.reachGoal(utils_1.MetrikaCounter.CrossSite, constants_1.BlogMetrikaGoalIds.showMore);
132
137
  handleAnalytics();
138
+ const nextPage = currentPage + 1;
133
139
  try {
134
- const fetchedData = await fetchData(currentPage + 1);
140
+ setIsFetching(true);
141
+ const fetchedData = await fetchData({
142
+ page: nextPage,
143
+ query: {
144
+ page: nextPage,
145
+ },
146
+ });
135
147
  handleChangeQueryParams({
136
- page: currentPage + 1,
148
+ page: nextPage,
137
149
  });
138
150
  if (fetchedData) {
139
151
  dispatch({
@@ -141,7 +153,7 @@ const Feed = ({ image }) => {
141
153
  payload: {
142
154
  posts: (postsOnPage !== null && postsOnPage !== void 0 ? postsOnPage : []).concat(fetchedData.posts),
143
155
  count: fetchedData.count,
144
- currentPage: currentPage + 1,
156
+ currentPage: nextPage,
145
157
  lastLoadedCount: fetchedData.posts.length,
146
158
  },
147
159
  });
@@ -150,13 +162,11 @@ const Feed = ({ image }) => {
150
162
  catch (err) {
151
163
  dispatch({ type: reducer_1.ActionTypes.SetErrorShowMore, payload: true });
152
164
  }
153
- dispatch({ type: reducer_1.ActionTypes.SetIsShowMoreFetching, payload: false });
165
+ setIsFetching(false);
154
166
  };
155
- (0, react_1.useEffect)(() => {
156
- if (isFetching) {
157
- fetchAndReplaceData(Number(queryParams.page || constants_2.DEFAULT_PAGE));
158
- }
159
- }, [fetchAndReplaceData, isFetching, queryParams.page]);
167
+ const handleOnErrorReload = (0, react_1.useCallback)(() => {
168
+ handleLoad({ page: currentPage, query: queryParams });
169
+ }, [currentPage, handleLoad, queryParams]);
160
170
  (0, react_1.useEffect)(() => {
161
171
  const loadedPostsCount = currentPage * perPageInQuery;
162
172
  dispatch({
@@ -174,11 +184,11 @@ const Feed = ({ image }) => {
174
184
  icon: tag.icon && react_1.default.createElement(uikit_1.Icon, { data: tag.icon }),
175
185
  })), [tags]);
176
186
  return (react_1.default.createElement("div", null,
177
- react_1.default.createElement(FeedHeader_1.FeedHeader, { verticalOffset: "s", tags: tagItems, services: serviceItems, setIsFetching: setIsFetching, handleChangeQuery: handleChangeQueryParams, queryParams: queryParams, background: {
187
+ react_1.default.createElement(FeedHeader_1.FeedHeader, { verticalOffset: "s", tags: tagItems, services: serviceItems, handleLoadData: handleLoad, queryParams: queryParams, background: {
178
188
  fullWidth: true,
179
189
  url: image,
180
190
  disableCompress: true,
181
191
  } }),
182
- errorLoad ? (react_1.default.createElement(PostsError_1.PostsError, { onButtonClick: () => fetchAndReplaceData(Number(queryParams.page || constants_2.DEFAULT_PAGE)) })) : (react_1.default.createElement(Posts_1.Posts, { containerId: CONTAINER_ID, currentPage: currentPage, isShowMoreVisible: isShowMoreVisible, errorShowMore: errorShowMore, postCountOnPage: postCountOnPage, perPageInQuery: perPageInQuery, handleShowMore: handleShowMore, handlePageChange: handlePageChange, postsOnPage: postsOnPage, pinnedPostOnPage: pinnedPostOnPage, isFetching: isFetching, isShowMoreFetching: isShowMoreFetching, pageCountForShowSupportButtons: pageCountForShowSupportButtons }))));
192
+ errorLoad ? (react_1.default.createElement(PostsError_1.PostsError, { onButtonClick: handleOnErrorReload })) : (react_1.default.createElement(Posts_1.Posts, { containerId: CONTAINER_ID, currentPage: currentPage, isShowMoreVisible: isShowMoreVisible, errorShowMore: errorShowMore, postCountOnPage: postCountOnPage, perPageInQuery: perPageInQuery, handleShowMore: handleShowMore, handlePageChange: handlePageChange, postsOnPage: postsOnPage, pinnedPostOnPage: pinnedPostOnPage, isFetching: isFetching, pageCountForShowSupportButtons: pageCountForShowSupportButtons }))));
183
193
  };
184
194
  exports.Feed = Feed;
@@ -6,7 +6,6 @@ export declare enum ActionTypes {
6
6
  SetPosts = "setPosts",
7
7
  SetShowMore = "setShowMore",
8
8
  SetIsFetching = "setIsFetching",
9
- SetIsShowMoreFetching = "setIsShowMoreFetching",
10
9
  PageChange = "pageChange",
11
10
  QueryParamsChange = "queryParamsChange"
12
11
  }
@@ -15,7 +14,6 @@ export type State = {
15
14
  errorLoad: boolean;
16
15
  errorShowMore: boolean;
17
16
  isFetching: boolean;
18
- isShowMoreFetching: boolean;
19
17
  isShowMoreVisible: boolean;
20
18
  lastLoadedCount: number;
21
19
  pinnedPostOnPage?: PostData;
@@ -29,9 +27,6 @@ type Action = {
29
27
  } | {
30
28
  type: ActionTypes.SetIsFetching;
31
29
  payload: boolean;
32
- } | {
33
- type: ActionTypes.SetIsShowMoreFetching;
34
- payload: boolean;
35
30
  } | {
36
31
  type: ActionTypes.SetPosts;
37
32
  payload: {
@@ -9,7 +9,6 @@ var ActionTypes;
9
9
  ActionTypes["SetPosts"] = "setPosts";
10
10
  ActionTypes["SetShowMore"] = "setShowMore";
11
11
  ActionTypes["SetIsFetching"] = "setIsFetching";
12
- ActionTypes["SetIsShowMoreFetching"] = "setIsShowMoreFetching";
13
12
  ActionTypes["PageChange"] = "pageChange";
14
13
  ActionTypes["QueryParamsChange"] = "queryParamsChange";
15
14
  })(ActionTypes = exports.ActionTypes || (exports.ActionTypes = {}));
@@ -20,11 +19,9 @@ const reducer = (state, { type, payload }) => {
20
19
  case ActionTypes.SetPosts:
21
20
  return Object.assign(Object.assign({}, state), { lastLoadedCount: payload.count, postCountOnPage: payload.count, pinnedPostOnPage: payload.pinnedPost, postsOnPage: payload.posts, currentPage: payload.page });
22
21
  case ActionTypes.SetShowMore:
23
- return Object.assign(Object.assign({}, state), { lastLoadedCount: payload.lastLoadedCount, postCountOnPage: payload.count, currentPage: payload.currentPage, postsOnPage: payload.posts, errorShowMore: false, isShowMoreFetching: true });
22
+ return Object.assign(Object.assign({}, state), { lastLoadedCount: payload.lastLoadedCount, postCountOnPage: payload.count, currentPage: payload.currentPage, postsOnPage: payload.posts, errorShowMore: false });
24
23
  case ActionTypes.SetIsFetching:
25
24
  return Object.assign(Object.assign({}, state), { isFetching: payload });
26
- case ActionTypes.SetIsShowMoreFetching:
27
- return Object.assign(Object.assign({}, state), { isShowMoreFetching: payload });
28
25
  case ActionTypes.PageChange:
29
26
  return Object.assign(Object.assign({}, state), { currentPage: payload, isFetching: true });
30
27
  case ActionTypes.SetErrorLoad:
@@ -9,7 +9,7 @@ const page_constructor_1 = require("@gravity-ui/page-constructor");
9
9
  const cn_1 = require("../../utils/cn");
10
10
  const Controls_1 = require("./components/Controls/Controls");
11
11
  const b = (0, cn_1.block)('feed-header');
12
- const FeedHeader = ({ tags, services, setIsFetching, offset = 'default', background, theme = 'default', verticalOffset = 'l', className, handleChangeQuery, queryParams, }) => {
12
+ const FeedHeader = ({ tags, services, handleLoadData, offset = 'default', background, theme = 'default', verticalOffset = 'l', className, queryParams, }) => {
13
13
  const backgroundThemed = background && (0, page_constructor_1.getThemedValue)(background, theme);
14
14
  return (react_1.default.createElement("header", { className: b('header', { ['has-background']: Boolean(background) }, className) },
15
15
  (backgroundThemed === null || backgroundThemed === void 0 ? void 0 : backgroundThemed.color) ? (react_1.default.createElement(page_constructor_1.FullWidthBackground, { style: { backgroundColor: backgroundThemed === null || backgroundThemed === void 0 ? void 0 : backgroundThemed.color }, theme: "rounded" })) : null,
@@ -19,6 +19,6 @@ const FeedHeader = ({ tags, services, setIsFetching, offset = 'default', backgro
19
19
  ? ''
20
20
  : backgroundThemed === null || backgroundThemed === void 0 ? void 0 : backgroundThemed.color,
21
21
  }, disableCompress: backgroundThemed === null || backgroundThemed === void 0 ? void 0 : backgroundThemed.disableCompress })) : null,
22
- react_1.default.createElement(Controls_1.Controls, { tags: tags, services: services, setIsFetching: setIsFetching, handleChangeQuery: handleChangeQuery, queryParams: queryParams }))));
22
+ react_1.default.createElement(Controls_1.Controls, { tags: tags, services: services, handleLoadData: handleLoadData, queryParams: queryParams }))));
23
23
  };
24
24
  exports.FeedHeader = FeedHeader;
@@ -1,15 +1,14 @@
1
1
  import React, { ReactNode } from 'react';
2
- import { HandleChangeQueryParams, Query, SetQueryType } from '../../../../models/common';
2
+ import { FetchArgs, Query, SetQueryType } from '../../../../models/common';
3
3
  export type SelectItem = {
4
4
  content: string;
5
5
  value: string;
6
6
  icon?: ReactNode;
7
7
  };
8
8
  export type ControlsProps = {
9
- setIsFetching: (value: boolean) => void;
9
+ handleLoadData: (props: FetchArgs) => void;
10
10
  tags?: SelectItem[];
11
11
  services?: SelectItem[];
12
- handleChangeQuery: HandleChangeQueryParams;
13
12
  queryParams: Query;
14
13
  setQuery?: SetQueryType;
15
14
  };
@@ -47,7 +47,7 @@ const b = (0, cn_1.block)('feed-controls');
47
47
  const ICON_SIZE = 16;
48
48
  const DEFAULT_PAGE = 1;
49
49
  const VIRTUALIZATION_THRESHOLD = 1000;
50
- const Controls = ({ setIsFetching, tags = [], services = [], handleChangeQuery, queryParams, }) => {
50
+ const Controls = ({ handleLoadData, tags = [], services = [], queryParams, }) => {
51
51
  const { hasLikes } = (0, react_1.useContext)(LikesContext_1.LikesContext);
52
52
  const handleAnalyticsTag = (0, page_constructor_1.useAnalytics)(common_1.DefaultEventNames.Tag);
53
53
  const handleAnalyticsService = (0, page_constructor_1.useAnalytics)(common_1.DefaultEventNames.Service);
@@ -56,21 +56,25 @@ const Controls = ({ setIsFetching, tags = [], services = [], handleChangeQuery,
56
56
  const [savedOnly, setSavedOnly] = (0, react_1.useState)(savedOnlyInitial === 'true');
57
57
  const [search, setSearch] = (0, react_1.useState)(searchInitial);
58
58
  const handleSavedOnly = () => {
59
- handleChangeQuery({
60
- savedOnly: savedOnly ? '' : 'true',
61
- search: '',
62
- tags: '',
63
- page: DEFAULT_PAGE,
64
- services: '',
65
- });
66
59
  handleAnalyticsSaveOnly();
67
60
  setSavedOnly(!savedOnly);
68
- setIsFetching(true);
61
+ handleLoadData({
62
+ page: DEFAULT_PAGE,
63
+ query: {
64
+ savedOnly: savedOnly ? '' : 'true',
65
+ search: '',
66
+ tags: '',
67
+ page: DEFAULT_PAGE,
68
+ services: '',
69
+ },
70
+ });
69
71
  };
70
72
  const handleSearch = (searchValue) => {
71
- handleChangeQuery({ search: searchValue, page: DEFAULT_PAGE });
72
73
  setSearch(searchValue);
73
- setIsFetching(true);
74
+ handleLoadData({
75
+ page: DEFAULT_PAGE,
76
+ query: { search: searchValue, page: DEFAULT_PAGE },
77
+ });
74
78
  };
75
79
  const handleTagSelect = (selectedTags) => {
76
80
  /**
@@ -83,11 +87,13 @@ const Controls = ({ setIsFetching, tags = [], services = [], handleChangeQuery,
83
87
  theme: selectedTags[0],
84
88
  });
85
89
  const isEmptyTag = selectedTags.some((tag) => tag === 'empty');
86
- handleChangeQuery({
87
- tags: isEmptyTag ? '' : selectedTags[0],
90
+ handleLoadData({
88
91
  page: DEFAULT_PAGE,
92
+ query: {
93
+ tags: isEmptyTag ? '' : selectedTags[0],
94
+ page: DEFAULT_PAGE,
95
+ },
89
96
  });
90
- setIsFetching(true);
91
97
  };
92
98
  const handleServicesSelect = (selectedServices) => {
93
99
  const forMetrikaServices = services.filter((service) => {
@@ -104,8 +110,10 @@ const Controls = ({ setIsFetching, tags = [], services = [], handleChangeQuery,
104
110
  service: metrikaAsString,
105
111
  });
106
112
  const servicesAsString = selectedServices.join(',');
107
- handleChangeQuery({ services: servicesAsString, page: DEFAULT_PAGE });
108
- setIsFetching(true);
113
+ handleLoadData({
114
+ page: DEFAULT_PAGE,
115
+ query: { services: servicesAsString, page: DEFAULT_PAGE },
116
+ });
109
117
  };
110
118
  const tagsItems = (0, react_1.useMemo)(() => [{ value: 'empty', content: (0, i18n_1.i18)(i18n_1.Keyset.AllTags) }, ...tags], [tags]);
111
119
  const servicesItems = (0, react_1.useMemo)(() => (servicesInitial ? [...servicesInitial.split(',')] : []), [servicesInitial]);
@@ -1,2 +1,2 @@
1
1
  import { PaginatorProps } from './types';
2
- export declare const Paginator: ({ itemsPerPage, totalItems, maxPages, page, className, loading, onPageChange, pageCountForShowSupportButtons, }: PaginatorProps) => JSX.Element | null;
2
+ export declare const Paginator: ({ itemsPerPage, totalItems, maxPages, page, className, onPageChange, pageCountForShowSupportButtons, }: PaginatorProps) => JSX.Element | null;
@@ -54,7 +54,7 @@ const types_1 = require("./types");
54
54
  const utils_2 = require("./utils");
55
55
  const b = (0, cn_1.block)('paginator');
56
56
  const DEFAULT_PAGE_COUNT_FOR_SHOW_SUPPORT_BUTTONS = 6;
57
- const Paginator = ({ itemsPerPage, totalItems, maxPages, page, className, loading, onPageChange, pageCountForShowSupportButtons = DEFAULT_PAGE_COUNT_FOR_SHOW_SUPPORT_BUTTONS, }) => {
57
+ const Paginator = ({ itemsPerPage, totalItems, maxPages, page, className, onPageChange, pageCountForShowSupportButtons = DEFAULT_PAGE_COUNT_FOR_SHOW_SUPPORT_BUTTONS, }) => {
58
58
  const [pagesCount, setPagesCount] = (0, react_1.useState)((0, utils_2.getPagesCount)({ itemsPerPage, totalItems, maxPages }));
59
59
  (0, react_1.useEffect)(() => {
60
60
  const count = (0, utils_2.getPagesCount)({ itemsPerPage, totalItems, maxPages });
@@ -123,7 +123,7 @@ const Paginator = ({ itemsPerPage, totalItems, maxPages, page, className, loadin
123
123
  }
124
124
  const renderPaginatorItem = (item) => {
125
125
  const { key } = item, rest = __rest(item, ["key"]);
126
- return react_1.default.createElement(PaginatorItem_1.PaginatorItem, Object.assign({ key: `page_${key}` }, rest, { loading: loading }));
126
+ return react_1.default.createElement(PaginatorItem_1.PaginatorItem, Object.assign({ key: `page_${key}` }, rest));
127
127
  };
128
128
  return (react_1.default.createElement("div", { className: b('pagination') },
129
129
  page > 1 && (react_1.default.createElement("div", { className: b('pagination-block') },
@@ -15,7 +15,6 @@ export type PaginatorProps = {
15
15
  totalItems: number;
16
16
  itemsPerPage: number;
17
17
  maxPages: number;
18
- loading: boolean;
19
18
  onPageChange: (page: number) => void;
20
19
  pageCountForShowSupportButtons?: number;
21
20
  } & ClassNameProps;
@@ -1,9 +1,15 @@
1
1
  /* use this for style redefinitions to awoid problems with
2
2
  unpredictable css rules order in build */
3
+ .bc-posts {
4
+ position: relative;
5
+ }
3
6
  .bc-posts__cards-container, .bc-posts__pinned-container {
4
7
  padding-top: 24px;
5
8
  scroll-margin: 48px;
6
9
  }
10
+ .bc-posts__cards-container_isLoading {
11
+ opacity: 0.7;
12
+ }
7
13
  .bc-posts__pagination {
8
14
  display: flex;
9
15
  flex-direction: column;
@@ -25,4 +31,23 @@ unpredictable css rules order in build */
25
31
  }
26
32
  .bc-posts__paginator {
27
33
  padding-top: 12px;
34
+ }
35
+ .bc-posts__loaderContainer {
36
+ z-index: 6;
37
+ position: absolute;
38
+ top: 0;
39
+ left: 0;
40
+ width: 70%;
41
+ height: 100%;
42
+ background: linear-gradient(90deg, rgba(255, 255, 255, 0), rgba(255, 255, 255, 0.3), rgba(255, 255, 255, 0.5), rgba(255, 255, 255, 0.3), rgba(255, 255, 255, 0));
43
+ animation: shimmer 2s infinite linear;
44
+ }
45
+
46
+ @keyframes shimmer {
47
+ from {
48
+ transform: translateX(-200%);
49
+ }
50
+ to {
51
+ transform: translateX(300%);
52
+ }
28
53
  }
@@ -8,7 +8,6 @@ type PostCardProps = {
8
8
  postCountOnPage: number;
9
9
  perPageInQuery: number;
10
10
  isFetching: boolean;
11
- isShowMoreFetching: boolean;
12
11
  handleShowMore: (value?: MouseEvent<HTMLButtonElement | HTMLAnchorElement>) => Promise<void> | void;
13
12
  handlePageChange: (value: number) => Promise<void> | void;
14
13
  postsOnPage?: PostData[];
@@ -13,8 +13,9 @@ const Paginator_1 = require("../Paginator/Paginator");
13
13
  const PostCard_1 = require("../PostCard/PostCard");
14
14
  const PostsEmpty_1 = require("../PostsEmpty/PostsEmpty");
15
15
  const b = (0, cn_1.block)('posts');
16
- const Posts = ({ containerId, pinnedPostOnPage, currentPage, postsOnPage, isShowMoreVisible, errorShowMore, postCountOnPage, perPageInQuery, isFetching, isShowMoreFetching, handleShowMore, handlePageChange, pageCountForShowSupportButtons, }) => (react_1.default.createElement("div", { className: b() },
17
- react_1.default.createElement("div", { id: containerId, className: b('cards-container') },
16
+ const Posts = ({ containerId, pinnedPostOnPage, currentPage, postsOnPage, isShowMoreVisible, errorShowMore, postCountOnPage, perPageInQuery, isFetching, handleShowMore, handlePageChange, pageCountForShowSupportButtons, }) => (react_1.default.createElement("div", { className: b() },
17
+ isFetching && react_1.default.createElement("div", { className: b('loaderContainer') }),
18
+ react_1.default.createElement("div", { id: containerId, className: b('cards-container', { isLoading: isFetching }) },
18
19
  pinnedPostOnPage && currentPage === 1 && (react_1.default.createElement("div", { className: b('pinned-container') },
19
20
  react_1.default.createElement(PostCard_1.PostCard, { post: pinnedPostOnPage, size: "m", fullWidth: true, showTag: true }))),
20
21
  (postsOnPage === null || postsOnPage === void 0 ? void 0 : postsOnPage.length) ? (react_1.default.createElement(page_constructor_1.CardLayoutBlock, { title: '', colSizes: {
@@ -23,10 +24,10 @@ const Posts = ({ containerId, pinnedPostOnPage, currentPage, postsOnPage, isShow
23
24
  md: 6,
24
25
  } }, postsOnPage === null || postsOnPage === void 0 ? void 0 : postsOnPage.map((post) => (react_1.default.createElement(PostCard_1.PostCard, { key: post.id, post: post, showTag: true }))))) : (react_1.default.createElement(PostsEmpty_1.PostsEmpty, null))),
25
26
  react_1.default.createElement("div", { className: b('pagination') },
26
- Boolean(isShowMoreVisible && (postsOnPage === null || postsOnPage === void 0 ? void 0 : postsOnPage.length)) && (react_1.default.createElement(uikit_1.Button, { view: "outlined", size: "xl", className: b('more-button'), onClick: handleShowMore, loading: isShowMoreFetching }, (0, i18n_1.i18)(i18n_1.Keyset.ActionLoadMore))),
27
+ Boolean(isShowMoreVisible && (postsOnPage === null || postsOnPage === void 0 ? void 0 : postsOnPage.length)) && (react_1.default.createElement(uikit_1.Button, { view: "outlined", size: "xl", className: b('more-button'), onClick: handleShowMore }, (0, i18n_1.i18)(i18n_1.Keyset.ActionLoadMore))),
27
28
  errorShowMore && (react_1.default.createElement("div", { className: b('error-show-more') },
28
29
  react_1.default.createElement("div", null, (0, i18n_1.i18)(i18n_1.Keyset.ErrorTitle)),
29
30
  react_1.default.createElement("div", null, (0, i18n_1.i18)(i18n_1.Keyset.PostLoadError)))),
30
31
  Boolean(currentPage && postCountOnPage) && (react_1.default.createElement("div", { className: b('paginator') },
31
- react_1.default.createElement(Paginator_1.Paginator, { onPageChange: handlePageChange, page: currentPage, totalItems: postCountOnPage, itemsPerPage: perPageInQuery, loading: isFetching, maxPages: Infinity, pageCountForShowSupportButtons: pageCountForShowSupportButtons }))))));
32
+ react_1.default.createElement(Paginator_1.Paginator, { onPageChange: handlePageChange, page: currentPage, totalItems: postCountOnPage, itemsPerPage: perPageInQuery, maxPages: Infinity, pageCountForShowSupportButtons: pageCountForShowSupportButtons }))))));
32
33
  exports.Posts = Posts;
@@ -161,3 +161,7 @@ export declare enum DefaultEventNames {
161
161
  Service = "selector-service-click",
162
162
  SaveOnly = "save-only-button-click"
163
163
  }
164
+ export type FetchArgs = {
165
+ page?: number;
166
+ query: Query;
167
+ };
@@ -8,27 +8,49 @@
8
8
  flex-direction: column;
9
9
  flex-grow: 1;
10
10
  padding: 32px;
11
- width: calc(33.3333333333% - (32px / 2));
11
+ }
12
+ .bc-cta__button {
13
+ display: flex;
14
+ padding-bottom: 16px;
12
15
  }
13
16
  .bc-cta__content {
14
17
  display: flex;
15
- flex-flow: row wrap;
16
- gap: 16px;
18
+ flex-direction: column;
17
19
  }
18
- @media (max-width: 769px) {
20
+ @media (min-width: 577px) {
19
21
  .bc-cta__content {
20
- flex-wrap: wrap;
22
+ display: flex;
23
+ flex-direction: row;
21
24
  }
22
- .bc-cta__card {
23
- width: calc((100% / 2) - 16px);
24
- flex-grow: 1;
25
- }
26
- }
27
- @media (max-width: 577px) {
28
- .bc-cta__content {
29
- flex-direction: column;
25
+ .bc-cta__button {
26
+ padding-bottom: 0px;
30
27
  }
31
- .bc-cta__card {
28
+ .bc-cta__button_layout {
32
29
  width: 100%;
30
+ max-width: 100%;
31
+ margin-right: 0px;
32
+ }
33
+ .bc-cta__button_layout_2 {
34
+ width: calc(50% - (16px / 2));
35
+ max-width: 50%;
36
+ margin-right: 16px;
37
+ }
38
+ .bc-cta__button_layout_2:nth-child(2n) {
39
+ margin-right: 0px;
40
+ }
41
+ .bc-cta__button_layout_3 {
42
+ width: calc(33.3333333333% - (32px / 3));
43
+ margin-right: 16px;
44
+ }
45
+ .bc-cta__button_layout_3:nth-child(3n) {
46
+ margin-right: 0px;
47
+ }
48
+ .bc-cta__button_layout_4 {
49
+ width: calc(25% - (48px / 4));
50
+ max-width: 50%;
51
+ margin-right: 16px;
52
+ }
53
+ .bc-cta__button_layout_4:nth-child(4n) {
54
+ margin-right: 0px;
33
55
  }
34
56
  }
@@ -7,7 +7,15 @@ import { block } from '../../utils/cn';
7
7
  import { getBlogElementMetrika, updateContentSizes } from '../../utils/common';
8
8
  import './CTA.css';
9
9
  const b = block('cta');
10
+ const MAX_COLUMN_COUNT = 4, MIN_COLUMN_COUNT = 2, DEFAULT_COLUMN_COUNT = 3;
10
11
  export const CTA = ({ items, paddingTop, paddingBottom }) => {
12
+ let count = items ? items.length : DEFAULT_COLUMN_COUNT;
13
+ if (count < MIN_COLUMN_COUNT) {
14
+ count = MIN_COLUMN_COUNT;
15
+ }
16
+ else if (count > MAX_COLUMN_COUNT) {
17
+ count = MAX_COLUMN_COUNT;
18
+ }
11
19
  /**
12
20
  * @deprecated Metrika will be deleted after launch of analyticsEvents
13
21
  */
@@ -18,14 +26,17 @@ export const CTA = ({ items, paddingTop, paddingBottom }) => {
18
26
  return (React.createElement(Wrapper, { paddings: {
19
27
  [PaddingsDirections.top]: paddingTop,
20
28
  [PaddingsDirections.bottom]: paddingBottom,
21
- }, className: b('content'), dataQa: "blog-cta-content" }, items.map((content, index) => {
29
+ }, className: b('content'), dataQa: "blog-cta-content" }, items.slice(0, count).map((content, index) => {
22
30
  var _a;
23
31
  const contentData = updateContentSizes(content);
24
32
  (_a = contentData.links) === null || _a === void 0 ? void 0 : _a.forEach((link) => {
25
33
  // eslint-disable-next-line no-not-accumulator-reassign/no-not-accumulator-reassign
26
34
  link.metrikaGoals = getBlogElementMetrika(metrikaGoal, link.metrikaGoals);
27
35
  });
28
- return (React.createElement("div", { key: index, className: b('card'), "data-qa": "blog-cta-card" },
29
- React.createElement(Content, Object.assign({}, contentData))));
36
+ return (React.createElement("div", { key: index, className: b('button', {
37
+ ['layout']: count,
38
+ }), "data-qa": "blog-cta-card" },
39
+ React.createElement("div", { className: b('card') },
40
+ React.createElement(Content, Object.assign({}, contentData)))));
30
41
  })));
31
42
  };
@@ -27,11 +27,10 @@ export const Feed = ({ image }) => {
27
27
  const { posts, totalCount, tags, services, pinnedPost, getPosts, pageCountForShowSupportButtons, } = useContext(FeedContext);
28
28
  const router = useContext(RouterContext);
29
29
  const handleAnalytics = useAnalytics(DefaultEventNames.ShowMore);
30
- const [{ errorLoad, errorShowMore, isFetching, isShowMoreFetching, isShowMoreVisible, lastLoadedCount, postCountOnPage, postsOnPage, pinnedPostOnPage, currentPage, queryParams, }, dispatch,] = useReducer(reducer, {
30
+ const [{ errorLoad, errorShowMore, isFetching, isShowMoreVisible, lastLoadedCount, postCountOnPage, postsOnPage, pinnedPostOnPage, currentPage, queryParams, }, dispatch,] = useReducer(reducer, {
31
31
  errorLoad: false,
32
32
  errorShowMore: false,
33
33
  isFetching: false,
34
- isShowMoreFetching: false,
35
34
  isShowMoreVisible: true,
36
35
  lastLoadedCount: (posts === null || posts === void 0 ? void 0 : posts.length) || 0,
37
36
  postCountOnPage: totalCount || 0,
@@ -46,36 +45,36 @@ export const Feed = ({ image }) => {
46
45
  const pageChange = (value) => {
47
46
  dispatch({ type: ActionTypes.PageChange, payload: value });
48
47
  };
49
- const handleChangeQueryParams = (value) => {
50
- dispatch({ type: ActionTypes.QueryParamsChange, payload: value });
51
- const hasFirstPageQuery = Object.keys(value).some((queryKey) => queryKey === PAGE_QUERY && value[queryKey] === FIRST_PAGE);
52
- if (hasFirstPageQuery) {
53
- // eslint-disable-next-line no-not-accumulator-reassign/no-not-accumulator-reassign
54
- value[PAGE_QUERY] = null;
55
- }
56
- router.updateQueryCallback(value);
48
+ const setIsFetching = (value) => {
49
+ dispatch({ type: ActionTypes.SetIsFetching, payload: value });
57
50
  };
58
- const handlePageChange = async (value) => {
59
- pageChange(value);
60
- handleChangeQueryParams({ page: value });
51
+ const setErrorLoad = (value) => {
52
+ dispatch({ type: ActionTypes.SetErrorLoad, payload: value });
61
53
  };
62
- const fetchData = useCallback(async (pageNumber) => {
63
- if (queryParams && getPosts) {
64
- const query = getFeedQueryParams(queryParams, pageNumber);
65
- const data = await getPosts(query);
54
+ const handleChangeQueryParams = useCallback((value) => {
55
+ dispatch({ type: ActionTypes.QueryParamsChange, payload: value });
56
+ const hasFirstPageQuery = Object.keys(value).some((queryKey) => queryKey === PAGE_QUERY && value[queryKey] === FIRST_PAGE);
57
+ const result = hasFirstPageQuery
58
+ ? Object.assign(Object.assign({}, value), { [PAGE_QUERY]: null }) : Object.assign({}, value);
59
+ router.updateQueryCallback(result);
60
+ }, [router]);
61
+ const fetchData = useCallback(async ({ page, query }) => {
62
+ if (query && getPosts) {
63
+ const queryParamsForRequest = getFeedQueryParams(Object.assign(Object.assign({}, queryParams), query), page);
64
+ const data = await getPosts(queryParamsForRequest);
66
65
  return data;
67
66
  }
68
67
  else {
69
68
  throw new Error('cant get request');
70
69
  }
71
70
  }, [getPosts, queryParams]);
72
- const setIsFetching = (value) => {
73
- dispatch({ type: ActionTypes.SetIsFetching, payload: value });
74
- };
75
- const fetchAndReplaceData = useCallback(async (pageNumber) => {
71
+ const handleLoad = useCallback(async ({ page, query }) => {
72
+ const pageNumber = Number(page || queryParams.page || DEFAULT_PAGE);
73
+ handleChangeQueryParams(query);
76
74
  try {
77
- dispatch({ type: ActionTypes.SetErrorLoad, payload: false });
78
- const fetchedData = await fetchData(pageNumber);
75
+ setErrorLoad(false);
76
+ setIsFetching(true);
77
+ const fetchedData = await fetchData({ page: pageNumber, query });
79
78
  if (fetchedData) {
80
79
  dispatch({
81
80
  type: ActionTypes.SetPosts,
@@ -89,22 +88,35 @@ export const Feed = ({ image }) => {
89
88
  }
90
89
  }
91
90
  catch (err) {
92
- dispatch({ type: ActionTypes.SetErrorLoad, payload: true });
91
+ setErrorLoad(true);
93
92
  }
94
93
  scrollOnPageChange(CONTAINER_ID);
95
94
  setIsFetching(false);
96
- }, [fetchData]);
95
+ }, [fetchData, handleChangeQueryParams, queryParams]);
96
+ const handlePageChange = async (value) => {
97
+ pageChange(value);
98
+ handleLoad({
99
+ page: value,
100
+ query: Object.assign(Object.assign({}, queryParams), { page: value }),
101
+ });
102
+ };
97
103
  const handleShowMore = async () => {
98
- dispatch({ type: ActionTypes.SetIsShowMoreFetching, payload: true });
99
104
  /**
100
105
  * @deprecated Metrika will be deleted after launch of analyticsEvents
101
106
  */
102
107
  metrika.reachGoal(MetrikaCounter.CrossSite, BlogMetrikaGoalIds.showMore);
103
108
  handleAnalytics();
109
+ const nextPage = currentPage + 1;
104
110
  try {
105
- const fetchedData = await fetchData(currentPage + 1);
111
+ setIsFetching(true);
112
+ const fetchedData = await fetchData({
113
+ page: nextPage,
114
+ query: {
115
+ page: nextPage,
116
+ },
117
+ });
106
118
  handleChangeQueryParams({
107
- page: currentPage + 1,
119
+ page: nextPage,
108
120
  });
109
121
  if (fetchedData) {
110
122
  dispatch({
@@ -112,7 +124,7 @@ export const Feed = ({ image }) => {
112
124
  payload: {
113
125
  posts: (postsOnPage !== null && postsOnPage !== void 0 ? postsOnPage : []).concat(fetchedData.posts),
114
126
  count: fetchedData.count,
115
- currentPage: currentPage + 1,
127
+ currentPage: nextPage,
116
128
  lastLoadedCount: fetchedData.posts.length,
117
129
  },
118
130
  });
@@ -121,13 +133,11 @@ export const Feed = ({ image }) => {
121
133
  catch (err) {
122
134
  dispatch({ type: ActionTypes.SetErrorShowMore, payload: true });
123
135
  }
124
- dispatch({ type: ActionTypes.SetIsShowMoreFetching, payload: false });
136
+ setIsFetching(false);
125
137
  };
126
- useEffect(() => {
127
- if (isFetching) {
128
- fetchAndReplaceData(Number(queryParams.page || DEFAULT_PAGE));
129
- }
130
- }, [fetchAndReplaceData, isFetching, queryParams.page]);
138
+ const handleOnErrorReload = useCallback(() => {
139
+ handleLoad({ page: currentPage, query: queryParams });
140
+ }, [currentPage, handleLoad, queryParams]);
131
141
  useEffect(() => {
132
142
  const loadedPostsCount = currentPage * perPageInQuery;
133
143
  dispatch({
@@ -145,10 +155,10 @@ export const Feed = ({ image }) => {
145
155
  icon: tag.icon && React.createElement(Icon, { data: tag.icon }),
146
156
  })), [tags]);
147
157
  return (React.createElement("div", null,
148
- React.createElement(FeedHeader, { verticalOffset: "s", tags: tagItems, services: serviceItems, setIsFetching: setIsFetching, handleChangeQuery: handleChangeQueryParams, queryParams: queryParams, background: {
158
+ React.createElement(FeedHeader, { verticalOffset: "s", tags: tagItems, services: serviceItems, handleLoadData: handleLoad, queryParams: queryParams, background: {
149
159
  fullWidth: true,
150
160
  url: image,
151
161
  disableCompress: true,
152
162
  } }),
153
- errorLoad ? (React.createElement(PostsError, { onButtonClick: () => fetchAndReplaceData(Number(queryParams.page || DEFAULT_PAGE)) })) : (React.createElement(Posts, { containerId: CONTAINER_ID, currentPage: currentPage, isShowMoreVisible: isShowMoreVisible, errorShowMore: errorShowMore, postCountOnPage: postCountOnPage, perPageInQuery: perPageInQuery, handleShowMore: handleShowMore, handlePageChange: handlePageChange, postsOnPage: postsOnPage, pinnedPostOnPage: pinnedPostOnPage, isFetching: isFetching, isShowMoreFetching: isShowMoreFetching, pageCountForShowSupportButtons: pageCountForShowSupportButtons }))));
163
+ errorLoad ? (React.createElement(PostsError, { onButtonClick: handleOnErrorReload })) : (React.createElement(Posts, { containerId: CONTAINER_ID, currentPage: currentPage, isShowMoreVisible: isShowMoreVisible, errorShowMore: errorShowMore, postCountOnPage: postCountOnPage, perPageInQuery: perPageInQuery, handleShowMore: handleShowMore, handlePageChange: handlePageChange, postsOnPage: postsOnPage, pinnedPostOnPage: pinnedPostOnPage, isFetching: isFetching, pageCountForShowSupportButtons: pageCountForShowSupportButtons }))));
154
164
  };
@@ -6,7 +6,6 @@ export declare enum ActionTypes {
6
6
  SetPosts = "setPosts",
7
7
  SetShowMore = "setShowMore",
8
8
  SetIsFetching = "setIsFetching",
9
- SetIsShowMoreFetching = "setIsShowMoreFetching",
10
9
  PageChange = "pageChange",
11
10
  QueryParamsChange = "queryParamsChange"
12
11
  }
@@ -15,7 +14,6 @@ export type State = {
15
14
  errorLoad: boolean;
16
15
  errorShowMore: boolean;
17
16
  isFetching: boolean;
18
- isShowMoreFetching: boolean;
19
17
  isShowMoreVisible: boolean;
20
18
  lastLoadedCount: number;
21
19
  pinnedPostOnPage?: PostData;
@@ -29,9 +27,6 @@ type Action = {
29
27
  } | {
30
28
  type: ActionTypes.SetIsFetching;
31
29
  payload: boolean;
32
- } | {
33
- type: ActionTypes.SetIsShowMoreFetching;
34
- payload: boolean;
35
30
  } | {
36
31
  type: ActionTypes.SetPosts;
37
32
  payload: {
@@ -6,7 +6,6 @@ export var ActionTypes;
6
6
  ActionTypes["SetPosts"] = "setPosts";
7
7
  ActionTypes["SetShowMore"] = "setShowMore";
8
8
  ActionTypes["SetIsFetching"] = "setIsFetching";
9
- ActionTypes["SetIsShowMoreFetching"] = "setIsShowMoreFetching";
10
9
  ActionTypes["PageChange"] = "pageChange";
11
10
  ActionTypes["QueryParamsChange"] = "queryParamsChange";
12
11
  })(ActionTypes || (ActionTypes = {}));
@@ -17,11 +16,9 @@ export const reducer = (state, { type, payload }) => {
17
16
  case ActionTypes.SetPosts:
18
17
  return Object.assign(Object.assign({}, state), { lastLoadedCount: payload.count, postCountOnPage: payload.count, pinnedPostOnPage: payload.pinnedPost, postsOnPage: payload.posts, currentPage: payload.page });
19
18
  case ActionTypes.SetShowMore:
20
- return Object.assign(Object.assign({}, state), { lastLoadedCount: payload.lastLoadedCount, postCountOnPage: payload.count, currentPage: payload.currentPage, postsOnPage: payload.posts, errorShowMore: false, isShowMoreFetching: true });
19
+ return Object.assign(Object.assign({}, state), { lastLoadedCount: payload.lastLoadedCount, postCountOnPage: payload.count, currentPage: payload.currentPage, postsOnPage: payload.posts, errorShowMore: false });
21
20
  case ActionTypes.SetIsFetching:
22
21
  return Object.assign(Object.assign({}, state), { isFetching: payload });
23
- case ActionTypes.SetIsShowMoreFetching:
24
- return Object.assign(Object.assign({}, state), { isShowMoreFetching: payload });
25
22
  case ActionTypes.PageChange:
26
23
  return Object.assign(Object.assign({}, state), { currentPage: payload, isFetching: true });
27
24
  case ActionTypes.SetErrorLoad:
@@ -4,7 +4,7 @@ import { block } from '../../utils/cn';
4
4
  import { Controls } from './components/Controls/Controls';
5
5
  import './FeedHeader.css';
6
6
  const b = block('feed-header');
7
- export const FeedHeader = ({ tags, services, setIsFetching, offset = 'default', background, theme = 'default', verticalOffset = 'l', className, handleChangeQuery, queryParams, }) => {
7
+ export const FeedHeader = ({ tags, services, handleLoadData, offset = 'default', background, theme = 'default', verticalOffset = 'l', className, queryParams, }) => {
8
8
  const backgroundThemed = background && getThemedValue(background, theme);
9
9
  return (React.createElement("header", { className: b('header', { ['has-background']: Boolean(background) }, className) },
10
10
  (backgroundThemed === null || backgroundThemed === void 0 ? void 0 : backgroundThemed.color) ? (React.createElement(FullWidthBackground, { style: { backgroundColor: backgroundThemed === null || backgroundThemed === void 0 ? void 0 : backgroundThemed.color }, theme: "rounded" })) : null,
@@ -14,5 +14,5 @@ export const FeedHeader = ({ tags, services, setIsFetching, offset = 'default',
14
14
  ? ''
15
15
  : backgroundThemed === null || backgroundThemed === void 0 ? void 0 : backgroundThemed.color,
16
16
  }, disableCompress: backgroundThemed === null || backgroundThemed === void 0 ? void 0 : backgroundThemed.disableCompress })) : null,
17
- React.createElement(Controls, { tags: tags, services: services, setIsFetching: setIsFetching, handleChangeQuery: handleChangeQuery, queryParams: queryParams }))));
17
+ React.createElement(Controls, { tags: tags, services: services, handleLoadData: handleLoadData, queryParams: queryParams }))));
18
18
  };
@@ -1,5 +1,5 @@
1
1
  import React, { ReactNode } from 'react';
2
- import { HandleChangeQueryParams, Query, SetQueryType } from '../../../../models/common';
2
+ import { FetchArgs, Query, SetQueryType } from '../../../../models/common';
3
3
  import './Controls.css';
4
4
  export type SelectItem = {
5
5
  content: string;
@@ -7,10 +7,9 @@ export type SelectItem = {
7
7
  icon?: ReactNode;
8
8
  };
9
9
  export type ControlsProps = {
10
- setIsFetching: (value: boolean) => void;
10
+ handleLoadData: (props: FetchArgs) => void;
11
11
  tags?: SelectItem[];
12
12
  services?: SelectItem[];
13
- handleChangeQuery: HandleChangeQueryParams;
14
13
  queryParams: Query;
15
14
  setQuery?: SetQueryType;
16
15
  };
@@ -10,7 +10,7 @@ import metrika from '../../../../counters/metrika.js';
10
10
  import { MetrikaCounter } from '../../../../counters/utils';
11
11
  import { Keyset, i18 } from '../../../../i18n';
12
12
  import { Save } from '../../../../icons/Save';
13
- import { DefaultEventNames, } from '../../../../models/common';
13
+ import { DefaultEventNames } from '../../../../models/common';
14
14
  import { block } from '../../../../utils/cn';
15
15
  import { Search } from '../../../Search/Search';
16
16
  import { renderFilter, renderOption, renderSwitcher } from './customRenders';
@@ -19,7 +19,7 @@ const b = block('feed-controls');
19
19
  const ICON_SIZE = 16;
20
20
  const DEFAULT_PAGE = 1;
21
21
  const VIRTUALIZATION_THRESHOLD = 1000;
22
- export const Controls = ({ setIsFetching, tags = [], services = [], handleChangeQuery, queryParams, }) => {
22
+ export const Controls = ({ handleLoadData, tags = [], services = [], queryParams, }) => {
23
23
  const { hasLikes } = useContext(LikesContext);
24
24
  const handleAnalyticsTag = useAnalytics(DefaultEventNames.Tag);
25
25
  const handleAnalyticsService = useAnalytics(DefaultEventNames.Service);
@@ -28,21 +28,25 @@ export const Controls = ({ setIsFetching, tags = [], services = [], handleChange
28
28
  const [savedOnly, setSavedOnly] = useState(savedOnlyInitial === 'true');
29
29
  const [search, setSearch] = useState(searchInitial);
30
30
  const handleSavedOnly = () => {
31
- handleChangeQuery({
32
- savedOnly: savedOnly ? '' : 'true',
33
- search: '',
34
- tags: '',
35
- page: DEFAULT_PAGE,
36
- services: '',
37
- });
38
31
  handleAnalyticsSaveOnly();
39
32
  setSavedOnly(!savedOnly);
40
- setIsFetching(true);
33
+ handleLoadData({
34
+ page: DEFAULT_PAGE,
35
+ query: {
36
+ savedOnly: savedOnly ? '' : 'true',
37
+ search: '',
38
+ tags: '',
39
+ page: DEFAULT_PAGE,
40
+ services: '',
41
+ },
42
+ });
41
43
  };
42
44
  const handleSearch = (searchValue) => {
43
- handleChangeQuery({ search: searchValue, page: DEFAULT_PAGE });
44
45
  setSearch(searchValue);
45
- setIsFetching(true);
46
+ handleLoadData({
47
+ page: DEFAULT_PAGE,
48
+ query: { search: searchValue, page: DEFAULT_PAGE },
49
+ });
46
50
  };
47
51
  const handleTagSelect = (selectedTags) => {
48
52
  /**
@@ -55,11 +59,13 @@ export const Controls = ({ setIsFetching, tags = [], services = [], handleChange
55
59
  theme: selectedTags[0],
56
60
  });
57
61
  const isEmptyTag = selectedTags.some((tag) => tag === 'empty');
58
- handleChangeQuery({
59
- tags: isEmptyTag ? '' : selectedTags[0],
62
+ handleLoadData({
60
63
  page: DEFAULT_PAGE,
64
+ query: {
65
+ tags: isEmptyTag ? '' : selectedTags[0],
66
+ page: DEFAULT_PAGE,
67
+ },
61
68
  });
62
- setIsFetching(true);
63
69
  };
64
70
  const handleServicesSelect = (selectedServices) => {
65
71
  const forMetrikaServices = services.filter((service) => {
@@ -76,8 +82,10 @@ export const Controls = ({ setIsFetching, tags = [], services = [], handleChange
76
82
  service: metrikaAsString,
77
83
  });
78
84
  const servicesAsString = selectedServices.join(',');
79
- handleChangeQuery({ services: servicesAsString, page: DEFAULT_PAGE });
80
- setIsFetching(true);
85
+ handleLoadData({
86
+ page: DEFAULT_PAGE,
87
+ query: { services: servicesAsString, page: DEFAULT_PAGE },
88
+ });
81
89
  };
82
90
  const tagsItems = useMemo(() => [{ value: 'empty', content: i18(Keyset.AllTags) }, ...tags], [tags]);
83
91
  const servicesItems = useMemo(() => (servicesInitial ? [...servicesInitial.split(',')] : []), [servicesInitial]);
@@ -1,3 +1,3 @@
1
1
  import { PaginatorProps } from './types';
2
2
  import './Paginator.css';
3
- export declare const Paginator: ({ itemsPerPage, totalItems, maxPages, page, className, loading, onPageChange, pageCountForShowSupportButtons, }: PaginatorProps) => JSX.Element | null;
3
+ export declare const Paginator: ({ itemsPerPage, totalItems, maxPages, page, className, onPageChange, pageCountForShowSupportButtons, }: PaginatorProps) => JSX.Element | null;
@@ -26,7 +26,7 @@ import { getPageConfigs, getPagesCount } from './utils';
26
26
  import './Paginator.css';
27
27
  const b = block('paginator');
28
28
  const DEFAULT_PAGE_COUNT_FOR_SHOW_SUPPORT_BUTTONS = 6;
29
- export const Paginator = ({ itemsPerPage, totalItems, maxPages, page, className, loading, onPageChange, pageCountForShowSupportButtons = DEFAULT_PAGE_COUNT_FOR_SHOW_SUPPORT_BUTTONS, }) => {
29
+ export const Paginator = ({ itemsPerPage, totalItems, maxPages, page, className, onPageChange, pageCountForShowSupportButtons = DEFAULT_PAGE_COUNT_FOR_SHOW_SUPPORT_BUTTONS, }) => {
30
30
  const [pagesCount, setPagesCount] = useState(getPagesCount({ itemsPerPage, totalItems, maxPages }));
31
31
  useEffect(() => {
32
32
  const count = getPagesCount({ itemsPerPage, totalItems, maxPages });
@@ -95,7 +95,7 @@ export const Paginator = ({ itemsPerPage, totalItems, maxPages, page, className,
95
95
  }
96
96
  const renderPaginatorItem = (item) => {
97
97
  const { key } = item, rest = __rest(item, ["key"]);
98
- return React.createElement(PaginatorItem, Object.assign({ key: `page_${key}` }, rest, { loading: loading }));
98
+ return React.createElement(PaginatorItem, Object.assign({ key: `page_${key}` }, rest));
99
99
  };
100
100
  return (React.createElement("div", { className: b('pagination') },
101
101
  page > 1 && (React.createElement("div", { className: b('pagination-block') },
@@ -15,7 +15,6 @@ export type PaginatorProps = {
15
15
  totalItems: number;
16
16
  itemsPerPage: number;
17
17
  maxPages: number;
18
- loading: boolean;
19
18
  onPageChange: (page: number) => void;
20
19
  pageCountForShowSupportButtons?: number;
21
20
  } & ClassNameProps;
@@ -1,9 +1,15 @@
1
1
  /* use this for style redefinitions to awoid problems with
2
2
  unpredictable css rules order in build */
3
+ .bc-posts {
4
+ position: relative;
5
+ }
3
6
  .bc-posts__cards-container, .bc-posts__pinned-container {
4
7
  padding-top: 24px;
5
8
  scroll-margin: 48px;
6
9
  }
10
+ .bc-posts__cards-container_isLoading {
11
+ opacity: 0.7;
12
+ }
7
13
  .bc-posts__pagination {
8
14
  display: flex;
9
15
  flex-direction: column;
@@ -25,4 +31,23 @@ unpredictable css rules order in build */
25
31
  }
26
32
  .bc-posts__paginator {
27
33
  padding-top: 12px;
34
+ }
35
+ .bc-posts__loaderContainer {
36
+ z-index: 6;
37
+ position: absolute;
38
+ top: 0;
39
+ left: 0;
40
+ width: 70%;
41
+ height: 100%;
42
+ background: linear-gradient(90deg, rgba(255, 255, 255, 0), rgba(255, 255, 255, 0.3), rgba(255, 255, 255, 0.5), rgba(255, 255, 255, 0.3), rgba(255, 255, 255, 0));
43
+ animation: shimmer 2s infinite linear;
44
+ }
45
+
46
+ @keyframes shimmer {
47
+ from {
48
+ transform: translateX(-200%);
49
+ }
50
+ to {
51
+ transform: translateX(300%);
52
+ }
28
53
  }
@@ -9,7 +9,6 @@ type PostCardProps = {
9
9
  postCountOnPage: number;
10
10
  perPageInQuery: number;
11
11
  isFetching: boolean;
12
- isShowMoreFetching: boolean;
13
12
  handleShowMore: (value?: MouseEvent<HTMLButtonElement | HTMLAnchorElement>) => Promise<void> | void;
14
13
  handlePageChange: (value: number) => Promise<void> | void;
15
14
  postsOnPage?: PostData[];
@@ -8,8 +8,9 @@ import { PostCard } from '../PostCard/PostCard';
8
8
  import { PostsEmpty } from '../PostsEmpty/PostsEmpty';
9
9
  import './Posts.css';
10
10
  const b = block('posts');
11
- export const Posts = ({ containerId, pinnedPostOnPage, currentPage, postsOnPage, isShowMoreVisible, errorShowMore, postCountOnPage, perPageInQuery, isFetching, isShowMoreFetching, handleShowMore, handlePageChange, pageCountForShowSupportButtons, }) => (React.createElement("div", { className: b() },
12
- React.createElement("div", { id: containerId, className: b('cards-container') },
11
+ export const Posts = ({ containerId, pinnedPostOnPage, currentPage, postsOnPage, isShowMoreVisible, errorShowMore, postCountOnPage, perPageInQuery, isFetching, handleShowMore, handlePageChange, pageCountForShowSupportButtons, }) => (React.createElement("div", { className: b() },
12
+ isFetching && React.createElement("div", { className: b('loaderContainer') }),
13
+ React.createElement("div", { id: containerId, className: b('cards-container', { isLoading: isFetching }) },
13
14
  pinnedPostOnPage && currentPage === 1 && (React.createElement("div", { className: b('pinned-container') },
14
15
  React.createElement(PostCard, { post: pinnedPostOnPage, size: "m", fullWidth: true, showTag: true }))),
15
16
  (postsOnPage === null || postsOnPage === void 0 ? void 0 : postsOnPage.length) ? (React.createElement(CardLayoutBlock, { title: '', colSizes: {
@@ -18,9 +19,9 @@ export const Posts = ({ containerId, pinnedPostOnPage, currentPage, postsOnPage,
18
19
  md: 6,
19
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))),
20
21
  React.createElement("div", { className: b('pagination') },
21
- Boolean(isShowMoreVisible && (postsOnPage === null || postsOnPage === void 0 ? void 0 : postsOnPage.length)) && (React.createElement(Button, { view: "outlined", size: "xl", className: b('more-button'), onClick: handleShowMore, loading: isShowMoreFetching }, i18(Keyset.ActionLoadMore))),
22
+ 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))),
22
23
  errorShowMore && (React.createElement("div", { className: b('error-show-more') },
23
24
  React.createElement("div", null, i18(Keyset.ErrorTitle)),
24
25
  React.createElement("div", null, i18(Keyset.PostLoadError)))),
25
26
  Boolean(currentPage && postCountOnPage) && (React.createElement("div", { className: b('paginator') },
26
- React.createElement(Paginator, { onPageChange: handlePageChange, page: currentPage, totalItems: postCountOnPage, itemsPerPage: perPageInQuery, loading: isFetching, maxPages: Infinity, pageCountForShowSupportButtons: pageCountForShowSupportButtons }))))));
27
+ React.createElement(Paginator, { onPageChange: handlePageChange, page: currentPage, totalItems: postCountOnPage, itemsPerPage: perPageInQuery, maxPages: Infinity, pageCountForShowSupportButtons: pageCountForShowSupportButtons }))))));
@@ -161,3 +161,7 @@ export declare enum DefaultEventNames {
161
161
  Service = "selector-service-click",
162
162
  SaveOnly = "save-only-button-click"
163
163
  }
164
+ export type FetchArgs = {
165
+ page?: number;
166
+ query: Query;
167
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gravity-ui/blog-constructor",
3
- "version": "3.3.0",
3
+ "version": "3.4.0-alpha.0",
4
4
  "description": "Gravity UI Blog Constructor",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -141,5 +141,8 @@
141
141
  "*.{json,yaml,yml,md}": [
142
142
  "prettier --write"
143
143
  ]
144
+ },
145
+ "publishConfig": {
146
+ "tag": "alpha"
144
147
  }
145
148
  }
@@ -161,3 +161,7 @@ export declare enum DefaultEventNames {
161
161
  Service = "selector-service-click",
162
162
  SaveOnly = "save-only-button-click"
163
163
  }
164
+ export type FetchArgs = {
165
+ page?: number;
166
+ query: Query;
167
+ };