Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
fa5b38e
feat: add new props to API
theerebuss Jun 2, 2022
359182d
chore: add new props to the component hook
theerebuss Jun 2, 2022
e7f05d0
chore: improve styling approach
theerebuss Jun 2, 2022
e3b082e
chore: add slot render
theerebuss Jun 2, 2022
cd5a709
chore: add selectable story
theerebuss Jun 2, 2022
88f6706
chore: apply `selected` styles when checked
theerebuss Jun 2, 2022
20d4322
chore: add all appearances to story
theerebuss Jun 2, 2022
7b8a5d6
chore: move select slot rendering to avoid z-index usage
theerebuss Jun 2, 2022
92e713f
chore: add change file
theerebuss Jun 2, 2022
f85bf7b
chore: update API files
theerebuss Jun 2, 2022
5851cc9
chore: trigger change only on Enter press
theerebuss Jun 2, 2022
584d425
chore: use @fluentui/keyboard-keys for consistency
theerebuss Jun 2, 2022
bf00233
chore: add missing dependency
theerebuss Jun 6, 2022
98c98ee
fix: make changes conformant
theerebuss Jun 6, 2022
f8b84eb
chore: add e2e tests
theerebuss Jun 6, 2022
21a194a
chore: revert `useCard` back to .ts
theerebuss Jun 6, 2022
44dace1
fix: use nullish operator for selection state initialization
theerebuss Jun 6, 2022
3e63168
feat: use react-checkbox for the selection
theerebuss Jun 6, 2022
3324522
chore: add documentation
theerebuss Jun 6, 2022
27718c3
fix: correct interactive tests
theerebuss Jun 6, 2022
713b3d8
Update packages/react-components/react-card/e2e/Card.e2e.tsx
theerebuss Jun 6, 2022
dab2b6b
fix: reintroduce interactive behavior
theerebuss Jun 6, 2022
641369d
feat: add Spacebar selection
theerebuss Jun 6, 2022
bb62575
feat: add focusMode + selectable interaction
theerebuss Jun 14, 2022
8af1b84
chore: update API
theerebuss Jun 14, 2022
8736b91
chore: add e2e tests
theerebuss Jun 14, 2022
9b0e88e
chore: add vr test for new behavior
theerebuss Jun 14, 2022
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
84 changes: 78 additions & 6 deletions apps/vr-tests-react-components/src/stories/Card.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -133,9 +133,9 @@ storiesOf('Card Converged', module)
steps={new Screener.Steps()
.snapshot('normal', { cropTo: '.testWrapper' })
.hover('[role="group"]')
.snapshot('focused', { cropTo: '.testWrapper' })
.snapshot('hover', { cropTo: '.testWrapper' })
.mouseDown('[role="group"]')
.snapshot('clicked', { cropTo: '.testWrapper' })
.snapshot('click', { cropTo: '.testWrapper' })
.end()}
>
<div className="testWrapper" style={{ width: '300px' }}>
Expand All @@ -151,7 +151,6 @@ storiesOf('Card Converged', module)
</Card>
),
{
includeRtl: true,
Copy link
Contributor Author

Choose a reason for hiding this comment

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

RTL not necessary, already tested in non-interactive test.

includeHighContrast: true,
includeDarkMode: true,
},
Expand All @@ -164,7 +163,6 @@ storiesOf('Card Converged', module)
</Card>
),
{
includeRtl: true,
includeHighContrast: true,
includeDarkMode: true,
},
Expand All @@ -177,7 +175,6 @@ storiesOf('Card Converged', module)
</Card>
),
{
includeRtl: true,
includeHighContrast: true,
includeDarkMode: true,
},
Expand All @@ -190,8 +187,83 @@ storiesOf('Card Converged', module)
</Card>
),
{
includeRtl: true,
includeHighContrast: true,
includeDarkMode: true,
},
);

