diff --git a/.changeset/migrate-cardhorizontal-css-modules.md b/.changeset/migrate-cardhorizontal-css-modules.md new file mode 100644 index 000000000..19a74bac2 --- /dev/null +++ b/.changeset/migrate-cardhorizontal-css-modules.md @@ -0,0 +1,12 @@ +--- +"@clickhouse/click-ui": patch +--- + +Migrate CardHorizontal component from styled-components to CSS Modules + +- Replace styled-components with CSS Modules using BEM naming convention +- Add CVA (class-variance-authority) for variant management +- Implement forwardRef for ref forwarding +- Improve accessibility with proper keyboard navigation (Space and Enter keys) +- Add comprehensive JSDoc documentation for props +- Fix visual bugs with disabled and selected states diff --git a/src/components/CardHorizontal/CardHorizontal.module.css b/src/components/CardHorizontal/CardHorizontal.module.css new file mode 100644 index 000000000..060482180 --- /dev/null +++ b/src/components/CardHorizontal/CardHorizontal.module.css @@ -0,0 +1,299 @@ +/* WARNING: Disabled `no-descending-specificity` due to complex state-based selectors (default, hover, active, disabled) with varying specificity are grouped logically by state for maintainability, not ordered by specificity. This makes the code easier to maintain at time of writing */ +/* stylelint-disable no-descending-specificity */ +.card-horizontal { + display: flex; + width: 100%; + max-width: 100%; + justify-content: flex-start; + align-items: center; + border: 1px solid transparent; + border-radius: var(--click-card-horizontal-radii-all); + font: var(--click-card-horizontal-typography-title-default); +} + +.card-horizontal__content { + display: flex; + width: 100%; + flex-direction: row; +} + +.card-horizontal__icon-text-content { + display: flex; + width: 100%; + flex-direction: row; + align-items: center; + gap: var(--click-card-horizontal-space-md-gap); +} + +.card-horizontal__icon { + width: var(--click-card-horizontal-icon-size-all); + height: var(--click-card-horizontal-icon-size-all); +} + +.card-horizontal__header { + display: flex; + width: 100%; + max-width: 100%; + flex-direction: row; + justify-content: space-between; + align-items: center; + gap: var(--click-card-horizontal-space-md-gap); +} + +.card-horizontal__description { + display: flex; + width: 100%; + flex: 1; + flex-direction: column; + align-self: start; + gap: var(--click-card-horizontal-space-md-gap); + font: var(--click-card-horizontal-typography-description-default); +} + +.card-horizontal__children { + display: flex; + width: 100%; + flex: 1; + flex-direction: column; + align-self: start; + gap: var(--click-card-horizontal-space-md-gap); + font: var(--click-card-horizontal-typography-description-default); +} + +.card-horizontal_md { + padding: var(--click-card-horizontal-space-md-y) var(--click-card-horizontal-space-md-x); +} + +.card-horizontal_sm { + padding: var(--click-card-horizontal-space-sm-y) var(--click-card-horizontal-space-sm-x); +} + +.card-horizontal_md .card-horizontal__content { + gap: var(--click-card-horizontal-space-md-gap); +} + +.card-horizontal_sm .card-horizontal__content { + gap: var(--click-card-horizontal-space-sm-gap); +} + +.card-horizontal_md .card-horizontal__icon-text-content { + gap: var(--click-card-horizontal-space-md-gap); +} + +.card-horizontal_sm .card-horizontal__icon-text-content { + gap: var(--click-card-horizontal-space-sm-gap); +} + +.card-horizontal_default { + border-color: var(--click-card-horizontal-default-color-stroke-default); + background: var(--click-card-horizontal-default-color-background-default); + color: var(--click-card-horizontal-default-color-title-default); +} + +.card-horizontal_default .card-horizontal__description { + color: var(--click-card-horizontal-default-color-description-default); +} + +.card-horizontal_muted { + border-color: var(--click-card-horizontal-muted-color-stroke-default); + background: var(--click-card-horizontal-muted-color-background-default); + color: var(--click-card-horizontal-muted-color-title-default); +} + +.card-horizontal_muted .card-horizontal__description { + color: var(--click-card-horizontal-muted-color-description-default); +} + +.card-horizontal_selectable { + cursor: pointer; +} + +.card-horizontal_default.card-horizontal_selectable:not(.card-horizontal_selected, .card-horizontal_disabled) { + border-color: var(--click-card-horizontal-default-color-stroke-hover); +} + +.card-horizontal_muted.card-horizontal_selectable:not(.card-horizontal_selected, .card-horizontal_disabled) { + border-color: var(--click-card-horizontal-muted-color-stroke-hover); +} + +.card-horizontal_default.card-horizontal_selectable:hover:not(.card-horizontal_disabled) { + border-color: var(--click-card-horizontal-default-color-stroke-default); + background-color: var(--click-card-horizontal-default-color-background-hover); + color: var(--click-card-horizontal-default-color-title-hover); + font: var(--click-card-horizontal-typography-title-hover); +} + +.card-horizontal_default.card-horizontal_selectable:hover:not(.card-horizontal_disabled) .card-horizontal__description { + color: var(--click-card-horizontal-default-color-description-hover); + font: var(--click-card-horizontal-typography-description-hover); +} + +.card-horizontal_muted.card-horizontal_selectable:hover:not(.card-horizontal_disabled) { + border-color: var(--click-card-horizontal-muted-color-stroke-default); + background-color: var(--click-card-horizontal-muted-color-background-hover); + color: var(--click-card-horizontal-muted-color-title-hover); + font: var(--click-card-horizontal-typography-title-hover); +} + +.card-horizontal_muted.card-horizontal_selectable:hover:not(.card-horizontal_disabled) .card-horizontal__description { + color: var(--click-card-horizontal-muted-color-description-hover); + font: var(--click-card-horizontal-typography-description-hover); +} + +.card-horizontal_default.card-horizontal_selected, +.card-horizontal_default.card-horizontal_selectable.card-horizontal_selected { + border-color: var(--click-card-horizontal-default-color-stroke-active); + background-color: var(--click-card-horizontal-default-color-background-active); + color: var(--click-card-horizontal-default-color-title-active); +} + +.card-horizontal_default.card-horizontal_selected .card-horizontal__description, +.card-horizontal_default.card-horizontal_selectable.card-horizontal_selected .card-horizontal__description { + color: var(--click-card-horizontal-default-color-description-active); + font: var(--click-card-horizontal-typography-description-active); +} + +.card-horizontal_muted.card-horizontal_selected, +.card-horizontal_muted.card-horizontal_selectable.card-horizontal_selected { + border-color: var(--click-card-horizontal-muted-color-stroke-active); + background-color: var(--click-card-horizontal-muted-color-background-active); + color: var(--click-card-horizontal-muted-color-title-active); +} + +.card-horizontal_muted.card-horizontal_selected .card-horizontal__description, +.card-horizontal_muted.card-horizontal_selectable.card-horizontal_selected .card-horizontal__description { + color: var(--click-card-horizontal-muted-color-description-active); + font: var(--click-card-horizontal-typography-description-active); +} + +.card-horizontal_default.card-horizontal_selectable:active:not(.card-horizontal_disabled), +.card-horizontal_default.card-horizontal_selectable:focus-visible:not(.card-horizontal_disabled), +.card-horizontal_default.card-horizontal_selectable:focus-within:not(.card-horizontal_disabled) { + border-color: var(--click-card-horizontal-default-color-stroke-active); + background-color: var(--click-card-horizontal-default-color-background-active); + color: var(--click-card-horizontal-default-color-title-active); +} + +.card-horizontal_default.card-horizontal_selectable:active:not(.card-horizontal_disabled) .card-horizontal__description, +.card-horizontal_default.card-horizontal_selectable:focus-visible:not(.card-horizontal_disabled) .card-horizontal__description, +.card-horizontal_default.card-horizontal_selectable:focus-within:not(.card-horizontal_disabled) .card-horizontal__description { + color: var(--click-card-horizontal-default-color-description-active); + font: var(--click-card-horizontal-typography-description-active); +} + +.card-horizontal_muted.card-horizontal_selectable:active:not(.card-horizontal_disabled), +.card-horizontal_muted.card-horizontal_selectable:focus-visible:not(.card-horizontal_disabled), +.card-horizontal_muted.card-horizontal_selectable:focus-within:not(.card-horizontal_disabled) { + border-color: var(--click-card-horizontal-muted-color-stroke-active); + background-color: var(--click-card-horizontal-muted-color-background-active); + color: var(--click-card-horizontal-muted-color-title-active); +} + +.card-horizontal_muted.card-horizontal_selectable:active:not(.card-horizontal_disabled) .card-horizontal__description, +.card-horizontal_muted.card-horizontal_selectable:focus-visible:not(.card-horizontal_disabled) .card-horizontal__description, +.card-horizontal_muted.card-horizontal_selectable:focus-within:not(.card-horizontal_disabled) .card-horizontal__description { + color: var(--click-card-horizontal-muted-color-description-active); + font: var(--click-card-horizontal-typography-description-active); +} + +.card-horizontal_default.card-horizontal_selected:hover:not(.card-horizontal_disabled), +.card-horizontal_default.card-horizontal_selectable.card-horizontal_selected:hover:not(.card-horizontal_disabled) { + border-color: var(--click-card-horizontal-default-color-stroke-active); +} + +.card-horizontal_muted.card-horizontal_selected:hover:not(.card-horizontal_disabled), +.card-horizontal_muted.card-horizontal_selectable.card-horizontal_selected:hover:not(.card-horizontal_disabled) { + border-color: var(--click-card-horizontal-muted-color-stroke-active); +} + +.card-horizontal_default.card-horizontal_selected.card-horizontal_disabled:hover { + border-color: var(--click-card-horizontal-default-color-stroke-active); +} + +.card-horizontal_muted.card-horizontal_selected.card-horizontal_disabled:hover { + border-color: var(--click-card-horizontal-muted-color-stroke-active); +} + +.card-horizontal_disabled { + cursor: not-allowed; +} + +.card-horizontal_default.card-horizontal_disabled, +.card-horizontal_default.card-horizontal_disabled:hover, +.card-horizontal_default.card-horizontal_disabled:active, +.card-horizontal_default.card-horizontal_disabled:focus-visible, +.card-horizontal_default.card-horizontal_disabled:focus-within { + border-color: var(--click-card-horizontal-default-color-stroke-disabled); + background-color: var(--click-card-horizontal-default-color-background-disabled); + color: var(--click-card-horizontal-default-color-title-disabled); +} + +.card-horizontal_default.card-horizontal_disabled .card-horizontal__description, +.card-horizontal_default.card-horizontal_disabled:hover .card-horizontal__description, +.card-horizontal_default.card-horizontal_disabled:active .card-horizontal__description, +.card-horizontal_default.card-horizontal_disabled:focus-visible .card-horizontal__description, +.card-horizontal_default.card-horizontal_disabled:focus-within .card-horizontal__description { + color: var(--click-card-horizontal-default-color-description-disabled); + font: var(--click-card-horizontal-typography-description-disabled); +} + +.card-horizontal_muted.card-horizontal_disabled, +.card-horizontal_muted.card-horizontal_disabled:hover, +.card-horizontal_muted.card-horizontal_disabled:active, +.card-horizontal_muted.card-horizontal_disabled:focus-visible, +.card-horizontal_muted.card-horizontal_disabled:focus-within { + border-color: var(--click-card-horizontal-muted-color-stroke-disabled); + background-color: var(--click-card-horizontal-muted-color-background-disabled); + color: var(--click-card-horizontal-muted-color-title-disabled); +} + +.card-horizontal_muted.card-horizontal_disabled .card-horizontal__description, +.card-horizontal_muted.card-horizontal_disabled:hover .card-horizontal__description, +.card-horizontal_muted.card-horizontal_disabled:active .card-horizontal__description, +.card-horizontal_muted.card-horizontal_disabled:focus-visible .card-horizontal__description, +.card-horizontal_muted.card-horizontal_disabled:focus-within .card-horizontal__description { + color: var(--click-card-horizontal-muted-color-description-disabled); + font: var(--click-card-horizontal-typography-description-disabled); +} + +.card-horizontal_default.card-horizontal_selected.card-horizontal_disabled, +.card-horizontal_default.card-horizontal_selected.card-horizontal_disabled:active, +.card-horizontal_default.card-horizontal_selected.card-horizontal_disabled:focus-visible, +.card-horizontal_default.card-horizontal_selected.card-horizontal_disabled:focus-within { + border-color: var(--click-card-horizontal-default-color-stroke-active); + background-color: var(--click-card-horizontal-default-color-background-disabled); + color: var(--click-card-horizontal-default-color-title-disabled); +} + +.card-horizontal_default.card-horizontal_selected.card-horizontal_disabled .card-horizontal__description, +.card-horizontal_default.card-horizontal_selected.card-horizontal_disabled:hover .card-horizontal__description, +.card-horizontal_default.card-horizontal_selected.card-horizontal_disabled:active .card-horizontal__description, +.card-horizontal_default.card-horizontal_selected.card-horizontal_disabled:focus-visible .card-horizontal__description, +.card-horizontal_default.card-horizontal_selected.card-horizontal_disabled:focus-within .card-horizontal__description { + color: var(--click-card-horizontal-default-color-description-disabled); + font: var(--click-card-horizontal-typography-description-disabled); +} + +.card-horizontal_muted.card-horizontal_selected.card-horizontal_disabled, +.card-horizontal_muted.card-horizontal_selected.card-horizontal_disabled:active, +.card-horizontal_muted.card-horizontal_selected.card-horizontal_disabled:focus-visible, +.card-horizontal_muted.card-horizontal_selected.card-horizontal_disabled:focus-within { + border-color: var(--click-card-horizontal-muted-color-stroke-active); + background-color: var(--click-card-horizontal-muted-color-background-disabled); + color: var(--click-card-horizontal-muted-color-title-disabled); +} + +.card-horizontal_muted.card-horizontal_selected.card-horizontal_disabled .card-horizontal__description, +.card-horizontal_muted.card-horizontal_selected.card-horizontal_disabled:hover .card-horizontal__description, +.card-horizontal_muted.card-horizontal_selected.card-horizontal_disabled:active .card-horizontal__description, +.card-horizontal_muted.card-horizontal_selected.card-horizontal_disabled:focus-visible .card-horizontal__description, +.card-horizontal_muted.card-horizontal_selected.card-horizontal_disabled:focus-within .card-horizontal__description { + color: var(--click-card-horizontal-muted-color-description-disabled); + font: var(--click-card-horizontal-typography-description-disabled); +} + +@media (max-width: 768px) { + .card-horizontal__content { + flex-direction: column; + } +} diff --git a/src/components/CardHorizontal/CardHorizontal.stories.tsx b/src/components/CardHorizontal/CardHorizontal.stories.tsx index 2ee9695eb..a82e4dc6e 100644 --- a/src/components/CardHorizontal/CardHorizontal.stories.tsx +++ b/src/components/CardHorizontal/CardHorizontal.stories.tsx @@ -1,16 +1,9 @@ import { Meta, StoryObj } from '@storybook/react-vite'; -import { styled } from 'styled-components'; import { ICON_NAMES } from '../Icon/IconCommon'; import { CardHorizontal } from '@/components/CardHorizontal'; -const GridCenter = styled.div` - display: grid; - width: 60%; - max-width: 480px; -`; - const meta: Meta = { component: CardHorizontal, title: 'Cards/Horizontal Card', @@ -29,9 +22,9 @@ const meta: Meta = { size: { type: { name: 'enum', value: ['sm', 'md'] } }, }, decorators: Story => ( - +
- +
), }; @@ -199,8 +192,8 @@ export const NonSelectable: Story = { args: { icon: 'building', title: 'Non-Selectable Card', - description: 'This card is not selectable (has infoText).', - infoText: 'Click me', + description: 'This card is not selectable.', + isSelectable: false, }, }; diff --git a/src/components/CardHorizontal/CardHorizontal.test.tsx b/src/components/CardHorizontal/CardHorizontal.test.tsx index 6ea89fdcd..04a324952 100644 --- a/src/components/CardHorizontal/CardHorizontal.test.tsx +++ b/src/components/CardHorizontal/CardHorizontal.test.tsx @@ -281,6 +281,20 @@ describe('CardHorizontal Component', () => { expect(onClickMock).not.toHaveBeenCalled(); }); + it('should call onClick on Enter key when selectable', () => { + const onClickMock = vitest.fn(); + const { container } = renderCard({ + title: 'Test Card', + isSelectable: true, + onButtonClick: onClickMock, + }); + + const wrapper = container.firstChild as HTMLElement; + fireEvent.keyDown(wrapper, { key: 'Enter' }); + + expect(onClickMock).toHaveBeenCalled(); + }); + it('should not have onKeyDown when not selectable', () => { const { container } = renderCard({ title: 'Test Card', diff --git a/src/components/CardHorizontal/CardHorizontal.tsx b/src/components/CardHorizontal/CardHorizontal.tsx index f330a310a..9c375c250 100644 --- a/src/components/CardHorizontal/CardHorizontal.tsx +++ b/src/components/CardHorizontal/CardHorizontal.tsx @@ -1,305 +1,187 @@ -import { type KeyboardEvent, type MouseEvent } from 'react'; -import { styled } from 'styled-components'; +import { forwardRef, type KeyboardEvent, type MouseEvent } from 'react'; import { Badge } from '@/components/Badge'; import { Button } from '@/components/Button'; import { Container } from '@/components/Container'; import { Icon } from '@/components/Icon'; -import { CardHorizontalProps, CardSize, CardColor } from './CardHorizontal.types'; +import { cn, cva } from '@/lib/cva'; +import { CardHorizontalProps } from './CardHorizontal.types'; +import styles from './CardHorizontal.module.css'; -const Header = styled.div` - max-width: 100%; - gap: inherit; -`; +const cardHorizontalVariants = cva(styles['card-horizontal'], { + variants: { + color: { + default: styles['card-horizontal_default'], + muted: styles['card-horizontal_muted'], + }, + size: { + sm: styles['card-horizontal_sm'], + md: styles['card-horizontal_md'], + }, + disabled: { + true: styles['card-horizontal_disabled'], + }, + selected: { + true: styles['card-horizontal_selected'], + }, + selectable: { + true: styles['card-horizontal_selectable'], + }, + }, + defaultVariants: { + color: 'default', + size: 'md', + }, +}); -const Description = styled.div` - display: flex; - flex-direction: column; - align-self: start; - gap: ${({ theme }) => theme.click.card.horizontal.space.md.gap}; - flex: 1; - width: 100%; -`; - -const Wrapper = styled.div<{ - $hasShadow?: boolean; - $disabled?: boolean; - $isSelected?: boolean; - $isSelectable?: boolean; - $color: CardColor; - $size?: CardSize; -}>` - display: inline-flex; - width: 100%; - max-width: 100%; - align-items: center; - justify-content: flex-start; - - ${({ theme, $color, $size, $isSelected, $isSelectable, $disabled }) => ` - background: ${theme.click.card.horizontal[$color].color.background.default}; - color: ${theme.click.card.horizontal[$color].color.title.default}; - border-radius: ${theme.click.card.horizontal.radii.all}; - border: 1px solid ${ - theme.click.card.horizontal[$color].color.stroke[ - $isSelectable ? ($isSelected ? 'active' : 'hover') : 'default' - ] - }; - padding: ${ - $size === 'md' - ? `${theme.click.card.horizontal.space.md.y} ${theme.click.card.horizontal.space.md.x}` - : `${theme.click.card.horizontal.space.sm.y} ${theme.click.card.horizontal.space.sm.x}` - }; - font: ${theme.click.card.horizontal.typography.title.default}; - ${Description} { - color: ${theme.click.card.horizontal[$color].color.description.default}; - font: ${theme.click.card.horizontal.typography.description.default}; - } - &:hover{ - background-color: ${ - theme.click.card.horizontal[$color].color.background[ - $isSelectable ? 'hover' : 'default' - ] - }; - color: ${ - theme.click.card.horizontal[$color].color.title[ - $isSelectable ? 'hover' : 'default' - ] - }; - border: 1px solid ${ - theme.click.card.horizontal[$color].color.stroke[ - $isSelectable ? ($isSelected ? 'active' : 'default') : 'default' - ] - }; - cursor: ${$isSelectable ? 'pointer' : 'default'}; - font: ${theme.click.card.horizontal.typography.title.hover}; - ${Description} { - color: ${ - theme.click.card.horizontal[$color].color.description[ - $isSelectable ? 'hover' : 'default' - ] - }; - font: ${ - theme.click.card.horizontal.typography.description[ - $isSelectable ? 'hover' : 'default' - ] - }; +export const CardHorizontal = forwardRef( + ( + { + title, + icon, + description, + disabled = false, + infoText, + infoUrl, + isSelected, + isSelectable = infoText ? false : true, + children, + color = 'default', + size = 'md', + badgeText, + badgeState, + badgeIcon, + badgeIconDir, + onButtonClick, + className, + ...props + }, + ref + ) => { + const handleInteraction = ( + e: MouseEvent | KeyboardEvent + ) => { + if (disabled) { + e.preventDefault(); + return; } - } - &:active, &:focus, &:focus-within { - background-color: ${ - theme.click.card.horizontal[$color].color.background[ - $isSelectable ? 'active' : 'default' - ] - }; - color: ${ - theme.click.card.horizontal[$color].color.title[ - $isSelectable ? 'active' : 'default' - ] - }; - border: 1px solid ${ - theme.click.card.horizontal[$color].color.stroke[ - $isSelectable ? 'active' : 'default' - ] - }; - ${Description} { - color: ${ - theme.click.card.horizontal[$color].color.description[ - $isSelectable ? 'active' : 'default' - ] - }; - font: ${ - theme.click.card.horizontal.typography.description[ - $isSelectable ? 'active' : 'default' - ] - }; + if (typeof onButtonClick === 'function') { + onButtonClick(e); } - } - ${ - $disabled - ? ` - pointer-events: none; - &, - &:hover, - &:active, &:focus, &:focus-within { - background-color: ${ - theme.click.card.horizontal[$color].color.background.disabled - }; - color: ${theme.click.card.horizontal[$color].color.title.disabled}; - border: 1px solid ${ - theme.click.card.horizontal[$color].color.stroke[ - $isSelected ? 'active' : 'disabled' - ] - }; - cursor: not-allowed; - ${Description} { - color: ${theme.click.card.horizontal[$color].color.description.disabled}; - font: ${theme.click.card.horizontal.typography.description.disabled}; - } - }, - &:active, &:focus, &:focus-within { - border: 1px solid ${theme.click.card.horizontal[$color].color.stroke.active}; - } - ` - : '' - } - `} -`; - -const CardIcon = styled(Icon)` - ${({ theme }) => ` - height: ${theme.click.card.horizontal.icon.size.all}; - width: ${theme.click.card.horizontal.icon.size.all}; - `} -`; - -const ContentWrapper = styled.div<{ $size: CardSize }>` - display: flex; - flex-direction: row; - width: 100%; - gap: ${({ theme, $size }) => - $size === 'md' - ? theme.click.card.horizontal.space.md.gap - : theme.click.card.horizontal.space.sm.gap}; - - @media (max-width: ${({ theme }) => theme.breakpoint.sizes.md}) { - flex-direction: column; - } -`; - -const IconTextContentWrapper = styled.div<{ $size: CardSize }>` - display: flex; - flex-direction: row; - align-items: center; - width: 100%; - gap: ${({ theme, $size }) => - $size === 'md' - ? theme.click.card.horizontal.space.md.gap - : theme.click.card.horizontal.space.sm.gap}; -`; - -export const CardHorizontal = ({ - title, - icon, - description, - disabled = false, - infoText, - infoUrl, - isSelected, - isSelectable = infoText ? false : true, - children, - color = 'default', - size = 'md', - badgeText, - badgeState, - badgeIcon, - badgeIconDir, - onButtonClick, - ...props -}: CardHorizontalProps) => { - const handleClick = (e: MouseEvent) => { - if (disabled) { - e.preventDefault(); - return; - } - - if (typeof onButtonClick === 'function') { - onButtonClick(e); - } - if (infoUrl && infoUrl.length > 0) { - window.open(infoUrl, '_blank'); - } - }; + if (infoUrl && infoUrl.length > 0) { + window.open(infoUrl, '_blank'); + } + }; - const handleKeyDown = (e: KeyboardEvent) => { - if (isSelectable && !disabled && e.key === ' ') { - e.preventDefault(); - handleClick(e as unknown as MouseEvent); - } - }; + const handleKeyDown = (e: KeyboardEvent) => { + if (isSelectable && !disabled && (e.key === ' ' || e.key === 'Enter')) { + e.preventDefault(); + handleInteraction(e); + } + }; - return ( - - - - {icon && ( - - )} - - {title && ( -
- - {title} - - {badgeText && ( + return ( +
+
+
+ {icon && ( + + )} + + {title && ( +
- + + {title} + + {badgeText && ( + + + + )} - )} -
- )} + + )} - {description && {description}} - {children && {children}} -
-
- {infoText && ( - -