diff --git a/src/components/Card/Card.spec.tsx b/src/components/Card/Card.spec.tsx
index 58e4b502c0..40d792d6d8 100644
--- a/src/components/Card/Card.spec.tsx
+++ b/src/components/Card/Card.spec.tsx
@@ -1,9 +1,45 @@
import { render, screen } from '@testing-library/react';
-import { describe, expect, it } from 'vitest';
+import { describe, expect, it, vi } from 'vitest';
import { Flowbite } from '~/src';
import { Card } from './Card';
describe('Components / Card', () => {
+ describe('Functionality', () => {
+ it('should render an image when `imgSrc` is provided', () => {
+ render();
+ expect(screen.queryAllByTestId('flowbite-card-image')).toHaveLength(1);
+ expect(screen.queryByTestId('flowbite-card-image')).toHaveAttribute(
+ 'src',
+ 'https://flowbite.com/docs/images/blog/image-1.jpg',
+ );
+ });
+ it('should not render an `` given an undefined `imgSrc`', () => {
+ render();
+ expect(screen.queryAllByTestId('flowbite-card-image')).toHaveLength(0);
+ });
+
+ it('should render the image from the `renderImage` prop', () => {
+ render( } />);
+
+ expect(screen.queryAllByTestId('dummy-div')).toHaveLength(1);
+ });
+ it('should use the `renderImage` prop even if the user provides an `imgSrc`', () => {
+ render(
+ /* @ts-expect-error should be illegal to use `renderImage` and `imgSrc` at the same time */
+ }
+ imgSrc="https://flowbite.com/docs/images/blog/image-1.jpg"
+ />,
+ );
+ expect(screen.queryAllByTestId('dummy-div2')).toHaveLength(1);
+ expect(screen.queryAllByTestId('flowbite-card-image')).toHaveLength(0);
+ });
+ it('should provide the theme and horizontal flag to the `renderImage` function', () => {
+ const spy = vi.fn(() => );
+ render();
+ expect(spy).toHaveBeenCalledWith(expect.any(Object), false);
+ });
+ });
describe('A11y', () => {
it('should allow `aria-label`', () => {
render();
diff --git a/src/components/Card/Card.stories.tsx b/src/components/Card/Card.stories.tsx
index 1dc097c133..d5a377bf6f 100644
--- a/src/components/Card/Card.stories.tsx
+++ b/src/components/Card/Card.stories.tsx
@@ -1,17 +1,12 @@
import type { Meta, Story } from '@storybook/react';
+import Image from 'next/image';
import type { CardProps } from './Card';
import { Card } from './Card';
export default {
title: 'Components/Card',
component: Card,
- decorators: [
- (Story): JSX.Element => (
-
-
-
- ),
- ],
+ decorators: [(Story): JSX.Element => {Story()}
],
} as Meta;
const Template: Story = (args) => (
@@ -45,3 +40,18 @@ WithDecorativeImage.storyName = 'With decorative image';
WithDecorativeImage.args = {
imgSrc: 'https://flowbite.com/docs/images/blog/image-1.jpg',
};
+
+export const WithNextImage = Template.bind({});
+WithNextImage.storyName = 'With Next.js Image component';
+WithNextImage.args = {
+ renderImage: () => (
+ src}
+ width={1200}
+ height={800}
+ src={'https://flowbite.com/docs/images/blog/image-1.jpg'}
+ />
+ ),
+};
diff --git a/src/components/Card/Card.tsx b/src/components/Card/Card.tsx
index 5270f19eb4..9332df9fad 100644
--- a/src/components/Card/Card.tsx
+++ b/src/components/Card/Card.tsx
@@ -3,6 +3,7 @@ import type { ComponentProps, FC, PropsWithChildren } from 'react';
import type { DeepPartial, FlowbiteBoolean } from '~/src';
import { useTheme } from '~/src';
import { mergeDeep } from '~/src/helpers/merge-deep';
+import { omit } from '~/src/helpers/omit';
export interface FlowbiteCardTheme {
root: FlowbiteCardRootTheme;
@@ -21,26 +22,32 @@ export interface FlowbiteCardImageTheme {
horizontal: FlowbiteBoolean;
}
-export interface CardProps extends PropsWithChildren> {
+interface CommonCardProps extends PropsWithChildren> {
horizontal?: boolean;
href?: string;
- imgAlt?: string;
- imgSrc?: string;
+ /** Overwrites the theme. Will be merged with the context theme.
+ * @default {}
+ */
theme?: DeepPartial;
}
-export const Card: FC = ({
- children,
- className,
- horizontal,
- href,
- imgAlt,
- imgSrc,
- theme: customTheme = {},
- ...props
-}) => {
+export type CardProps =
+ | (
+ | { imgAlt?: string; imgSrc?: string; renderImage?: never }
+ | {
+ /** Allows to provide a custom render function for the image component. Useful in Next.JS and Gatsby. **Setting this will disable `imgSrc` and `imgAlt`**.
+ */
+ renderImage?: (theme: DeepPartial, horizontal: boolean) => JSX.Element;
+ imgAlt?: never;
+ imgSrc?: never;
+ }
+ ) &
+ CommonCardProps;
+
+export const Card: FC = (props) => {
+ const { children, className, horizontal, href, theme: customTheme = {} } = props;
const Component = typeof href === 'undefined' ? 'div' : 'a';
- const theirProps = props as object;
+ const theirProps = removeCustomProps(props);
const theme = mergeDeep(useTheme().theme.card, customTheme);
@@ -56,16 +63,37 @@ export const Card: FC = ({
)}
{...theirProps}
>
- {imgSrc && (
-
- )}
+
{children}
);
};
-Card.displayName = 'Card';
+const Image: FC = ({ theme: customTheme = {}, ...props }) => {
+ const theme = mergeDeep(useTheme().theme.card, customTheme);
+ if (props.renderImage) {
+ return props.renderImage(theme, props.horizontal ?? false);
+ }
+ if (props.imgSrc) {
+ return (
+
+ );
+ }
+ return null;
+};
+
+const removeCustomProps = omit([
+ 'renderImage',
+ 'imgSrc',
+ 'imgAlt',
+ 'children',
+ 'className',
+ 'horizontal',
+ 'href',
+ 'theme',
+]);
diff --git a/src/helpers/omit.spec.ts b/src/helpers/omit.spec.ts
new file mode 100644
index 0000000000..c31f3108ae
--- /dev/null
+++ b/src/helpers/omit.spec.ts
@@ -0,0 +1,8 @@
+import { describe, expect, it } from 'vitest';
+import { omit } from './omit';
+
+describe('omit', () => {
+ it('should omit keys from object', () => {
+ expect(omit(['a', 'b'])({ a: 'a', b: 'b', c: 'c' })).toEqual({ c: 'c' });
+ });
+});
diff --git a/src/helpers/omit.ts b/src/helpers/omit.ts
new file mode 100644
index 0000000000..04b43f466d
--- /dev/null
+++ b/src/helpers/omit.ts
@@ -0,0 +1,14 @@
+export const omit =
+ (keys: readonly K[]) =>
+ (obj: T): Omit => {
+ const result = {} as Omit;
+ Object.keys(obj).forEach((key) => {
+ //@ts-expect-error - Somehow TS does not like this.
+ if (keys.includes(key)) {
+ return;
+ }
+ //@ts-expect-error - Somehow TS does not like this.
+ result[key] = obj[key];
+ });
+ return result;
+ };
diff --git a/yarn.lock b/yarn.lock
index 14b69a7fbf..474586bc19 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -12493,11 +12493,16 @@ tslib@^1.8.1:
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
-tslib@^2.0.0, tslib@^2.0.1, tslib@^2.0.3, tslib@^2.1.0, tslib@^2.4.0, tslib@^2.5.0:
+tslib@^2.0.0, tslib@^2.0.1, tslib@^2.0.3, tslib@^2.1.0, tslib@^2.5.0:
version "2.5.2"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.5.2.tgz#1b6f07185c881557b0ffa84b111a0106989e8338"
integrity sha512-5svOrSA2w3iGFDs1HibEVBGbDrAY82bFQ3HZ3ixB+88nsbsWQoKqDRb5UBYAUPEzbBn6dAp5gRNXglySbx1MlA==
+tslib@^2.4.0:
+ version "2.5.0"
+ resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.5.0.tgz#42bfed86f5787aeb41d031866c8f402429e0fddf"
+ integrity sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==
+
tsutils@^3.21.0:
version "3.21.0"
resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623"