storiesOf('Card Converged', module)
.addDecorator(story => (
<Screener
steps={new Screener.Steps()
.snapshot('normal', { cropTo: '.testWrapper' })
.hover('[role="group"]')
.snapshot('hover', { cropTo: '.testWrapper' })
.mouseDown('[role="group"]')
.snapshot('click', { cropTo: '.testWrapper' })
.mouseUp('[role="group"]')
.snapshot('selected', { cropTo: '.testWrapper' })
.end()}
>
<div className="testWrapper" style={{ width: '300px' }}>
{story()}
</div>
</Screener>
))
.addStory(
'appearance selectable - Filled',
() => (
<Card selectable appearance="filled">
<SampleCardContent />
</Card>
),
{
includeRtl: true,
includeHighContrast: true,
includeDarkMode: true,
},
)
.addStory(
'appearance selectable - Filled Alternative',
() => (
<Card selectable appearance="filled-alternative">
<SampleCardContent />
</Card>
),
{
includeRtl: true,
includeHighContrast: true,
includeDarkMode: true,
},
)
.addStory(
'appearance selectable - Outline',
() => (
<Card selectable appearance="outline">
<SampleCardContent />
</Card>
),
{
includeRtl: true,
includeHighContrast: true,
includeDarkMode: true,
},
)
.addStory(
'appearance selectable - Subtle',
() => (
<Card selectable appearance="subtle">
<SampleCardContent />
</Card>
),
{
includeRtl: true,
includeHighContrast: true,
includeDarkMode: true,
},
)
.addStory('appearance focusable + selectable', () => (
<Card focusMode="no-tab" selectable>
<SampleCardContent />
</Card>
));
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "prerelease",
"comment": "feat: add card selection functionality",
"packageName": "@fluentui/react-card",
"email": "39736248+andrefcdias@users.noreply.github.com",
"dependentChangeType": "patch"
}
119 changes: 116 additions & 3 deletions packages/react-components/react-card/e2e/Card.e2e.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import type {} from '@cypress/react';
import { FluentProvider } from '@fluentui/react-provider';
import { webLightTheme } from '@fluentui/react-theme';
import { Button } from '@fluentui/react-button';
import { checkboxClassNames } from '@fluentui/react-checkbox';
import { Card, CardFooter, CardHeader } from '@fluentui/react-card';
import type { CardProps } from '@fluentui/react-card';

Expand All @@ -12,7 +13,7 @@ const mountFluent = (element: JSX.Element) => {
};

const CardSample = (props: CardProps) => {
const ASSET_URL = 'https://raw.githubusercontent.com/microsoft/fluentui/master/packages/react-card';
const ASSET_URL = 'https://raw.githubusercontent.com/microsoft/fluentui/master/packages/react-components/react-card';

const powerpointLogoURL = ASSET_URL + '/assets/powerpoint_logo.svg';

Expand All @@ -32,10 +33,10 @@ const CardSample = (props: CardProps) => {
plum.
</div>
<CardFooter>
<Button id="open-button" onClick={alert} appearance="primary">
<Button id="open-button" onClick={() => console.log('open-button clicked')} appearance="primary">
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Avoid usage of alert.

Copy link
Contributor

Choose a reason for hiding this comment

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

wondering what is the best practice here ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I found that alert doesn't work with Cypress (on WSL at least) so I went with something more reliable. Output isn't really used for anything, so maybe we remove it?

Open
</Button>
<Button id="close-button" appearance="outline">
<Button id="close-button" onClick={() => console.log('close-button clicked')} appearance="outline">
Close
</Button>
</CardFooter>
Expand Down Expand Up @@ -240,4 +241,116 @@ describe('Card', () => {
});
});
});

describe('selectable', () => {
it('should not be selectable by default', () => {
mountFluent(<CardSample />);

cy.get(`.${checkboxClassNames.input}`).should('not.exist');
});

it('should show checkbox when selectable', () => {
mountFluent(<CardSample selectable />);

cy.get(`.${checkboxClassNames.input}`).should('exist').should('not.be.checked');
});

it('should select with a mouse click', () => {
mountFluent(<CardSample selectable />);

cy.get(`.${checkboxClassNames.input}`).realClick();

cy.get(`.${checkboxClassNames.input}`).should('be.checked');
});

it('should select with the Enter key', () => {
mountFluent(<CardSample selectable />);

cy.get(`.${checkboxClassNames.input}`).focus().realPress('Enter');

cy.get(`.${checkboxClassNames.input}`).should('be.checked');
});

it('should select with the Space key', () => {
mountFluent(<CardSample selectable />);

cy.get(`.${checkboxClassNames.input}`).focus().realPress('Space');

cy.get(`.${checkboxClassNames.input}`).should('be.checked');
});

it('should select with a mouse click anywhere on the card', () => {
mountFluent(<CardSample selectable />);

cy.get(`#card`).realClick();

cy.get(`.${checkboxClassNames.input}`).should('be.checked');
});

it('should select with the Enter key anywhere on the card', () => {
mountFluent(<CardSample selectable />);

cy.get(`#card`).focus().realPress('Enter');

cy.get(`.${checkboxClassNames.input}`).should('be.checked');
});

it('should select with the Space key anywhere on the card', () => {
mountFluent(<CardSample selectable />);

cy.get(`#card`).focus().realPress('Space');

cy.get(`.${checkboxClassNames.input}`).should('be.checked');
});
});

