Skip to content

Commit 4299564

Browse files
dkrai0412-aryan
andauthored
feat: added the height and minHeight prop to the card component (#518)
* feat: added the height and minHeight prop to the card component * fix: fixed duplicate menu items occurance * fix: added maxHeight instead of height prop in card --------- Co-authored-by: Aryan <aryan.pidiha@juspay.in>
1 parent 060e6fd commit 4299564

File tree

4 files changed

+206
-76
lines changed

4 files changed

+206
-76
lines changed

apps/site/src/demos/CardDemo.tsx

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1086,6 +1086,172 @@ const CardDemo = () => {
10861086
</div>
10871087
</div>
10881088

1089+
{/* Height Control Examples */}
1090+
<div>
1091+
<h2
1092+
style={{
1093+
fontSize: '20px',
1094+
fontWeight: '600',
1095+
marginBottom: '16px',
1096+
}}
1097+
>
1098+
Height Control Examples
1099+
</h2>
1100+
<p
1101+
style={{
1102+
color: '#666',
1103+
fontSize: '14px',
1104+
marginBottom: '20px',
1105+
}}
1106+
>
1107+
Cards support <strong>height</strong> and{' '}
1108+
<strong>minHeight</strong> props for flexible sizing:
1109+
<br />• Accepts any CSS height value: pixels, percentages,
1110+
viewport units
1111+
<br /><strong>Note:</strong> For percentage heights,
1112+
parent container must have a defined height
1113+
</p>
1114+
1115+
<div
1116+
style={{
1117+
display: 'grid',
1118+
gridTemplateColumns:
1119+
'repeat(auto-fit, minmax(320px, 1fr))',
1120+
gap: '20px',
1121+
marginBottom: '32px',
1122+
}}
1123+
>
1124+
{/* Fixed Height Card */}
1125+
<Card
1126+
maxHeight="250px"
1127+
headerTitle="Fixed Height"
1128+
headerTag={
1129+
<Tag
1130+
text="250px"
1131+
variant={TagVariant.SUBTLE}
1132+
color={TagColor.PRIMARY}
1133+
size={TagSize.SM}
1134+
/>
1135+
}
1136+
subHeader="height='250px'"
1137+
bodyTitle="Fixed Dimensions"
1138+
content="This card has a fixed height of 250px, useful for grid layouts where uniform card heights are needed."
1139+
actionButton={{
1140+
text: 'Learn More',
1141+
buttonType: ButtonType.SECONDARY,
1142+
subType: ButtonSubType.INLINE,
1143+
size: ButtonSize.SMALL,
1144+
}}
1145+
/>
1146+
1147+
{/* Min Height Card */}
1148+
<Card
1149+
minHeight="250px"
1150+
headerTitle="Minimum Height"
1151+
headerTag={
1152+
<Tag
1153+
text="minHeight: 250px"
1154+
variant={TagVariant.SUBTLE}
1155+
color={TagColor.SUCCESS}
1156+
size={TagSize.SM}
1157+
/>
1158+
}
1159+
subHeader="minHeight='250px'"
1160+
bodyTitle="Flexible Growth"
1161+
content="This card has a minimum height of 250px but can grow with content. Perfect when you want a baseline height but need flexibility."
1162+
bodySlot2={
1163+
<div
1164+
style={{
1165+
display: 'flex',
1166+
gap: '8px',
1167+
flexWrap: 'wrap',
1168+
}}
1169+
>
1170+
<Tag
1171+
text="Flexible"
1172+
variant={TagVariant.SUBTLE}
1173+
color={TagColor.PRIMARY}
1174+
size={TagSize.SM}
1175+
/>
1176+
<Tag
1177+
text="Grows with content"
1178+
variant={TagVariant.SUBTLE}
1179+
color={TagColor.SUCCESS}
1180+
size={TagSize.SM}
1181+
/>
1182+
</div>
1183+
}
1184+
actionButton={{
1185+
text: 'Explore',
1186+
buttonType: ButtonType.PRIMARY,
1187+
size: ButtonSize.SMALL,
1188+
}}
1189+
/>
1190+
1191+
{/* Percentage Height Card */}
1192+
<div style={{ height: '300px' }}>
1193+
<Card
1194+
minHeight="100%"
1195+
variant={CardVariant.ALIGNED}
1196+
alignment={CardAlignment.VERTICAL}
1197+
centerAlign={true}
1198+
cardSlot={
1199+
<div
1200+
style={{
1201+
width: '80px',
1202+
height: '80px',
1203+
borderRadius: '50%',
1204+
background:
1205+
'linear-gradient(135deg, #10b981 0%, #059669 100%)',
1206+
display: 'flex',
1207+
alignItems: 'center',
1208+
justifyContent: 'center',
1209+
color: 'white',
1210+
fontSize: '20px',
1211+
fontWeight: '600',
1212+
}}
1213+
>
1214+
100%
1215+
</div>
1216+
}
1217+
headerTitle="Full Height"
1218+
headerTag={
1219+
<Tag
1220+
text="100%"
1221+
variant={TagVariant.ATTENTIVE}
1222+
color={TagColor.SUCCESS}
1223+
size={TagSize.SM}
1224+
/>
1225+
}
1226+
subHeader="height='100%' (fills 300px parent)"
1227+
content="This card fills 100% of its parent container's height (300px). Perfect for responsive layouts."
1228+
actionButton={{
1229+
text: 'View More',
1230+
buttonType: ButtonType.SECONDARY,
1231+
subType: ButtonSubType.INLINE,
1232+
size: ButtonSize.SMALL,
1233+
}}
1234+
/>
1235+
</div>
1236+
</div>
1237+
1238+
<div
1239+
style={{
1240+
padding: '16px',
1241+
backgroundColor: '#fef3c7',
1242+
borderRadius: '8px',
1243+
border: '1px solid #fbbf24',
1244+
fontSize: '14px',
1245+
color: '#92400e',
1246+
marginBottom: '32px',
1247+
}}
1248+
>
1249+
<strong>Tip:</strong> The percentage height example (100%)
1250+
is wrapped in a div with height: 300px. Without a parent
1251+
height, percentage heights will collapse to auto.
1252+
</div>
1253+
</div>
1254+
10891255
{/* Usage Guidelines */}
10901256
<div
10911257
style={{

packages/blend/lib/components/Card/Card.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import {
1313
import { DefaultCard, AlignedCard, CustomCard } from './CardComponents'
1414

1515
const Card = forwardRef<HTMLDivElement, CardProps>(
16-
({ maxWidth = 'auto', ...props }, ref) => {
16+
({ maxWidth = 'auto', minHeight, maxHeight, ...props }, ref) => {
1717
const cardToken = useResponsiveTokens<CardTokenType>('CARD')
1818
const variant = getCardVariant(
1919
'variant' in props ? props.variant : undefined
@@ -76,6 +76,8 @@ const Card = forwardRef<HTMLDivElement, CardProps>(
7676
? toPixels(maxWidth)
7777
: cardToken.maxWidth
7878
}
79+
maxHeight={maxHeight}
80+
minHeight={minHeight}
7981
outline={cardToken.border}
8082
borderRadius={cardToken.borderRadius}
8183
backgroundColor={cardToken.backgroundColor}

packages/blend/lib/components/Card/types.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,4 +52,15 @@ export type CustomCardProps = {
5252
export type CardProps = {
5353
className?: string
5454
maxWidth?: string
55+
/**
56+
* Height of the card. Accepts any valid CSS height value.
57+
* Examples: "200px", "100%", "50vh", "auto"
58+
* Note: For percentage heights to work, parent container must have a defined height.
59+
*/
60+
maxHeight?: string
61+
/**
62+
* Minimum height of the card. Accepts any valid CSS height value.
63+
* Useful when you want the card to grow with content but maintain a minimum height.
64+
*/
65+
minHeight?: string
5566
} & (DefaultCardProps | AlignedCardProps | CustomCardProps)

packages/blend/lib/components/Menu/Menu.tsx

Lines changed: 26 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,7 @@ const Menu = ({
180180
>
181181
<RadixMenu.Trigger asChild>{trigger}</RadixMenu.Trigger>
182182
<Content
183+
data-dropdown="dropdown"
183184
sideOffset={sideOffset}
184185
alignOffset={alignOffset}
185186
side={side}
@@ -247,73 +248,7 @@ const Menu = ({
247248
/>
248249
</Block>
249250
)}
250-
{filteredItems &&
251-
filteredItems.map((group, groupId) => (
252-
<React.Fragment key={groupId}>
253-
{group.label && (
254-
<RadixMenu.Label asChild>
255-
<PrimitiveText
256-
fontSize={
257-
menuTokens.item.optionsLabel
258-
.fontSize
259-
}
260-
paddingY={
261-
menuTokens.item.optionsLabel.padding
262-
.y
263-
}
264-
paddingX={
265-
menuTokens.item.optionsLabel.padding
266-
.x
267-
}
268-
userSelect="none"
269-
marginY={
270-
menuTokens.item.optionsLabel.margin
271-
.y
272-
}
273-
marginX={
274-
menuTokens.item.optionsLabel.margin
275-
.x
276-
}
277-
textTransform="uppercase"
278-
color={
279-
menuTokens.item.optionsLabel.color
280-
}
281-
>
282-
{group.label}
283-
</PrimitiveText>
284-
</RadixMenu.Label>
285-
)}
286-
{group.items.map((item, itemIndex) => (
287-
<MenuItem
288-
key={`${groupId}-${itemIndex}`}
289-
item={item}
290-
idx={itemIndex}
291-
maxHeight={maxHeight}
292-
/>
293-
))}
294-
{groupId !== filteredItems.length - 1 &&
295-
group.showSeparator && (
296-
<RadixMenu.Separator asChild>
297-
<Block
298-
height={
299-
menuTokens.item.seperator.height
300-
}
301-
backgroundColor={
302-
menuTokens.item.seperator.color
303-
}
304-
marginY={
305-
menuTokens.item.seperator.margin
306-
.y
307-
}
308-
marginX={
309-
menuTokens.item.seperator.margin
310-
.x
311-
}
312-
></Block>
313-
</RadixMenu.Separator>
314-
)}
315-
</React.Fragment>
316-
))}
251+
317252
{shouldUseVirtualScrolling ? (
318253
<Block
319254
padding={FOUNDATION_THEME.unit[6]}
@@ -339,11 +274,10 @@ const Menu = ({
339274
</Block>
340275
) : (
341276
<Block
342-
padding={FOUNDATION_THEME.unit[6]}
343277
style={{
344278
paddingTop: enableSearch
345-
? 0
346-
: FOUNDATION_THEME.unit[6],
279+
? FOUNDATION_THEME.unit[6]
280+
: 0,
347281
}}
348282
>
349283
{filteredItems &&
@@ -352,14 +286,31 @@ const Menu = ({
352286
{group.label && (
353287
<RadixMenu.Label asChild>
354288
<PrimitiveText
355-
fontSize={12}
356-
padding="6px 8px"
289+
fontSize={
290+
menuTokens.item.optionsLabel
291+
.fontSize
292+
}
293+
paddingY={
294+
menuTokens.item.optionsLabel
295+
.padding.y
296+
}
297+
paddingX={
298+
menuTokens.item.optionsLabel
299+
.padding.x
300+
}
357301
userSelect="none"
358-
margin="0px 6px"
302+
marginY={
303+
menuTokens.item.optionsLabel
304+
.margin.y
305+
}
306+
marginX={
307+
menuTokens.item.optionsLabel
308+
.margin.x
309+
}
359310
textTransform="uppercase"
360311
color={
361-
FOUNDATION_THEME.colors
362-
.gray[400]
312+
menuTokens.item.optionsLabel
313+
.color
363314
}
364315
>
365316
{group.label}

0 commit comments

Comments
 (0)