Skip to content

Commit ad37780

Browse files
committed
fix: resolve React warning for aspectRatio prop on Card.Image
Fixes #2 ## Changes - Add `aspectRatio` prop to CardImage component with CSS variable pattern - Extract aspectRatio from props to prevent DOM leakage - Apply CSS variable `--card-image-aspect-ratio` matching ImageCard pattern - Add CSS support in CardParts.module.css ## Tests - Add test for aspectRatio prop application - Add test for no CSS variable when prop not provided - All 510 tests passing ## Documentation - Add Storybook section "Card.Image with Aspect Ratio" with 5 visual examples - Add Card.Image props table to Storybook - Update JSDoc for TypeScript IntelliSense - Regenerate markdown docs ## Cleanup - Add /packages/ui/schemas/ to .gitignore - Update README with correct scoped command for schema generation - Clarify schemas are generated on-demand, not committed
1 parent d5df526 commit ad37780

File tree

6 files changed

+154
-11
lines changed

6 files changed

+154
-11
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,9 @@ docs/.docusaurus/
8282
docs/build/
8383
planning/.cache/
8484

85+
# Auto-generated JSON schemas (not actively used, regenerate with 'pnpm generate:schemas')
86+
/packages/ui/schemas/
87+
8588
# ===========================================
8689
# 🧪 Local environment & secrets
8790
# ===========================================

packages/ui/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -243,7 +243,7 @@ const card = renderComponent(config); // Renders <SummaryCard {...props} />
243243

244244
**AI Integration:**
245245
- 🤖 **Context7:** Documentation via "use context7" command
246-
- 📚 **Schemas:** Available in GitHub repo for tooling
246+
- 📚 **Schemas:** Can be generated with `pnpm --filter @ainativekit/ui generate:schemas` for external tooling (not used in runtime)
247247
- 📖 **Guide:** See `/docs` folder for comprehensive documentation
248248