describe('focusable + selectable', () => {
it('should not select on click', () => {
mountFluent(<CardSample focusMode="no-tab" selectable />);

cy.get(`#card`).realClick();

cy.get(`.${checkboxClassNames.input}`).should('not.be.checked');
});

it('should not select on Enter key', () => {
mountFluent(<CardSample focusMode="no-tab" selectable />);

cy.get(`#card`).focus().realPress('Enter');

cy.get(`.${checkboxClassNames.input}`).should('not.be.checked');
});

it('should not select on Space key', () => {
mountFluent(<CardSample focusMode="no-tab" selectable />);

cy.get(`#card`).focus().realPress('Space');

cy.get(`.${checkboxClassNames.input}`).should('not.be.checked');
});

it('should select on checkbox click', () => {
mountFluent(<CardSample focusMode="no-tab" selectable />);

cy.get(`.${checkboxClassNames.input}`).realClick();

cy.get(`.${checkboxClassNames.input}`).should('be.checked');
});

it('should not select on checkbox Enter key press', () => {
mountFluent(<CardSample focusMode="no-tab" selectable />);

cy.get(`.${checkboxClassNames.input}`).focus().realPress('Enter');

cy.get(`.${checkboxClassNames.input}`).should('not.be.checked');
});

it('should select on checkbox Space key press', () => {
mountFluent(<CardSample focusMode="no-tab" selectable />);

cy.get(`.${checkboxClassNames.input}`).focus().realPress('Space');

cy.get(`.${checkboxClassNames.input}`).should('be.checked');
});
});
});
10 changes: 9 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 @@ -4,6 +4,9 @@

```ts

/// <reference types="react" />

import type { Checkbox } from '@fluentui/react-checkbox';
import type { ComponentProps } from '@fluentui/react-utilities';
import type { ComponentState } from '@fluentui/react-utilities';
import type { ForwardRefComponent } from '@fluentui/react-utilities';
Expand Down Expand Up @@ -87,15 +90,20 @@ export type CardProps = ComponentProps<CardSlots> & {
focusMode?: 'off' | 'no-tab' | 'tab-exit' | 'tab-only';
orientation?: 'horizontal' | 'vertical';
size?: 'small' | 'medium' | 'large';
selectable?: boolean;
selected?: boolean;
defaultSelected?: boolean;
onCardSelect?: (event: React_2.MouseEvent | React_2.KeyboardEvent | React_2.ChangeEvent, data: CardOnSelectData) => void;
};

// @public
export type CardSlots = {
root: Slot<'div'>;
select?: Slot<typeof Checkbox>;
};

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

// @public
export const renderCard_unstable: (state: CardState) => JSX.Element;
Expand Down
4 changes: 3 additions & 1 deletion packages/react-components/react-card/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,12 @@
"@fluentui/react-button": "9.0.0-rc.13"
},
"dependencies": {
"@griffel/react": "1.1.0",
"@fluentui/keyboard-keys": "9.0.0-rc.6",
"@fluentui/react-checkbox": "9.0.0-rc.5",
"@fluentui/react-utilities": "9.0.0-rc.10",
"@fluentui/react-tabster": "9.0.0-rc.13",
"@fluentui/react-theme": "9.0.0-rc.9",
"@griffel/react": "1.1.0",
"tslib": "^2.1.0"
},
"peerDependencies": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ describe('Card', () => {
isConformant({
Component: Card,
displayName: 'Card',
requiredProps: {
selectable: true,
},
disabledTests: ['component-has-static-classname-exported'],
});

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,13 @@
import type { ComponentProps, ComponentState, Slot } from '@fluentui/react-utilities';
import type { Checkbox } from '@fluentui/react-checkbox';
import * as React from 'react';

/**
* Data sent from the selection events on a selectable card.
*/
export type CardOnSelectData = {
selected: boolean;
};

/**
* Slots available in the Card component.
Expand All @@ -8,6 +17,11 @@ export type CardSlots = {
* Root element of the component.
*/
root: Slot<'div'>;

/**
* Checkbox slot used when `selectable` prop is enabled.
*/
select?: Slot<typeof Checkbox>;
};

/**
Expand Down Expand Up @@ -51,9 +65,36 @@ export type CardProps = ComponentProps<CardSlots> & {
* @default 'medium'
*/
size?: 'small' | 'medium' | 'large';

/**
* Enables selection of the card.
*
* @default false
*/
selectable?: boolean;

/**
* Defines the controlled selected state of the card.
*
* @default false
*/
selected?: boolean;

/**
* Defines whether the card is initially in a selected state or not when rendered.
*
* @default false
*/
defaultSelected?: boolean;

/**
* Callback to be called when the selected state value changes.
*/
onCardSelect?: (event: React.MouseEvent | React.KeyboardEvent | React.ChangeEvent, data: CardOnSelectData) => void;
};

/**
* State used in rendering Card.
*/
export type CardState = ComponentState<CardSlots> & Required<Pick<CardProps, 'appearance' | 'orientation' | 'size'>>;
export type CardState = ComponentState<CardSlots> &
Required<Pick<CardProps, 'appearance' | 'focusMode' | 'orientation' | 'selectable' | 'selected' | 'size'>>;
Loading