Skip to content

Commit 63238ba

Browse files
committed
feat: add FeedImage component
1 parent 5192351 commit 63238ba

7 files changed

Lines changed: 181 additions & 0 deletions

File tree

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import { FeedImage, themeClass, typography, vars } from '@comma/design-system';
2+
import type { Meta, StoryObj } from '@storybook/react-vite';
3+
4+
type FeedImageStoryArgs = {
5+
heart: boolean;
6+
imageSrc?: string;
7+
};
8+
9+
const meta = {
10+
title: 'Design System/FeedImage',
11+
argTypes: {
12+
heart: {
13+
control: 'boolean'
14+
},
15+
imageSrc: {
16+
control: 'text'
17+
}
18+
},
19+
args: {
20+
heart: false
21+
}
22+
} satisfies Meta<FeedImageStoryArgs>;
23+
24+
export default meta;
25+
26+
type Story = StoryObj<FeedImageStoryArgs>;
27+
28+
const storySurfaceStyle: React.CSSProperties = {
29+
minHeight: 620,
30+
display: 'grid',
31+
placeItems: 'center',
32+
padding: 32,
33+
background: vars.color.backgroundPrimary,
34+
fontFamily: vars.font.body
35+
};
36+
37+
const variantsSurfaceStyle: React.CSSProperties = {
38+
minHeight: 620,
39+
display: 'grid',
40+
alignContent: 'center',
41+
gap: 16,
42+
padding: 32,
43+
background: vars.color.backgroundPrimary,
44+
fontFamily: vars.font.body
45+
};
46+
47+
const variantGridStyle: React.CSSProperties = {
48+
display: 'grid',
49+
gridTemplateColumns: 'repeat(auto-fit, minmax(240px, 393px))',
50+
gap: 16
51+
};
52+
53+
const labelStyle: React.CSSProperties = {
54+
margin: 0,
55+
color: vars.color.textTertiary,
56+
...typography.captionB
57+
};
58+
59+
export const HeartOff: Story = {
60+
render: ({ heart, imageSrc }) => (
61+
<div className={themeClass} style={storySurfaceStyle}>
62+
<FeedImage heart={heart} imageSrc={imageSrc} />
63+
</div>
64+
)
65+
};
66+
67+
export const HeartOn: Story = {
68+
args: {
69+
heart: true
70+
},
71+
render: ({ heart, imageSrc }) => (
72+
<div className={themeClass} style={storySurfaceStyle}>
73+
<FeedImage heart={heart} imageSrc={imageSrc} />
74+
</div>
75+
)
76+
};
77+
78+
export const Variants: Story = {
79+
render: () => (
80+
<div className={themeClass} style={variantsSurfaceStyle}>
81+
<div style={variantGridStyle}>
82+
<div style={{ display: 'grid', gap: 8 }}>
83+
<h3 style={labelStyle}>heart off</h3>
84+
<FeedImage />
85+
</div>
86+
<div style={{ display: 'grid', gap: 8 }}>
87+
<h3 style={labelStyle}>heart on</h3>
88+
<FeedImage heart />
89+
</div>
90+
</div>
91+
</div>
92+
)
93+
};
Lines changed: 15 additions & 0 deletions
Loading
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { style } from '@vanilla-extract/css';
2+
import { vars } from '../theme.css';
3+
4+
export const feedImage = style({
5+
position: 'relative',
6+
width: 393,
7+
maxWidth: '100%',
8+
aspectRatio: '393 / 499',
9+
overflow: 'hidden',
10+
background: vars.color.backgroundFill
11+
});
12+
13+
export const image = style({
14+
position: 'absolute',
15+
inset: 0,
16+
width: '100%',
17+
height: '100%',
18+
display: 'block',
19+
objectFit: 'cover'
20+
});
21+
22+
export const heartIcon = style({
23+
position: 'absolute',
24+
right: 24,
25+
bottom: 24,
26+
width: 32,
27+
height: 32,
28+
display: 'grid',
29+
placeItems: 'center',
30+
color: vars.color.iconPrimary
31+
});
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import type { ComponentPropsWithoutRef } from 'react';
2+
import { designAssets } from '../assets';
3+
import { Icon } from '../Icon';
4+
import { feedImage, heartIcon, image } from './FeedImage.css';
5+
6+
export type FeedImageProps = Omit<ComponentPropsWithoutRef<'div'>, 'children'> & {
7+
imageSrc?: string;
8+
imageAlt?: string;
9+
heart?: boolean;
10+
};
11+
12+
export function FeedImage({
13+
imageSrc = designAssets.feed.image.src,
14+
imageAlt = '',
15+
heart = false,
16+
className,
17+
...divProps
18+
}: FeedImageProps) {
19+
const rootClassName = [feedImage, className].filter(Boolean).join(' ');
20+
21+
return (
22+
<div className={rootClassName} {...divProps}>
23+
<img alt={imageAlt} className={image} src={imageSrc} />
24+
<span aria-hidden="true" className={heartIcon}>
25+
<Icon height={32} name="heart" variant={heart ? 'on' : 'off'} width={32} />
26+
</span>
27+
</div>
28+
);
29+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export type { FeedImageProps } from './FeedImage';
2+
export { FeedImage } from './FeedImage';

packages/design-system/src/assets.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,15 @@ export type DesignAsset = {
77
};
88

99
export const designAssets = {
10+
feed: {
11+
image: {
12+
src: new URL('../assets/feed/feed-image.svg', import.meta.url).href,
13+
width: 393,
14+
height: 499,
15+
figmaNodeId: '1:13781',
16+
description: 'Feed image'
17+
}
18+
},
1019
logos: {
1120
symbolDefault: {
1221
src: new URL('../assets/logos/symbol-default.png', import.meta.url).href,

packages/design-system/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ export type { CtaButtonProps, CtaButtonState } from './CtaButton';
1010
export { CtaButton } from './CtaButton';
1111
export type { ActionButtonVariants } from './components.css';
1212
export { actionButton, description, eyebrow, panel, screen, title } from './components.css';
13+
export type { FeedImageProps } from './FeedImage';
14+
export { FeedImage } from './FeedImage';
1315
export type { IconName, IconProps, IconVariant } from './Icon';
1416
export { Icon } from './Icon';
1517
export type { ImageUploadProps, ImageUploadState } from './ImageUpload';

0 commit comments

Comments
 (0)