Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(SubBlocks): add possibility to position controls within cards at footer #823

Merged
merged 6 commits into from
Mar 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions src/components/Buttons/Buttons.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
@import '../../../styles/variables.scss';

$block: '.#{$ns}buttons';

#{$block} {
display: flex;
flex-wrap: wrap;
column-gap: $indentXXS;

&_size {
&_s {
row-gap: $indentXXXS;
}

&_l {
row-gap: $indentXXS;
}
}
}
49 changes: 49 additions & 0 deletions src/components/Buttons/Buttons.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import React from 'react';

import {ButtonProps, ContentSize} from '../../models';
import {block} from '../../utils';
import Button from '../Button/Button';

import './Buttons.scss';

const b = block('buttons');

type ButtonsProps = {
className?: string;
buttons?: ButtonProps[];
size?: ContentSize;
titleId?: string;
qa?: string;
buttonQa?: string;
};

function getButtonSize(size: ContentSize) {
switch (size) {
case 's':
return 'm';
case 'l':
default:
return 'xl';
}
}

const Buttons: React.FC<ButtonsProps> = ({className, titleId, buttons, size = 's', qa, buttonQa}) =>
buttons ? (
<div className={b({size}, className)} data-qa={qa}>
{buttons.map((item) => (
<Button
className={b('button')}
{...item}
key={item.url}
size={getButtonSize(size)}
qa={buttonQa}
extraProps={{
'aria-describedby': item.urlTitle ? undefined : titleId,
...item.extraProps,
}}
/>
))}
</div>
) : null;

export default Buttons;
22 changes: 14 additions & 8 deletions src/components/CardBase/CardBase.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
import React, {Children, Fragment, HTMLAttributeAnchorTarget, ReactElement} from 'react';
import React, {
Children,
Fragment,
HTMLAttributeAnchorTarget,
PropsWithChildren,
ReactElement,
isValidElement,
} from 'react';
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is the purpose of isValidElement? didn't find any usage examples in pr

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please have a look at line 96. It used to handle children elements correctly.


import {Link} from '@gravity-ui/uikit';

Expand All @@ -19,11 +26,10 @@ import RouterLink from '../RouterLink/RouterLink';

import './CardBase.scss';

