Skip to content

Commit 8a38ca4

Browse files
authoredDec 12, 2024··
Add ProductCard (#1870)
1 parent daed30b commit 8a38ca4

File tree

10 files changed

+148
-90
lines changed

10 files changed

+148
-90
lines changed
 

‎.changeset/short-donkeys-raise.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@theguild/components': minor
3+
---
4+
5+
Add ProductCard

‎packages/components/src/components/explore-main-product-cards.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ import { cn } from '../cn';
33
import { FOUR_MAIN_PRODUCTS } from '../products';
44
import { Heading } from './heading';
55
import { ArrowIcon } from './icons';
6+
import { MainProductCard } from './product-card';
67
import { TextLink } from './text-link';
7-
import { MainProductCard } from './tools-and-libraries-cards';
88

99
export type ExploreMainProductCardsProps = HTMLAttributes<HTMLDivElement>;
1010

‎packages/components/src/components/index.ts

+2-5
Original file line numberDiff line numberDiff line change
@@ -26,15 +26,12 @@ export {
2626
type GraphQLConfCardProps,
2727
} from './hive-navigation';
2828
export { HiveFooter } from './hive-footer';
29-
export {
30-
ToolsAndLibrariesCards,
31-
MainProductCard,
32-
AncillaryProductCard,
33-
} from './tools-and-libraries-cards';
29+
export { ToolsAndLibrariesCards } from './tools-and-libraries-cards';
3430
export * from './decorations';
3531
export * from './call-to-action';
3632
export * from './cookies-consent';
3733
export * from './heading';
3834
export * from './info-card';
3935
export * from './stud';
4036
export * from './explore-main-product-cards';
37+
export * from './product-card';

‎packages/components/src/components/tools-and-libraries-cards/hive-decoration.svg ‎packages/components/src/components/product-card/hive-decoration.svg

+1-1
Loading

‎packages/components/src/components/tools-and-libraries-cards/hive-gateway-decoration.svg ‎packages/components/src/components/product-card/hive-gateway-decoration.svg

+1-1
Loading
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import { cn } from '../../cn';
2+
import { FOUR_MAIN_PRODUCTS, ProductInfo, PRODUCTS } from '../../products';
3+
import { HighlightDecoration } from '../decorations';
4+
import { ArrowIcon } from '../icons';
5+
import { ReactComponent as HiveDecoration } from './hive-decoration.svg';
6+
import { ReactComponent as HiveGatewayDecoration } from './hive-gateway-decoration.svg';
7+
import { ReactComponent as MeshDecoration } from './mesh-decoration.svg';
8+
import { ReactComponent as YogaDecoration } from './yoga-decoration.svg';
9+
10+
const cardDecorations = {
11+
[PRODUCTS.HIVE.name]: HiveDecoration,
12+
[PRODUCTS.YOGA.name]: YogaDecoration,
13+
[PRODUCTS.MESH.name]: MeshDecoration,
14+
[PRODUCTS.HIVE_GATEWAY.name]: HiveGatewayDecoration,
15+
};
16+
17+
export function MainProductCard({ as: Root, product, className, ...rest }: ProductCardProps) {
18+
const Decoration = cardDecorations[product.name];
19+
const Icon = product.logo;
20+
21+
const isHive = product.name === PRODUCTS.HIVE.name;
22+
23+
return (
24+
<Root
25+
className={cn(
26+
'hive-focus-within group relative flex-1 shrink-0 basis-[283.5px] overflow-hidden rounded-2xl bg-blue-400 text-green-1000 max-md:w-[283.5px]',
27+
isHive && 'bg-green-1000 text-white',
28+
className,
29+
)}
30+
{...rest}
31+
>
32+
<a
33+
className="relative z-10 flex h-full flex-1 flex-col justify-between p-8 outline-none focus-visible:outline-none"
34+
href={product.href}
35+
>
36+
<p className="font-medium">{product.name}</p>
37+
<Icon className="mt-8" />
38+
<ArrowIcon className="absolute bottom-8 right-8" />
39+
</a>
40+
<Decoration
41+
strokeWidth="0.5px"
42+
className={cn(
43+
'pointer-events-none absolute bottom-0 right-0 h-full fill-blue-200 opacity-0 transition-opacity duration-500 group-focus-within:opacity-100 group-hover:opacity-100',
44+
isHive && 'fill-blue-700',
45+
)}
46+
preserveAspectRatio="xMidYMid meet"
47+
/>
48+
<HighlightDecoration className="pointer-events-none absolute left-0 top-[-15%] h-[150%] w-full opacity-0 transition-opacity duration-1000 group-focus-within:opacity-100 group-hover:opacity-100" />
49+
</Root>
50+
);
51+
}
52+
53+
export function AncillaryProductCard({ product, as: Root, className, ...rest }: ProductCardProps) {
54+
const Logo = product.logo;
55+
return (
56+
<Root
57+
className={cn(
58+
'hive-focus-within shrink-0 basis-[283.5px] rounded-2xl bg-beige-200 text-green-1000 transition-colors duration-500 hover:bg-beige-400 max-sm:min-w-[283.5px]',
59+
className,
60+
)}
61+
{...rest}
62+
>
63+
<a
64+
href={product.href}
65+
className="relative flex h-full flex-col justify-between rounded-[inherit] p-8 focus:outline-none focus-visible:outline-none"
66+
>
67+
<div>
68+
<p className="font-medium">{product.name}</p>
69+
<p className="mt-2 text-sm text-green-800">{product.title}</p>
70+
</div>
71+
<div
72+
role="presentation"
73+
className="mt-8 flex size-8 items-center justify-center rounded bg-green-1000 p-[5px] text-sm/5 font-medium text-white"
74+
>
75+
<Logo />
76+
</div>
77+
<ArrowIcon className="absolute bottom-8 right-8" />
78+
</a>
79+
</Root>
80+
);
81+
}
82+
83+
export interface ProductCardProps extends React.HTMLAttributes<HTMLElement> {
84+
as: 'div' | 'li';
85+
product: ProductInfo;
86+
}
87+
88+
export function ProductCard(props: ProductCardProps) {
89+
const isMainProduct = FOUR_MAIN_PRODUCTS.some(p => p.name === props.product.name);
90+
91+
return isMainProduct ? <MainProductCard {...props} /> : <AncillaryProductCard {...props} />;
92+
}

‎packages/components/src/components/tools-and-libraries-cards/mesh-decoration.svg ‎packages/components/src/components/product-card/mesh-decoration.svg

+1-1
Loading
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { Meta, StoryObj } from '@storybook/react';
2+
import { hiveThemeDecorator } from '../../../../../.storybook/hive-theme-decorator';
3+
import { PRODUCTS } from '../../products';
4+
import { ProductCard } from './index';
5+
6+
export default {
7+
title: 'Hive/ProductCard',
8+
component: ProductCard,
9+
decorators: [hiveThemeDecorator],
10+
parameters: {
11+
forcedLightMode: true,
12+
},
13+
} satisfies Meta;
14+
15+
// interweaved to make sure main product cards look good alongside ancillary product cards
16+
const productsLexicographically = Object.values(PRODUCTS).sort((a, b) =>
17+
a.name.localeCompare(b.name),
18+
);
19+
20+
export const Default: StoryObj<typeof ProductCard> = {
21+
name: 'ProductCard',
22+
render() {
23+
return (
24+
<ul className="mt-5 grid grid-cols-4 gap-5 overflow-x-auto p-4 last-of-type:mb-24">
25+
{productsLexicographically.map(product => (
26+
<ProductCard as="li" key={product.name} product={product} />
27+
))}
28+
</ul>
29+
);
30+
},
31+
};

‎packages/components/src/components/tools-and-libraries-cards/yoga-decoration.svg ‎packages/components/src/components/product-card/yoga-decoration.svg

+1-1
Loading
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,16 @@
11
import { cn } from '../../cn';
2-
import {
3-
FOUR_MAIN_PRODUCTS,
4-
ProductInfo,
5-
PRODUCTS,
6-
SIX_HIGHLIGHTED_PRODUCTS,
7-
} from '../../products';
2+
import { FOUR_MAIN_PRODUCTS, SIX_HIGHLIGHTED_PRODUCTS } from '../../products';
83
import { CallToAction } from '../call-to-action';
9-
import { HighlightDecoration } from '../decorations';
104
import { Heading } from '../heading';
11-
import { ArrowIcon } from '../icons';
12-
import { ReactComponent as HiveDecoration } from './hive-decoration.svg';
13-
import { ReactComponent as HiveGatewayDecoration } from './hive-gateway-decoration.svg';
14-
import { ReactComponent as MeshDecoration } from './mesh-decoration.svg';
15-
import { ReactComponent as YogaDecoration } from './yoga-decoration.svg';
5+
import { AncillaryProductCard, MainProductCard } from '../product-card';
166

17-
const cardDecorations = {
18-
[PRODUCTS.HIVE.name]: HiveDecoration,
19-
[PRODUCTS.YOGA.name]: YogaDecoration,
20-
[PRODUCTS.MESH.name]: MeshDecoration,
21-
[PRODUCTS.HIVE_GATEWAY.name]: HiveGatewayDecoration,
22-
};
23-
24-
export function ToolsAndLibrariesCards({ className }: { className?: string }) {
7+
export function ToolsAndLibrariesCards({
8+
className,
9+
isHive,
10+
}: {
11+
className?: string;
12+
isHive?: boolean;
13+
}) {
2514
return (
2615
<section
2716
className={cn(
@@ -44,68 +33,12 @@ export function ToolsAndLibrariesCards({ className }: { className?: string }) {
4433
<AncillaryProductCard key={product.name} as="li" product={product} />
4534
))}
4635
</ul>
47-
<CallToAction href="https://github.com/the-guild-org" variant="primary">
36+
<CallToAction
37+
href={isHive ? '/ecosystem' : 'https://the-guild.dev/graphql/hive/ecosystem'}
38+
variant="primary"
39+
>
4840
Explore the Ecosystem
4941
</CallToAction>
5042
</section>
5143
);
5244
}
53-
54-
export function MainProductCard({ as: Root, product }: { as: 'div' | 'li'; product: ProductInfo }) {
55-
const Decoration = cardDecorations[product.name];
56-
const Icon = product.logo;
57-
58-
return (
59-
<Root
60-
key={product.name}
61-
className="hive-focus-within group relative flex-1 shrink-0 basis-[283.5px] overflow-hidden rounded-2xl bg-blue-400 text-green-1000 first-of-type:bg-green-1000 first-of-type:text-white max-md:w-[283.5px]"
62-
>
63-
<a
64-
className="relative z-10 block flex-1 p-8 outline-none focus-visible:outline-none"
65-
href={product.href}
66-
>
67-
<p className="font-medium">{product.name}</p>
68-
<Icon className="mt-8" />
69-
<ArrowIcon className="absolute bottom-8 right-8" />
70-
</a>
71-
<Decoration
72-
strokeWidth="0.5px"
73-
className="pointer-events-none absolute bottom-0 right-0 fill-blue-200 opacity-0 transition-opacity duration-500 group-first-of-type:fill-blue-700 group-focus-within:opacity-100 group-hover:opacity-100"
74-
preserveAspectRatio="xMidYMid meet"
75-
/>
76-
<HighlightDecoration className="pointer-events-none absolute left-0 top-[-15%] h-[150%] w-full opacity-0 transition-opacity duration-1000 group-focus-within:opacity-100 group-hover:opacity-100" />
77-
</Root>
78-
);
79-
}
80-
81-
export function AncillaryProductCard({
82-
product,
83-
as: Root,
84-
}: {
85-
product: ProductInfo;
86-
as: 'div' | 'li';
87-
}) {
88-
const Logo = product.logo;
89-
return (
90-
<Root
91-
key={product.name}
92-
className="hive-focus-within shrink-0 basis-[283.5px] rounded-2xl bg-beige-200 text-green-1000 transition-colors duration-500 hover:bg-beige-400 max-sm:min-w-[283.5px]"
93-
>
94-
<a
95-
href={product.href}
96-
className="relative flex h-full flex-col rounded-[inherit] p-8 focus:outline-none focus-visible:outline-none"
97-
>
98-
<p className="font-medium">{product.name}</p>
99-
<p className="mt-2 text-sm text-green-800">{product.title}</p>
100-
<div className="h-8 grow" />
101-
<div
102-
role="presentation"
103-
className="flex size-8 items-center justify-center rounded bg-green-1000 p-[5px] text-sm/5 font-medium text-white"
104-
>
105-
<Logo />
106-
</div>
107-
<ArrowIcon className="absolute bottom-8 right-8" />
108-
</a>
109-
</Root>
110-
);
111-
}

0 commit comments

Comments
 (0)
Please sign in to comment.