Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -27,20 +27,20 @@ export type CardBaseState = Omit<CardState, 'appearance' | 'orientation' | 'size
export const cardClassNames: SlotClassNames<CardSlots>;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🕵🏾‍♀️ visual changes to review in the Visual Change Report

vr-tests-react-components/Menu Converged - submenuIndicator slotted content 1 screenshots
Image Name Diff(in Pixels) Image Type
vr-tests-react-components/Menu Converged - submenuIndicator slotted content.default.submenus open.chromium.png 413 Changed
vr-tests-react-components/Positioning 2 screenshots
Image Name Diff(in Pixels) Image Type
vr-tests-react-components/Positioning.Positioning end.chromium.png 957 Changed
vr-tests-react-components/Positioning.Positioning end.updated 2 times.chromium.png 509 Changed
vr-tests-react-components/ProgressBar converged 2 screenshots
Image Name Diff(in Pixels) Image Type
vr-tests-react-components/ProgressBar converged.Indeterminate + thickness.default.chromium.png 27 Changed
vr-tests-react-components/ProgressBar converged.Indeterminate + thickness - High Contrast.default.chromium.png 47 Changed
vr-tests-react-components/TagPicker 1 screenshots
Image Name Diff(in Pixels) Image Type
vr-tests-react-components/TagPicker.disabled.disabled input hover.chromium.png 677 Changed

There were 1 duplicate changes discarded. Check the build logs for more information.


// @public
export interface CardContextValue {
// (undocumented)
export type CardContextValue = {
selectableA11yProps: {
referenceId?: string;
setReferenceId: (referenceId: string) => void;
referenceLabel?: string;
setReferenceLabel: (referenceLabel: string) => void;
};
}
} & Required<Pick<CardProps, 'orientation' | 'size'>>;

// @public
export const cardCSSVars: {
cardSizeVar: string;
cardBorderRadiusVar: string;
cardChildMarginVar: string;
};

// @public
Expand Down Expand Up @@ -115,7 +115,9 @@ export type CardPreviewBaseState = CardPreviewState;
export const cardPreviewClassNames: SlotClassNames<CardPreviewSlots>;

// @public
export type CardPreviewProps = ComponentProps<CardPreviewSlots>;
export type CardPreviewProps = ComponentProps<CardPreviewSlots> & {
layout?: 'full' | 'contained';
};

// @public
export type CardPreviewSlots = {
Expand All @@ -124,7 +126,7 @@ export type CardPreviewSlots = {
};

// @public
export type CardPreviewState = ComponentState<CardPreviewSlots>;
export type CardPreviewState = ComponentState<CardPreviewSlots> & Required<Pick<CardPreviewProps, 'layout'> & Pick<CardContextValue, 'orientation' | 'size'>>;

// @public
export type CardProps = ComponentProps<CardSlots> & {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,14 @@ export type CardOnSelectData = {
/**
* Data shared between card components
*/
export interface CardContextValue {
export type CardContextValue = {
selectableA11yProps: {
referenceId?: string;
setReferenceId: (referenceId: string) => void;
referenceLabel?: string;
setReferenceLabel: (referenceLabel: string) => void;
};
}
} & Required<Pick<CardProps, 'orientation' | 'size'>>;

/**
* Slots available in the Card component.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ export const cardContextDefaultValue: CardContextValue = {
/* Noop */
},
},
orientation: 'vertical',
size: 'medium',
};

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/**
* CSS variable names used internally for uniform styling in Card.
*
* Extracted into a dedicated module so descendants (e.g. CardPreview styles)
* can reference them without creating a circular import via `useCardStyles.styles.ts`,
* which itself imports class names from descendant components.
*/
export const cardCSSVars = {
cardSizeVar: '--fui-Card--size',
cardBorderRadiusVar: '--fui-Card--border-radius',
cardChildMarginVar: '--fui-Card--child-margin',
};
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { CardContextValue, CardState } from './Card.types';

