diff --git a/site/theme/__tests__/__snapshots__/doc-theme.test.ts.snap b/site/theme/__tests__/__snapshots__/doc-theme.test.ts.snap index 275e76e52e..59c92ecd8f 100644 --- a/site/theme/__tests__/__snapshots__/doc-theme.test.ts.snap +++ b/site/theme/__tests__/__snapshots__/doc-theme.test.ts.snap @@ -855,7 +855,9 @@ Object { }, "cardContainer": Object { "base": Object { + "backgroundColor": "{{colors.interface010}}", "borderRadius": false, + "color": "{{colors.inkBase}}", }, }, "cardContainerActions": Object { @@ -3615,7 +3617,9 @@ Object { }, "cardContainer": Object { "base": Object { + "backgroundColor": "{{colors.interface010}}", "borderRadius": false, + "color": "{{colors.inkBase}}", }, }, "cardContainerActions": Object { @@ -6378,7 +6382,9 @@ Object { }, "cardContainer": Object { "base": Object { + "backgroundColor": "{{colors.interface010}}", "borderRadius": false, + "color": "{{colors.inkBase}}", }, }, "cardContainerActions": Object { @@ -9138,7 +9144,9 @@ Object { }, "cardContainer": Object { "base": Object { + "backgroundColor": "{{colors.interface010}}", "borderRadius": false, + "color": "{{colors.inkBase}}", }, }, "cardContainerActions": Object { @@ -11901,7 +11909,9 @@ Object { }, "cardContainer": Object { "base": Object { + "backgroundColor": "{{colors.interface010}}", "borderRadius": false, + "color": "{{colors.inkBase}}", }, }, "cardContainerActions": Object { @@ -14661,7 +14671,9 @@ Object { }, "cardContainer": Object { "base": Object { + "backgroundColor": "{{colors.interface010}}", "borderRadius": false, + "color": "{{colors.inkBase}}", }, }, "cardContainerActions": Object { @@ -17424,7 +17436,9 @@ Object { }, "cardContainer": Object { "base": Object { + "backgroundColor": "{{colors.interface010}}", "borderRadius": false, + "color": "{{colors.inkBase}}", }, }, "cardContainerActions": Object { @@ -20184,7 +20198,9 @@ Object { }, "cardContainer": Object { "base": Object { + "backgroundColor": "{{colors.interface010}}", "borderRadius": false, + "color": "{{colors.inkBase}}", }, }, "cardContainerActions": Object { diff --git a/src/__tests__/__snapshots__/index.test.ts.snap b/src/__tests__/__snapshots__/index.test.ts.snap index dbb2db386d..97a0f47be5 100644 --- a/src/__tests__/__snapshots__/index.test.ts.snap +++ b/src/__tests__/__snapshots__/index.test.ts.snap @@ -27,7 +27,12 @@ Array [ "Byline", "Caption", "Card", + "CardActions", + "CardComposable", + "CardContent", "CardInset", + "CardLink", + "CardMedia", "Cell", "CharacterCount", "Checkbox", diff --git a/src/card-composable/__tests__/__snapshots__/card-composable.test.tsx.snap b/src/card-composable/__tests__/__snapshots__/card-composable.test.tsx.snap new file mode 100644 index 0000000000..02d6aee959 --- /dev/null +++ b/src/card-composable/__tests__/__snapshots__/card-composable.test.tsx.snap @@ -0,0 +1,530 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`CardComposable renders defaults 1`] = ` + + .emotion-0 { + margin: 0; + padding: 0; + display: grid; + grid-template-areas: "media" "content" "actions"; + color: #3B3B3B; + background-color: #FFFFFF; + position: relative; +} + +@media screen and (prefers-reduced-motion: no-preference) { + .emotion-0 { + transition-property: background-color; + transition-duration: 200ms; + transition-timing-function: cubic-bezier(0, 0, .5, 1); + } +} + +@media screen and (prefers-reduced-motion: reduce) { + .emotion-0 { + transition-property: background-color; + transition-duration: 0ms; + transition-timing-function: cubic-bezier(0, 0, .5, 1); + } +} + +.emotion-1 { + margin: 0; + padding: 0; + display: grid; + justify-items: start; + -webkit-align-items: start; + -webkit-box-align: start; + -ms-flex-align: start; + align-items: start; + grid-area: content; +} + +.emotion-2 { + display: inline-block; + color: #3358CC; + -webkit-text-decoration: none; + text-decoration: none; + -webkit-text-decoration: none; + text-decoration: none; +} + +@media screen and (prefers-reduced-motion: no-preference) { + .emotion-2 { + transition-property: color,fill; + transition-duration: 200ms,200ms; + transition-timing-function: cubic-bezier(0, 0, .5, 1),cubic-bezier(0, 0, .5, 1); + } +} + +@media screen and (prefers-reduced-motion: reduce) { + .emotion-2 { + transition-property: color,fill; + transition-duration: 0ms; + transition-timing-function: cubic-bezier(0, 0, .5, 1),cubic-bezier(0, 0, .5, 1); + } +} + +.emotion-2 svg { + fill: #3358CC; +} + +.emotion-2:hover:not(:disabled) { + color: #254CAC; + -webkit-text-decoration: underline; + text-decoration: underline; +} + +.emotion-2:hover:not(:disabled) svg { + fill: #254CAC; +} + +.emotion-2:active:not(:disabled) { + color: #12387A; + -webkit-text-decoration: underline; + text-decoration: underline; +} + +.emotion-2:active:not(:disabled) svg { + fill: #12387A; +} + +.emotion-2:focus-visible:not(:disabled) { + outline-color: #3768FB; + outline-style: solid; + outline-width: 2px; + outline-offset: 2px; +} + +@media not all and (min-resolution: 0.001dpcm) { + @supports (-webkit-appearance: none) and (stroke-color: transparent) { + .emotion-2:focus-visible:not(:disabled) { + outline-style: auto; + } + } +} + +.emotion-2:before { + content: ''; + position: absolute; + inset: 0; + z-index: 1; +} + +.emotion-3 { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + height: 100%; + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + -webkit-flex-direction: row; + -ms-flex-direction: row; + flex-direction: row; + -webkit-box-pack: start; + -ms-flex-pack: start; + -webkit-justify-content: flex-start; + justify-content: flex-start; +} + +.emotion-4 { + margin: 0; + font-family: "Poppins",sans-serif; + font-size: 14px; + line-height: 21px; + font-weight: 500; + letter-spacing: 0; + padding: 0.5px 0px; + display: inline-block; + display: block; +} + +.emotion-4::before { + content: ''; + margin-bottom: -0.403em; + display: block; +} + +.emotion-4::after { + content: ''; + margin-top: -0.4em; + display: block; +} + +.emotion-5 { + margin: 0; + padding: 0; + display: grid; + grid-area: media; +} + +.emotion-6 { + position: relative; + width: 100%; + display: block; + padding-top: 0; + height: 0; + width: 100%; + border-radius: 0; + background-color: #F1F1F1; +} + +.emotion-6 svg { + fill: #ABABAB; +} + +.emotion-7 { + top: 0; + left: 0; + position: absolute; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-flex-direction: column; + -ms-flex-direction: column; + flex-direction: column; + -webkit-box-pack: center; + -ms-flex-pack: center; + -webkit-justify-content: center; + justify-content: center; + width: 100%; + height: 100%; + margin: 0; +} + +.emotion-8 { + opacity: 0; + display: block; + border-radius: inherit; + height: auto; + width: 100%; + top: 0; + left: 0; + position: absolute; +} + +.emotion-9 { + margin: 0; + padding: 0; + display: grid; + -webkit-box-pack: start; + -ms-flex-pack: start; + -webkit-justify-content: start; + justify-content: start; + -webkit-align-items: start; + -webkit-box-align: start; + -ms-flex-align: start; + align-items: start; + grid-area: actions; + position: relative; + z-index: 2; +} + +
+ +
+ +
+ image + +
+
+ actions +
+
+ +`; + +exports[`CardComposable renders without areas 1`] = ` + + .emotion-0 { + margin: 0; + padding: 0; + display: grid; + color: #3B3B3B; + background-color: #FFFFFF; + position: relative; +} + +@media screen and (prefers-reduced-motion: no-preference) { + .emotion-0 { + transition-property: background-color; + transition-duration: 200ms; + transition-timing-function: cubic-bezier(0, 0, .5, 1); + } +} + +@media screen and (prefers-reduced-motion: reduce) { + .emotion-0 { + transition-property: background-color; + transition-duration: 0ms; + transition-timing-function: cubic-bezier(0, 0, .5, 1); + } +} + +.emotion-1 { + margin: 0; + padding: 0; + display: grid; + justify-items: start; + -webkit-align-items: start; + -webkit-box-align: start; + -ms-flex-align: start; + align-items: start; +} + +.emotion-2 { + display: inline-block; + color: #3358CC; + -webkit-text-decoration: none; + text-decoration: none; + -webkit-text-decoration: none; + text-decoration: none; +} + +@media screen and (prefers-reduced-motion: no-preference) { + .emotion-2 { + transition-property: color,fill; + transition-duration: 200ms,200ms; + transition-timing-function: cubic-bezier(0, 0, .5, 1),cubic-bezier(0, 0, .5, 1); + } +} + +@media screen and (prefers-reduced-motion: reduce) { + .emotion-2 { + transition-property: color,fill; + transition-duration: 0ms; + transition-timing-function: cubic-bezier(0, 0, .5, 1),cubic-bezier(0, 0, .5, 1); + } +} + +.emotion-2 svg { + fill: #3358CC; +} + +.emotion-2:hover:not(:disabled) { + color: #254CAC; + -webkit-text-decoration: underline; + text-decoration: underline; +} + +.emotion-2:hover:not(:disabled) svg { + fill: #254CAC; +} + +.emotion-2:active:not(:disabled) { + color: #12387A; + -webkit-text-decoration: underline; + text-decoration: underline; +} + +.emotion-2:active:not(:disabled) svg { + fill: #12387A; +} + +.emotion-2:focus-visible:not(:disabled) { + outline-color: #3768FB; + outline-style: solid; + outline-width: 2px; + outline-offset: 2px; +} + +@media not all and (min-resolution: 0.001dpcm) { + @supports (-webkit-appearance: none) and (stroke-color: transparent) { + .emotion-2:focus-visible:not(:disabled) { + outline-style: auto; + } + } +} + +.emotion-3 { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + height: 100%; + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + -webkit-flex-direction: row; + -ms-flex-direction: row; + flex-direction: row; + -webkit-box-pack: start; + -ms-flex-pack: start; + -webkit-justify-content: flex-start; + justify-content: flex-start; +} + +.emotion-4 { + margin: 0; + font-family: "Poppins",sans-serif; + font-size: 14px; + line-height: 21px; + font-weight: 500; + letter-spacing: 0; + padding: 0.5px 0px; + display: inline-block; + display: block; +} + +.emotion-4::before { + content: ''; + margin-bottom: -0.403em; + display: block; +} + +.emotion-4::after { + content: ''; + margin-top: -0.4em; + display: block; +} + +.emotion-5 { + margin: 0; + padding: 0; + display: grid; +} + +.emotion-6 { + position: relative; + width: 100%; + display: block; + padding-top: 0; + height: 0; + width: 100%; + border-radius: 0; + background-color: #F1F1F1; +} + +.emotion-6 svg { + fill: #ABABAB; +} + +.emotion-7 { + top: 0; + left: 0; + position: absolute; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-flex-direction: column; + -ms-flex-direction: column; + flex-direction: column; + -webkit-box-pack: center; + -ms-flex-pack: center; + -webkit-justify-content: center; + justify-content: center; + width: 100%; + height: 100%; + margin: 0; +} + +.emotion-8 { + opacity: 0; + display: block; + border-radius: inherit; + height: auto; + width: 100%; + top: 0; + left: 0; + position: absolute; +} + +.emotion-9 { + margin: 0; + padding: 0; + display: grid; + -webkit-box-pack: start; + -ms-flex-pack: start; + -webkit-justify-content: start; + justify-content: start; + -webkit-align-items: start; + -webkit-box-align: start; + -ms-flex-align: start; + align-items: start; + position: relative; + z-index: 2; +} + +
+ +
+ +
+ image + +
+
+ actions +
+
+ +`; diff --git a/src/card-composable/__tests__/__stories/card-composable.stories.tsx b/src/card-composable/__tests__/__stories/card-composable.stories.tsx new file mode 100644 index 0000000000..54d722c32d --- /dev/null +++ b/src/card-composable/__tests__/__stories/card-composable.stories.tsx @@ -0,0 +1,948 @@ +/* eslint-disable no-script-url */ +import React from 'react'; +import {Story as StoryType} from '@storybook/react'; +import { + CardComposable, + CardActions, + CardContent, + CardLink, + CardMedia, +} from '../../card-composable'; +import {Headline, HeadlineProps} from '../../../headline'; +import {CreateThemeArgs, ThemeProvider} from '../../../theme'; +import {createCustomThemeWithBaseThemeSwitch} from '../../../test/theme-select-object'; +import {Flag} from '../../../flag'; +import {StorybookCase, StorybookPage} from '../../../test/storybook-comps'; +import {Button} from '../../../button'; +import {UnorderedList} from '../../../unordered-list'; +import {TextBlock, TextBlockProps} from '../../../text-block'; +import {GridLayout} from '../../../grid-layout'; +import {Divider} from '../../../divider'; +import {Block} from '../../../block'; +import { + IconFilledStarOutline, + IconFilledStar, + IconFilledFigma, + IconFilledAccountBalance, + IconFilledAccountTree, + IconFilledBookmarkBorder, +} from '../../../icons'; +import {LinkInline} from '../../../link'; +import {Tag} from '../../../tag'; +import {VideoPlayer} from '../../../video-player'; +import {DEFATULT_VIDEO_PLAYER_CONFIG} from '../../../video-player/__tests__/config'; + +const H = ({overrides, ...props}: Omit) => ( + + Short title of the card describing the main content + +); + +const P = ({...props}: Omit) => ( + + Short paragraph description of the article, outlining main story and focus. + +); + +const cardCustomThemeObject: CreateThemeArgs = { + name: 'card-custom-theme', + overrides: { + stylePresets: { + // split stories + firstSplitBarCustom: { + base: { + color: '{{colors.inkBase}}', + backgroundColor: '{{colors.interactivePrimary010}}', + textAlign: 'center', + }, + }, + secondSplitBarCustom: { + base: { + color: '{{colors.inkBase}}', + backgroundColor: '{{colors.interactivePrimary020}}', + textAlign: 'center', + }, + }, + // Other stories + cardBook: { + base: { + borderStyle: 'solid', + borderColor: '{{colors.interface020}}', + borderWidth: '{{borders.borderWidth010}}', + boxShadow: '{{shadows.shadow030}}', + borderRadius: '{{borders.borderRadiusDefault}}', + backgroundColor: '{{colors.interfaceBackground}}', + }, + hover: { + boxShadow: '{{shadows.shadow060}}', + backgroundColor: '{{colors.interface020}}', + borderColor: '{{colors.interface030}}', + }, + }, + // Other stories + cardBookActions: { + base: { + borderStyle: 'solid', + borderColor: '{{colors.interface020}}', + borderWidth: + '{{borders.borderWidth010}} {{borders.borderWidth000}} {{borders.borderWidth000}} {{borders.borderWidth000}}', + }, + }, + // Other stories + centered: { + base: { + textAlign: 'center', + }, + }, + // Whole card as a link by applying the 'expand' prop + cardContentSeparateColor: { + base: { + boxShadow: '{{shadows.shadow020}}', + backgroundColor: '{{colors.interface010}}', + }, + hover: { + backgroundColor: '{{colors.interface020}}', + }, + }, + // CardInset & Padding overrides + cardInset: { + base: { + backgroundColor: '{{colors.interface020}}', + }, + }, + // Style preset - card and flag colours + cardContainerWithHover: { + base: { + backgroundColor: '{{colors.interfaceInformative020}}', + color: '{{colors.inkBrand010}}', + }, + hover: { + boxShadow: '{{shadows.shadow030}}', + backgroundColor: '{{colors.interfaceInformative010}}', + color: '{{colors.inkInverse}}', + }, + }, + currentColor: { + base: { + color: 'currentColor', + }, + }, + currentColorTag: { + base: { + color: 'currentColor', + borderStyle: 'solid', + borderColor: 'currentColor', + borderWidth: '{{borders.borderWidth010}}', + }, + }, + }, + }, +}; + +const href = 'javascript:void(0);'; +const areasGap = 'space050'; +const contentGap = 'space040'; + +export const StoryDefault = () => ( + + + + Flag + +

+ + + + + + Tag + + + +); +StoryDefault.storyName = 'Default'; + +export const StoryCardAreas = () => ( + + + + + Flag + +

+ + + + + + + + + + + + + + Tag + + + + + + + + + Flag + + + +

+ + + Tag + + + + +); +StoryCardAreas.storyName = 'Card areas'; + +export const StoryVariations = () => ( + + + + + + + + + + + + + + + Flag + + + +

+ + + + Tag + + + + + + + + Flag + +

+ + + + + + + + + + + Flag + +

+ + + + Unordered list item + + + Unordered list item + + + Unordered list item + + + + + + + + + Flag + +

+ + + Tag + + + + + + + Flag + +

+ + + + + + Tag + + + + +); +StoryVariations.storyName = 'Variations'; + +export const StoryInsetCard = () => ( + + + + + + Flag + +

+ + + Tag + + + + +); +StoryInsetCard.storyName = 'Inset card'; + +export const StoryLayout = () => ( + + + + + + Flag + +

+ + + Tag + + + + + + + + Flag + +

+ + + Tag + + + + +); +StoryLayout.storyName = 'Layout'; + +const SplitBars = ({ + columns, + maxWidth, +}: { + columns: string; + maxWidth: string; +}) => { + const [first, second] = columns.split(' '); + + return ( + + + {first} + + + {second} + + + ); +}; + +const SplitCard = ({columns}: {columns: string}) => { + const maxWidth = '600px'; + return ( +

+ + + + +

+ + + + Tag + + +

+ ); +}; + +export const StorySpan = () => ( + + + + + + + + + + + +); +StorySpan.storyName = 'Span'; + +export const StoryOrder = () => ( + + + + + +

+ + + + Tag + + + + +); +StoryOrder.storyName = 'Order'; + +export const StoryResponsiveCard = () => ( + + + + + +

+ + {[ + 'Unordered list item', + 'Unordered list item', + 'Unordered list item', + ]} + + + + + + +); +StoryResponsiveCard.storyName = 'Responsive card'; + +export const StoryLogicalProps = () => ( + + + + + Flag + +

+ + + + + Tag + + + + + + + + Flag + +

+ + + + + Tag + + + + + + + + Flag + +

+ + + + + Tag + + + + + +); +StoryLogicalProps.storyName = 'Logical props'; + +export const StoryOverrides = () => ( + + + + + + Flag + + {/* Unfortunately in NewsKit there is not a way for parent hover to trigger the children one + the easiest way to do that is using CSS currentColor */} + +

+ + + + Tag + + + + + + + + + Flag + +

+ + + + + Tag + + + + + +); +StoryOverrides.storyName = 'Styling overrides'; + +// https://www.linkedin.com/search/results/content/?keywords=design&origin=FACETED_SEARCH&postedBy=%5B%22first%22%2C%22following%22%5D&sid=xxV&sortBy=%22relevance%22 +export const ComplexStory = () => ( + + + + + + + Mountain retreat + + + Snowy Peaks, Austria + + + + + + + + + + 4.1 (38 reviews) + + + + + + + + + + Wi-Fi + + + + + + SPA + + + + + + Air-co + + + + + + 24/7 + + + + + + Short paragraph description of the article, outlining main story and + focus. + + + + + + + $299 + + + /night + + + + + + +); + +ComplexStory.storyName = 'Other story'; + +export default { + title: 'Components/CardComposable', + component: () => 'None', + decorators: [ + ( + Story: StoryType, + context: {name: string; globals: {backgrounds: {value: string}}}, + ) => ( + + + + ), + ], +}; diff --git a/src/card-composable/__tests__/card-composable.test.tsx b/src/card-composable/__tests__/card-composable.test.tsx new file mode 100644 index 0000000000..070574117d --- /dev/null +++ b/src/card-composable/__tests__/card-composable.test.tsx @@ -0,0 +1,44 @@ +import React from 'react'; +import {renderToFragmentWithTheme} from '../../test/test-utils'; +import { + CardComposable, + CardMedia, + CardContent, + CardActions, + CardLink, +} from '..'; + +describe('CardComposable', () => { + test('renders defaults', () => { + const fragment = renderToFragmentWithTheme(CardComposable, { + children: ( + <> + + + content + + + + actions + + ), + }); + expect(fragment).toMatchSnapshot(); + }); + + test('renders without areas', () => { + const fragment = renderToFragmentWithTheme(CardComposable, { + areas: '', + children: ( + <> + + content + + + actions + + ), + }); + expect(fragment).toMatchSnapshot(); + }); +}); diff --git a/src/card-composable/card-composable.tsx b/src/card-composable/card-composable.tsx new file mode 100644 index 0000000000..dc38fb45e4 --- /dev/null +++ b/src/card-composable/card-composable.tsx @@ -0,0 +1,130 @@ +import * as React from 'react'; +import {Image} from '../image'; + +import { + StyledActions, + StyledCard, + StyledContent, + StyledLink, + StyledMedia, +} from './styled'; + +import { + CardComposableProps, + CardMediaProps, + CardContentProps, + CardActionsProps, + CardLinkProps, + ComponentWithOverrides, +} from './types'; +import {useTheme} from '../theme'; +import {filterOutFalsyProperties} from '../utils/filter-object'; +import {withOwnTheme} from '../utils/with-own-theme'; +import defaults from './defaults'; +import stylePresets from './style-presets'; +import {CardProvider, useCardContext} from './context'; + +const useGetOverrides = ( + {overrides}: TCO, + componentName: string, +) => { + const theme = useTheme(); + + return { + ...theme.componentDefaults[componentName], + ...filterOutFalsyProperties(overrides), + }; +}; + +const defaultAreas = ` + media + content + actions + `; + +const ThemelessCardComposable = React.forwardRef< + HTMLDivElement, + CardComposableProps +>(({children, areas = defaultAreas, ...props}, ref) => { + const overrides = useGetOverrides( + props, + 'cardComposable', + ); + + return ( + + + {children} + + + ); +}); + +export const CardComposable = withOwnTheme(ThemelessCardComposable)({ + defaults, + stylePresets, +}); + +export const CardMedia = React.forwardRef( + ({media, children, ...props}, ref) => { + const {useAreas} = useCardContext(); + const overrides = useGetOverrides(props, 'cardMedia'); + return ( + + {children || } + + ); + }, +); + +export const CardContent = React.forwardRef( + (props, ref) => { + const {useAreas} = useCardContext(); + const overrides = useGetOverrides( + props, + 'cardContent', + ); + return ( + + ); + }, +); + +export const CardActions = React.forwardRef( + (props, ref) => { + const {useAreas} = useCardContext(); + const overrides = useGetOverrides( + props, + 'cardActions', + ); + return ( + + ); + }, +); + +export const CardLink = React.forwardRef( + (props, ref) => { + const overrides = useGetOverrides(props, 'cardLink'); + return ; + }, +); diff --git a/src/card-composable/context.ts b/src/card-composable/context.ts new file mode 100644 index 0000000000..1d74d78fba --- /dev/null +++ b/src/card-composable/context.ts @@ -0,0 +1,22 @@ +import {createContext, useContext} from 'react'; + +const CardContext = createContext({useAreas: false}); + +export const CardProvider = CardContext.Provider; + +export const useCardContext = () => { + const context = useContext(CardContext); + + /* istanbul ignore if */ + if ( + process.env.NODE_ENV !== 'production' && + Object.keys(context).length === 0 + ) { + // eslint-disable-next-line no-console + console.error( + 'You are using a component which needs to be a child of ', + ); + } + + return context; +}; diff --git a/src/card-composable/defaults.ts b/src/card-composable/defaults.ts new file mode 100644 index 0000000000..6763a31421 --- /dev/null +++ b/src/card-composable/defaults.ts @@ -0,0 +1,10 @@ +export default { + cardComposable: { + stylePreset: 'cardContainer', + transitionPreset: 'backgroundColorChange', + }, + cardMedia: {}, + cardContent: {}, + cardActions: {}, + cardLink: {}, +}; diff --git a/src/card-composable/index.ts b/src/card-composable/index.ts new file mode 100644 index 0000000000..5c422a56cf --- /dev/null +++ b/src/card-composable/index.ts @@ -0,0 +1,8 @@ +export * from './card-composable'; +export type { + CardComposableProps, + CardMediaProps, + CardContentProps, + CardActionsProps, + CardLinkProps, +} from './types'; diff --git a/src/card-composable/style-presets.ts b/src/card-composable/style-presets.ts new file mode 100644 index 0000000000..31918ce12c --- /dev/null +++ b/src/card-composable/style-presets.ts @@ -0,0 +1,3 @@ +import {StylePreset} from '../theme/types'; + +export default {} as Record; diff --git a/src/card-composable/styled.tsx b/src/card-composable/styled.tsx new file mode 100644 index 0000000000..3721418512 --- /dev/null +++ b/src/card-composable/styled.tsx @@ -0,0 +1,41 @@ +import {getStylePreset, getTransitionPreset, styled} from '../utils/style'; + +import {GridLayout} from '../grid-layout/grid-layout'; +import {CardLinkProps, StylableGridLayout} from './types'; +import {LinkStandalone} from '../link'; + +type StyledGridLayoutProps = StylableGridLayout & { + areaName?: string; +}; + +const StyledGrid = styled(GridLayout)` + ${getStylePreset('', '')}; + ${getTransitionPreset('', '')}; + ${({areaName}) => areaName && `grid-area: ${areaName};`} +`; + +export const StyledCard = styled(StyledGrid)` + position: relative; +`; + +export const StyledMedia = StyledGrid; +export const StyledContent = StyledGrid; + +export const StyledActions = styled(StyledGrid)` + position: relative; + z-index: 2; +`; + +export const StyledLink = styled(LinkStandalone)` + text-decoration: none; + ${({expand}) => + expand && + ` + &:before { + content: ''; + position: absolute; + inset: 0; + z-index: 1; + } + `} +`; diff --git a/src/card-composable/types.ts b/src/card-composable/types.ts new file mode 100644 index 0000000000..a9433df545 --- /dev/null +++ b/src/card-composable/types.ts @@ -0,0 +1,32 @@ +import {ReactNode} from 'react'; +import {GridLayoutProps} from '../grid-layout/types'; +import {ImageProps} from '../image'; +import {MQ} from '../utils'; +import {LinkProps} from '../link'; +import {TransitionToken} from '../theme'; + +export type StylableGridLayout = GridLayoutProps & { + overrides?: { + stylePreset?: MQ; + transitionPreset?: TransitionToken | TransitionToken[]; + }; +}; + +export type ComponentWithOverrides = { + overrides?: object; +}; + +export type CardComposableProps = StylableGridLayout; + +export type CardMediaProps = { + media?: ImageProps; + children?: ReactNode; +}; + +export type CardContentProps = StylableGridLayout; + +export type CardActionsProps = StylableGridLayout; + +export type CardLinkProps = LinkProps & { + expand?: boolean; +}; diff --git a/src/card/style-presets.ts b/src/card/style-presets.ts index c263651330..b7190bc926 100644 --- a/src/card/style-presets.ts +++ b/src/card/style-presets.ts @@ -2,12 +2,6 @@ import {StylePreset} from '../theme/types'; import {defaultFocusVisible} from '../utils/default-focus-visible'; export default { - cardContainer: { - base: { - color: '{{colors.inkBase}}', - backgroundColor: '{{colors.interface010}}', - }, - }, headlineHeadingInteractive: { base: { color: '{{colors.inkContrast}}', diff --git a/src/grid-layout/__tests__/stories/grid-card.tsx b/src/grid-layout/__tests__/stories/grid-card.tsx deleted file mode 100644 index 35bdec0b14..0000000000 --- a/src/grid-layout/__tests__/stories/grid-card.tsx +++ /dev/null @@ -1,177 +0,0 @@ -import * as React from 'react'; -import {Headline} from '../../../headline'; -import {TextBlock} from '../../../text-block'; -import {Block} from '../../../block'; -import {styled} from '../../../utils/style'; -import {Button} from '../../../button'; -import {Image} from '../../../image'; -import {Flag, getMediaQueryFromTheme} from '../../..'; -import {IconFilledEmail} from '../../../icons'; -import {GridLayout} from '../../grid-layout'; - -const StyledAdvancedCard = styled(GridLayout)` - border: 1px solid gray; -`; - -const StyledLink = styled.a` - grid-area: 1 / 1 / 2 / 3; - z-index: 3; - ${getMediaQueryFromTheme('md')} { - grid-area: 1 / 1 / 3 / 3; - } - &:hover { - background: rgba(0, 0, 0, 0.1); - } -`; - -export const GridCard = ({title = '', teaser = '', image = '', href = ''}) => { - const xsLayout = ` - "thumb content" - "tags share" - `; - const mdLayout = ` - "thumb thumb" - "content content" - "tags share" - `; - - return ( - - {Areas => ( - <> - - - - - Flag - - - - - {title} - - - - - {teaser} - - - - - - - - - {href ? : null} - - )} - - ); -}; - -export const GridTeaser = ({ - title = '', - teaser = '', - image = '', - href = '', -}) => { - const xsLayout = ` - "thumb" - "tags" - `; - - return ( - - {Areas => ( - <> - - - - - - - {title} - - - - - {teaser} - - - - - - {href ? : null} - - )} - - ); -}; diff --git a/src/grid-layout/__tests__/stories/grid-layout.stories.tsx b/src/grid-layout/__tests__/stories/grid-layout.stories.tsx index c75e603285..5bf58a815f 100644 --- a/src/grid-layout/__tests__/stories/grid-layout.stories.tsx +++ b/src/grid-layout/__tests__/stories/grid-layout.stories.tsx @@ -3,7 +3,6 @@ import {styled} from '../../../utils'; import {Block} from '../../../block'; import {Divider} from '../../../divider'; import {GridLayout, GridLayoutItem} from '../../grid-layout'; -import {GridCard, GridTeaser} from './grid-card'; import {GridBox} from './common'; import {Grid, Cell} from '../../../grid'; import {Label} from '../../..'; @@ -412,27 +411,6 @@ export const StoryWithLogicalPropsOverrides = () => ( StoryWithLogicalPropsOverrides.storyName = 'with-logical-props'; -export const StoryCardWithGrid = () => ( - <> - Card with grid - - - - - - -); -StoryCardWithGrid.storyName = 'card-with-grid'; - export * from './the-times'; export * from './the-sun'; diff --git a/src/index.ts b/src/index.ts index e3cba68a5b..85e1f8c3c0 100644 --- a/src/index.ts +++ b/src/index.ts @@ -10,6 +10,7 @@ export * from './button'; export * from './byline'; export * from './caption'; export * from './card'; +export * from './card-composable'; export * from './character-count'; export * from './checkbox'; export * from './consent'; diff --git a/src/theme/__tests__/__snapshots__/creator.test.ts.snap b/src/theme/__tests__/__snapshots__/creator.test.ts.snap index 34b81d716f..cb9ccf37f9 100644 --- a/src/theme/__tests__/__snapshots__/creator.test.ts.snap +++ b/src/theme/__tests__/__snapshots__/creator.test.ts.snap @@ -413,6 +413,12 @@ Object { "spaceInsetStretch060": "{{sizing.sizing080}} {{sizing.sizing060}}", }, "stylePresets": Object { + "cardContainer": Object { + "base": Object { + "backgroundColor": "{{colors.interface010}}", + "color": "{{colors.inkBase}}", + }, + }, "controlLabel": Object { "base": Object { "color": "{{colors.inkBase}}", diff --git a/src/theme/__tests__/__snapshots__/theme.test.ts.snap b/src/theme/__tests__/__snapshots__/theme.test.ts.snap index 5bd3a1d6c1..04036c550d 100644 --- a/src/theme/__tests__/__snapshots__/theme.test.ts.snap +++ b/src/theme/__tests__/__snapshots__/theme.test.ts.snap @@ -418,6 +418,12 @@ Object { "spaceInsetStretch060": "{{sizing.sizing080}} {{sizing.sizing060}}", }, "stylePresets": Object { + "cardContainer": Object { + "base": Object { + "backgroundColor": "{{colors.interface010}}", + "color": "{{colors.inkBase}}", + }, + }, "controlLabel": Object { "base": Object { "color": "{{colors.inkBase}}", @@ -1710,6 +1716,12 @@ Object { "paddingInline": "space000", }, }, + "cardActions": Object {}, + "cardComposable": Object { + "stylePreset": "cardContainer", + "transitionPreset": "backgroundColorChange", + }, + "cardContent": Object {}, "cardInset": Object { "actionsContainer": Object { "minHeight": "sizing000", @@ -1746,6 +1758,8 @@ Object { }, }, }, + "cardLink": Object {}, + "cardMedia": Object {}, "characterCount": Object { "large": Object { "marginBlockEnd": "space020", diff --git a/src/theme/compiler/__tests__/__snapshots__/index.test.ts.snap b/src/theme/compiler/__tests__/__snapshots__/index.test.ts.snap index 761df18fc0..6a4509c7d2 100644 --- a/src/theme/compiler/__tests__/__snapshots__/index.test.ts.snap +++ b/src/theme/compiler/__tests__/__snapshots__/index.test.ts.snap @@ -414,6 +414,12 @@ Object { "spaceInsetStretch060": "48px 32px", }, "stylePresets": Object { + "cardContainer": Object { + "base": Object { + "backgroundColor": "#FFFFFF", + "color": "#3B3B3B", + }, + }, "controlLabel": Object { "base": Object { "color": "#3B3B3B", diff --git a/src/theme/presets/style-presets.ts b/src/theme/presets/style-presets.ts index 3901941022..48153a283c 100644 --- a/src/theme/presets/style-presets.ts +++ b/src/theme/presets/style-presets.ts @@ -179,3 +179,10 @@ stylePresets.selectOptionItemIcon = { iconColor: '{{colors.interactiveInput040}}', }, }; + +stylePresets.cardContainer = { + base: { + color: '{{colors.inkBase}}', + backgroundColor: '{{colors.interface010}}', + }, +};