diff --git a/change/@fluentui-react-button-2020-07-24-18-12-08-card.json b/change/@fluentui-react-button-2020-07-24-18-12-08-card.json new file mode 100644 index 00000000000000..3e77e564583f9d --- /dev/null +++ b/change/@fluentui-react-button-2020-07-24-18-12-08-card.json @@ -0,0 +1,8 @@ +{ + "type": "minor", + "comment": "Moving SizeValue const from react-button to theme package.", + "packageName": "@fluentui/react-button", + "email": "makotom@microsoft.com", + "dependentChangeType": "patch", + "date": "2020-07-25T01:11:26.080Z" +} diff --git a/change/@fluentui-react-theme-provider-2020-07-24-18-12-08-card.json b/change/@fluentui-react-theme-provider-2020-07-24-18-12-08-card.json new file mode 100644 index 00000000000000..e6db89821b203b --- /dev/null +++ b/change/@fluentui-react-theme-provider-2020-07-24-18-12-08-card.json @@ -0,0 +1,8 @@ +{ + "type": "minor", + "comment": "Adding Card tokens to theme.", + "packageName": "@fluentui/react-theme-provider", + "email": "makotom@microsoft.com", + "dependentChangeType": "patch", + "date": "2020-07-25T01:12:07.950Z" +} diff --git a/change/@fluentui-theme-2020-09-14-17-50-32-card.json b/change/@fluentui-theme-2020-09-14-17-50-32-card.json new file mode 100644 index 00000000000000..3fb487f09a1333 --- /dev/null +++ b/change/@fluentui-theme-2020-09-14-17-50-32-card.json @@ -0,0 +1,8 @@ +{ + "type": "minor", + "comment": "Moving SizeValue const from react-button to theme package.", + "packageName": "@fluentui/theme", + "email": "makotom@microsoft.com", + "dependentChangeType": "patch", + "date": "2020-09-14T01:11:58.698Z" +} diff --git a/change/@uifabric-react-cards-2020-07-24-18-12-08-card.json b/change/@uifabric-react-cards-2020-07-24-18-12-08-card.json new file mode 100644 index 00000000000000..7d4a00615b969d --- /dev/null +++ b/change/@uifabric-react-cards-2020-07-24-18-12-08-card.json @@ -0,0 +1,8 @@ +{ + "type": "minor", + "comment": "Card: Adding initial implementation based on latest technology stack - compose, scss, tokens.", + "packageName": "@uifabric/react-cards", + "email": "makotom@microsoft.com", + "dependentChangeType": "patch", + "date": "2020-07-25T01:11:58.698Z" +} diff --git a/packages/react-button/etc/react-button.api.md b/packages/react-button/etc/react-button.api.md index 95b5e4312af3ac..54fc18c70dc835 100644 --- a/packages/react-button/etc/react-button.api.md +++ b/packages/react-button/etc/react-button.api.md @@ -10,6 +10,7 @@ import { ComponentProps } from '@fluentui/react-compose/lib/next/index'; import * as React from 'react'; import { RecursivePartial } from '@fluentui/theme'; import { ShorthandProps } from '@fluentui/react-compose/lib/next/index'; +import { SizeValue } from '@fluentui/theme'; import { SlotProps } from '@fluentui/react-compose'; // @public @@ -180,9 +181,6 @@ export interface MenuButtonState extends MenuButtonProps, Omit & React.RefAttributes>; diff --git a/packages/react-button/src/components/Button/Button.types.ts b/packages/react-button/src/components/Button/Button.types.ts index 6eae045f28c3bf..f0bcd461658eb7 100644 --- a/packages/react-button/src/components/Button/Button.types.ts +++ b/packages/react-button/src/components/Button/Button.types.ts @@ -1,9 +1,7 @@ import * as React from 'react'; import { BaseSlots, SlotProps } from '@fluentui/react-compose'; import { ComponentProps, ShorthandProps } from '@fluentui/react-compose/lib/next/index'; -import { ColorTokenSet, RecursivePartial } from '@fluentui/theme'; - -export type SizeValue = 'smallest' | 'smaller' | 'small' | 'medium' | 'large' | 'larger' | 'largest'; +import { ColorTokenSet, RecursivePartial, SizeValue } from '@fluentui/theme'; export type ButtonProps = ComponentProps & React.HTMLAttributes & { diff --git a/packages/react-cards/.storybook/decorators/index.js b/packages/react-cards/.storybook/decorators/index.js new file mode 100644 index 00000000000000..2008f434af610f --- /dev/null +++ b/packages/react-cards/.storybook/decorators/index.js @@ -0,0 +1 @@ +export * from './withThemeProvider'; diff --git a/packages/react-cards/.storybook/decorators/withThemeProvider.js b/packages/react-cards/.storybook/decorators/withThemeProvider.js new file mode 100644 index 00000000000000..19da837fb92f56 --- /dev/null +++ b/packages/react-cards/.storybook/decorators/withThemeProvider.js @@ -0,0 +1,9 @@ +import React from 'react'; +import { ThemeProvider } from '@fluentui/react-theme-provider'; +import { useTheme } from '../knobs/theme'; + +export const withThemeProvider = storyFn => { + const { theme } = useTheme(); + + return {storyFn()}; +}; diff --git a/packages/react-cards/.storybook/knobs/theme.js b/packages/react-cards/.storybook/knobs/theme.js new file mode 100644 index 00000000000000..bce75eb9fb1df6 --- /dev/null +++ b/packages/react-cards/.storybook/knobs/theme.js @@ -0,0 +1,51 @@ +import { select } from '@storybook/addon-knobs'; + +const themeSelectorLabel = 'Theme'; + +const TeamsTheme = { + tokens: { + body: { + background: 'white', + contentColor: 'black', + }, + + card: { + size: { + small: { + height: '100%', + width: '200px', + }, + smaller: { + height: '100%', + width: '200px', + }, + smallest: { + height: '100%', + width: '200px', + }, + medium: { + height: '100%', + width: '300px', + }, + large: { + height: '100%', + width: '500px', + }, + larger: { + height: '100%', + width: '500px', + }, + largest: { + height: '100%', + width: '500px', + }, + }, + }, + }, + stylesheets: [], +}; + +const themeOptions = [{ label: 'Teams', theme: TeamsTheme }]; +const defaultThemeOption = themeOptions[0]; + +export const useTheme = () => select(themeSelectorLabel, themeOptions, defaultThemeOption); diff --git a/packages/react-cards/.storybook/main.js b/packages/react-cards/.storybook/main.js index 2914f69af39061..a281bd93fec551 100644 --- a/packages/react-cards/.storybook/main.js +++ b/packages/react-cards/.storybook/main.js @@ -4,5 +4,5 @@ module.exports = { webpackFinal: config => { return custom({ config }); }, - addons: ['@storybook/addon-a11y/register'], + addons: ['@storybook/addon-a11y/register', '@storybook/addon-knobs/register'], }; diff --git a/packages/react-cards/.storybook/preview.js b/packages/react-cards/.storybook/preview.js index 7f5f02a74aab8b..2fd781e2f49fa1 100644 --- a/packages/react-cards/.storybook/preview.js +++ b/packages/react-cards/.storybook/preview.js @@ -3,9 +3,13 @@ import generateStoriesFromExamples from '@uifabric/build/storybook/generateStori import { configure, addParameters, addDecorator } from '@storybook/react'; import { withInfo } from '@storybook/addon-info'; import { withA11y } from '@storybook/addon-a11y'; +import { withKnobs } from '@storybook/addon-knobs'; +import { withThemeProvider } from './decorators/withThemeProvider'; addDecorator(withInfo()); addDecorator(withA11y()); +addDecorator(withKnobs({ escapeHTML: false })); +addDecorator(withThemeProvider); addParameters({ a11y: { manual: true, @@ -14,7 +18,7 @@ addParameters({ initializeIcons(); -const req = require.context('../src/components', true, /\.Example\.tsx$/); +const req = require.context('../src', true, /\.(Example|stories)\.tsx$/); function loadStories() { const stories = new Map(); diff --git a/packages/react-cards/package.json b/packages/react-cards/package.json index 6263f8457760fa..1c4019de94323e 100644 --- a/packages/react-cards/package.json +++ b/packages/react-cards/package.json @@ -27,6 +27,7 @@ "update-snapshots": "just-scripts jest -u" }, "devDependencies": { + "@fluentui/common-styles": "^0.2.6", "@fluentui/eslint-plugin": "^0.54.1", "@types/enzyme": "3.10.3", "@types/enzyme-adapter-react-16": "1.0.3", @@ -48,10 +49,19 @@ "react-test-renderer": "^16.3.0" }, "dependencies": { + "@fluentui/keyboard-key": "^0.2.12", + "@fluentui/react-compose": "^0.19.4", + "@fluentui/react-theme-provider": "^0.13.4", + "@fluentui/theme": "^0.4.0", "@microsoft/load-themed-styles": "^1.10.26", + "@uifabric/example-data": "^7.1.4", "@uifabric/file-type-icons": "^7.6.7", "@uifabric/foundation": "^7.9.6", + "@uifabric/react-hooks": "^7.13.5", "@uifabric/set-version": "^7.0.23", + "@uifabric/styling": "^7.16.6", + "@uifabric/theme-samples": "^7.1.17", + "@uifabric/utilities": "^7.32.3", "office-ui-fabric-react": "^7.139.1", "tslib": "^1.10.0" }, diff --git a/packages/react-cards/src/next.ts b/packages/react-cards/src/next.ts new file mode 100644 index 00000000000000..6f5c3796790d85 --- /dev/null +++ b/packages/react-cards/src/next.ts @@ -0,0 +1 @@ +export * from './next/index'; diff --git a/packages/react-cards/src/next/Card.Teams.stories.tsx b/packages/react-cards/src/next/Card.Teams.stories.tsx new file mode 100644 index 00000000000000..8319f43304ff83 --- /dev/null +++ b/packages/react-cards/src/next/Card.Teams.stories.tsx @@ -0,0 +1,79 @@ +import * as React from 'react'; +import { TestImages } from '@uifabric/example-data'; +import { + DefaultButton, + IconButton, + Image, + ImageFit, + Persona, + PersonaPresence, + Stack, + Text, +} from 'office-ui-fabric-react'; +import { Card, CardBody, CardFooter, CardHeader } from '@uifabric/react-cards/lib/next'; + +function onClick() { + // eslint-disable-next-line no-alert + alert('Card was clicked'); +} + +export const TeamsCards = () => ( + +
+ Basic Card + + + + + + Voluptatibus commodi ut. Neque eum odit eius repellat molestiae illo aut ut illum. Nulla vel et odit + consequatur dolorem molestias. Rem rerum animi consequatur. + + +
+
+ Only header + + + + + +
+
+ Only body + + + + + Citizens of distant epochs muse about at theedge of forever hearts of the... + + + +
+
+ Only footer + + + + + + + + + + + + +
+
+); diff --git a/packages/react-cards/src/next/Card.scss b/packages/react-cards/src/next/Card.scss new file mode 100644 index 00000000000000..28f328478af4e6 --- /dev/null +++ b/packages/react-cards/src/next/Card.scss @@ -0,0 +1,292 @@ +@import '~@fluentui/common-styles/dist/sass/focusBorder'; + +:root { + /* sizing */ + /* smallest */ + --card-size-smallest-borderRadius: 4px; + --card-size-smallest-height: auto; + --card-size-smallest-margin: 0; + --card-size-smallest-padding: 8px; + --card-size-smallest-width: auto; + + /* smaller */ + --card-size-smaller-borderRadius: 4px; + --card-size-smaller-height: auto; + --card-size-smaller-margin: 0; + --card-size-smaller-padding: 8px; + --card-size-smaller-width: auto; + + /* small */ + --card-size-small-borderRadius: 4px; + --card-size-small-height: auto; + --card-size-small-margin: 0; + --card-size-small-padding: 8px; + --card-size-small-width: auto; + + /* medium */ + --card-size-medium-borderRadius: 4px; + --card-size-medium-height: auto; + --card-size-medium-margin: 0; + --card-size-medium-padding: 16px; + --card-size-medium-width: auto; + + /* large */ + --card-size-large-borderRadius: 6px; + --card-size-large-height: auto; + --card-size-large-margin: 0; + --card-size-large-padding: 16px; + --card-size-large-width: auto; + + /* larger */ + --card-size-larger-borderRadius: 6px; + --card-size-larger-height: auto; + --card-size-larger-margin: 0; + --card-size-larger-padding: 16px; + --card-size-larger-width: auto; + + /* largest */ + --card-size-largest-borderRadius: 6px; + --card-size-largest-height: auto; + --card-size-largest-margin: 0; + --card-size-largest-padding: 16px; + --card-size-largest-width: auto; + + /* card variables */ + --card-backgroundColor: #ffffff; + --card-borderColor: transparent; + --card-borderWidth: 1px; + --card-boxShadow: 0 1.6px 3.6px 0 rgba(0, 0, 0, 0.1); + --card-minHeight: 32px; + --card-minWidth: 100px; + + --card-borderRadius: var(--card-size-medium-borderRadius); + --card-height: var(--card-size-medium-height); + --card-margin: var(--card-size-medium-margin); + --card-padding: var(--card-size-medium-padding); + --card-width: var(--card-size-medium-width); + + /* selectors */ + --card-hovered-backgroundColor: var(--card-backgroundColor); + --card-hovered-borderColor: var(--card-borderColor); + --card-hovered-borderWidth: var(--card-borderWidth); + --card-hovered-boxShadow: var(--card-boxShadow); + + --card-pressed-backgroundColor: var(--card-backgroundColor); + --card-pressed-borderColor: var(--card-borderColor); + --card-pressed-borderWidth: var(--card-borderWidth); + --card-pressed-boxShadow: var(--card-boxShadow); + + /* modifiers */ + /* clickable cards */ + --card-clickable-backgroundColor: #ffffff; + --card-clickable-borderColor: transparent; + --card-clickable-borderWidth: 1px; + --card-clickable-boxShadow: 0 1.6px 3.6px 0 rgba(0, 0, 0, 0.1); + + --card-clickable-hovered-backgroundColor: #fafafa; + --card-clickable-hovered-borderColor: var(--card-clickable-borderColor); + --card-clickable-hovered-borderWidth: var(--card-clickable-borderWidth); + --card-clickable-hovered-boxShadow: 0 3.2px 7.2px rgba(0, 0, 0, 0.1); + + --card-clickable-pressed-backgroundColor: #f5f5f5; + --card-clickable-pressed-borderColor: var(--card-clickable-borderColor); + --card-clickable-pressed-borderWidth: 2px; + --card-clickable-pressed-boxShadow: var(--card-clickable-boxShadow); + + /* compact cards */ + --card-compact-padding: 0; + + /* disabled cards */ + --card-disabled-backgroundColor: #f0f0f0; + --card-disabled-borderColor: var(--card-borderColor); + --card-disabled-borderWidth: var(--card-borderWidth); + --card-disabled-boxShadow: 0 0.8px 1.8px 0 rgba(0, 0, 0, 0.1); + + /* fluid cards */ + --card-fluid-height: 100%; + --card-fluid-width: 100%; + + /* selected cards */ + --card-selected-backgroundColor: #fafafa; + --card-selected-borderColor: var(--card-borderColor); + --card-selected-borderWidth: var(--card-borderWidth); + --card-selected-boxShadow: var(--card-boxShadow); +} + +/* slots */ +.root { + box-sizing: border-box; + display: flex; + flex-direction: column; + position: relative; + + background-color: var(--card-backgroundColor); + border-color: var(--card-borderColor); + border-radius: var(--card-borderRadius); + border-width: var(--card-borderWidth); + box-shadow: var(--card-boxShadow); + height: var(--card-height); + margin: var(--card-margin); + min-height: var(--card-minHeight); + min-width: var(--card-minWidth); + padding: var(--card-padding); + width: var(--card-width); + + --card-borderRadius: var(--card-size-medium-borderRadius); + --card-height: var(--card-size-medium-height); + --card-margin: var(--card-size-medium-margin); + --card-padding: var(--card-size-medium-padding); + --card-width: var(--card-size-medium-width); + + --card-hovered-backgroundColor: var(--card-backgroundColor); + --card-hovered-borderColor: var(--card-borderColor); + --card-hovered-borderWidth: var(--card-borderWidth); + --card-hovered-boxShadow: var(--card-boxShadow); + + --card-pressed-backgroundColor: var(--card-backgroundColor); + --card-pressed-borderColor: var(--card-borderColor); + --card-pressed-borderWidth: var(--card-borderWidth); + --card-pressed-boxShadow: var(--card-boxShadow); + + @include focus-border(); + + /* selectors */ + &:hover { + background-color: var(--card-hovered-backgroundColor, var(--card-backgroundColor)); + border-color: var(--card-hovered-borderColor, var(--card-borderColor)); + border-width: var(--card-hovered-borderWidth, var(--card-borderWidth)); + box-shadow: var(--card-hovered-boxShadow, var(--card-boxShadow)); + } + + &:active { + background-color: var(--card-pressed-backgroundColor, var(--card-backgroundColor)); + border-color: var(--card-pressed-borderColor, var(--card-borderColor)); + border-width: var(--card-pressed-borderWidth, var(--card-borderWidth)); + box-shadow: var(--card-pressed-boxShadow, var(--card-boxShadow)); + } +} + +/* modifiers */ +/* sizing */ +/* smallest */ +.root._size_smallest { + --card-borderRadius: var(--card-size-smallest-borderRadius); + --card-height: var(--card-size-smallest-height); + --card-margin: var(--card-size-smallest-margin); + --card-padding: var(--card-size-smallest-padding); + --card-width: var(--card-size-smallest-width); +} + +/* smaller */ +.root._size_smaller { + --card-borderRadius: var(--card-size-smaller-borderRadius); + --card-height: var(--card-size-smaller-height); + --card-margin: var(--card-size-smaller-margin); + --card-padding: var(--card-size-smaller-padding); + --card-width: var(--card-size-smaller-width); +} + +/* small */ +.root._size_small { + --card-borderRadius: var(--card-size-small-borderRadius); + --card-height: var(--card-size-small-height); + --card-margin: var(--card-size-small-margin); + --card-padding: var(--card-size-small-padding); + --card-width: var(--card-size-small-width); +} + +/* large */ +.root._size_large { + --card-borderRadius: var(--card-size-large-borderRadius); + --card-height: var(--card-size-large-height); + --card-margin: var(--card-size-large-margin); + --card-padding: var(--card-size-large-padding); + --card-width: var(--card-size-large-width); +} + +/* larger */ +.root._size_larger { + --card-borderRadius: var(--card-size-larger-borderRadius); + --card-height: var(--card-size-larger-height); + --card-margin: var(--card-size-larger-margin); + --card-padding: var(--card-size-larger-padding); + --card-width: var(--card-size-larger-width); +} + +/* largest */ +.root._size_largest { + --card-borderRadius: var(--card-size-largest-borderRadius); + --card-height: var(--card-size-largest-height); + --card-margin: var(--card-size-largest-margin); + --card-padding: var(--card-size-largest-padding); + --card-width: var(--card-size-largest-width); +} + +/* clickable cards */ +.root._onClick { + cursor: pointer; + + --card-backgroundColor: var(--card-clickable-backgroundColor); + --card-borderColor: var(--card-clickable-borderColor); + --card-borderWidth: var(--card-clickable-borderWidth); + --card-boxShadow: var(--card-clickable-boxShadow); + + /* selectors */ + &:hover { + --card-hovered-backgroundColor: var(--card-clickable-hovered-backgroundColor); + --card-hovered-borderColor: var(--card-clickable-hovered-borderColor); + --card-hovered-borderWidth: var(--card-clickable-hovered-borderWidth); + --card-hovered-boxShadow: var(--card-clickable-hovered-boxShadow); + } + + &:active { + --card-pressed-backgroundColor: var(--card-clickable-pressed-backgroundColor); + --card-pressed-borderColor: var(--card-clickable-pressed-borderColor); + --card-pressed-borderWidth: var(--card-clickable-pressed-borderWidth); + --card-pressed-boxShadow: var(--card-clickable-pressed-boxShadow); + } +} + +/* compact cards */ +.root._compact { + --card-padding: var(--card-compact-padding); +} + +/* disabled cards */ +.root._disabled { + cursor: not-allowed; + + --card-backgroundColor: var(--card-disabled-backgroundColor); + --card-borderColor: var(--card-disabled-borderColor); + --card-borderWidth: var(--card-disabled-borderWidth); + --card-boxShadow: var(--card-disabled-boxShadow); + + /* selectors */ + &:hover { + --card-hovered-backgroundColor: var(--card-disabled-backgroundColor); + --card-hovered-borderColor: var(--card-disabled-borderColor); + --card-hovered-borderWidth: var(--card-disabled-borderWidth); + --card-hovered-boxShadow: var(--card-disabled-boxShadow); + } + + &:active { + --card-pressed-backgroundColor: var(--card-disabled-backgroundColor); + --card-pressed-borderColor: var(--card-disabled-borderColor); + --card-pressed-borderWidth: var(--card-disabled-borderWidth); + --card-pressed-boxShadow: var(--card-disabled-boxShadow); + } +} + +/* fluid cards */ +.root._fluid { + --card-height: var(--card-fluid-height); + --card-width: var(--card-fluid-width); +} + +/* selected cards */ +.root._selected { + --card-backgroundColor: var(--card-selected-backgroundColor); + --card-borderColor: var(--card-selected-borderColor); + --card-borderWidth: var(--card-selected-borderWidth); + --card-boxShadow: var(--card-selected-boxShadow); +} diff --git a/packages/react-cards/src/next/Card.stories.scss b/packages/react-cards/src/next/Card.stories.scss new file mode 100644 index 00000000000000..20ab20387f4239 --- /dev/null +++ b/packages/react-cards/src/next/Card.stories.scss @@ -0,0 +1,19 @@ +.hStack { + display: flex; + flex-direction: row; + flex-wrap: wrap; + margin: calc(-1 * var(--gap, 8px) / 2); + + > * { + margin: calc(var(--gap, 8px) / 2); + } +} + +.vStack { + display: flex; + flex-direction: column; + align-items: flex-start; + > *:not(:first-child) { + margin-top: var(--gap, 20px); + } +} diff --git a/packages/react-cards/src/next/Card.stories.tsx b/packages/react-cards/src/next/Card.stories.tsx new file mode 100644 index 00000000000000..cec7f21eb1741d --- /dev/null +++ b/packages/react-cards/src/next/Card.stories.tsx @@ -0,0 +1,85 @@ +import * as React from 'react'; +import { Card } from '@uifabric/react-cards/lib/next'; +import * as classes from './Card.stories.scss'; + +function onClick() { + // eslint-disable-next-line no-alert + alert('Card was clicked'); +} + +/** + * Temporary Stack until there's one in its own package. + */ +const Stack = (props: React.PropsWithChildren<{ horizontal?: boolean }>) => { + const { horizontal, ...rest } = props; + + return
; +}; + +export const DefaultCard = () => ( + + This is a card + This is a card + This is a card + +); + +export const ClickableCard = () => ( + + This is a card + This is a card + This is a card + +); + +export const CompactCard = () => ( + + This is a card + This is a card + This is a card + +); + +export const DisabledCard = () => ( + + This is a card + This is a card + This is a card + +); + +export const SelectedCard = () => ( + + This is a card + This is a card + This is a card + +); + +export const FluidCard = () => ( + + This is a card + This is a card + This is a card + +); + +export const CardSizes = () => ( + + + This is a card + This is a card + This is a card + + + This is a card + This is a card + This is a card + + + This is a card + This is a card + This is a card + + +); diff --git a/packages/react-cards/src/next/Card.tsx b/packages/react-cards/src/next/Card.tsx new file mode 100644 index 00000000000000..336f03b5b0d4ec --- /dev/null +++ b/packages/react-cards/src/next/Card.tsx @@ -0,0 +1,21 @@ +import * as React from 'react'; +import { useInlineTokens } from '@fluentui/react-theme-provider'; +import { useFocusRects } from '@uifabric/utilities'; +import { CardProps } from './Card.types'; +import { useCard } from './useCard'; +import { useCardClasses } from './useCardClasses'; + +/** + * Define a styled Card, using the `useCard` hook. + */ +export const Card = React.forwardRef((props, ref) => { + const { render, state } = useCard(props, ref); + + useCardClasses(state); + useFocusRects(state.ref as any); + useInlineTokens(state, '--card'); + + return render(state); +}); + +Card.displayName = 'Card'; diff --git a/packages/react-cards/src/next/Card.types.ts b/packages/react-cards/src/next/Card.types.ts new file mode 100644 index 00000000000000..860b552e220fee --- /dev/null +++ b/packages/react-cards/src/next/Card.types.ts @@ -0,0 +1,55 @@ +import * as React from 'react'; +import { BaseSlots, SlotProps } from '@fluentui/react-compose'; +import { ComponentProps } from '@fluentui/react-compose/lib/next/index'; +import { ColorTokenSet, SizeValue } from '@fluentui/theme'; + +/* eslint-disable @typescript-eslint/naming-convention */ + +export type CardProps = ComponentProps & + React.HTMLAttributes & { + /** A card can be compact, without any padding inside. */ + compact?: boolean; + + /** A card will used horizontal layout. */ + horizontal?: boolean; + + /** Centers content in a card. */ + centered?: boolean; + + /** A card can be sized. */ + size?: SizeValue; + + /** A card can take up the width and height of its container. */ + fluid?: boolean; + + /** A card can show that it cannot be interacted with. */ + disabled?: boolean; + + /** A card can be hiding part of the content and expand on hover/focus. */ + expandable?: boolean; + + /** A card can have elevation styles. */ + // TODO: Should we remove? It's not accounted for in design spec and a card is elevated by default. + // elevated?: boolean; + + /** A card can have inverted background styles. */ + inverted?: boolean; + + /** A card can have quiet styles. */ + // TODO: Is this the correct name? Appears as ghost in design spec. + quiet?: boolean; + + /** A card can show that it is currently selected or not. */ + // TODO: This should probably have a `defaultSelected` property at the same time. + selected?: boolean; + }; + +export interface CardState extends CardProps { + cardRef: React.RefObject; +} + +export interface CardSlots extends BaseSlots {} + +export type CardSlotProps = SlotProps>; + +export type CardTokens = ColorTokenSet & {}; diff --git a/packages/react-cards/src/next/CardBody/CardBody.scss b/packages/react-cards/src/next/CardBody/CardBody.scss new file mode 100644 index 00000000000000..da69fbe4f98581 --- /dev/null +++ b/packages/react-cards/src/next/CardBody/CardBody.scss @@ -0,0 +1,25 @@ +@import '../CardSection/CardSection.scss'; + +:root { + --card-body-margin: var(--card-section-margin); + + /* modifiers */ + /* fitted card body */ + --card-body-fitted-margin: var(--card-section-fitted-margin); +} + +/* slots */ +.root { + display: flex; + flex-direction: column; + margin: var(--card-body-margin); + + --card-body-margin: var(--card-section-margin); +} + +/* modifiers */ +/* fitted card body */ +.root._fitted { + --card-body-margin: var(--card-body-fitted-margin); + --card-body-fitted-margin: var(--card-section-fitted-margin); +} diff --git a/packages/react-cards/src/next/CardBody/CardBody.tsx b/packages/react-cards/src/next/CardBody/CardBody.tsx new file mode 100644 index 00000000000000..172782b01cac69 --- /dev/null +++ b/packages/react-cards/src/next/CardBody/CardBody.tsx @@ -0,0 +1,18 @@ +import * as React from 'react'; +import { useInlineTokens } from '@fluentui/react-theme-provider'; +import { useFocusRects } from '@uifabric/utilities'; +import { CardSectionProps } from '../CardSection/CardSection.types'; +import { useCardSection } from '../CardSection/useCardSection'; +import { useCardBodyClasses } from './useCardBodyClasses'; + +export const CardBody = React.forwardRef((props, ref) => { + const { render, state } = useCardSection(props, ref); + + useCardBodyClasses(state); + useFocusRects(state.ref as any); + useInlineTokens(state, '--cardBody'); + + return render(state); +}); + +CardBody.displayName = 'CardBody'; diff --git a/packages/react-cards/src/next/CardBody/index.ts b/packages/react-cards/src/next/CardBody/index.ts new file mode 100644 index 00000000000000..7ad963b32a8599 --- /dev/null +++ b/packages/react-cards/src/next/CardBody/index.ts @@ -0,0 +1,2 @@ +export * from './CardBody'; +export * from './useCardBodyClasses'; diff --git a/packages/react-cards/src/next/CardBody/useCardBodyClasses.ts b/packages/react-cards/src/next/CardBody/useCardBodyClasses.ts new file mode 100644 index 00000000000000..a4962387cef5ec --- /dev/null +++ b/packages/react-cards/src/next/CardBody/useCardBodyClasses.ts @@ -0,0 +1,24 @@ +import { makeClasses } from '@fluentui/react-theme-provider'; +import { CardSectionState } from '../CardSection/CardSection.types'; + +const GlobalClassNames = { + root: 'ms-CardBody', +}; + +export const useCardBodyClasses = makeClasses({ + root: [ + GlobalClassNames.root, + { + display: 'flex', + flexDirection: 'column', + margin: 'var(--card-body-margin)', + + '--card-body-margin': 'var(--card-section-margin)', + }, + ], + + _fitted: { + '--card-body-margin': 'var(--card-body-fitted-margin)', + '--card-body-fitted-margin': 'var(--card-section-fitted-margin)', + }, +}); diff --git a/packages/react-cards/src/next/CardFooter/CardFooter.scss b/packages/react-cards/src/next/CardFooter/CardFooter.scss new file mode 100644 index 00000000000000..115c26234a8239 --- /dev/null +++ b/packages/react-cards/src/next/CardFooter/CardFooter.scss @@ -0,0 +1,25 @@ +@import '../CardSection/CardSection.scss'; + +:root { + --card-footer-margin: var(--card-section-margin); + + /* modifiers */ + /* fitted card body */ + --card-footer-fitted-margin: var(--card-section-fitted-margin); +} + +/* slots */ +.root { + display: flex; + flex-direction: column; + margin: var(--card-footer-margin); + + --card-footer-margin: var(--card-section-margin); +} + +/* modifiers */ +/* fitted card body */ +.root._fitted { + --card-footer-margin: var(--card-footer-fitted-margin); + --card-footer-fitted-margin: var(--card-section-fitted-margin); +} diff --git a/packages/react-cards/src/next/CardFooter/CardFooter.tsx b/packages/react-cards/src/next/CardFooter/CardFooter.tsx new file mode 100644 index 00000000000000..6cee41d7824647 --- /dev/null +++ b/packages/react-cards/src/next/CardFooter/CardFooter.tsx @@ -0,0 +1,18 @@ +import * as React from 'react'; +import { useInlineTokens } from '@fluentui/react-theme-provider'; +import { useFocusRects } from '@uifabric/utilities'; +import { CardSectionProps } from '../CardSection/CardSection.types'; +import { useCardSection } from '../CardSection/useCardSection'; +import { useCardFooterClasses } from './useCardFooterClasses'; + +export const CardFooter = React.forwardRef((props, ref) => { + const { render, state } = useCardSection(props, ref); + + useCardFooterClasses(state); + useFocusRects(state.ref as any); + useInlineTokens(state, '--cardFooter'); + + return render(state); +}); + +CardFooter.displayName = 'CardFooter'; diff --git a/packages/react-cards/src/next/CardFooter/index.ts b/packages/react-cards/src/next/CardFooter/index.ts new file mode 100644 index 00000000000000..76562fe1a99c5b --- /dev/null +++ b/packages/react-cards/src/next/CardFooter/index.ts @@ -0,0 +1,2 @@ +export * from './CardFooter'; +export * from './useCardFooterClasses'; diff --git a/packages/react-cards/src/next/CardFooter/useCardFooterClasses.ts b/packages/react-cards/src/next/CardFooter/useCardFooterClasses.ts new file mode 100644 index 00000000000000..a2f91cfbcff678 --- /dev/null +++ b/packages/react-cards/src/next/CardFooter/useCardFooterClasses.ts @@ -0,0 +1,24 @@ +import { makeClasses } from '@fluentui/react-theme-provider'; +import { CardSectionState } from '../CardSection/CardSection.types'; + +const GlobalClassNames = { + root: 'ms-CardFooter', +}; + +export const useCardFooterClasses = makeClasses({ + root: [ + GlobalClassNames.root, + { + display: 'flex', + flexDirection: 'column', + margin: 'var(--card-footer-margin)', + + '--card-footer-margin': 'var(--card-section-margin)', + }, + ], + + _fitted: { + '--card-footer-margin': 'var(--card-footer-fitted-margin)', + '--card-footer-fitted-margin': 'var(--card-section-fitted-margin)', + }, +}); diff --git a/packages/react-cards/src/next/CardHeader/CardHeader.scss b/packages/react-cards/src/next/CardHeader/CardHeader.scss new file mode 100644 index 00000000000000..fafe088212d96f --- /dev/null +++ b/packages/react-cards/src/next/CardHeader/CardHeader.scss @@ -0,0 +1,25 @@ +@import '../CardSection/CardSection.scss'; + +:root { + --card-header-margin: var(--card-section-margin); + + /* modifiers */ + /* fitted card body */ + --card-header-fitted-margin: var(--card-section-fitted-margin); +} + +/* slots */ +.root { + display: flex; + flex-direction: column; + margin: var(--card-header-margin); + + --card-header-margin: var(--card-section-margin); +} + +/* modifiers */ +/* fitted card body */ +.root._fitted { + --card-header-margin: var(--card-header-fitted-margin); + --card-header-fitted-margin: var(--card-section-fitted-margin); +} diff --git a/packages/react-cards/src/next/CardHeader/CardHeader.tsx b/packages/react-cards/src/next/CardHeader/CardHeader.tsx new file mode 100644 index 00000000000000..b975c11b3e1f40 --- /dev/null +++ b/packages/react-cards/src/next/CardHeader/CardHeader.tsx @@ -0,0 +1,18 @@ +import * as React from 'react'; +import { useInlineTokens } from '@fluentui/react-theme-provider'; +import { useFocusRects } from '@uifabric/utilities'; +import { CardSectionProps } from '../CardSection/CardSection.types'; +import { useCardSection } from '../CardSection/useCardSection'; +import { useCardHeaderClasses } from './useCardHeaderClasses'; + +export const CardHeader = React.forwardRef((props, ref) => { + const { render, state } = useCardSection(props, ref); + + useCardHeaderClasses(state); + useFocusRects(state.ref as any); + useInlineTokens(state, '--cardHeader'); + + return render(state); +}); + +CardHeader.displayName = 'CardHeader'; diff --git a/packages/react-cards/src/next/CardHeader/index.ts b/packages/react-cards/src/next/CardHeader/index.ts new file mode 100644 index 00000000000000..d9b5f18959de62 --- /dev/null +++ b/packages/react-cards/src/next/CardHeader/index.ts @@ -0,0 +1,2 @@ +export * from './CardHeader'; +export * from './useCardHeaderClasses'; diff --git a/packages/react-cards/src/next/CardHeader/useCardHeaderClasses.ts b/packages/react-cards/src/next/CardHeader/useCardHeaderClasses.ts new file mode 100644 index 00000000000000..d7b543be2e9606 --- /dev/null +++ b/packages/react-cards/src/next/CardHeader/useCardHeaderClasses.ts @@ -0,0 +1,24 @@ +import { makeClasses } from '@fluentui/react-theme-provider'; +import { CardSectionState } from '../CardSection/CardSection.types'; + +const GlobalClassNames = { + root: 'ms-CardHeader', +}; + +export const useCardHeaderClasses = makeClasses({ + root: [ + GlobalClassNames.root, + { + display: 'flex', + flexDirection: 'column', + margin: 'var(--card-header-margin)', + + '--card-header-margin': 'var(--card-section-margin)', + }, + ], + + _fitted: { + '--card-header-margin': 'var(--card-header-fitted-margin)', + '--card-header-fitted-margin': 'var(--card-section-fitted-margin)', + }, +}); diff --git a/packages/react-cards/src/next/CardPreview/CardPreview.scss b/packages/react-cards/src/next/CardPreview/CardPreview.scss new file mode 100644 index 00000000000000..c7c334c13c47f4 --- /dev/null +++ b/packages/react-cards/src/next/CardPreview/CardPreview.scss @@ -0,0 +1,25 @@ +@import '../CardSection/CardSection.scss'; + +:root { + --card-preview-margin: var(--card-section-margin); + + /* modifiers */ + /* fitted card body */ + --card-preview-fitted-margin: var(--card-section-fitted-margin); +} + +/* slots */ +.root { + display: flex; + flex-direction: column; + margin: var(--card-preview-margin); + + --card-preview-margin: var(--card-section-margin); +} + +/* modifiers */ +/* fitted card body */ +.root._fitted { + --card-preview-margin: var(--card-preview-fitted-margin); + --card-preview-fitted-margin: var(--card-section-fitted-margin); +} diff --git a/packages/react-cards/src/next/CardPreview/CardPreview.tsx b/packages/react-cards/src/next/CardPreview/CardPreview.tsx new file mode 100644 index 00000000000000..fab099102a3956 --- /dev/null +++ b/packages/react-cards/src/next/CardPreview/CardPreview.tsx @@ -0,0 +1,18 @@ +import * as React from 'react'; +import { useInlineTokens } from '@fluentui/react-theme-provider'; +import { useFocusRects } from '@uifabric/utilities'; +import { CardSectionProps } from '../CardSection/CardSection.types'; +import { useCardSection } from '../CardSection/useCardSection'; +import { useCardPreviewClasses } from './useCardPreviewClasses'; + +export const CardPreview = React.forwardRef((props, ref) => { + const { render, state } = useCardSection(props, ref); + + useCardPreviewClasses(state); + useFocusRects(state.ref as any); + useInlineTokens(state, '--cardPreview'); + + return render(state); +}); + +CardPreview.displayName = 'CardPreview'; diff --git a/packages/react-cards/src/next/CardPreview/index.ts b/packages/react-cards/src/next/CardPreview/index.ts new file mode 100644 index 00000000000000..d874fac84c6cfd --- /dev/null +++ b/packages/react-cards/src/next/CardPreview/index.ts @@ -0,0 +1,2 @@ +export * from './CardPreview'; +export * from './useCardPreviewClasses'; diff --git a/packages/react-cards/src/next/CardPreview/useCardPreviewClasses.ts b/packages/react-cards/src/next/CardPreview/useCardPreviewClasses.ts new file mode 100644 index 00000000000000..cc24964f20fdab --- /dev/null +++ b/packages/react-cards/src/next/CardPreview/useCardPreviewClasses.ts @@ -0,0 +1,24 @@ +import { makeClasses } from '@fluentui/react-theme-provider'; +import { CardSectionState } from '../CardSection/CardSection.types'; + +const GlobalClassNames = { + root: 'ms-CardPreview', +}; + +export const useCardPreviewClasses = makeClasses({ + root: [ + GlobalClassNames.root, + { + display: 'flex', + flexDirection: 'column', + margin: 'var(--card-preview-margin)', + + '--card-preview-margin': 'var(--card-section-margin)', + }, + ], + + _fitted: { + '--card-preview-margin': 'var(--card-preview-fitted-margin)', + '--card-preview-fitted-margin': 'var(--card-section-fitted-margin)', + }, +}); diff --git a/packages/react-cards/src/next/CardSection/CardSection.scss b/packages/react-cards/src/next/CardSection/CardSection.scss new file mode 100644 index 00000000000000..4ec538b71dc6de --- /dev/null +++ b/packages/react-cards/src/next/CardSection/CardSection.scss @@ -0,0 +1,7 @@ +:root { + --card-section-margin: 0 0 10px 0; + + /* modifiers */ + /* fitted card section */ + --card-section-fitted-margin: 0; +} diff --git a/packages/react-cards/src/next/CardSection/CardSection.types.ts b/packages/react-cards/src/next/CardSection/CardSection.types.ts new file mode 100644 index 00000000000000..62c6c5ed74f2dd --- /dev/null +++ b/packages/react-cards/src/next/CardSection/CardSection.types.ts @@ -0,0 +1,20 @@ +import * as React from 'react'; +import { BaseSlots, SlotProps } from '@fluentui/react-compose'; +import { ComponentProps } from '@fluentui/react-compose/lib/next/index'; +import { ColorTokenSet } from '@fluentui/theme'; + +/* eslint-disable @typescript-eslint/naming-convention */ + +export type CardSectionProps = ComponentProps & + React.HTMLAttributes & { + /** A card section can be fitted, without any space above or below it. */ + fitted?: boolean; + }; + +export interface CardSectionState extends CardSectionProps {} + +export interface CardSectionSlots extends BaseSlots {} + +export type CardSectionSlotProps = SlotProps>; + +export type CardSectionTokens = ColorTokenSet & {}; diff --git a/packages/react-cards/src/next/CardSection/index.ts b/packages/react-cards/src/next/CardSection/index.ts new file mode 100644 index 00000000000000..7c83d2c1dac814 --- /dev/null +++ b/packages/react-cards/src/next/CardSection/index.ts @@ -0,0 +1,3 @@ +export * from './CardSection.types'; +export * from './renderCardSection'; +export * from './useCardSection'; diff --git a/packages/react-cards/src/next/CardSection/renderCardSection.tsx b/packages/react-cards/src/next/CardSection/renderCardSection.tsx new file mode 100644 index 00000000000000..eec76258565c80 --- /dev/null +++ b/packages/react-cards/src/next/CardSection/renderCardSection.tsx @@ -0,0 +1,10 @@ +import * as React from 'react'; +import { getSlots } from '@fluentui/react-compose/lib/next/index'; +import { CardSectionState } from './CardSection.types'; + +export const renderCardSection = (state: CardSectionState) => { + const { slots, slotProps } = getSlots(state); + const { children } = state; + + return {children}; +}; diff --git a/packages/react-cards/src/next/CardSection/useCardSection.ts b/packages/react-cards/src/next/CardSection/useCardSection.ts new file mode 100644 index 00000000000000..d04ece211482b9 --- /dev/null +++ b/packages/react-cards/src/next/CardSection/useCardSection.ts @@ -0,0 +1,30 @@ +import * as React from 'react'; +import { makeMergeProps, resolveShorthandProps } from '@fluentui/react-compose/lib/next/index'; +import { CardSectionProps, CardSectionState } from './CardSection.types'; +import { renderCardSection } from './renderCardSection'; + +const mergeProps = makeMergeProps({ deepMerge: [] }); + +/** + * Given user props, returns state and render function for a CardSection. + */ +export const useCardSection = ( + props: CardSectionProps, + ref: React.Ref, + defaultProps?: CardSectionProps, +) => { + // Ensure that the `ref` prop can be used by other things (like useFocusRects) to refer to the root. + // NOTE: We are assuming refs should not mutate to undefined. Either they are passed or not. + // eslint-disable-next-line react-hooks/rules-of-hooks + const resolvedRef = ref || React.useRef(); + const state = mergeProps( + { + ref: resolvedRef, + as: 'div', + }, + defaultProps, + resolveShorthandProps(props, []), + ) as CardSectionState; + + return { state, render: renderCardSection }; +}; diff --git a/packages/react-cards/src/next/index.ts b/packages/react-cards/src/next/index.ts new file mode 100644 index 00000000000000..e21621006a2847 --- /dev/null +++ b/packages/react-cards/src/next/index.ts @@ -0,0 +1,10 @@ +export * from './Card'; +export * from './Card.types'; +export * from './useCard'; +export * from './useCardClasses'; +export * from './useCardState'; +export * from './CardBody/index'; +export * from './CardFooter/index'; +export * from './CardHeader/index'; +export * from './CardPreview/index'; +export * from './CardSection/index'; diff --git a/packages/react-cards/src/next/renderCard.tsx b/packages/react-cards/src/next/renderCard.tsx new file mode 100644 index 00000000000000..28b8e35f0ea370 --- /dev/null +++ b/packages/react-cards/src/next/renderCard.tsx @@ -0,0 +1,13 @@ +import * as React from 'react'; +import { getSlots } from '@fluentui/react-compose/lib/next/index'; +import { CardState } from './Card.types'; + +/** + * Define the render function. Given the state of a card, renders it. + */ +export const renderCard = (state: CardState) => { + const { slots, slotProps } = getSlots(state); + const { children } = state; + + return {children}; +}; diff --git a/packages/react-cards/src/next/useCard.ts b/packages/react-cards/src/next/useCard.ts new file mode 100644 index 00000000000000..46b8c8bb28a3c7 --- /dev/null +++ b/packages/react-cards/src/next/useCard.ts @@ -0,0 +1,30 @@ +import * as React from 'react'; +import { makeMergeProps, resolveShorthandProps } from '@fluentui/react-compose/lib/next/index'; +import { useMergedRefs } from '@uifabric/react-hooks'; +import { CardProps, CardState } from './Card.types'; +import { renderCard } from './renderCard'; +import { useCardState } from './useCardState'; + +const mergeProps = makeMergeProps({ deepMerge: [] }); + +/** + * Given user props, returns state and render function for a Card. + */ +export const useCard = (props: CardProps, ref: React.Ref, defaultProps?: CardProps) => { + // Ensure that the `ref` prop can be used by other things (like useFocusRects) to refer to the root. + // NOTE: We are assuming refs should not mutate to undefined. Either they are passed or not. + // eslint-disable-next-line react-hooks/rules-of-hooks + const resolvedRef = ref || React.useRef(); + const state = mergeProps( + { + ref: useMergedRefs(resolvedRef, props.cardRef), + as: 'div', + }, + defaultProps, + resolveShorthandProps(props, []), + ) as CardState; + + useCardState(state); + + return { state, render: renderCard }; +}; diff --git a/packages/react-cards/src/next/useCardClasses.ts b/packages/react-cards/src/next/useCardClasses.ts new file mode 100644 index 00000000000000..4086b5231c0a07 --- /dev/null +++ b/packages/react-cards/src/next/useCardClasses.ts @@ -0,0 +1,177 @@ +import { makeClasses } from '@fluentui/react-theme-provider'; +import { CardState } from './Card.types'; + +const GlobalClassNames = { + root: 'ms-Card', +}; + +export const useCardClasses = makeClasses({ + root: [ + GlobalClassNames.root, + { + boxSizing: 'border-box', + display: 'flex', + flexDirection: 'column', + position: 'relative', + + backgroundColor: 'var(--card-backgroundColor)', + borderColor: 'var(--card-borderColor)', + borderRadius: 'var(--card-borderRadius)', + borderWidth: 'var(--card-borderWidth)', + boxShadow: 'var(--card-boxShadow)', + height: 'var(--card-height)', + margin: 'var(--card-margin)', + minHeight: 'var(--card-minHeight)', + minWidth: 'var(--card-minWidth)', + padding: 'var(--card-padding)', + width: 'var(--card-width)', + + ':global(.ms-Fabric--isFocusVisible) &:focus::after': { + content: '""', + position: 'absolute', + left: 1, + right: 1, + top: 1, + bottom: 1, + borderWidth: 'var(--card-focusWidth, 1px)', + borderStyle: 'solid', + borderColor: 'var(--card-focusBorderColor, white)', + outlineWidth: 'var(--card-focusWidth, 1px)', + outlineStyle: 'solid', + outlineColor: 'var(--card-focusOutlineColor, #605e5c)', + }, + + '&:hover': { + backgroundColor: 'var(--card-hovered-backgroundColor, var(--card-backgroundColor))', + borderColor: 'var(--card-hovered-borderColor, var(--card-borderColor))', + borderWidth: 'var(--card-hovered-borderWidth, var(--card-borderWidth))', + boxShadow: 'var(--card-hovered-boxShadow, var(--card-boxShadow))', + }, + + '&:active': { + backgroundColor: 'var(--card-pressed-backgroundColor, var(--card-backgroundColor))', + borderColor: 'var(--card-pressed-borderColor, var(--card-borderColor))', + borderWidth: 'var(--card-pressed-borderWidth, var(--card-borderWidth))', + boxShadow: 'var(--card-pressed-boxShadow, var(--card-boxShadow))', + }, + }, + ], + + _onClick: { + cursor: 'pointer', + + '--card-backgroundColor': 'var(--card-clickable-backgroundColor)', + '--card-borderColor': 'var(--card-clickable-borderColor)', + '--card-borderWidth': 'var(--card-clickable-borderWidth)', + '--card-boxShadow': 'var(--card-clickable-boxShadow)', + + /* selectors */ + '&:hover': { + '--card-hovered-backgroundColor': 'var(--card-clickable-hovered-backgroundColor)', + '--card-hovered-borderColor': 'var(--card-clickable-hovered-borderColor)', + '--card-hovered-borderWidth': 'var(--card-clickable-hovered-borderWidth)', + '--card-hovered-boxShadow': 'var(--card-clickable-hovered-boxShadow)', + }, + + '&:active': { + '--card-pressed-backgroundColor': 'var(--card-clickable-pressed-backgroundColor)', + '--card-pressed-borderColor': 'var(--card-clickable-pressed-borderColor)', + '--card-pressed-borderWidth': 'var(--card-clickable-pressed-borderWidth)', + '--card-pressed-boxShadow': 'var(--card-clickable-pressed-boxShadow)', + }, + }, + + _compact: { + '--card-padding': 'var(--card-compact-padding)', + }, + + _disabled: { + cursor: 'not-allowed', + + '--card-backgroundColor': 'var(--card-disabled-backgroundColor)', + '--card-borderColor': 'var(--card-disabled-borderColor)', + '--card-borderWidth': 'var(--card-disabled-borderWidth)', + '--card-boxShadow': 'var(--card-disabled-boxShadow)', + + /* selectors */ + '&:hover': { + '--card-hovered-backgroundColor': 'var(--card-disabled-backgroundColor)', + '--card-hovered-borderColor': 'var(--card-disabled-borderColor)', + '--card-hovered-borderWidth': 'var(--card-disabled-borderWidth)', + '--card-hovered-boxShadow': 'var(--card-disabled-boxShadow)', + }, + + '&:active': { + '--card-pressed-backgroundColor': 'var(--card-disabled-backgroundColor)', + '--card-pressed-borderColor': 'var(--card-disabled-borderColor)', + '--card-pressed-borderWidth': 'var(--card-disabled-borderWidth)', + '--card-pressed-boxShadow': 'var(--card-disabled-boxShadow)', + }, + }, + + _fluid: { + '--card-height': 'var(--card-fluid-height)', + '--card-width': 'var(--card-fluid-width)', + }, + + _selected: { + '--card-backgroundColor': 'var(--card-selected-backgroundColor)', + '--card-borderColor': 'var(--card-selected-borderColor)', + '--card-borderWidth': 'var(--card-selected-borderWidth)', + '--card-boxShadow': 'var(--card-selected-boxShadow)', + }, + + // eslint-disable-next-line @typescript-eslint/naming-convention + _size_smallest: { + '--card-borderRadius': 'var(--card-size-smallest-borderRadius)', + '--card-height': 'var(--card-size-smallest-height)', + '--card-margin': 'var(--card-size-smallest-margin)', + '--card-padding': 'var(--card-size-smallest-padding)', + '--card-width': 'var(--card-size-smallest-width)', + }, + + // eslint-disable-next-line @typescript-eslint/naming-convention + _size_smaller: { + '--card-borderRadius': 'var(--card-size-smaller-borderRadius)', + '--card-height': 'var(--card-size-smaller-height)', + '--card-margin': 'var(--card-size-smaller-margin)', + '--card-padding': 'var(--card-size-smaller-padding)', + '--card-width': 'var(--card-size-smaller-width)', + }, + + // eslint-disable-next-line @typescript-eslint/naming-convention + _size_small: { + '--card-borderRadius': 'var(--card-size-small-borderRadius)', + '--card-height': 'var(--card-size-small-height)', + '--card-margin': 'var(--card-size-small-margin)', + '--card-padding': 'var(--card-size-small-padding)', + '--card-width': 'var(--card-size-small-width)', + }, + + // eslint-disable-next-line @typescript-eslint/naming-convention + _size_large: { + '--card-borderRadius': 'var(--card-size-large-borderRadius)', + '--card-height': 'var(--card-size-large-height)', + '--card-margin': 'var(--card-size-large-margin)', + '--card-padding': 'var(--card-size-large-padding)', + '--card-width': 'var(--card-size-large-width)', + }, + + // eslint-disable-next-line @typescript-eslint/naming-convention + _size_larger: { + '--card-borderRadius': 'var(--card-size-larger-borderRadius)', + '--card-height': 'var(--card-size-larger-height)', + '--card-margin': 'var(--card-size-larger-margin)', + '--card-padding': 'var(--card-size-larger-padding)', + '--card-width': 'var(--card-size-larger-width)', + }, + + // eslint-disable-next-line @typescript-eslint/naming-convention + _size_largest: { + '--card-borderRadius': 'var(--card-size-largest-borderRadius)', + '--card-height': 'var(--card-size-largest-height)', + '--card-margin': 'var(--card-size-largest-margin)', + '--card-padding': 'var(--card-size-largest-padding)', + '--card-width': 'var(--card-size-largest-width)', + }, +}); diff --git a/packages/react-cards/src/next/useCardState.ts b/packages/react-cards/src/next/useCardState.ts new file mode 100644 index 00000000000000..9360f84b8bd44f --- /dev/null +++ b/packages/react-cards/src/next/useCardState.ts @@ -0,0 +1,37 @@ +import * as React from 'react'; +import { getCode, EnterKey, SpacebarKey } from '@fluentui/keyboard-key'; +import { CardState } from './Card.types'; + +/** + * The useCard hook processes the Card draft state. + * @param draftState - Card draft state to mutate. + */ +export const useCardState = (draftState: CardState) => { + // Update the card's tab-index, keyboard handling, and aria attributes. + const { disabled, onClick, onKeyDown } = draftState; + + draftState['aria-disabled'] = disabled; + draftState.role = 'group'; + draftState.tabIndex = !disabled && onClick ? 0 : undefined; + + draftState.onClick = onClick + ? (ev: React.MouseEvent) => { + if (!disabled) { + onClick(ev); + } + } + : undefined; + + draftState.onKeyDown = (ev: React.KeyboardEvent) => { + if (!disabled) { + onKeyDown?.(ev); + + if (draftState.onClick) { + const eventCode = getCode(ev); + if (eventCode === EnterKey || eventCode === SpacebarKey) { + draftState.onClick(ev as any); + } + } + } + }; +}; diff --git a/packages/react-theme-provider/src/__snapshots__/ThemeProvider.test.tsx.snap b/packages/react-theme-provider/src/__snapshots__/ThemeProvider.test.tsx.snap index 706243d1a56ec3..23efa4836d573d 100644 --- a/packages/react-theme-provider/src/__snapshots__/ThemeProvider.test.tsx.snap +++ b/packages/react-theme-provider/src/__snapshots__/ThemeProvider.test.tsx.snap @@ -73,6 +73,83 @@ exports[`ThemeProvider can apply body theme to body 1`] = ` --button-size-small: 24px; --button-size-smaller: 24px; --button-size-smallest: 24px; + --card-backgroundColor: #ffffff; + --card-borderColor: transparent; + --card-borderRadius: var(--card-size-medium-borderRadius); + --card-borderWidth: 1px; + --card-boxShadow: 0 1.6px 3.6px 0 rgba(0, 0, 0, 0.1); + --card-clickable-backgroundColor: #ffffff; + --card-clickable-borderColor: transparent; + --card-clickable-borderWidth: 1px; + --card-clickable-boxShadow: 0 1.6px 3.6px 0 rgba(0, 0, 0, 0.1); + --card-clickable-hovered-backgroundColor: #fafafa; + --card-clickable-hovered-borderColor: var(--card-clickable-borderColor); + --card-clickable-hovered-borderWidth: var(--card-clickable-borderWidth); + --card-clickable-hovered-boxShadow: 0 3.2px 7.2px 0 rgba(0, 0, 0, 0.1); + --card-clickable-pressed-backgroundColor: #f5f5f5; + --card-clickable-pressed-borderColor: var(--card-clickable-borderColor); + --card-clickable-pressed-borderWidth: 2px; + --card-clickable-pressed-boxShadow: var(--card-clickable-boxShadow); + --card-compact-padding: 0; + --card-disabled-backgroundColor: #f0f0f0; + --card-disabled-borderColor: var(--card-borderColor); + --card-disabled-borderWidth: var(--card-borderWidth); + --card-disabled-boxShadow: 0 0.8px 1.8px 0 rgba(0, 0, 0, 0.1); + --card-fluid-height: 100%; + --card-fluid-width: 100%; + --card-height: var(--card-size-medium-height); + --card-hovered-backgroundColor: var(--card-backgroundColor); + --card-hovered-borderColor: var(--card-borderColor); + --card-hovered-borderWidth: var(--card-borderWidth); + --card-hovered-boxShadow: var(--card-boxShadow); + --card-margin: var(--card-size-medium-margin); + --card-minHeight: 32px; + --card-minWidth: 100px; + --card-padding: var(--card-size-medium-padding); + --card-pressed-backgroundColor: var(--card-backgroundColor); + --card-pressed-borderColor: var(--card-borderColor); + --card-pressed-borderWidth: var(--card-borderWidth); + --card-pressed-boxShadow: var(--card-boxShadow); + --card-selected-backgroundColor: #fafafa; + --card-selected-borderColor: var(--card-borderColor); + --card-selected-borderWidth: var(--card-borderWidth); + --card-selected-boxShadow: var(--card-boxShadow); + --card-size-large-borderRadius: 6px; + --card-size-large-height: auto; + --card-size-large-margin: 0; + --card-size-large-padding: 16px; + --card-size-large-width: auto; + --card-size-larger-borderRadius: 6px; + --card-size-larger-height: auto; + --card-size-larger-margin: 0; + --card-size-larger-padding: 16px; + --card-size-larger-width: auto; + --card-size-largest-borderRadius: 6px; + --card-size-largest-height: auto; + --card-size-largest-margin: 0; + --card-size-largest-padding: 16px; + --card-size-largest-width: auto; + --card-size-medium-borderRadius: 4px; + --card-size-medium-height: auto; + --card-size-medium-margin: 0; + --card-size-medium-padding: 16px; + --card-size-medium-width: auto; + --card-size-small-borderRadius: 4px; + --card-size-small-height: auto; + --card-size-small-margin: 0; + --card-size-small-padding: 8px; + --card-size-small-width: auto; + --card-size-smaller-borderRadius: 4px; + --card-size-smaller-height: auto; + --card-size-smaller-margin: 0; + --card-size-smaller-padding: 8px; + --card-size-smaller-width: auto; + --card-size-smallest-borderRadius: 4px; + --card-size-smallest-height: auto; + --card-size-smallest-margin: 0; + --card-size-smallest-padding: 8px; + --card-size-smallest-width: auto; + --card-width: var(--card-size-medium-width); --color-body-background: black; --color-body-contentColor: white; --color-brand-background: #0078d4; @@ -227,6 +304,83 @@ exports[`ThemeProvider can apply body theme to body 2`] = ` --button-size-small: 24px; --button-size-smaller: 24px; --button-size-smallest: 24px; + --card-backgroundColor: #ffffff; + --card-borderColor: transparent; + --card-borderRadius: var(--card-size-medium-borderRadius); + --card-borderWidth: 1px; + --card-boxShadow: 0 1.6px 3.6px 0 rgba(0, 0, 0, 0.1); + --card-clickable-backgroundColor: #ffffff; + --card-clickable-borderColor: transparent; + --card-clickable-borderWidth: 1px; + --card-clickable-boxShadow: 0 1.6px 3.6px 0 rgba(0, 0, 0, 0.1); + --card-clickable-hovered-backgroundColor: #fafafa; + --card-clickable-hovered-borderColor: var(--card-clickable-borderColor); + --card-clickable-hovered-borderWidth: var(--card-clickable-borderWidth); + --card-clickable-hovered-boxShadow: 0 3.2px 7.2px 0 rgba(0, 0, 0, 0.1); + --card-clickable-pressed-backgroundColor: #f5f5f5; + --card-clickable-pressed-borderColor: var(--card-clickable-borderColor); + --card-clickable-pressed-borderWidth: 2px; + --card-clickable-pressed-boxShadow: var(--card-clickable-boxShadow); + --card-compact-padding: 0; + --card-disabled-backgroundColor: #f0f0f0; + --card-disabled-borderColor: var(--card-borderColor); + --card-disabled-borderWidth: var(--card-borderWidth); + --card-disabled-boxShadow: 0 0.8px 1.8px 0 rgba(0, 0, 0, 0.1); + --card-fluid-height: 100%; + --card-fluid-width: 100%; + --card-height: var(--card-size-medium-height); + --card-hovered-backgroundColor: var(--card-backgroundColor); + --card-hovered-borderColor: var(--card-borderColor); + --card-hovered-borderWidth: var(--card-borderWidth); + --card-hovered-boxShadow: var(--card-boxShadow); + --card-margin: var(--card-size-medium-margin); + --card-minHeight: 32px; + --card-minWidth: 100px; + --card-padding: var(--card-size-medium-padding); + --card-pressed-backgroundColor: var(--card-backgroundColor); + --card-pressed-borderColor: var(--card-borderColor); + --card-pressed-borderWidth: var(--card-borderWidth); + --card-pressed-boxShadow: var(--card-boxShadow); + --card-selected-backgroundColor: #fafafa; + --card-selected-borderColor: var(--card-borderColor); + --card-selected-borderWidth: var(--card-borderWidth); + --card-selected-boxShadow: var(--card-boxShadow); + --card-size-large-borderRadius: 6px; + --card-size-large-height: auto; + --card-size-large-margin: 0; + --card-size-large-padding: 16px; + --card-size-large-width: auto; + --card-size-larger-borderRadius: 6px; + --card-size-larger-height: auto; + --card-size-larger-margin: 0; + --card-size-larger-padding: 16px; + --card-size-larger-width: auto; + --card-size-largest-borderRadius: 6px; + --card-size-largest-height: auto; + --card-size-largest-margin: 0; + --card-size-largest-padding: 16px; + --card-size-largest-width: auto; + --card-size-medium-borderRadius: 4px; + --card-size-medium-height: auto; + --card-size-medium-margin: 0; + --card-size-medium-padding: 16px; + --card-size-medium-width: auto; + --card-size-small-borderRadius: 4px; + --card-size-small-height: auto; + --card-size-small-margin: 0; + --card-size-small-padding: 8px; + --card-size-small-width: auto; + --card-size-smaller-borderRadius: 4px; + --card-size-smaller-height: auto; + --card-size-smaller-margin: 0; + --card-size-smaller-padding: 8px; + --card-size-smaller-width: auto; + --card-size-smallest-borderRadius: 4px; + --card-size-smallest-height: auto; + --card-size-smallest-margin: 0; + --card-size-smallest-padding: 8px; + --card-size-smallest-width: auto; + --card-width: var(--card-size-medium-width); --color-body-background: black; --color-body-contentColor: white; --color-brand-background: #0078d4; @@ -374,6 +528,83 @@ exports[`ThemeProvider can apply body theme to none 1`] = ` --button-size-small: 24px; --button-size-smaller: 24px; --button-size-smallest: 24px; + --card-backgroundColor: #ffffff; + --card-borderColor: transparent; + --card-borderRadius: var(--card-size-medium-borderRadius); + --card-borderWidth: 1px; + --card-boxShadow: 0 1.6px 3.6px 0 rgba(0, 0, 0, 0.1); + --card-clickable-backgroundColor: #ffffff; + --card-clickable-borderColor: transparent; + --card-clickable-borderWidth: 1px; + --card-clickable-boxShadow: 0 1.6px 3.6px 0 rgba(0, 0, 0, 0.1); + --card-clickable-hovered-backgroundColor: #fafafa; + --card-clickable-hovered-borderColor: var(--card-clickable-borderColor); + --card-clickable-hovered-borderWidth: var(--card-clickable-borderWidth); + --card-clickable-hovered-boxShadow: 0 3.2px 7.2px 0 rgba(0, 0, 0, 0.1); + --card-clickable-pressed-backgroundColor: #f5f5f5; + --card-clickable-pressed-borderColor: var(--card-clickable-borderColor); + --card-clickable-pressed-borderWidth: 2px; + --card-clickable-pressed-boxShadow: var(--card-clickable-boxShadow); + --card-compact-padding: 0; + --card-disabled-backgroundColor: #f0f0f0; + --card-disabled-borderColor: var(--card-borderColor); + --card-disabled-borderWidth: var(--card-borderWidth); + --card-disabled-boxShadow: 0 0.8px 1.8px 0 rgba(0, 0, 0, 0.1); + --card-fluid-height: 100%; + --card-fluid-width: 100%; + --card-height: var(--card-size-medium-height); + --card-hovered-backgroundColor: var(--card-backgroundColor); + --card-hovered-borderColor: var(--card-borderColor); + --card-hovered-borderWidth: var(--card-borderWidth); + --card-hovered-boxShadow: var(--card-boxShadow); + --card-margin: var(--card-size-medium-margin); + --card-minHeight: 32px; + --card-minWidth: 100px; + --card-padding: var(--card-size-medium-padding); + --card-pressed-backgroundColor: var(--card-backgroundColor); + --card-pressed-borderColor: var(--card-borderColor); + --card-pressed-borderWidth: var(--card-borderWidth); + --card-pressed-boxShadow: var(--card-boxShadow); + --card-selected-backgroundColor: #fafafa; + --card-selected-borderColor: var(--card-borderColor); + --card-selected-borderWidth: var(--card-borderWidth); + --card-selected-boxShadow: var(--card-boxShadow); + --card-size-large-borderRadius: 6px; + --card-size-large-height: auto; + --card-size-large-margin: 0; + --card-size-large-padding: 16px; + --card-size-large-width: auto; + --card-size-larger-borderRadius: 6px; + --card-size-larger-height: auto; + --card-size-larger-margin: 0; + --card-size-larger-padding: 16px; + --card-size-larger-width: auto; + --card-size-largest-borderRadius: 6px; + --card-size-largest-height: auto; + --card-size-largest-margin: 0; + --card-size-largest-padding: 16px; + --card-size-largest-width: auto; + --card-size-medium-borderRadius: 4px; + --card-size-medium-height: auto; + --card-size-medium-margin: 0; + --card-size-medium-padding: 16px; + --card-size-medium-width: auto; + --card-size-small-borderRadius: 4px; + --card-size-small-height: auto; + --card-size-small-margin: 0; + --card-size-small-padding: 8px; + --card-size-small-width: auto; + --card-size-smaller-borderRadius: 4px; + --card-size-smaller-height: auto; + --card-size-smaller-margin: 0; + --card-size-smaller-padding: 8px; + --card-size-smaller-width: auto; + --card-size-smallest-borderRadius: 4px; + --card-size-smallest-height: auto; + --card-size-smallest-margin: 0; + --card-size-smallest-padding: 8px; + --card-size-smallest-width: auto; + --card-width: var(--card-size-medium-width); --color-body-background: black; --color-body-contentColor: white; --color-brand-background: #0078d4; @@ -521,6 +752,83 @@ exports[`ThemeProvider can handle a partial theme 1`] = ` --button-size-small: 24px; --button-size-smaller: 24px; --button-size-smallest: 24px; + --card-backgroundColor: #ffffff; + --card-borderColor: transparent; + --card-borderRadius: var(--card-size-medium-borderRadius); + --card-borderWidth: 1px; + --card-boxShadow: 0 1.6px 3.6px 0 rgba(0, 0, 0, 0.1); + --card-clickable-backgroundColor: #ffffff; + --card-clickable-borderColor: transparent; + --card-clickable-borderWidth: 1px; + --card-clickable-boxShadow: 0 1.6px 3.6px 0 rgba(0, 0, 0, 0.1); + --card-clickable-hovered-backgroundColor: #fafafa; + --card-clickable-hovered-borderColor: var(--card-clickable-borderColor); + --card-clickable-hovered-borderWidth: var(--card-clickable-borderWidth); + --card-clickable-hovered-boxShadow: 0 3.2px 7.2px 0 rgba(0, 0, 0, 0.1); + --card-clickable-pressed-backgroundColor: #f5f5f5; + --card-clickable-pressed-borderColor: var(--card-clickable-borderColor); + --card-clickable-pressed-borderWidth: 2px; + --card-clickable-pressed-boxShadow: var(--card-clickable-boxShadow); + --card-compact-padding: 0; + --card-disabled-backgroundColor: #f0f0f0; + --card-disabled-borderColor: var(--card-borderColor); + --card-disabled-borderWidth: var(--card-borderWidth); + --card-disabled-boxShadow: 0 0.8px 1.8px 0 rgba(0, 0, 0, 0.1); + --card-fluid-height: 100%; + --card-fluid-width: 100%; + --card-height: var(--card-size-medium-height); + --card-hovered-backgroundColor: var(--card-backgroundColor); + --card-hovered-borderColor: var(--card-borderColor); + --card-hovered-borderWidth: var(--card-borderWidth); + --card-hovered-boxShadow: var(--card-boxShadow); + --card-margin: var(--card-size-medium-margin); + --card-minHeight: 32px; + --card-minWidth: 100px; + --card-padding: var(--card-size-medium-padding); + --card-pressed-backgroundColor: var(--card-backgroundColor); + --card-pressed-borderColor: var(--card-borderColor); + --card-pressed-borderWidth: var(--card-borderWidth); + --card-pressed-boxShadow: var(--card-boxShadow); + --card-selected-backgroundColor: #fafafa; + --card-selected-borderColor: var(--card-borderColor); + --card-selected-borderWidth: var(--card-borderWidth); + --card-selected-boxShadow: var(--card-boxShadow); + --card-size-large-borderRadius: 6px; + --card-size-large-height: auto; + --card-size-large-margin: 0; + --card-size-large-padding: 16px; + --card-size-large-width: auto; + --card-size-larger-borderRadius: 6px; + --card-size-larger-height: auto; + --card-size-larger-margin: 0; + --card-size-larger-padding: 16px; + --card-size-larger-width: auto; + --card-size-largest-borderRadius: 6px; + --card-size-largest-height: auto; + --card-size-largest-margin: 0; + --card-size-largest-padding: 16px; + --card-size-largest-width: auto; + --card-size-medium-borderRadius: 4px; + --card-size-medium-height: auto; + --card-size-medium-margin: 0; + --card-size-medium-padding: 16px; + --card-size-medium-width: auto; + --card-size-small-borderRadius: 4px; + --card-size-small-height: auto; + --card-size-small-margin: 0; + --card-size-small-padding: 8px; + --card-size-small-width: auto; + --card-size-smaller-borderRadius: 4px; + --card-size-smaller-height: auto; + --card-size-smaller-margin: 0; + --card-size-smaller-padding: 8px; + --card-size-smaller-width: auto; + --card-size-smallest-borderRadius: 4px; + --card-size-smallest-height: auto; + --card-size-smallest-margin: 0; + --card-size-smallest-padding: 8px; + --card-size-smallest-width: auto; + --card-width: var(--card-size-medium-width); --color-body-background: #ffffff; --color-body-contentColor: #323130; --color-brand-background: #0078d4; @@ -678,6 +986,83 @@ exports[`ThemeProvider renders a div 1`] = ` --button-size-small: 24px; --button-size-smaller: 24px; --button-size-smallest: 24px; + --card-backgroundColor: #ffffff; + --card-borderColor: transparent; + --card-borderRadius: var(--card-size-medium-borderRadius); + --card-borderWidth: 1px; + --card-boxShadow: 0 1.6px 3.6px 0 rgba(0, 0, 0, 0.1); + --card-clickable-backgroundColor: #ffffff; + --card-clickable-borderColor: transparent; + --card-clickable-borderWidth: 1px; + --card-clickable-boxShadow: 0 1.6px 3.6px 0 rgba(0, 0, 0, 0.1); + --card-clickable-hovered-backgroundColor: #fafafa; + --card-clickable-hovered-borderColor: var(--card-clickable-borderColor); + --card-clickable-hovered-borderWidth: var(--card-clickable-borderWidth); + --card-clickable-hovered-boxShadow: 0 3.2px 7.2px 0 rgba(0, 0, 0, 0.1); + --card-clickable-pressed-backgroundColor: #f5f5f5; + --card-clickable-pressed-borderColor: var(--card-clickable-borderColor); + --card-clickable-pressed-borderWidth: 2px; + --card-clickable-pressed-boxShadow: var(--card-clickable-boxShadow); + --card-compact-padding: 0; + --card-disabled-backgroundColor: #f0f0f0; + --card-disabled-borderColor: var(--card-borderColor); + --card-disabled-borderWidth: var(--card-borderWidth); + --card-disabled-boxShadow: 0 0.8px 1.8px 0 rgba(0, 0, 0, 0.1); + --card-fluid-height: 100%; + --card-fluid-width: 100%; + --card-height: var(--card-size-medium-height); + --card-hovered-backgroundColor: var(--card-backgroundColor); + --card-hovered-borderColor: var(--card-borderColor); + --card-hovered-borderWidth: var(--card-borderWidth); + --card-hovered-boxShadow: var(--card-boxShadow); + --card-margin: var(--card-size-medium-margin); + --card-minHeight: 32px; + --card-minWidth: 100px; + --card-padding: var(--card-size-medium-padding); + --card-pressed-backgroundColor: var(--card-backgroundColor); + --card-pressed-borderColor: var(--card-borderColor); + --card-pressed-borderWidth: var(--card-borderWidth); + --card-pressed-boxShadow: var(--card-boxShadow); + --card-selected-backgroundColor: #fafafa; + --card-selected-borderColor: var(--card-borderColor); + --card-selected-borderWidth: var(--card-borderWidth); + --card-selected-boxShadow: var(--card-boxShadow); + --card-size-large-borderRadius: 6px; + --card-size-large-height: auto; + --card-size-large-margin: 0; + --card-size-large-padding: 16px; + --card-size-large-width: auto; + --card-size-larger-borderRadius: 6px; + --card-size-larger-height: auto; + --card-size-larger-margin: 0; + --card-size-larger-padding: 16px; + --card-size-larger-width: auto; + --card-size-largest-borderRadius: 6px; + --card-size-largest-height: auto; + --card-size-largest-margin: 0; + --card-size-largest-padding: 16px; + --card-size-largest-width: auto; + --card-size-medium-borderRadius: 4px; + --card-size-medium-height: auto; + --card-size-medium-margin: 0; + --card-size-medium-padding: 16px; + --card-size-medium-width: auto; + --card-size-small-borderRadius: 4px; + --card-size-small-height: auto; + --card-size-small-margin: 0; + --card-size-small-padding: 8px; + --card-size-small-width: auto; + --card-size-smaller-borderRadius: 4px; + --card-size-smaller-height: auto; + --card-size-smaller-margin: 0; + --card-size-smaller-padding: 8px; + --card-size-smaller-width: auto; + --card-size-smallest-borderRadius: 4px; + --card-size-smallest-height: auto; + --card-size-smallest-margin: 0; + --card-size-smallest-padding: 8px; + --card-size-smallest-width: auto; + --card-width: var(--card-size-medium-width); --color-body-background: #ffffff; --color-body-contentColor: #323130; --color-brand-background: #0078d4; @@ -834,6 +1219,83 @@ exports[`ThemeProvider renders a div with styling 1`] = ` --button-size-small: 24px; --button-size-smaller: 24px; --button-size-smallest: 24px; + --card-backgroundColor: #ffffff; + --card-borderColor: transparent; + --card-borderRadius: var(--card-size-medium-borderRadius); + --card-borderWidth: 1px; + --card-boxShadow: 0 1.6px 3.6px 0 rgba(0, 0, 0, 0.1); + --card-clickable-backgroundColor: #ffffff; + --card-clickable-borderColor: transparent; + --card-clickable-borderWidth: 1px; + --card-clickable-boxShadow: 0 1.6px 3.6px 0 rgba(0, 0, 0, 0.1); + --card-clickable-hovered-backgroundColor: #fafafa; + --card-clickable-hovered-borderColor: var(--card-clickable-borderColor); + --card-clickable-hovered-borderWidth: var(--card-clickable-borderWidth); + --card-clickable-hovered-boxShadow: 0 3.2px 7.2px 0 rgba(0, 0, 0, 0.1); + --card-clickable-pressed-backgroundColor: #f5f5f5; + --card-clickable-pressed-borderColor: var(--card-clickable-borderColor); + --card-clickable-pressed-borderWidth: 2px; + --card-clickable-pressed-boxShadow: var(--card-clickable-boxShadow); + --card-compact-padding: 0; + --card-disabled-backgroundColor: #f0f0f0; + --card-disabled-borderColor: var(--card-borderColor); + --card-disabled-borderWidth: var(--card-borderWidth); + --card-disabled-boxShadow: 0 0.8px 1.8px 0 rgba(0, 0, 0, 0.1); + --card-fluid-height: 100%; + --card-fluid-width: 100%; + --card-height: var(--card-size-medium-height); + --card-hovered-backgroundColor: var(--card-backgroundColor); + --card-hovered-borderColor: var(--card-borderColor); + --card-hovered-borderWidth: var(--card-borderWidth); + --card-hovered-boxShadow: var(--card-boxShadow); + --card-margin: var(--card-size-medium-margin); + --card-minHeight: 32px; + --card-minWidth: 100px; + --card-padding: var(--card-size-medium-padding); + --card-pressed-backgroundColor: var(--card-backgroundColor); + --card-pressed-borderColor: var(--card-borderColor); + --card-pressed-borderWidth: var(--card-borderWidth); + --card-pressed-boxShadow: var(--card-boxShadow); + --card-selected-backgroundColor: #fafafa; + --card-selected-borderColor: var(--card-borderColor); + --card-selected-borderWidth: var(--card-borderWidth); + --card-selected-boxShadow: var(--card-boxShadow); + --card-size-large-borderRadius: 6px; + --card-size-large-height: auto; + --card-size-large-margin: 0; + --card-size-large-padding: 16px; + --card-size-large-width: auto; + --card-size-larger-borderRadius: 6px; + --card-size-larger-height: auto; + --card-size-larger-margin: 0; + --card-size-larger-padding: 16px; + --card-size-larger-width: auto; + --card-size-largest-borderRadius: 6px; + --card-size-largest-height: auto; + --card-size-largest-margin: 0; + --card-size-largest-padding: 16px; + --card-size-largest-width: auto; + --card-size-medium-borderRadius: 4px; + --card-size-medium-height: auto; + --card-size-medium-margin: 0; + --card-size-medium-padding: 16px; + --card-size-medium-width: auto; + --card-size-small-borderRadius: 4px; + --card-size-small-height: auto; + --card-size-small-margin: 0; + --card-size-small-padding: 8px; + --card-size-small-width: auto; + --card-size-smaller-borderRadius: 4px; + --card-size-smaller-height: auto; + --card-size-smaller-margin: 0; + --card-size-smaller-padding: 8px; + --card-size-smaller-width: auto; + --card-size-smallest-borderRadius: 4px; + --card-size-smallest-height: auto; + --card-size-smallest-margin: 0; + --card-size-smallest-padding: 8px; + --card-size-smallest-width: auto; + --card-width: var(--card-size-medium-width); --color-body-background: white; --color-body-contentColor: black; --color-brand-background: #0078d4; @@ -990,6 +1452,83 @@ exports[`ThemeProvider renders nested themes 1`] = ` --button-size-small: 24px; --button-size-smaller: 24px; --button-size-smallest: 24px; + --card-backgroundColor: #ffffff; + --card-borderColor: transparent; + --card-borderRadius: var(--card-size-medium-borderRadius); + --card-borderWidth: 1px; + --card-boxShadow: 0 1.6px 3.6px 0 rgba(0, 0, 0, 0.1); + --card-clickable-backgroundColor: #ffffff; + --card-clickable-borderColor: transparent; + --card-clickable-borderWidth: 1px; + --card-clickable-boxShadow: 0 1.6px 3.6px 0 rgba(0, 0, 0, 0.1); + --card-clickable-hovered-backgroundColor: #fafafa; + --card-clickable-hovered-borderColor: var(--card-clickable-borderColor); + --card-clickable-hovered-borderWidth: var(--card-clickable-borderWidth); + --card-clickable-hovered-boxShadow: 0 3.2px 7.2px 0 rgba(0, 0, 0, 0.1); + --card-clickable-pressed-backgroundColor: #f5f5f5; + --card-clickable-pressed-borderColor: var(--card-clickable-borderColor); + --card-clickable-pressed-borderWidth: 2px; + --card-clickable-pressed-boxShadow: var(--card-clickable-boxShadow); + --card-compact-padding: 0; + --card-disabled-backgroundColor: #f0f0f0; + --card-disabled-borderColor: var(--card-borderColor); + --card-disabled-borderWidth: var(--card-borderWidth); + --card-disabled-boxShadow: 0 0.8px 1.8px 0 rgba(0, 0, 0, 0.1); + --card-fluid-height: 100%; + --card-fluid-width: 100%; + --card-height: var(--card-size-medium-height); + --card-hovered-backgroundColor: var(--card-backgroundColor); + --card-hovered-borderColor: var(--card-borderColor); + --card-hovered-borderWidth: var(--card-borderWidth); + --card-hovered-boxShadow: var(--card-boxShadow); + --card-margin: var(--card-size-medium-margin); + --card-minHeight: 32px; + --card-minWidth: 100px; + --card-padding: var(--card-size-medium-padding); + --card-pressed-backgroundColor: var(--card-backgroundColor); + --card-pressed-borderColor: var(--card-borderColor); + --card-pressed-borderWidth: var(--card-borderWidth); + --card-pressed-boxShadow: var(--card-boxShadow); + --card-selected-backgroundColor: #fafafa; + --card-selected-borderColor: var(--card-borderColor); + --card-selected-borderWidth: var(--card-borderWidth); + --card-selected-boxShadow: var(--card-boxShadow); + --card-size-large-borderRadius: 6px; + --card-size-large-height: auto; + --card-size-large-margin: 0; + --card-size-large-padding: 16px; + --card-size-large-width: auto; + --card-size-larger-borderRadius: 6px; + --card-size-larger-height: auto; + --card-size-larger-margin: 0; + --card-size-larger-padding: 16px; + --card-size-larger-width: auto; + --card-size-largest-borderRadius: 6px; + --card-size-largest-height: auto; + --card-size-largest-margin: 0; + --card-size-largest-padding: 16px; + --card-size-largest-width: auto; + --card-size-medium-borderRadius: 4px; + --card-size-medium-height: auto; + --card-size-medium-margin: 0; + --card-size-medium-padding: 16px; + --card-size-medium-width: auto; + --card-size-small-borderRadius: 4px; + --card-size-small-height: auto; + --card-size-small-margin: 0; + --card-size-small-padding: 8px; + --card-size-small-width: auto; + --card-size-smaller-borderRadius: 4px; + --card-size-smaller-height: auto; + --card-size-smaller-margin: 0; + --card-size-smaller-padding: 8px; + --card-size-smaller-width: auto; + --card-size-smallest-borderRadius: 4px; + --card-size-smallest-height: auto; + --card-size-smallest-margin: 0; + --card-size-smallest-padding: 8px; + --card-size-smallest-width: auto; + --card-width: var(--card-size-medium-width); --color-body-background: white; --color-body-contentColor: black; --color-brand-background: #0078d4; @@ -1144,6 +1683,83 @@ exports[`ThemeProvider renders nested themes 1`] = ` --button-size-small: 24px; --button-size-smaller: 24px; --button-size-smallest: 24px; + --card-backgroundColor: #ffffff; + --card-borderColor: transparent; + --card-borderRadius: var(--card-size-medium-borderRadius); + --card-borderWidth: 1px; + --card-boxShadow: 0 1.6px 3.6px 0 rgba(0, 0, 0, 0.1); + --card-clickable-backgroundColor: #ffffff; + --card-clickable-borderColor: transparent; + --card-clickable-borderWidth: 1px; + --card-clickable-boxShadow: 0 1.6px 3.6px 0 rgba(0, 0, 0, 0.1); + --card-clickable-hovered-backgroundColor: #fafafa; + --card-clickable-hovered-borderColor: var(--card-clickable-borderColor); + --card-clickable-hovered-borderWidth: var(--card-clickable-borderWidth); + --card-clickable-hovered-boxShadow: 0 3.2px 7.2px 0 rgba(0, 0, 0, 0.1); + --card-clickable-pressed-backgroundColor: #f5f5f5; + --card-clickable-pressed-borderColor: var(--card-clickable-borderColor); + --card-clickable-pressed-borderWidth: 2px; + --card-clickable-pressed-boxShadow: var(--card-clickable-boxShadow); + --card-compact-padding: 0; + --card-disabled-backgroundColor: #f0f0f0; + --card-disabled-borderColor: var(--card-borderColor); + --card-disabled-borderWidth: var(--card-borderWidth); + --card-disabled-boxShadow: 0 0.8px 1.8px 0 rgba(0, 0, 0, 0.1); + --card-fluid-height: 100%; + --card-fluid-width: 100%; + --card-height: var(--card-size-medium-height); + --card-hovered-backgroundColor: var(--card-backgroundColor); + --card-hovered-borderColor: var(--card-borderColor); + --card-hovered-borderWidth: var(--card-borderWidth); + --card-hovered-boxShadow: var(--card-boxShadow); + --card-margin: var(--card-size-medium-margin); + --card-minHeight: 32px; + --card-minWidth: 100px; + --card-padding: var(--card-size-medium-padding); + --card-pressed-backgroundColor: var(--card-backgroundColor); + --card-pressed-borderColor: var(--card-borderColor); + --card-pressed-borderWidth: var(--card-borderWidth); + --card-pressed-boxShadow: var(--card-boxShadow); + --card-selected-backgroundColor: #fafafa; + --card-selected-borderColor: var(--card-borderColor); + --card-selected-borderWidth: var(--card-borderWidth); + --card-selected-boxShadow: var(--card-boxShadow); + --card-size-large-borderRadius: 6px; + --card-size-large-height: auto; + --card-size-large-margin: 0; + --card-size-large-padding: 16px; + --card-size-large-width: auto; + --card-size-larger-borderRadius: 6px; + --card-size-larger-height: auto; + --card-size-larger-margin: 0; + --card-size-larger-padding: 16px; + --card-size-larger-width: auto; + --card-size-largest-borderRadius: 6px; + --card-size-largest-height: auto; + --card-size-largest-margin: 0; + --card-size-largest-padding: 16px; + --card-size-largest-width: auto; + --card-size-medium-borderRadius: 4px; + --card-size-medium-height: auto; + --card-size-medium-margin: 0; + --card-size-medium-padding: 16px; + --card-size-medium-width: auto; + --card-size-small-borderRadius: 4px; + --card-size-small-height: auto; + --card-size-small-margin: 0; + --card-size-small-padding: 8px; + --card-size-small-width: auto; + --card-size-smaller-borderRadius: 4px; + --card-size-smaller-height: auto; + --card-size-smaller-margin: 0; + --card-size-smaller-padding: 8px; + --card-size-smaller-width: auto; + --card-size-smallest-borderRadius: 4px; + --card-size-smallest-height: auto; + --card-size-smallest-margin: 0; + --card-size-smallest-padding: 8px; + --card-size-smallest-width: auto; + --card-width: var(--card-size-medium-width); --color-body-background: black; --color-body-contentColor: white; --color-brand-background: #0078d4; diff --git a/packages/react-theme-provider/src/getTokens.ts b/packages/react-theme-provider/src/getTokens.ts index c89281df9addb1..2a565be40bde4b 100644 --- a/packages/react-theme-provider/src/getTokens.ts +++ b/packages/react-theme-provider/src/getTokens.ts @@ -229,6 +229,132 @@ export function getTokens(theme: Theme): Tokens { iconColor: 'var(--button-disabled-contentColor)', }, }, + + // TODO: This will be moved out as a card variant. + card: { + size: { + smallest: { + borderRadius: '4px', + height: 'auto', + margin: 0, + padding: '8px', + width: 'auto', + }, + smaller: { + borderRadius: '4px', + height: 'auto', + margin: 0, + padding: '8px', + width: 'auto', + }, + small: { + borderRadius: '4px', + height: 'auto', + margin: 0, + padding: '8px', + width: 'auto', + }, + medium: { + borderRadius: '4px', + height: 'auto', + margin: 0, + padding: '16px', + width: 'auto', + }, + large: { + borderRadius: '6px', + height: 'auto', + margin: 0, + padding: '16px', + width: 'auto', + }, + larger: { + borderRadius: '6px', + height: 'auto', + margin: 0, + padding: '16px', + width: 'auto', + }, + largest: { + borderRadius: '6px', + height: 'auto', + margin: 0, + padding: '16px', + width: 'auto', + }, + }, + + backgroundColor: '#ffffff', + borderColor: 'transparent', + borderWidth: '1px', + boxShadow: '0 1.6px 3.6px 0 rgba(0, 0, 0, 0.1)', + minHeight: '32px', + minWidth: '100px', + + borderRadius: 'var(--card-size-medium-borderRadius)', + height: 'var(--card-size-medium-height)', + margin: 'var(--card-size-medium-margin)', + padding: 'var(--card-size-medium-padding)', + width: 'var(--card-size-medium-width)', + + hovered: { + backgroundColor: 'var(--card-backgroundColor)', + borderColor: 'var(--card-borderColor)', + borderWidth: 'var(--card-borderWidth)', + boxShadow: 'var(--card-boxShadow)', + }, + + pressed: { + backgroundColor: 'var(--card-backgroundColor)', + borderColor: 'var(--card-borderColor)', + borderWidth: 'var(--card-borderWidth)', + boxShadow: 'var(--card-boxShadow)', + }, + + selected: { + backgroundColor: '#fafafa', + borderColor: 'var(--card-borderColor)', + borderWidth: 'var(--card-borderWidth)', + boxShadow: 'var(--card-boxShadow)', + }, + + disabled: { + backgroundColor: '#f0f0f0', + borderColor: 'var(--card-borderColor)', + borderWidth: 'var(--card-borderWidth)', + boxShadow: '0 0.8px 1.8px 0 rgba(0, 0, 0, 0.1)', + }, + + clickable: { + backgroundColor: '#ffffff', + borderColor: 'transparent', + borderWidth: '1px', + boxShadow: '0 1.6px 3.6px 0 rgba(0, 0, 0, 0.1)', + + hovered: { + backgroundColor: '#fafafa', + borderColor: 'var(--card-clickable-borderColor)', + borderWidth: 'var(--card-clickable-borderWidth)', + boxShadow: '0 3.2px 7.2px 0 rgba(0, 0, 0, 0.1)', + }, + + pressed: { + backgroundColor: '#f5f5f5', + borderColor: 'var(--card-clickable-borderColor)', + borderWidth: '2px', + boxShadow: 'var(--card-clickable-boxShadow)', + }, + }, + + compact: { + padding: 0, + }, + + fluid: { + height: '100%', + width: '100%', + }, + }, }, tokens, ); diff --git a/packages/react-theme-provider/src/types.ts b/packages/react-theme-provider/src/types.ts index 9a55a90c2d1dd8..7d1c0399a8184f 100644 --- a/packages/react-theme-provider/src/types.ts +++ b/packages/react-theme-provider/src/types.ts @@ -1,6 +1,5 @@ import * as React from 'react'; import { ColorTokenSet } from '@fluentui/theme'; - export { Theme, PartialTheme } from '@fluentui/theme'; /** diff --git a/packages/theme/etc/theme.api.md b/packages/theme/etc/theme.api.md index 851bcb5afc6207..a719206e699289 100644 --- a/packages/theme/etc/theme.api.md +++ b/packages/theme/etc/theme.api.md @@ -702,6 +702,9 @@ export namespace SharedColors { gray10 = "#a0aeb2"; } +// @public +export type SizeValue = 'smallest' | 'smaller' | 'small' | 'medium' | 'large' | 'larger' | 'largest'; + // @public export interface Theme extends ITheme { // (undocumented) diff --git a/packages/theme/src/types/Theme.ts b/packages/theme/src/types/Theme.ts index c416aca0b01e92..0f3869ca0d6eed 100644 --- a/packages/theme/src/types/Theme.ts +++ b/packages/theme/src/types/Theme.ts @@ -1,6 +1,11 @@ import { IPartialTheme, ITheme } from './ITheme'; import { IStyleFunctionOrObject } from '@uifabric/utilities'; +/** + * A ramp of size values. + */ +export type SizeValue = 'smallest' | 'smaller' | 'small' | 'medium' | 'large' | 'larger' | 'largest'; + /** * A baseline set of color plates. */