Skip to content

Commit b861b86

Browse files
authored
chore: storybook custom blocks (#1138)
* chore: storybook custom blocks * chore: fix preview and dark theme * fix: more verbosity * fix: lint * fix: custom card bug
1 parent 04ccad7 commit b861b86

File tree

22 files changed

+992
-0
lines changed

22 files changed

+992
-0
lines changed

src/containers/PageConstructor/__stories__/PageConstructor.stories.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {Meta, StoryFn} from '@storybook/react';
33
import {PageConstructor, PageConstructorProps} from '../PageConstructor';
44

55
import data from './data.json';
6+
import {CustomBlocksTemplate} from './components/CustomBlocksTemplate';
67

78
export default {
89
title: 'Containers/PageConstructor',
@@ -19,6 +20,7 @@ export const Default = DefaultTemplate.bind({});
1920
export const withNavigation = DefaultTemplate.bind({});
2021
export const WithFullWidthBackgroundMedia = WithFullWidthBackgroundMediaTemplate.bind({});
2122
export const Branded = DefaultTemplate.bind({});
23+
export const CustomBlocks = CustomBlocksTemplate.bind({});
2224

2325
Default.args = data.default as PageConstructorProps;
2426
withNavigation.args = {
@@ -37,3 +39,4 @@ Branded.args = {
3739
...data.default,
3840
isBranded: true,
3941
};
42+
CustomBlocks.args = data.custom as unknown as PageConstructorProps;
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
@import '../../../../../../styles/variables';
2+
@import '../../../../../../styles/mixins';
3+
@import '../../../../../../styles/root';
4+
@import '../../../../../../styles/yfm.scss';
5+
@import '../mixins.scss';
6+
@import '../variables.scss';
7+
8+
.custom-block {
9+
@include custom-block-title($selector: '.pc-title-item__text');
10+
11+
display: flex;
12+
flex-direction: column;
13+
align-items: stretch;
14+
gap: 24px;
15+
width: 100%;
16+
overflow: hidden;
17+
18+
code {
19+
color: var(--custom-code-color);
20+
}
21+
22+
&__description {
23+
color: var(--g-color-text-primary);
24+
}
25+
26+
&__code-wrap {
27+
@include image-shadow();
28+
29+
code {
30+
display: block;
31+
white-space: pre-wrap;
32+
padding: 24px;
33+
}
34+
}
35+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import {Link} from '@gravity-ui/uikit';
2+
3+
import './CustomBlock.scss';
4+
import {cn} from '../../../../../utils';
5+
import {Title, YFMWrapper} from '../../../../../components';
6+
import {yfmTransform} from '../../../../../../.storybook/utils';
7+
8+
const b = cn('custom-block');
9+
10+
const CUSTOM_BLOCK_CODE = `
11+
const CustomBlock = ...;
12+
13+
const customConfig: CustomConfig = {
14+
...
15+
blocks: {
16+
['custom-block']: CustomBlock,
17+
},
18+
};
19+
20+
...
21+
22+
<PageConstructor {...props} custom={customConfig} />
23+
`
24+
.trim()
25+
.replace(/</g, '&lt;')
26+
.replace(/>/g, '&gt;');
27+
28+
interface CustomBlockProps {
29+
url?: string;
30+
title?: string;
31+
description?: string;
32+
}
33+
34+
export const CustomBlock = ({url, title, description}: CustomBlockProps) => {
35+
const Wrapper = url ? Link : 'div';
36+
37+
return (
38+
<div className={b()}>
39+
<Title className={b('title')} title={title} />
40+
{description && (
41+
<YFMWrapper className={b('description')} content={yfmTransform(description)} />
42+
)}
43+
<Wrapper className={b('code-wrap')} href={url ?? ''}>
44+
<YFMWrapper content={`<code>${CUSTOM_BLOCK_CODE}</code>`} />
45+
</Wrapper>
46+
</div>
47+
);
48+
};
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export {CustomBlock} from './CustomBlock';
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import {StoryFn} from '@storybook/react';
2+
import {CustomConfig} from '../../../../models';
3+
import {PageConstructor, PageConstructorProps} from '../../PageConstructor';
4+
import {CustomBlock} from './CustomBlock';
5+
import {CustomCard} from './CustomCard';
6+
import {CustomHeader} from './CustomHeader';
7+
import {CustomNavigationItem} from './CustomNavigationItem';
8+
import {CustomLoadableCard, loadCustomCardData} from './CustomLoadableCard';
9+
import {customDecorator} from './CustomDecorator';
10+
11+
const customConfig: CustomConfig = {
12+
blocks: {
13+
['custom-block']: CustomBlock,
14+
},
15+
subBlocks: {
16+
['custom-card']: CustomCard,
17+
},
18+
headers: {
19+
['custom-header']: CustomHeader,
20+
},
21+
navigation: {
22+
['custom-navigation-item']: CustomNavigationItem,
23+
},
24+
decorators: {block: [customDecorator]},
25+
loadable: {
26+
['custom-loadable-card']: {
27+
fetch: loadCustomCardData,
28+
component: CustomLoadableCard,
29+
},
30+
},
31+
};
32+
33+
export const CustomBlocksTemplate: StoryFn<PageConstructorProps> = (args) => (
34+
<PageConstructor {...args} custom={customConfig} />
35+
);
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
@import '../../../../../../styles/variables';
2+
@import '../../../../../../styles/mixins';
3+
@import '../../../../../../styles/root';
4+
@import '../../../../../../styles/yfm.scss';
5+
@import '../mixins.scss';
6+
7+
.custom-card {
8+
@include custom-block-title($selector: '.custom-card__card-header-content-title');
9+
$class: &;
10+
11+
position: relative;
12+
display: grid;
13+
grid-template-columns: repeat(2, 1fr);
14+
width: 200%;
15+
height: 100%;
16+
pointer-events: none;
17+
transform: rotateY(0);
18+
transform-style: preserve-3d;
19+
transform-origin: 25% 50%;
20+
perspective: 100px;
21+
transition: all 1s;
22+
23+
code {
24+
color: var(--custom-code-color);
25+
}
26+
27+
&__card-wrap {
28+
backface-visibility: hidden;
29+
30+
&:not(&_bottom) {
31+
position: relative;
32+
left: -100%;
33+
pointer-events: initial;
34+
}
35+
36+
&_bottom {
37+
flex-basis: 100%;
38+
transform: rotateY(180deg);
39+
pointer-events: none;
40+
}
41+
}
42+
43+
&_flipped {
44+
transform: rotateY(-180deg);
45+
46+
#{$class}__card-wrap {
47+
pointer-events: none;
48+
49+
&_bottom {
50+
pointer-events: initial;
51+
}
52+
}
53+
}
54+
55+
&__card {
56+
&-header {
57+
margin: -16px 0;
58+
59+
&-content {
60+
display: flex;
61+
align-items: center;
62+
justify-content: space-between;
63+
width: 100%;
64+
65+
&-flip-icon {
66+
cursor: pointer;
67+
}
68+
}
69+
}
70+
71+
&-body {
72+
height: calc(100% - 114px);
73+
74+
@include add-specificity(&) {
75+
padding: 0;
76+
}
77+
78+
&-link {
79+
display: block;
80+
height: 100%;
81+
82+
&-code {
83+
display: block;
84+
height: 100%;
85+
86+
code {
87+
display: block;
88+
white-space: pre-wrap;
89+
height: 100%;
90+
font-size: 12px;
91+
padding: 12px;
92+
border-radius: 0;
93+
}
94+
}
95+
}
96+
97+
&-description {
98+
@include text-body-1();
99+
100+
color: var(--g-color-text-primary);
101+
padding: 0 32px 16px;
102+
103+
p,
104+
ol {
105+
margin-bottom: 10px;
106+
}
107+
}
108+
}
109+
}
110+
}
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
/* eslint-disable jsx-a11y/no-static-element-interactions */
2+
/* eslint-disable jsx-a11y/click-events-have-key-events */
3+
import * as React from 'react';
4+
import {ArrowUturnCcwLeft} from '@gravity-ui/icons';
5+
import {CardBase, YFMWrapper} from '../../../../../components';
6+
import {cn} from '../../../../../utils';
7+
8+
import './CustomCard.scss';
9+
import {Link} from '@gravity-ui/uikit';
10+
import {yfmTransform} from '../../../../../../.storybook/utils';
11+
12+
const b = cn('custom-card');
13+
14+
const CUSTOM_CARD_CODE = `
15+
const CustomCard = ...;
16+
17+
const customConfig: CustomConfig = {
18+
...
19+
subBlocks: {
20+
['custom-card']: CustomCard,
21+
},
22+
};
23+
24+
...
25+
26+
<PageConstructor {...props} custom={customConfig} />
27+
`
28+
.trim()
29+
.replace(/</g, '&lt;')
30+
.replace(/>/g, '&gt;');
31+
32+
export interface CustomCardProps {
33+
url?: string;
34+
title?: string;
35+
description?: string;
36+
content?: string;
37+
}
38+
39+
export const CustomCard = ({
40+
url,
41+
title,
42+
description,
43+
content = CUSTOM_CARD_CODE,
44+
}: CustomCardProps) => {
45+
const [isFlipped, setIsFlipped] = React.useState(false);
46+
47+
const flip = () => setIsFlipped((prev) => !prev);
48+
49+
return (
50+
<div className={b({flipped: isFlipped})}>
51+
<div className={b('card-wrap', {bottom: true})} onClick={flip}>
52+
<CardBase className={b('card')} bodyClassName={b('card-body')}>
53+
<CardBase.Header className={b('card-header')}>
54+
<div className={b('card-header-content')}>
55+
<h2 className={b('card-header-content-title')}>{title}</h2>
56+
<ArrowUturnCcwLeft
57+
className={b('card-header-content-flip-icon')}
58+
width={20}
59+
height={20}
60+
/>
61+
</div>
62+
</CardBase.Header>
63+
<CardBase.Content>
64+
<Link
65+
className={b('card-body-link')}
66+
href={url ?? ''}
67+
onClick={(e) => e.stopPropagation()}
68+
>
69+
<YFMWrapper
70+
className={b('card-body-link-code')}
71+
content={`<code>${content}</code>`}
72+
/>
73+
</Link>
74+
</CardBase.Content>
75+
</CardBase>
76+
</div>
77+
<div className={b('card-wrap')} onClick={flip}>
78+
<CardBase className={b('card')} bodyClassName={b('card-body')}>
79+
<CardBase.Header className={b('card-header')}>
80+
<div className={b('card-header-content')}>
81+
<h2 className={b('card-header-content-title')}>{title}</h2>
82+
<ArrowUturnCcwLeft
83+
className={b('card-header-content-flip-icon')}
84+
width={20}
85+
height={20}
86+
/>
87+
</div>
88+
</CardBase.Header>
89+
<CardBase.Content>
90+
{description && (
91+
<YFMWrapper
92+
className={b('card-body-description')}
93+
content={yfmTransform(description)}
94+
/>
95+
)}
96+
</CardBase.Content>
97+
</CardBase>
98+
</div>
99+
</div>
100+
);
101+
};
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export {CustomCard, CustomCardProps} from './CustomCard';

0 commit comments

Comments
 (0)