Skip to content

Commit

Permalink
feat(react-card): add orientation prop (#23037)
Browse files Browse the repository at this point in the history
  • Loading branch information
andrefcdias authored Jun 3, 2022
1 parent 7954271 commit 06030b7
Show file tree
Hide file tree
Showing 10 changed files with 144 additions and 7 deletions.
18 changes: 17 additions & 1 deletion apps/vr-tests-react-components/src/stories/Card.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ const SampleCardContent = () => (
storiesOf('Card Converged', module)
.addDecorator(story => (
<Screener steps={new Screener.Steps().snapshot('normal', { cropTo: '.testWrapper' }).end()}>
<div className="testWrapper" style={{ width: '300px' }}>
<div className="testWrapper" style={{ width: '600px' }}>
{story()}
</div>
</Screener>
Expand Down Expand Up @@ -109,6 +109,22 @@ storiesOf('Card Converged', module)
<SampleCardContent />
</Card>
</div>
))
.addStory('orientation', () => (
<div style={{ display: 'flex', flexDirection: 'column', gap: '16px' }}>
<div>
<h1>Vertical</h1>
<Card orientation="vertical">
<SampleCardContent />
</Card>
</div>
<div>
<h1>Horizontal</h1>
<Card orientation="horizontal">
<SampleCardContent />
</Card>
</div>
</div>
));

storiesOf('Card Converged', module)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "prerelease",
"comment": "feat: add `orientation` prop",
"packageName": "@fluentui/react-card",
"email": "[email protected]",
"dependentChangeType": "patch"
}
9 changes: 9 additions & 0 deletions packages/react-components/react-card/assets/logo.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 2 additions & 1 deletion packages/react-components/react-card/etc/react-card.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ export type CardPreviewState = ComponentState<CardPreviewSlots>;
export type CardProps = ComponentProps<CardSlots> & {
appearance?: 'filled' | 'filled-alternative' | 'outline' | 'subtle';
focusMode?: 'off' | 'no-tab' | 'tab-exit' | 'tab-only';
orientation?: 'horizontal' | 'vertical';
size?: 'small' | 'medium' | 'large';
};

Expand All @@ -93,7 +94,7 @@ export type CardSlots = {
};

// @public
export type CardState = ComponentState<CardSlots> & Required<Pick<CardProps, 'appearance' | 'size'>>;
export type CardState = ComponentState<CardSlots> & Required<Pick<CardProps, 'appearance' | 'orientation' | 'size'>>;

// @public
export const renderCard_unstable: (state: CardState) => JSX.Element;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,12 @@ export type CardProps = ComponentProps<CardSlots> & {
*/
focusMode?: 'off' | 'no-tab' | 'tab-exit' | 'tab-only';

orientation?: 'horizontal' | 'vertical';

size?: 'small' | 'medium' | 'large';
};

/**
* State used in rendering Card
*/
export type CardState = ComponentState<CardSlots> & Required<Pick<CardProps, 'appearance' | 'size'>>;
export type CardState = ComponentState<CardSlots> & Required<Pick<CardProps, 'appearance' | 'orientation' | 'size'>>;
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { useFocusableGroup } from '@fluentui/react-tabster';
* @param ref - reference to root HTMLElement of Card
*/
export const useCard_unstable = (props: CardProps, ref: React.Ref<HTMLElement>): CardState => {
const { appearance = 'filled', focusMode = 'off', size = 'medium' } = props;
const { appearance = 'filled', focusMode = 'off', orientation = 'vertical', size = 'medium' } = props;

const focusMap = {
off: undefined,
Expand All @@ -30,6 +30,7 @@ export const useCard_unstable = (props: CardProps, ref: React.Ref<HTMLElement>):

return {
appearance,
orientation,
size,

components: { root: 'div' },
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { shorthands, makeStyles, mergeClasses } from '@griffel/react';
import { tokens } from '@fluentui/react-theme';
import { cardPreviewClassNames } from '../CardPreview/index';
import { cardPreviewClassNames } from '../CardPreview/useCardPreviewStyles';
import { cardHeaderClassNames } from '../CardHeader/useCardHeaderStyles';
import { cardFooterClassNames } from '../CardFooter/useCardFooterStyles';
import type { CardSlots, CardState } from './Card.types';
import type { SlotClassNames } from '@fluentui/react-utilities';

Expand All @@ -17,11 +19,11 @@ export const cardCSSVars = {
const useStyles = makeStyles({
root: {
display: 'flex',
flexDirection: 'column',
position: 'relative',
...shorthands.overflow('hidden'),
color: tokens.colorNeutralForeground1,

// Border setting using after pseudo element to allow CardPreview to render behind it
'::after': {
position: 'absolute',
top: 0,
Expand All @@ -38,10 +40,43 @@ const useStyles = makeStyles({
...shorthands.padding(`var(${cardCSSVars.cardSizeVar})`),
...shorthands.gap(`var(${cardCSSVars.cardSizeVar})`),

// Prevents CardHeader and CardFooter from shrinking.
[`> .${cardHeaderClassNames.root}, > .${cardFooterClassNames.root}`]: {
flexShrink: 0,
},
// Allows non-card components to grow to fill the available space.
[`> :not(.${cardPreviewClassNames.root}):not(.${cardHeaderClassNames.root}):not(.${cardFooterClassNames.root})`]: {
flexGrow: 1,
},
},

orientationHorizontal: {
flexDirection: 'row',
alignItems: 'center',

// Remove vertical padding to keep CardPreview content flush with Card's borders.
[`> .${cardPreviewClassNames.root}`]: {
marginTop: `calc(var(${cardCSSVars.cardSizeVar}) * -1)`,
marginBottom: `calc(var(${cardCSSVars.cardSizeVar}) * -1)`,
},
// Due to Tabster's "Groupper" focus functionality, hidden elements are injected before and after Card's content.
// As such, the code below targets a CardPreview, when it's the first element.
// Since this is on horizontal cards, the left padding is removed to keep the content flush with the border.
[`> :not([aria-hidden="true"]):first-of-type.${cardPreviewClassNames.root}`]: {
marginLeft: `calc(var(${cardCSSVars.cardSizeVar}) * -1)`,
},
},
orientationVertical: {
flexDirection: 'column',

// Remove lateral padding to keep CardPreview content flush with Card's borders.
[`> .${cardPreviewClassNames.root}`]: {
marginLeft: `calc(var(${cardCSSVars.cardSizeVar}) * -1)`,
marginRight: `calc(var(${cardCSSVars.cardSizeVar}) * -1)`,
},
// Due to Tabster's "Groupper" focus functionality, hidden elements are injected before and after Card's content.
// As such, the code below targets a CardPreview, when it's the first element.
// Since this is on vertical cards, the top padding is removed to keep the content flush with the border.
[`> :not([aria-hidden="true"]):first-of-type.${cardPreviewClassNames.root}`]: {
marginTop: `calc(var(${cardCSSVars.cardSizeVar}) * -1)`,
},
Expand Down Expand Up @@ -183,6 +218,11 @@ const useStyles = makeStyles({
export const useCardStyles_unstable = (state: CardState): CardState => {
const styles = useStyles();

const orientationMap = {
horizontal: styles.orientationHorizontal,
vertical: styles.orientationVertical,
} as const;

const sizeMap = {
small: styles.sizeSmall,
medium: styles.sizeMedium,
Expand All @@ -201,6 +241,7 @@ export const useCardStyles_unstable = (state: CardState): CardState => {
state.root.className = mergeClasses(
cardClassNames.root,
styles.root,
orientationMap[state.orientation],
sizeMap[state.size],
state.appearance === 'filled' && styles.filled,
state.appearance === 'filled-alternative' && styles.filledAlternative,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,9 @@ const useStyles = makeStyles({
root: {
position: 'relative',

'> *': {
[`> :not(.${cardPreviewClassNames.logo})`]: {
display: 'block',
height: '100%',
width: '100%',
},
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import descriptionMd from './CardDescription.md';
export { Default } from './CardDefault.stories';
export { Appearance } from './CardAppearance.stories';
export { FocusMode } from './CardFocusMode.stories';
export { Orientation } from './CardOrientation.stories';
export { Size } from './CardSize.stories';

export default {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import * as React from 'react';
import { makeStyles, shorthands } from '@griffel/react';
import { Card, CardHeader, CardPreview } from '../index';
import { SampleCard, Title } from './SampleCard.stories';
import { Avatar, Body1, Button, Caption1 } from '@fluentui/react-components';
import Logo from '../../assets/logo.svg';
import { MoreHorizontal24Regular } from '@fluentui/react-icons';

export const ASSET_URL =
'https://raw.githubusercontent.com/microsoft/fluentui/master/packages/react-components/react-card';

const avatarElviaURL = ASSET_URL + '/assets/avatar_elvia.svg';

const useStyles = makeStyles({
root: {
display: 'flex',
flexDirection: 'column',
...shorthands.gap('30px'),

['> *']: {
width: 'fit-content',
},
},
horizontalPreview: {
height: '60px',
},
});

export const Orientation = () => {
const styles = useStyles();

return (
<div className={styles.root}>
<div>
<Title title="'horizontal'" />
<Card size="small" orientation="horizontal">
<CardPreview className={styles.horizontalPreview}>
<img src={Logo} alt="company logo template" />
</CardPreview>
<CardHeader
image={<Avatar badge={{ status: 'available' }} image={{ src: avatarElviaURL }} />}
header={
<Body1>
<b>Strategy 2021</b>
</Body1>
}
description={<Caption1>https://aka.ms/fluentui</Caption1>}
action={<Button appearance="transparent" icon={<MoreHorizontal24Regular />} />}
/>
</Card>
</div>
<div>
<Title title="'vertical' (Default)" />
<SampleCard />
</div>
</div>
);
};

0 comments on commit 06030b7

Please sign in to comment.