export function useCardContextValue({ selectableA11yProps }: CardState): CardContextValue {
return { selectableA11yProps };
export function useCardContextValue({ selectableA11yProps, orientation, size }: CardState): CardContextValue {
return { selectableA11yProps, orientation, size };
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export const cardClassNames: SlotClassNames<CardSlots> = {
export const cardCSSVars = {
cardSizeVar: '--fui-Card--size',
cardBorderRadiusVar: '--fui-Card--border-radius',
cardChildMarginVar: '--fui-Card--child-margin',
};

const focusOutlineStyle: Partial<FocusOutlineStyleOptions> = {
Expand Down Expand Up @@ -107,21 +108,23 @@ const useCardStyles = makeStyles({
alignItems: 'center',

// Remove vertical padding to keep CardPreview content flush with Card's borders.
// The margin is driven by `cardChildMarginVar` (set by CardPreview's `layout` styles);
// the fallback preserves the legacy bleed-to-edge behavior when the var isn't set.
[`> .${cardPreviewClassNames.root}`]: {
marginTop: `calc(var(${cardCSSVars.cardSizeVar}) * -1)`,
marginBottom: `calc(var(${cardCSSVars.cardSizeVar}) * -1)`,
marginTop: `var(${cardCSSVars.cardChildMarginVar}, calc(var(${cardCSSVars.cardSizeVar}) * -1))`,
marginBottom: `var(${cardCSSVars.cardChildMarginVar}, 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"]).${cardPreviewClassNames.root}:first-of-type`]: {
marginLeft: `calc(var(${cardCSSVars.cardSizeVar}) * -1)`,
marginLeft: `var(${cardCSSVars.cardChildMarginVar}, 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 last element.
// Since this is on horizontal cards, the right padding is removed to keep the content flush with the border.
[`> :not([aria-hidden="true"]).${cardPreviewClassNames.root}:last-of-type`]: {
marginRight: `calc(var(${cardCSSVars.cardSizeVar}) * -1)`,
marginRight: `var(${cardCSSVars.cardChildMarginVar}, calc(var(${cardCSSVars.cardSizeVar}) * -1))`,
},

// If the last child is a CardHeader or CardFooter, allow it to grow to fill the available space.
Expand All @@ -133,26 +136,28 @@ const useCardStyles = makeStyles({
flexDirection: 'column',

// Remove lateral padding to keep CardPreview content flush with Card's borders.
// The margin is driven by `cardChildMarginVar` (set by CardPreview's `layout` styles);
// the fallback preserves the legacy bleed-to-edge behavior when the var isn't set.
[`> .${cardPreviewClassNames.root}`]: {
marginLeft: `calc(var(${cardCSSVars.cardSizeVar}) * -1)`,
marginRight: `calc(var(${cardCSSVars.cardSizeVar}) * -1)`,
marginLeft: `var(${cardCSSVars.cardChildMarginVar}, calc(var(${cardCSSVars.cardSizeVar}) * -1))`,
marginRight: `var(${cardCSSVars.cardChildMarginVar}, 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"]).${cardPreviewClassNames.root}:first-of-type`]: {
marginTop: `calc(var(${cardCSSVars.cardSizeVar}) * -1)`,
marginTop: `var(${cardCSSVars.cardChildMarginVar}, calc(var(${cardCSSVars.cardSizeVar}) * -1))`,
},
[`> .${cardClassNames.floatingAction} + .${cardPreviewClassNames.root}`]: {
marginTop: `calc(var(${cardCSSVars.cardSizeVar}) * -1)`,
marginTop: `var(${cardCSSVars.cardChildMarginVar}, 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 bottom padding is removed to keep the content flush with the border.
[`> :not([aria-hidden="true"]).${cardPreviewClassNames.root}:last-of-type`]: {
marginBottom: `calc(var(${cardCSSVars.cardSizeVar}) * -1)`,
marginBottom: `var(${cardCSSVars.cardChildMarginVar}, calc(var(${cardCSSVars.cardSizeVar}) * -1))`,
},
},

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { ComponentProps, ComponentState, Slot } from '@fluentui/react-utilities';
import type { CardContextValue } from '../Card/Card.types';

/**
* Slots available in the Card component.
Expand All @@ -18,7 +19,17 @@ export type CardPreviewSlots = {
/**
* CardPreview component props.
*/
export type CardPreviewProps = ComponentProps<CardPreviewSlots>;
export type CardPreviewProps = ComponentProps<CardPreviewSlots> & {
/**
* Layout of the content.
*
* - `full` (default): Pushes out to align with the edges of the Card.
* - `contained`: Content stays within the Card's spacing.
*
* @default 'full'
*/
layout?: 'full' | 'contained';
};

/**
* CardPreview base props (same as CardPreviewProps since CardPreview has no design props)
Expand All @@ -27,8 +38,12 @@ export type CardPreviewBaseProps = CardPreviewProps;

/**
* State used in rendering CardPreview.
*
* `orientation` and `size` are inherited from the parent Card via context so descendant
* slots (and external theme libraries) can react to them without re-reading context.
*/
export type CardPreviewState = ComponentState<CardPreviewSlots>;
export type CardPreviewState = ComponentState<CardPreviewSlots> &
Required<Pick<CardPreviewProps, 'layout'> & Pick<CardContextValue, 'orientation' | 'size'>>;

/**
* CardPreview base state (same as CardPreviewState since CardPreview has no design props)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,12 @@ export const useCardPreviewBase_unstable = (
props: CardPreviewBaseProps,
ref: React.Ref<HTMLElement>,
): CardPreviewBaseState => {
const { logo } = props;
const { logo, layout = 'full', ...rest } = props;

const {
selectableA11yProps: { referenceLabel, referenceId, setReferenceLabel, setReferenceId },
orientation,
size,
} = useCardContext_unstable();
// FIXME:
// `ref` is wrongly assigned to be `HTMLElement` instead of `HTMLDivElement`
Expand Down Expand Up @@ -73,12 +75,14 @@ export const useCardPreviewBase_unstable = (
root: 'div',
logo: 'div',
},

layout,
orientation,
size,
root: slot.always(
// eslint-disable-next-line react-hooks/refs
getIntrinsicElementProps('div', {
ref: previewRef,
...props,
...rest,
}),
{ elementType: 'div' },
),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
'use client';

import type { SlotClassNames } from '@fluentui/react-utilities';
import { tokens } from '@fluentui/react-theme';
import { makeStyles, mergeClasses } from '@griffel/react';
import { cardCSSVars } from '../Card/cardCSSVars';
import type { CardPreviewSlots, CardPreviewState } from './CardPreview.types';

/**
Expand Down Expand Up @@ -32,13 +34,29 @@ const useStyles = makeStyles({
},
});

const useLayoutStyles = makeStyles({
full: {
[cardCSSVars.cardChildMarginVar]: `calc(-1 * var(${cardCSSVars.cardSizeVar}))`,
},
contained: {
[cardCSSVars.cardChildMarginVar]: '0',
borderRadius: tokens.borderRadiusXLarge,
},
});

/**
* Apply styling to the CardPreview slots based on the state.
*/
export const useCardPreviewStyles_unstable = (state: CardPreviewState): CardPreviewState => {
const styles = useStyles();
const layoutStyles = useLayoutStyles();
// eslint-disable-next-line react-hooks/immutability
state.root.className = mergeClasses(cardPreviewClassNames.root, styles.root, state.root.className);
state.root.className = mergeClasses(
cardPreviewClassNames.root,
styles.root,
layoutStyles[state.layout],
state.root.className,
);

if (state.logo) {
// eslint-disable-next-line react-hooks/immutability
Expand Down
Loading