Skip to content

Commit 4b6852c

Browse files
feat(FilterBlock): add block that filters its child's subblocks (#180)
* feat(FilterBlock): add block that filters its child's subblocks * fix: comments and check errors * fix: remove unnecessary newlines * fix: fix rest comments * fix: lint * fix: make all tag null * fix: simplify the FilterBlock to show only cards * feat: make header and tab panel centerable * fix: resolve comments * fix: move button mixin to shared lib * fix: fix merge * fix: rename tagSize to tagButtonSize
1 parent 6ec74c7 commit 4b6852c

File tree

26 files changed

+500
-123
lines changed

26 files changed

+500
-123
lines changed

src/blocks/CardLayout/CardLayout.tsx

Lines changed: 15 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,37 @@
11
import React from 'react';
22

33
import {block} from '../../utils';
4-
import {CardLayoutBlockProps as CardLayoutBlockParams} from '../../models';
5-
import {Row, Col} from '../../grid';
4+
import {CardLayoutBlockProps as CardLayoutBlockParams, WithChildren} from '../../models';
5+
import {Col, GridColumnSizesType, Row} from '../../grid';
66
import {BlockHeader, AnimateBlock} from '../../components';
77

88
import './CardLayout.scss';
99

10-
export interface CardLayoutBlockProps extends Omit<CardLayoutBlockParams, 'children'> {
11-
children?: React.ReactNode;
12-
}
13-
14-
const b = block('card-layout-block');
15-
16-
const DEFAULT_SIZES = {
10+
const DEFAULT_SIZES: GridColumnSizesType = {
1711
all: 12,
1812
sm: 6,
1913
md: 4,
2014
};
15+
export type CardLayoutBlockProps = WithChildren<Omit<CardLayoutBlockParams, 'children'>>;
16+
17+
const b = block('card-layout-block');
2118

22-
const CardLayout = ({
19+
const CardLayout: React.FC<CardLayoutBlockProps> = ({
2320
title,
2421
description,
2522
animated,
2623
colSizes = DEFAULT_SIZES,
2724
children,
28-
}: CardLayoutBlockProps) => (
25+
}) => (
2926
<AnimateBlock className={b()} animate={animated}>
3027
<BlockHeader title={title} description={description} />
31-
<div>
32-
<Row>
33-
{children &&
34-
React.Children.map(children, (child, i) => {
35-
return (
36-
<Col sizes={colSizes} key={i} className={b('item')}>
37-
{child}
38-
</Col>
39-
);
40-
})}
41-
</Row>
42-
</div>
28+
<Row>
29+
{React.Children.map(children, (child, index) => (
30+
<Col key={index} sizes={colSizes} className={b('item')}>
31+
{child}
32+
</Col>
33+
))}
34+
</Row>
4335
</AnimateBlock>
4436
);
4537

src/blocks/CardLayout/__stories__/CardLayout.stories.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import CardLayout from '../CardLayout';
55
import {
66
CardLayoutBlockModel,
77
CardLayoutBlockProps,
8+
CardWithImageModel,
89
CardWithImageProps,
910
SubBlockModels,
1011
} from '../../../models';
@@ -17,7 +18,10 @@ export default {
1718
component: CardLayout,
1819
} as Meta;
1920

20-
const createCardArray = (count: number, card: CardWithImageProps) => new Array(count).fill(card);
21+
const createCardArray: (count: number, shared: Partial<CardWithImageProps>) => SubBlockModels[] = (
22+
count,
23+
shared,
24+
) => Array.from({length: count}, () => ({...shared} as CardWithImageModel));
2125

2226
const DefaultTemplate: Story<CardLayoutBlockModel> = (args) => (
2327
<PageConstructor content={{blocks: [args]}} />
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
@import '../../../styles/variables';
2+
@import '../../../styles/mixins';
3+
4+
$block: '.#{$ns}filter-block';
5+
$innerBlock: '.#{$ns}block-base';
6+
7+
#{$block} {
8+
&__title {
9+
margin-bottom: $indentSM;
10+
11+
@include centerable-title();
12+
}
13+
14+
&__tabs {
15+
margin-bottom: 0;
16+
17+
@include tab-panel();
18+
}
19+
20+
.row &__block-container.row {
21+
margin: 0px;
22+
}
23+
24+
--pc-first-block-indent: 0;
25+
--pc-first-block-mobile-indent: 0;
26+
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import React, {useMemo, useState} from 'react';
2+
3+
import i18n from './i18n';
4+
import {block} from '../../utils';
5+
import {BlockType, ConstructorItem, FilterBlockProps, FilterItem} from '../../models';
6+
import {Row, Col} from '../../grid';
7+
import {BlockHeader, AnimateBlock} from '../../components';
8+
import ButtonTabs, {ButtonTabsItemProps} from '../../components/ButtonTabs/ButtonTabs';
9+
import {ConstructorBlocks} from '../../containers/PageConstructor/components/ConstructorBlocks';
10+
11+
import './FilterBlock.scss';
12+
13+
const b = block('filter-block');
14+
15+
const FilterBlock: React.FC<FilterBlockProps> = ({
16+
title,
17+
description,
18+
tags,
19+
tagButtonSize,
20+
allTag,
21+
items,
22+
colSizes,
23+
centered,
24+
animated,
25+
}) => {
26+
const tabButtons = useMemo(() => {
27+
const allButton: ButtonTabsItemProps | undefined = allTag
28+
? {id: null, title: typeof allTag === 'boolean' ? i18n('label-all-tag') : allTag}
29+
: undefined;
30+
const otherButtons: ButtonTabsItemProps[] | undefined =
31+
tags && tags.map((tag) => ({id: tag.id, title: tag.label}));
32+
return [...(allButton ? [allButton] : []), ...(otherButtons ? otherButtons : [])];
33+
}, [allTag, tags]);
34+
35+
const [selectedTag, setSelectedTag] = useState(tabButtons.length ? tabButtons[0].id : null);
36+
37+
const actualTag: string | null = useMemo(() => {
38+
return tabButtons.length && !tabButtons.find((tab) => tab.id === selectedTag)
39+
? tabButtons[0].id
40+
: selectedTag;
41+
}, [tabButtons, selectedTag]);
42+
43+
const container: ConstructorItem[] = useMemo(() => {
44+
const itemsToShow: FilterItem[] = actualTag
45+
? items.filter((item) => item.tags.includes(actualTag))
46+
: items;
47+
return [
48+
{
49+
type: BlockType.CardLayoutBlock,
50+
title: '',
51+
colSizes: colSizes,
52+
children: itemsToShow.map((item) => item.card),
53+
},
54+
];
55+
}, [actualTag, items, colSizes]);
56+
57+
return (
58+
<AnimateBlock className={b()} animate={animated}>
59+
{title && (
60+
<BlockHeader
61+
className={b('title', {centered: centered})}
62+
title={title}
63+
description={description}
64+
/>
65+
)}
66+
{tabButtons.length && (
67+
<Row>
68+
<Col>
69+
<ButtonTabs
70+
className={b('tabs', {centered: centered})}
71+
items={tabButtons}
72+
activeTab={selectedTag}
73+
onSelectTab={setSelectedTag}
74+
tabSize={tagButtonSize}
75+
/>
76+
</Col>
77+
</Row>
78+
)}
79+
<Row className={b('block-container')}>
80+
<ConstructorBlocks items={container} />
81+
</Row>
82+
</AnimateBlock>
83+
);
84+
};
85+
export default FilterBlock;

src/blocks/FilterBlock/README.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
`type: "filter-block"`
2+
3+
`title?: Title | string` — Title.
4+
5+
`description?: string` — Text.
6+
7+
`filterTags: []` - Tags by which content can be filtered.
8+
9+
`tagButtonSize: 's' | 'm' | 'l' | 'xl'` - Size of filter tags.
10+
11+
`allTag: boolean | string` - Specifies whether to show the 'All' tag. If the value is a non-falsy string, the block uses the value as label for the `All` tag.
12+
13+
`items:` — Items, the block displays.
14+
15+
- `tags: string[]` - tags assigned to the card.
16+
17+
- `card: SubBlock` - card to show.
18+
19+
`centered?: boolean` - Specifies whether the header and the tab panel are centered.
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import React from 'react';
2+
import {Meta, Story} from '@storybook/react/types-6-0';
3+
4+
import FilterBlock from '../FilterBlock';
5+
import {
6+
FilterBlockModel,
7+
CardWithImageProps,
8+
FilterBlockProps,
9+
FilterItem,
10+
FilterTag,
11+
} from '../../../models';
12+
import {CardWithImageModel} from '../../../models/constructor-items/sub-blocks';
13+
import {PageConstructor} from '../../../containers/PageConstructor';
14+
15+
import data from './data.json';
16+
17+
export default {
18+
title: 'Blocks/Filter Block',
19+
component: FilterBlock,
20+
} as Meta;
21+
22+
const createItemList: (
23+
count: number,
24+
shared: CardWithImageProps,
25+
tagList: FilterTag[],
26+
) => FilterItem[] = (count, shared, tagList) =>
27+
Array.from({length: count}, (_, index) => ({
28+
tags: [tagList[index % tagList.length].id],
29+
card: {
30+
...shared,
31+
title: shared.title ? `${shared.title}&nbsp;${index + 1}` : `${index + 1}`,
32+
} as CardWithImageModel,
33+
}));
34+
35+
const createArgs = (overrides: Partial<FilterBlockProps>) =>
36+
({
37+
type: 'filter-block',
38+
title: data.default.content.title,
39+
description: data.default.content.description,
40+
tags: data.default.filters,
41+
items: createItemList(6, data.default.card, data.default.filters),
42+
...overrides,
43+
} as FilterBlockProps);
44+
45+
const DefaultTemplate: Story<FilterBlockModel> = (args) => (
46+
<PageConstructor content={{blocks: [args]}} />
47+
);
48+
49+
export const Default = DefaultTemplate.bind({});
50+
Default.args = createArgs({allTag: false});
51+
52+
export const WithDefaultAllTag = DefaultTemplate.bind({});
53+
WithDefaultAllTag.args = createArgs({allTag: true});
54+
55+
export const WithCustomAllTag = DefaultTemplate.bind({});
56+
WithCustomAllTag.args = createArgs({allTag: 'Custom All Tag Label'});
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
{
2+
"default": {
3+
"card": {
4+
"type": "card-with-image",
5+
"image": "https://storage.yandexcloud.net/cloud-www-assets/constructor/storybook/images/img-mini_4-12_light.png",
6+
"title": "Lorem&nbsp;ipsum",
7+
"description": "Dolor sit amet"
8+
},
9+
"content": {
10+
"type": "card-layout-block",
11+
"title": "Card Layout",
12+
"description": "Three cards in a row on the desktop, two cards in a row on a tablet, one card in a row on a mobile phone."
13+
},
14+
"filters": [
15+
{
16+
"id": "one",
17+
"label": "First very long label"
18+
},
19+
{
20+
"id": "two",
21+
"label": "Second very long label"
22+
},
23+
{
24+
"id": "three",
25+
"label": "Third very long label"
26+
}
27+
]
28+
}
29+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"label-all-tag": "All"
3+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import {registerKeyset} from '../../../utils/registerKeyset';
2+
import en from './en.json';
3+
import ru from './ru.json';
4+
5+
const COMPONENT = 'FilterBlock';
6+
7+
export default registerKeyset({en, ru}, COMPONENT);
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"label-all-tag": "Все"
3+
}

0 commit comments

Comments
 (0)