From d18ead0f48dbefbc87f2695aa9909d3afd2bc641 Mon Sep 17 00:00:00 2001 From: Daniel Darritchon Date: Fri, 17 Oct 2025 13:51:55 -0300 Subject: [PATCH 01/10] ExpandableSection tewaks --- .../__docs__/stories/basic.stories.tsx | 45 ++++++++++++++----- .../expandableSection/expandableSection.tsx | 28 +++++++----- 2 files changed, 53 insertions(+), 20 deletions(-) diff --git a/src/components/expandableSection/__docs__/stories/basic.stories.tsx b/src/components/expandableSection/__docs__/stories/basic.stories.tsx index 92bcd368..07e35668 100644 --- a/src/components/expandableSection/__docs__/stories/basic.stories.tsx +++ b/src/components/expandableSection/__docs__/stories/basic.stories.tsx @@ -3,7 +3,7 @@ import React, {useState} from 'react'; import styled from 'styled-components'; import {Icon} from '../../../../elements/icon/icon'; -import {greys, palette} from '../../../../helpers/colorHelpers'; +import {alphas, greys, palette} from '../../../../helpers/colorHelpers'; import {VisualSizesEnum} from '../../../../helpers/fontHelpers'; import {DefaultStyleProvider} from '../../../../utils/defaultStyleProvider'; import {Button} from '../../../button/button'; @@ -59,7 +59,7 @@ export const Basic: StoryObj = { render: () => ( - + This is the content of the expandable section. It can contain any React elements that you want to display when the section is expanded. @@ -68,6 +68,7 @@ export const Basic: StoryObj = { + + + + + + ), + decorators: [ + (Story) => ( +
+ +
+ ) + ] +}; + +const WithCheckboxAndBodyComponent = () => { + const [isChecked, setIsChecked] = useState(false); + + return ( + + + + + + Customer Feedback + + + + +
+ Problem - 30 Sep, 2025 +
+
+
+
+ ); +}; + +export const WithCheckboxAndBody: Story = { + render: () => , + decorators: [ + (Story) => ( +
+ +
+ ) + ] +}; + +const WithCheckboxBodyAndFooterComponent = () => { + const [isChecked, setIsChecked] = useState(false); + + return ( + + console.log('Edit clicked!') + }, + { + label: 'Log content', + icon: 'AttachmentGeneric', + tooltip: 'Log content', + onClick: () => console.log('Log content clicked!') + } + ]}> + + + + Customer Feedback + + + + +
+ Speak with Lance about pricing proposal +
+
+ +
+ Call - 30 Sep, 2025 +
+
+
+
+ ); +}; + +export const WithCheckboxBodyAndFooter: Story = { + args: { + size: VisualSizesEnum.MEDIUM + }, + render: () => , + + decorators: [ + (Story) => ( +
+ +
+ ) + ] +}; + +export const CardWithActions: Story = { + render: (args) => ( + +
+ console.log('Edit clicked!') + } + ]}> + Card with Single Action + Hover over this card to see the edit action in the top right corner. + Card footer content + + console.log('Edit clicked!') + }, + { + label: 'Duplicate', + icon: 'Copy', + tooltip: 'Duplicate this card', + onClick: () => console.log('Duplicate clicked!') + }, + { + label: 'Delete', + icon: 'Trash', + tooltip: 'Delete this card', + onClick: () => console.log('Delete clicked!') + } + ]}> + Always Visible Actions + The action menu is always visible in the top right corner, no hover needed. + Card footer content + + console.log('Edit clicked!') + }, + { + label: 'Duplicate', + icon: 'Copy', + tooltip: 'Duplicate this card', + onClick: () => console.log('Duplicate clicked!') + }, + { + label: 'Delete', + icon: 'Trash', + tooltip: 'Delete this card', + onClick: () => console.log('Delete clicked!') + } + ]}> + Actions Show on Hover + + Hover over this card to see the action menu (three dots) in the top right corner. + + Card footer content + + console.log('Edit clicked!') + }, + { + label: 'Copy', + icon: 'Copy', + tooltip: 'Copy this card', + onClick: () => console.log('Copy clicked!') + }, + { + label: 'Share', + icon: 'ExternalLink', + tooltip: 'Share this card', + onClick: () => console.log('Share clicked!') + } + ]}> + Grouped Actions (groupActions=true) + + When groupActions=true, all actions are grouped into a single dropdown menu. This keeps the + interface clean and compact, especially useful when you have many actions or limited space. + + Card footer content + +
+
+ ), + decorators: [ + (Story) => ( +
+ +
+ ) + ] +}; diff --git a/src/components/card/card.tsx b/src/components/card/card.tsx new file mode 100644 index 00000000..0be3dca5 --- /dev/null +++ b/src/components/card/card.tsx @@ -0,0 +1,250 @@ +import {FC, ReactNode} from 'react'; +import styled from 'styled-components'; + +import {Icon, IconName} from '../../elements/icon/icon'; +import {alphas, greys} from '../../helpers/colorHelpers'; +import {VisualSizesEnum} from '../../helpers/fontHelpers'; +import {makeSizeConstants} from '../../helpers/styleHelpers'; +import {ActionMenu} from '../_pre-built/actionMenu/actionMenu'; +import {ActionMenuItem} from '../_pre-built/actionMenu/actionMenuItem'; +import {Button} from '../button/button'; +import {Tooltip} from '../tooltip/tooltip'; +import {TooltipCoordinator} from '../tooltip/tooltipCoordinator'; +import {CardBody} from './cardBody'; +import {CardFooter} from './cardFooter'; +import {CardHeader} from './cardHeader'; + +/* + * Props. + */ + +export interface CardAction { + /** The label for the action. */ + label: string; + /** The icon name for the action. */ + icon?: IconName; + /** The tooltip text for the action (optional, defaults to label). */ + tooltip?: string; + /** Called when the action is clicked. */ + onClick: () => void; +} + +export interface CardProps { + /** Content to render inside the card. */ + children?: ReactNode; + /** The size of the card. */ + size?: VisualSizesEnum; + /** Whether the card has a shadow. */ + hasShadow?: boolean; + /** Whether the card has a border. */ + hasBorder?: boolean; + /** Class name to allow custom styling of the card. */ + className?: string; + /** Whether the card is clickable. */ + isClickable?: boolean; + /** Called when the card is clicked. */ + onClick?: () => void; + /** Optional actions to display in the top right corner. */ + actions?: CardAction[]; + /** Whether actions should only be visible on hover (default: true - actions always visible). */ + showActionsOnHover?: boolean; + /** Whether to group actions into a dropdown menu (default: false - show as individual icon buttons). */ + groupActions?: boolean; +} + +/* + * Style. + */ + +interface StyledCardProps { + $size: VisualSizesEnum; + $hasShadow: boolean; + $hasBorder: boolean; + $isClickable: boolean; +} + +const cardPadding = makeSizeConstants(12, 16, 20); +const cardBorderRadius = makeSizeConstants(6, 8, 10); + +const StyledCard = styled.div` + display: flex; + flex-direction: column; + background: ${greys.white}; + border-radius: ${(p) => cardBorderRadius[p.$size]}px; + box-sizing: border-box; + overflow: hidden; + width: 100%; + + /* Padding based on size */ + padding: ${(p) => cardPadding[p.$size]}px; + + /* Border styling */ + border: ${(p) => (p.$hasBorder ? `1px solid ${alphas.black10}` : 'none')}; + + /* Shadow styling */ + box-shadow: ${(p) => (p.$hasShadow ? `0 2px 8px ${alphas.black10}` : 'none')}; + + /* Clickable styling */ + cursor: ${(p) => (p.$isClickable ? 'pointer' : 'default')}; + transition: ${(p) => (p.$isClickable ? 'box-shadow 0.2s ease, transform 0.2s ease' : 'none')}; + + &:hover { + ${(p) => + p.$isClickable + ? ` + box-shadow: 0 4px 12px ${alphas.black20}; + transform: translateY(-1px); + ` + : ''} + } + + &:active { + ${(p) => + p.$isClickable + ? ` + transform: translateY(0); + box-shadow: 0 2px 8px ${alphas.black10}; + ` + : ''} + } +`; + +const RelativeContainer = styled.div` + position: relative; + width: 100%; + height: 100%; +`; + +const ActionButtonContainer = styled.div<{zIndex?: number; $showOnHover?: boolean}>` + position: absolute; + top: 8px; + right: 8px; + z-index: ${(p) => p.zIndex || 10}; + display: flex; + align-items: center; + gap: 4px; + opacity: ${(p) => (p.$showOnHover ? 0 : 1)}; + transition: opacity 0.2s ease; + + /* Show on parent hover (only if showOnHover is true) */ + ${(p) => + p.$showOnHover && + `${RelativeContainer}:hover & { + opacity: 1; + }`} +`; + +/* + * Component. + */ + +const CardComponent: FC = ({ + children, + size = VisualSizesEnum.MEDIUM, + hasShadow = true, + hasBorder = false, + className, + isClickable = false, + onClick, + actions = [], + showActionsOnHover = false, + groupActions = false +}) => { + const handleClick = () => { + if (isClickable && onClick) onClick(); + }; + + const handleActionClick = (action: CardAction) => { + action.onClick(); + }; + + // If no actions, render the card normally + if (actions.length === 0) + return ( + + {children} + + ); + + // Render actions based on groupActions setting + const renderActions = () => { + if (groupActions) + // Group all actions into a dropdown menu + return ( + + + {actions.map((action) => ( + { + handleActionClick(action); + }}> + {action.label} + + ))} + + + ); + + // Show actions as individual icon buttons + return ( + + {actions.map((action) => ( + {action.tooltip ?? action.label}}> + + + ))} + + ); + }; + + return ( + + + {children} + + {renderActions()} + + ); +}; + +/* + * Sub-components. + */ + +// Create a compound component by assigning sub-components to the main component +const Card = Object.assign(CardComponent, { + Header: CardHeader, + Body: CardBody, + Footer: CardFooter +}) as typeof CardComponent & { + Header: typeof CardHeader; + Body: typeof CardBody; + Footer: typeof CardFooter; +}; + +Card.Header = CardHeader; +Card.Body = CardBody; +Card.Footer = CardFooter; + +export {Card}; diff --git a/src/components/card/cardBody.tsx b/src/components/card/cardBody.tsx new file mode 100644 index 00000000..8e6c1450 --- /dev/null +++ b/src/components/card/cardBody.tsx @@ -0,0 +1,58 @@ +import {FC, ReactNode} from 'react'; +import styled from 'styled-components'; + +import {greys} from '../../helpers/colorHelpers'; +import {VisualSizesEnum} from '../../helpers/fontHelpers'; +import {makeSizeConstants} from '../../helpers/styleHelpers'; + +/* + * Props. + */ + +export interface CardBodyProps { + /** Content to render inside the card body. */ + children?: ReactNode; + /** The size of the card body. */ + size?: VisualSizesEnum; + /** Class name to allow custom styling of the card body. */ + className?: string; + /** Whether the body has padding. */ + hasPadding?: boolean; +} + +/* + * Style. + */ + +interface StyledCardBodyProps { + $size: VisualSizesEnum; + $hasPadding: boolean; +} + +const bodyPadding = makeSizeConstants(0, 0, 0); +const bodyMarginVertical = makeSizeConstants(4, 8, 12); + +const StyledCardBody = styled.div` + display: flex; + flex-direction: column; + flex: 1; + width: 100%; + margin: ${(p) => (p.$hasPadding ? `${bodyMarginVertical[p.$size]}px 0` : '0')}; + padding: ${(p) => (p.$hasPadding ? `${bodyPadding[p.$size]}px` : '0')}; + color: ${greys.shade80}; +`; + +/* + * Component. + */ + +export const CardBody: FC = ({ + children, + size = VisualSizesEnum.MEDIUM, + className, + hasPadding = false +}) => ( + + {children} + +); diff --git a/src/components/card/cardFooter.tsx b/src/components/card/cardFooter.tsx new file mode 100644 index 00000000..f5af00a4 --- /dev/null +++ b/src/components/card/cardFooter.tsx @@ -0,0 +1,62 @@ +import {FC, ReactNode} from 'react'; +import styled from 'styled-components'; + +import {greys} from '../../helpers/colorHelpers'; +import {VisualSizesEnum} from '../../helpers/fontHelpers'; +import {makeSizeConstants} from '../../helpers/styleHelpers'; + +/* + * Props. + */ + +export interface CardFooterProps { + /** Content to render inside the card footer. */ + children?: ReactNode; + /** The size of the card footer. */ + size?: VisualSizesEnum; + /** Class name to allow custom styling of the card footer. */ + className?: string; + /** Whether the footer has a top border. */ + hasBorder?: boolean; +} + +/* + * Style. + */ + +interface StyledCardFooterProps { + $size: VisualSizesEnum; + $hasBorder: boolean; +} + +const footerPadding = makeSizeConstants(0, 0, 0); +const footerMarginTop = makeSizeConstants(8, 12, 16); +const footerBorderRadius = makeSizeConstants(6, 8, 10); + +const StyledCardFooter = styled.div` + display: flex; + align-items: center; + width: 100%; + margin-top: ${(p) => footerMarginTop[p.$size]}px; + padding: ${(p) => footerPadding[p.$size]}px; + border-radius: 0 0 ${(p) => footerBorderRadius[p.$size]}px ${(p) => footerBorderRadius[p.$size]}px; + + /* Border styling */ + border-top: ${(p) => (p.$hasBorder ? `1px solid ${greys.shade20}` : 'none')}; + padding-top: ${(p) => (p.$hasBorder ? footerMarginTop[p.$size] : 0)}px; +`; + +/* + * Component. + */ + +export const CardFooter: FC = ({ + children, + size = VisualSizesEnum.MEDIUM, + className, + hasBorder = false +}) => ( + + {children} + +); diff --git a/src/components/card/cardHeader.tsx b/src/components/card/cardHeader.tsx new file mode 100644 index 00000000..f43759cd --- /dev/null +++ b/src/components/card/cardHeader.tsx @@ -0,0 +1,62 @@ +import {FC, ReactNode} from 'react'; +import styled from 'styled-components'; + +import {greys} from '../../helpers/colorHelpers'; +import {VisualSizesEnum} from '../../helpers/fontHelpers'; +import {makeSizeConstants} from '../../helpers/styleHelpers'; + +/* + * Props. + */ + +export interface CardHeaderProps { + /** Content to render inside the card header. */ + children?: ReactNode; + /** The size of the card header. */ + size?: VisualSizesEnum; + /** Class name to allow custom styling of the card header. */ + className?: string; + /** Whether the header has a bottom border. */ + hasBorder?: boolean; +} + +/* + * Style. + */ + +interface StyledCardHeaderProps { + $size: VisualSizesEnum; + $hasBorder: boolean; +} + +const headerPadding = makeSizeConstants(0, 0, 0); +const headerMarginBottom = makeSizeConstants(4, 8, 12); +const headerBorderRadius = makeSizeConstants(6, 8, 10); + +const StyledCardHeader = styled.div` + display: flex; + align-items: center; + width: 100%; + margin-bottom: ${(p) => headerMarginBottom[p.$size]}px; + padding: ${(p) => headerPadding[p.$size]}px; + border-radius: ${(p) => headerBorderRadius[p.$size]}px ${(p) => headerBorderRadius[p.$size]}px 0 0; + + /* Border styling */ + border-bottom: ${(p) => (p.$hasBorder ? `1px solid ${greys.shade20}` : 'none')}; + padding-bottom: ${(p) => (p.$hasBorder ? headerMarginBottom[p.$size] : 0)}px; +`; + +/* + * Component. + */ + +export const CardHeader: FC = ({ + children, + size = VisualSizesEnum.MEDIUM, + className, + hasBorder = false +}) => ( + + {children} + +); diff --git a/src/index.ts b/src/index.ts index ddb3468e..02581326 100644 --- a/src/index.ts +++ b/src/index.ts @@ -42,6 +42,11 @@ export {ButtonContent} from './components/button/buttonContent'; export {ButtonContentIcon} from './components/button/buttonContentIcon'; export {ButtonGroup} from './components/button/buttonGroup'; +export {Card} from './components/card/card'; +export {CardHeader} from './components/card/cardHeader'; +export {CardBody} from './components/card/cardBody'; +export {CardFooter} from './components/card/cardFooter'; + export {Checkbox} from './components/checkbox/checkbox'; export {DatePickerDropdown as DatePicker} from './components/datepicker/datepickerDropdown'; From 39dca1630963260be0aa34d068746f2f174e2817 Mon Sep 17 00:00:00 2001 From: Daniel Darritchon Date: Fri, 17 Oct 2025 16:09:19 -0300 Subject: [PATCH 03/10] revert expandable setion related changes --- .../__docs__/stories/basic.stories.tsx | 45 +++++-------------- .../expandableSection/expandableSection.tsx | 28 +++++------- 2 files changed, 20 insertions(+), 53 deletions(-) diff --git a/src/components/expandableSection/__docs__/stories/basic.stories.tsx b/src/components/expandableSection/__docs__/stories/basic.stories.tsx index 07e35668..92bcd368 100644 --- a/src/components/expandableSection/__docs__/stories/basic.stories.tsx +++ b/src/components/expandableSection/__docs__/stories/basic.stories.tsx @@ -3,7 +3,7 @@ import React, {useState} from 'react'; import styled from 'styled-components'; import {Icon} from '../../../../elements/icon/icon'; -import {alphas, greys, palette} from '../../../../helpers/colorHelpers'; +import {greys, palette} from '../../../../helpers/colorHelpers'; import {VisualSizesEnum} from '../../../../helpers/fontHelpers'; import {DefaultStyleProvider} from '../../../../utils/defaultStyleProvider'; import {Button} from '../../../button/button'; @@ -59,7 +59,7 @@ export const Basic: StoryObj = { render: () => ( - + This is the content of the expandable section. It can contain any React elements that you want to display when the section is expanded. @@ -68,7 +68,6 @@ export const Basic: StoryObj = { @@ -55,7 +46,7 @@ export const WithButtons: Story = { ), decorators: [ (Story) => ( -
+
) @@ -100,7 +91,7 @@ export const WithCheckboxAndBody: Story = { render: () => , decorators: [ (Story) => ( -
+
) @@ -175,7 +166,7 @@ export const WithCheckboxBodyAndFooter: Story = { decorators: [ (Story) => ( -
+
) @@ -185,7 +176,7 @@ export const WithCheckboxBodyAndFooter: Story = { export const CardWithActions: Story = { render: (args) => ( -
+
( -
+
) From 1a7d222b76fc027f8df9f0327283fe9cc37d5ec2 Mon Sep 17 00:00:00 2001 From: Daniel Darritchon Date: Tue, 21 Oct 2025 16:22:01 -0300 Subject: [PATCH 08/10] yarn fix --- .../card/__docs__/index.stories.tsx | 32 +++++++++---------- .../__docs__/stories/basic.stories.tsx | 4 --- 2 files changed, 16 insertions(+), 20 deletions(-) diff --git a/src/components/card/__docs__/index.stories.tsx b/src/components/card/__docs__/index.stories.tsx index c19a1949..c7650182 100644 --- a/src/components/card/__docs__/index.stories.tsx +++ b/src/components/card/__docs__/index.stories.tsx @@ -1,12 +1,12 @@ -import type { Meta, StoryObj } from '@storybook/react'; -import React, { useState } from 'react'; +import type {Meta, StoryObj} from '@storybook/react'; +import React, {useState} from 'react'; -import { greys, palette } from '../../../helpers/colorHelpers'; -import { fontSizes, fontWeights, VisualSizesEnum } from '../../../helpers/fontHelpers'; -import { DefaultStyleProvider } from '../../../utils/defaultStyleProvider'; -import { Button } from '../../button/button'; -import { Checkbox } from '../../checkbox/checkbox'; -import { Card } from '../card'; +import {greys, palette} from '../../../helpers/colorHelpers'; +import {fontSizes, fontWeights, VisualSizesEnum} from '../../../helpers/fontHelpers'; +import {DefaultStyleProvider} from '../../../utils/defaultStyleProvider'; +import {Button} from '../../button/button'; +import {Checkbox} from '../../checkbox/checkbox'; +import {Card} from '../card'; const meta: Meta = { title: 'Components/Card', @@ -16,9 +16,9 @@ const meta: Meta = { }, argTypes: { size: { - control: { type: 'select' }, + control: {type: 'select'}, options: ['SMALL', 'MEDIUM', 'LARGE'] - }, + } } }; @@ -32,7 +32,7 @@ export const WithButtons: Story = { Card with Buttons This card contains buttons in the footer. -
+
@@ -46,7 +46,7 @@ export const WithButtons: Story = { ), decorators: [ (Story) => ( -
+
) @@ -91,7 +91,7 @@ export const WithCheckboxAndBody: Story = { render: () => , decorators: [ (Story) => ( -
+
) @@ -166,7 +166,7 @@ export const WithCheckboxBodyAndFooter: Story = { decorators: [ (Story) => ( -
+
) @@ -176,7 +176,7 @@ export const WithCheckboxBodyAndFooter: Story = { export const CardWithActions: Story = { render: (args) => ( -
+
( -
+
) diff --git a/src/components/expandableSection/__docs__/stories/basic.stories.tsx b/src/components/expandableSection/__docs__/stories/basic.stories.tsx index 2e71702e..f39e001b 100644 --- a/src/components/expandableSection/__docs__/stories/basic.stories.tsx +++ b/src/components/expandableSection/__docs__/stories/basic.stories.tsx @@ -195,7 +195,6 @@ export const ActionsOnHover: StoryObj = { = { = { = { Date: Wed, 22 Oct 2025 16:49:57 -0300 Subject: [PATCH 09/10] yarn lint --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b424b0c0..5cf90cc2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ ### Bug Fixes -* dropdown border color ([#293](https://github.com/frontapp/front-ui-kit/issues/293)) ([eb7a37c](https://github.com/frontapp/front-ui-kit/commit/eb7a37c8a5cb898eab020828ba9e03fee19c1879)) +- dropdown border color ([#293](https://github.com/frontapp/front-ui-kit/issues/293)) ([eb7a37c](https://github.com/frontapp/front-ui-kit/commit/eb7a37c8a5cb898eab020828ba9e03fee19c1879)) ## [0.17.0](https://github.com/frontapp/front-ui-kit/compare/v0.16.1...v0.17.0) (2025-10-22) From 21c0b56bebd6fc7c5b0449fc2eaaa6c4e25b37db Mon Sep 17 00:00:00 2001 From: Daniel Darritchon Date: Wed, 22 Oct 2025 17:59:48 -0300 Subject: [PATCH 10/10] remove expandable section component --- .../__docs__/stories/basic.stories.tsx | 44 ++++++++++++++----- .../expandableSection/expandableSection.tsx | 26 +++++++---- 2 files changed, 50 insertions(+), 20 deletions(-) diff --git a/src/components/expandableSection/__docs__/stories/basic.stories.tsx b/src/components/expandableSection/__docs__/stories/basic.stories.tsx index f39e001b..2b36eb80 100644 --- a/src/components/expandableSection/__docs__/stories/basic.stories.tsx +++ b/src/components/expandableSection/__docs__/stories/basic.stories.tsx @@ -2,7 +2,7 @@ import {StoryObj} from '@storybook/react'; import React, {useState} from 'react'; import styled from 'styled-components'; -import {greys, palette} from '../../../../helpers/colorHelpers'; +import {alphas, greys, palette} from '../../../../helpers/colorHelpers'; import {DefaultStyleProvider} from '../../../../utils/defaultStyleProvider'; import {Button} from '../../../button/button'; import {ExpandableSection} from '../../expandableSection'; @@ -38,7 +38,7 @@ export const Basic: StoryObj = { render: () => ( - + This is the content of the expandable section. It can contain any React elements that you want to display when the section is expanded. @@ -47,6 +47,7 @@ export const Basic: StoryObj = { = { - +

Complex Content Example

This section demonstrates that you can include any type of content:

@@ -100,6 +101,7 @@ const ControlledComponent = () => { = { render: () => ( - + Content for section 1. Each section operates independently. - + Content for section 2. Multiple sections can be open at the same time. - + Content for section 3. The sections have a square, card-like appearance with proper spacing and borders. @@ -154,7 +156,8 @@ export const Customized: StoryObj = { = { - + This section combines both customizations: text before icon and a custom chevron icon. - + + + This section has no expand/collapse icon. The section can still be clicked to expand and collapse. + + + + This section has a custom light blue hover background color instead of the default grey. @@ -195,6 +211,7 @@ export const ActionsOnHover: StoryObj = { = { = { = { = { Custom Title with Icon New - }> + } + backgroundColor={greys.white}> This expandable section demonstrates using a custom React component as the title. The title includes an icon, styled text, and a badge component. This provides much more flexibility than @@ -366,7 +387,8 @@ export const CustomTitleComponent: StoryObj = { ⚠️ Important Notice
- }> + } + backgroundColor={greys.white}> You can also use inline styles or any other React elements as the title. This example shows a warning icon with styled text. diff --git a/src/components/expandableSection/expandableSection.tsx b/src/components/expandableSection/expandableSection.tsx index ccd99e7b..de14c8df 100644 --- a/src/components/expandableSection/expandableSection.tsx +++ b/src/components/expandableSection/expandableSection.tsx @@ -38,8 +38,8 @@ interface ExpandableSectionProps { actions?: ExpandableSectionAction[]; /** Whether to show the text before the icon (default: false) */ showTextBeforeIcon?: boolean; - /** Custom icon name to use instead of the default CaretExpand */ - iconName?: 'CaretExpand' | 'ChevronDown'; + /** Custom icon name to use instead of the default CaretExpand. If empty or undefined, no icon will be shown. */ + iconName?: 'CaretExpand' | 'ChevronDown' | ''; /** Border radius for the expandable section (default: 8px) */ borderRadius?: string; /** Background color for the header on hover (default: greys.shade10) */ @@ -50,6 +50,8 @@ interface ExpandableSectionProps { showContentBorder?: boolean; /** Maximum height when expanded (default: 1000px) */ maxHeight?: string; + /** Background color for the expandable section (default: greys.white) */ + backgroundColor?: string; /** Whether actions should only be visible on hover (default: false - actions always visible). */ showActionsOnHover?: boolean; /** Whether to group actions into a dropdown menu (default: false - show as individual icon buttons). */ @@ -60,12 +62,16 @@ interface ExpandableSectionProps { * Styles */ -const StyledExpandableSectionDiv = styled.div<{borderRadius?: string; showBorder?: boolean}>` +const StyledExpandableSectionDiv = styled.div<{ + borderRadius?: string; + showBorder?: boolean; + backgroundColor?: string; +}>` display: flex; flex-direction: column; border: ${({showBorder = true}) => (showBorder ? `1px solid ${greys.shade30}` : 'none')}; border-radius: ${({borderRadius}) => borderRadius || '8px'}; - background: ${greys.white}; + background: ${({backgroundColor}) => backgroundColor || greys.white}; overflow: hidden; `; @@ -75,7 +81,6 @@ const StyledHeaderDiv = styled.div<{hoverBackgroundColor?: string}>` justify-content: space-between; padding: 16px 20px; cursor: pointer; - background: ${greys.white}; transition: background-color 0.2s ease; &:hover { @@ -95,7 +100,6 @@ const StyledTitleDiv = styled.div` font-size: ${fontSizes.large}; font-weight: ${fontWeights.medium}; line-height: 24px; - color: ${greys.shade80}; `; const StyledCaretIconDiv = styled.div<{isOpen: boolean}>` @@ -146,6 +150,7 @@ export const ExpandableSection: FC = (props) => { showBorder = true, showContentBorder = true, maxHeight = '1000px', + backgroundColor, showActionsOnHover = false, groupActions = false } = props; @@ -164,11 +169,11 @@ export const ExpandableSection: FC = (props) => { }; const titleElement = typeof title === 'string' ? {title} : title; - const iconElement = ( + const iconElement = iconName ? ( - ); + ) : null; // Render actions based on groupActions setting const renderActions = () => { @@ -214,7 +219,10 @@ export const ExpandableSection: FC = (props) => { }; return ( - + {showTextBeforeIcon ? (