249249
## 🧰 Development
@@ -267,7 +267,7 @@ pnpm lint # lint
267267
268268
Repository (for documentation & tooling):
269269
├── /docs # Markdown documentation (Context7)
270-
├── /schemas # JSON schemas (dev tooling)
270+
├── /schemas # JSON schemas (generated on-demand, not committed)
271271
└── /metadata # Component registry (dev tooling)
272272
```
273273

packages/ui/src/components/Card/Card.stories.tsx

Lines changed: 101 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,87 @@ const CardsComponent: React.FC = () => {
129129
</div>
130130
</section>
131131

132+
{/* Card.Image with Aspect Ratio */}
133+
<section style={{ marginBottom: '64px' }}>
134+
<header style={{ marginBottom: '24px', alignItems: 'start' }}>
135+
<h2 style={{ marginBottom: '8px' }}>Card.Image with Aspect Ratio</h2>
136+
<p style={{ color: 'var(--ai-color-text-secondary)', margin: 0, fontSize: '14px' }}>
137+
Control image dimensions with the aspectRatio prop for consistent layouts
138+
</p>
139+
</header>
140+
141+
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(280px, 1fr))', gap: '24px', alignItems: 'start' }}>
142+
<Card>
143+
<Card.Image
144+
src="https://images.unsplash.com/photo-1682687220742-aba13b6e50ba?w=400&h=300&fit=crop"
145+
alt="Square aspect ratio"
146+
aspectRatio="1 / 1"
147+
/>
148+
<Card.Body>
149+
<Card.Title as="h4">Square (1:1)</Card.Title>
150+
<Card.Description>
151+
Perfect for avatars, product thumbnails, or Instagram-style images
152+
</Card.Description>
153+
</Card.Body>
154+
</Card>
155+
156+
<Card>
157+
<Card.Image
158+
src="https://images.unsplash.com/photo-1682687220742-aba13b6e50ba?w=400&h=300&fit=crop"
159+
alt="Widescreen aspect ratio"
160+
aspectRatio="16 / 9"
161+
/>
162+
<Card.Body>
163+
<Card.Title as="h4">Widescreen (16:9)</Card.Title>
164+
<Card.Description>
165+
Ideal for video thumbnails and landscape photography
166+
</Card.Description>
167+
</Card.Body>
168+
</Card>
169+
170+
<Card>
171+
<Card.Image
172+
src="https://images.unsplash.com/photo-1682687220742-aba13b6e50ba?w=400&h=300&fit=crop"
173+
alt="Classic photo aspect ratio"
174+
aspectRatio="3 / 2"
175+
/>
176+
<Card.Body>
177+
<Card.Title as="h4">Classic (3:2)</Card.Title>
178+
<Card.Description>
179+
Traditional photography ratio for balanced composition
180+
</Card.Description>
181+
</Card.Body>
182+
</Card>
183+
184+
<Card>
185+
<Card.Image
186+
src="https://images.unsplash.com/photo-1682687220742-aba13b6e50ba?w=400&h=300&fit=crop"
187+
alt="Portrait aspect ratio"
188+
aspectRatio="2 / 3"
189+
/>
190+
<Card.Body>
191+
<Card.Title as="h4">Portrait (2:3)</Card.Title>
192+
<Card.Description>
193+
Vertical orientation for portraits and mobile-first content
194+
</Card.Description>
195+
</Card.Body>
196+
</Card>
197+
198+
<Card>
199+
<Card.Image
200+
src="https://images.unsplash.com/photo-1682687220742-aba13b6e50ba?w=400&h=300&fit=crop"
201+
alt="No aspect ratio (natural)"
202+
/>
203+
<Card.Body>
204+
<Card.Title as="h4">Natural (No Ratio)</Card.Title>
205+
<Card.Description>
206+
Image uses its natural dimensions when aspectRatio is not specified
207+
</Card.Description>
208+
</Card.Body>
209+
</Card>
210+
</div>
211+
</section>
212+
132213
{/* Loading States */}
133214
<section style={{ marginBottom: '64px' }}>
134215
<header style={{ marginBottom: '24px', alignItems: 'start' }}>
@@ -846,9 +927,28 @@ const CardsComponent: React.FC = () => {
846927
]}
847928
/>
848929

930+
<h3 style={{ fontSize: '18px', marginBottom: '12px', marginTop: '32px' }}>Card.Image</h3>
931+
<PropsTable
932+
hideThemeColumn
933+
rows={[
934+
{
935+
name: 'src',
936+
description: 'Image source URL (required)',
937+
},
938+
{
939+
name: 'alt',
940+
description: 'Alt text for accessibility (required)',
941+
},
942+
{
943+
name: 'aspectRatio',
944+
description: 'Optional aspect ratio for the image (e.g., "16/9", "3/2", "1/1"). Controls the display ratio using CSS aspect-ratio.',
945+
},
946+
]}
947+
/>
948+
849949
<h3 style={{ fontSize: '18px', marginBottom: '12px', marginTop: '32px' }}>Other Compound Components</h3>
850950
<p style={{ color: 'var(--ai-color-text-secondary)', fontSize: '14px' }}>
851-
Card.Header, Card.Body, Card.Footer, Card.Description, Card.Meta, Card.ChipGroup, Card.Image accept standard HTML props for their respective elements.
951+
Card.Header, Card.Body, Card.Footer, Card.Description, Card.Meta, Card.ChipGroup accept standard HTML props for their respective elements.
852952
Card.Chip inherits all <a href="?path=/story/primitive-components-chips--chips" style={{ color: 'var(--ai-color-accent-blue)', textDecoration: 'none' }}>Chip props</a>.
853953
</p>
854954
</section>

packages/ui/src/components/Card/CardCompound.test.tsx

Lines changed: 38 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -118,14 +118,14 @@ describe('Card Compound Components', () => {
118118
it('should render image with src and alt', () => {
119119
render(
120120
<Card>
121-
<Card.Image
122-
src="https://example.com/image.jpg"
121+
<Card.Image
122+
src="https://example.com/image.jpg"
123123
alt="Test image"
124124
data-testid="card-image"
125125
/>
126126
</Card>
127127
);
128-
128+
129129
const image = screen.getByTestId('card-image') as HTMLImageElement;
130130
expect(image).toBeInTheDocument();
131131
expect(image.src).toBe('https://example.com/image.jpg');
@@ -135,18 +135,49 @@ describe('Card Compound Components', () => {
135135
it('should apply custom className', () => {
136136
render(
137137
<Card>
138-
<Card.Image
139-
src="https://example.com/image.jpg"
138+
<Card.Image
139+
src="https://example.com/image.jpg"
140140
alt="Test"
141141
className="custom-class"
142142
data-testid="card-image"
143143
/>
144144
</Card>
145145
);
146-
146+
147147
expect(screen.getByTestId('card-image')).toHaveClass('custom-class');
148148
});
149149

150+
it('should apply aspectRatio when provided', () => {
151+
render(
152+
<Card>
153+
<Card.Image
154+
src="https://example.com/image.jpg"
155+
alt="Test"
156+
aspectRatio="16 / 9"
157+
data-testid="card-image"
158+
/>
159+
</Card>
160+
);
161+
162+
const image = screen.getByTestId('card-image') as HTMLImageElement;
163+
expect(image.style.getPropertyValue('--card-image-aspect-ratio')).toBe('16 / 9');
164+
});
165+
166+
it('should not apply aspectRatio CSS variable when not provided', () => {
167+
render(
168+
<Card>
169+
<Card.Image
170+
src="https://example.com/image.jpg"
171+
alt="Test"
172+
data-testid="card-image"
173+
/>
174+
</Card>
175+
);
176+
177+
const image = screen.getByTestId('card-image') as HTMLImageElement;
178+
expect(image.style.getPropertyValue('--card-image-aspect-ratio')).toBe('');
179+
});
180+
150181
it('should forward ref', () => {
151182
const ref = { current: null as HTMLImageElement | null };
152183
render(
@@ -158,7 +189,7 @@ describe('Card Compound Components', () => {
158189
/>
159190
</Card>
160191
);
161-
192+
162193
expect(ref.current).toBeInstanceOf(HTMLImageElement);
163194
});
164195
});

packages/ui/src/components/Card/CardImage.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ export interface CardImageProps extends ComponentPropsWithoutRef<'img'> {
1212
* Alt text for accessibility
1313
*/
1414
alt: string;
15+
/**
16+
* Optional aspect ratio for the image (e.g., "16/9", "3/2", "1/1")
17+
*/
18+
aspectRatio?: string;
1519
/**
1620
* Optional test ID for testing purposes
1721
*/
@@ -20,7 +24,7 @@ export interface CardImageProps extends ComponentPropsWithoutRef<'img'> {
2024

2125
export const CardImage = React.forwardRef<HTMLImageElement, CardImageProps>(
2226
(props, ref) => {
23-
const { className, src, alt, 'data-testid': testId, ...rest } = props;
27+
const { className, src, alt, aspectRatio, style, 'data-testid': testId, ...rest } = props;
2428

2529
return (
2630
<img
@@ -29,6 +33,10 @@ export const CardImage = React.forwardRef<HTMLImageElement, CardImageProps>(
2933
alt={alt}
3034
className={cn(styles.cardImage, className)}
3135
data-testid={testId}
36+
style={{
37+
...(aspectRatio ? { '--card-image-aspect-ratio': aspectRatio } as React.CSSProperties : {}),
38+
...style,
39+
}}
3240
{...rest}
3341
/>
3442
);

packages/ui/src/components/Card/CardParts.module.css

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
display: block;
2929
border-radius: var(--ai-radius-base); /* 12px - matches our card system */
3030
object-fit: cover;
31+
aspect-ratio: var(--card-image-aspect-ratio); /* Applied when aspectRatio prop is provided */
3132
}
3233

3334
/* Card Actions */

0 commit comments

Comments
 (0)