export interface CardBaseProps extends AnalyticsEventsBase, CardBaseParams {
export interface CardBaseProps extends AnalyticsEventsBase, CardBaseParams, PropsWithChildren {
className?: string;
bodyClassName?: string;
contentClassName?: string;
children: ReactElement | ReactElement[];
url?: string;
urlTitle?: string;
target?: HTMLAttributeAnchorTarget;
Expand Down Expand Up @@ -86,11 +92,11 @@ export const Layout = (props: CardBaseProps) => {
}
}

if (Children.count(children) === 1) {
handleChild(children as ReactElement);
} else {
Children.forEach(children, handleChild);
}
Children.toArray(children).forEach((child) => {
if (isValidElement(child)) {
handleChild(child);
}
});

const cardContent = (
<Fragment>
Expand Down
27 changes: 0 additions & 27 deletions src/components/Link/Links.tsx

This file was deleted.

23 changes: 23 additions & 0 deletions src/components/Links/Links.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
@import '../../../styles/variables.scss';

$block: '.#{$ns}links';

#{$block} {
display: flex;
flex-direction: column;
align-items: baseline;

&__link {
margin-top: 0px;
}

&_size {
&_s {
gap: $indentXXXS;
}

&_l {
gap: $indentXXS;
}
}
}
49 changes: 49 additions & 0 deletions src/components/Links/Links.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import React from 'react';

import {ContentSize, LinkProps} from '../../models';
import {block} from '../../utils';
import Link from '../Link/Link';

import './Links.scss';

const b = block('links');

function getLinkSize(size: ContentSize) {
switch (size) {
case 's':
return 'm';
case 'l':
default:
return 'l';
}
}

type LinksProps = {
className?: string;
titleId?: string;
links?: LinkProps[];
size?: ContentSize;
qa?: string;
linkQa?: string;
};

const Links: React.FC<LinksProps> = ({className, titleId, links, size = 's', qa, linkQa}) =>
links ? (
<div className={b({size}, className)} data-qa={qa}>
{links?.map((link) => (
<Link
className={b('link')}
{...link}
textSize={getLinkSize(size)}
key={link.url}
qa={linkQa}
extraProps={{
'aria-describedby': link.urlTitle ? undefined : titleId,
...link.extraProps,
}}
/>
))}
</div>
) : null;

export default Links;
3 changes: 2 additions & 1 deletion src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export {default as BackLink} from './BackLink/BackLink';
export {default as BalancedMasonry} from './BalancedMasonry/BalancedMasonry';
export {default as BlockBase} from './BlockBase/BlockBase';
export {default as Button} from './Button/Button';
export {default as Buttons} from './Buttons/Buttons';
export {default as CardBase} from './CardBase/CardBase';
export {default as ErrorWrapper} from './ErrorWrapper/ErrorWrapper';
export {default as FileLink} from './FileLink/FileLink';
Expand All @@ -16,7 +17,7 @@ export {default as HeaderBreadcrumbs} from './HeaderBreadcrumbs/HeaderBreadcrumb
export {default as Image} from './Image/Image';
export {default as ImageBase} from './ImageBase/ImageBase';
export {default as Link} from './Link/Link';
export {default as Links} from './Link/Links';
export {default as Links} from './Links/Links';
export {default as Media} from './Media/Media';
export {default as OutsideClick} from './OutsideClick/OutsideClick';
export {default as ReactPlayer} from './ReactPlayer/ReactPlayer';
Expand Down
5 changes: 5 additions & 0 deletions src/models/constructor-items/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -392,11 +392,16 @@ export type MediaView = 'fit' | 'full';
// card
export type MediaBorder = 'shadow' | 'line' | 'none';
export type CardBorder = MediaBorder;
export type ControlPosition = 'content' | 'footer';

export interface CardBaseProps {
border?: CardBorder;
}

export type CardLayoutProps = {
controlPosition?: ControlPosition;
};

//price
export interface PriceDescriptionProps {
title: string;
Expand Down
5 changes: 4 additions & 1 deletion src/models/constructor-items/sub-blocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
ButtonPixel,
ButtonProps,
CardBaseProps,
CardLayoutProps,
ContentTheme,
DividerSize,
ImageCardMargins,
Expand Down Expand Up @@ -134,6 +135,7 @@ export interface QuoteProps extends Themable, CardBaseProps {
export interface BackgroundCardProps
extends CardBaseProps,
AnalyticsEventsBase,
CardLayoutProps,
Omit<ContentBlockProps, 'colSizes' | 'centered'> {
url?: string;
urlTitle?: string;
Expand All @@ -145,6 +147,7 @@ export interface BackgroundCardProps
export interface BasicCardProps
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

`interface CardLayoutProps = {
controlPosition?: 'content' | 'footer';
}

...

export interface BannerCardProps extends CardLayoutProps
`

extends CardBaseProps,
AnalyticsEventsBase,
CardLayoutProps,
Omit<ContentBlockProps, 'colSizes' | 'centered' | 'size' | 'theme'> {
url: string;
urlTitle?: string;
Expand Down Expand Up @@ -178,7 +181,7 @@ export interface PriceCardProps extends CardBaseProps, Pick<ContentBlockProps, '
list?: string[];
}

export interface LayoutItemProps extends ClassNameProps, AnalyticsEventsBase {
export interface LayoutItemProps extends ClassNameProps, CardLayoutProps, AnalyticsEventsBase {
content: Omit<ContentBlockProps, 'colSizes' | 'centered' | 'size'>;
media?: MediaProps;
metaInfo?: string[];
Expand Down
30 changes: 27 additions & 3 deletions src/sub-blocks/BackgroundCard/BackgroundCard.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import React from 'react';
import React, {useMemo} from 'react';

import {useUniqId} from '@gravity-ui/uikit';

import {BackgroundImage, CardBase} from '../../components/';
import {useTheme} from '../../context/theme';
import {BackgroundCardProps} from '../../models';
import {block, getThemedValue} from '../../utils';
import renderContentControls from '../../utils/renderContentControls/renderContentControls';
import Content from '../Content/Content';
import renderCardFooterControlsContainer from '../renderCardFooterControlsContainer/renderCardFooterControlsContainer';

import './BackgroundCard.scss';

Expand All @@ -25,11 +29,29 @@ const BackgroundCard = (props: BackgroundCardProps) => {
buttons,
analyticsEvents,
urlTitle,
controlPosition = 'content',
} = props;

const titleId = useUniqId();

const theme = useTheme();
const hasBackgroundColor = backgroundColor || cardTheme !== 'default';
const borderType = hasBackgroundColor ? 'none' : border;
const areControlsInFooter = !paddingBottom && controlPosition === 'footer';

const footerControls = useMemo(
() =>
renderContentControls(
{
links: areControlsInFooter ? links : undefined,
buttons: areControlsInFooter ? buttons : undefined,
size: 's',
titleId,
},
renderCardFooterControlsContainer,
),
[areControlsInFooter, links, buttons, titleId],
);

return (
<CardBase
Expand All @@ -46,16 +68,18 @@ const BackgroundCard = (props: BackgroundCardProps) => {
style={{backgroundColor}}
/>
<Content
titleId={titleId}
title={title}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

const controlPositionFooter = controlPosition === 'footer'
...
links={controlPositionFooter ? undefined : links}

text={text}
additionalInfo={additionalInfo}
size="s"
theme={cardTheme}
links={links}
buttons={buttons}
links={areControlsInFooter ? undefined : links}
buttons={areControlsInFooter ? undefined : buttons}
colSizes={{all: 12, md: 12}}
/>
</CardBase.Content>
{footerControls}
</CardBase>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

<ContentControls ...>
  {(children) => renderCardFooterControlsContainer(children)}
</ContentControls>

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately, this won't work within CardBase.

);
};
Expand Down
Loading
Loading