diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 28afa3f5b3aeb5..9b3751d46b8dc0 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -252,6 +252,7 @@ packages/react-components/react-timepicker-compat @microsoft/teams-prg packages/react-components/react-icons-compat @microsoft/cxe-red @tomi-msft packages/react-components/react-tag-picker @microsoft/teams-prg packages/react-components/react-carousel-preview @microsoft/xc-uxe @Mitch-At-Work +packages/react-components/recipes @microsoft/fluentui-react @sopranopillow # <%= NX-CODEOWNER-PLACEHOLDER %> ## Components diff --git a/apps/public-docsite-v9/.storybook/preview.js b/apps/public-docsite-v9/.storybook/preview.js index be30ab259c4df1..561973bd2f1289 100644 --- a/apps/public-docsite-v9/.storybook/preview.js +++ b/apps/public-docsite-v9/.storybook/preview.js @@ -45,6 +45,7 @@ export const parameters = { ['Component Mapping', 'Color Mapping', 'Troubleshooting'], 'from v0', ], + 'Recipes', ], 'Theme', [ diff --git a/apps/public-docsite-v9/package.json b/apps/public-docsite-v9/package.json index 4068259a58ad4a..154e74293b30a1 100644 --- a/apps/public-docsite-v9/package.json +++ b/apps/public-docsite-v9/package.json @@ -36,6 +36,7 @@ "@fluentui/react-storybook-addon-export-to-sandbox": "*", "@fluentui/theme-designer": "*", "@fluentui/react-motions-preview": "*", + "@fluentui/recipes": "*", "@fluentui/react-timepicker-compat": "*", "@griffel/react": "^1.5.22", "@microsoft/applicationinsights-web": "^3", diff --git a/packages/react-components/recipes/.babelrc.json b/packages/react-components/recipes/.babelrc.json new file mode 100644 index 00000000000000..45fb71ca16d2c3 --- /dev/null +++ b/packages/react-components/recipes/.babelrc.json @@ -0,0 +1,4 @@ +{ + "extends": "../../../.babelrc-v9.json", + "plugins": ["annotate-pure-calls", "@babel/transform-react-pure-annotations"] +} diff --git a/packages/react-components/recipes/.eslintrc.json b/packages/react-components/recipes/.eslintrc.json new file mode 100644 index 00000000000000..ceea884c70dccc --- /dev/null +++ b/packages/react-components/recipes/.eslintrc.json @@ -0,0 +1,4 @@ +{ + "extends": ["plugin:@fluentui/eslint-plugin/react"], + "root": true +} diff --git a/packages/react-components/recipes/.storybook/main.js b/packages/react-components/recipes/.storybook/main.js new file mode 100644 index 00000000000000..26536b61b387f6 --- /dev/null +++ b/packages/react-components/recipes/.storybook/main.js @@ -0,0 +1,14 @@ +const rootMain = require('../../../../.storybook/main'); + +module.exports = /** @type {Omit} */ ({ + ...rootMain, + stories: [...rootMain.stories, '../stories/**/*.stories.mdx', '../stories/**/index.stories.@(ts|tsx)'], + addons: [...rootMain.addons], + webpackFinal: (config, options) => { + const localConfig = { ...rootMain.webpackFinal(config, options) }; + + // add your own webpack tweaks if needed + + return localConfig; + }, +}); diff --git a/packages/react-components/recipes/.storybook/preview.js b/packages/react-components/recipes/.storybook/preview.js new file mode 100644 index 00000000000000..1939500a3d18c7 --- /dev/null +++ b/packages/react-components/recipes/.storybook/preview.js @@ -0,0 +1,7 @@ +import * as rootPreview from '../../../../.storybook/preview'; + +/** @type {typeof rootPreview.decorators} */ +export const decorators = [...rootPreview.decorators]; + +/** @type {typeof rootPreview.parameters} */ +export const parameters = { ...rootPreview.parameters }; diff --git a/packages/react-components/recipes/.storybook/tsconfig.json b/packages/react-components/recipes/.storybook/tsconfig.json new file mode 100644 index 00000000000000..ea89218a3d916f --- /dev/null +++ b/packages/react-components/recipes/.storybook/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "outDir": "", + "allowJs": true, + "checkJs": true, + "types": ["static-assets", "environment", "storybook__addons"] + }, + "include": ["../stories/**/*.stories.ts", "../stories/**/*.stories.tsx", "*.js"] +} diff --git a/packages/react-components/recipes/.swcrc b/packages/react-components/recipes/.swcrc new file mode 100644 index 00000000000000..b4ffa86dee3067 --- /dev/null +++ b/packages/react-components/recipes/.swcrc @@ -0,0 +1,30 @@ +{ + "$schema": "https://json.schemastore.org/swcrc", + "exclude": [ + "/testing", + "/**/*.cy.ts", + "/**/*.cy.tsx", + "/**/*.spec.ts", + "/**/*.spec.tsx", + "/**/*.test.ts", + "/**/*.test.tsx" + ], + "jsc": { + "parser": { + "syntax": "typescript", + "tsx": true, + "decorators": false, + "dynamicImport": false + }, + "externalHelpers": true, + "transform": { + "react": { + "runtime": "classic", + "useSpread": true + } + }, + "target": "es2019" + }, + "minify": false, + "sourceMaps": true +} diff --git a/packages/react-components/recipes/LICENSE b/packages/react-components/recipes/LICENSE new file mode 100644 index 00000000000000..92d1a2acaf81b0 --- /dev/null +++ b/packages/react-components/recipes/LICENSE @@ -0,0 +1,15 @@ +@fluentui/recipes + +Copyright (c) Microsoft Corporation + +All rights reserved. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the ""Software""), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED _AS IS_, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Note: Usage of the fonts and icons referenced in Fluent UI React is subject to the terms listed at https://aka.ms/fluentui-assets-license diff --git a/packages/react-components/recipes/README.md b/packages/react-components/recipes/README.md new file mode 100644 index 00000000000000..217d865998f549 --- /dev/null +++ b/packages/react-components/recipes/README.md @@ -0,0 +1,35 @@ +# @fluentui/recipes + +## Recipes for @fluentui/react-components + +> **⚠️⚠️ This package should never be released ⚠️⚠️** + +### What is a recipe? + +A recipe is a document that allows you to communicate how to build a component, how to explain a concept, or how to use our library to build an experience. + +### What is expected of a recipe? + +A recipe should have the following aspects: + +- Concise information with a clear goal. +- It should cover more abstract concepts and not just code snippets. +- It should also be able to be used by a developer that is new to Fluent UI. +- When writing a recipe for a component, it should be generic enough to be used in multiple scenarios and not just one. +- It should promote the use of our composition model and already created components. + +### How can a recipe help you? + +#### Component recipes + +When a component is simple enough to be built with already created components, we can provide a recipe that will guide the user on how to build said component. This will help by not creating a component that does not provide high value and keep the library small. + +These recipes will also help when we get requests to create a component, but we won't work on it in the near future. By providing a recipe, we can help fill the requests partially and allow the user to build the component themselves. If the recipe gets enough traction, we can revisit prioritizing working on this component. + +#### Concept recipes + +When a concept in our library is hard to grasp and our documentation doesn't provide enough information, we can provide a recipe that will guide the user on how to use/understand the concept. These recipes should not be of every concept specific to our library, instead they should be used as a way to help users understand concepts that are critical to becoming more familiar with our library. + +#### Prototype recipes + +When a user wants to build an experience that is not covered by our library and will not be added because it is not atomic enough or because of its complexity, we can provide a recipe to guide the user on how to build these experiences. These should be created by using our library and do not have specific implementation details as they may not apply to all scenarios. A common example of such experience is a message for a chat application. diff --git a/packages/react-components/recipes/config/api-extractor.json b/packages/react-components/recipes/config/api-extractor.json new file mode 100644 index 00000000000000..e533bf30b48a2b --- /dev/null +++ b/packages/react-components/recipes/config/api-extractor.json @@ -0,0 +1,4 @@ +{ + "$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json", + "extends": "@fluentui/scripts-api-extractor/api-extractor.common.v-next.json" +} diff --git a/packages/react-components/recipes/config/tests.js b/packages/react-components/recipes/config/tests.js new file mode 100644 index 00000000000000..2e211ae9e21420 --- /dev/null +++ b/packages/react-components/recipes/config/tests.js @@ -0,0 +1 @@ +/** Jest test setup file. */ diff --git a/packages/react-components/recipes/docs/Spec.md b/packages/react-components/recipes/docs/Spec.md new file mode 100644 index 00000000000000..e4d6e0bc447e59 --- /dev/null +++ b/packages/react-components/recipes/docs/Spec.md @@ -0,0 +1,63 @@ +# @fluentui/recipes Spec + +## Background + +_Description and use cases of this component_ + +## Prior Art + +_Include background research done for this component_ + +- _Link to Open UI research_ +- _Link to comparison of v7 and v0_ +- _Link to GitHub epic issue for the converged component_ + +## Sample Code + +_Provide some representative example code that uses the proposed API for the component_ + +## Variants + +_Describe visual or functional variants of this control, if applicable. For example, a slider could have a 2D variant._ + +## API + +_List the **Props** and **Slots** proposed for the component. Ideally this would just be a link to the component's `.types.ts` file_ + +## Structure + +- _**Public**_ +- _**Internal**_ +- _**DOM** - how the component will be rendered as HTML elements_ + +## Migration + +_Describe what will need to be done to upgrade from the existing implementations:_ + +- _Migration from v8_ +- _Migration from v0_ + +## Behaviors + +_Explain how the component will behave in use, including:_ + +- _Component States_ +- _Interaction_ + - _Keyboard_ + - _Cursor_ + - _Touch_ + - _Screen readers_ + +## Accessibility + +Base accessibility information is included in the design document. After the spec is filled and review, outcomes from it need to be communicated to design and incorporated in the design document. + +- Decide whether to use **native element** or folow **ARIA** and provide reasons +- Identify the **[ARIA](https://www.w3.org/TR/wai-aria-practices-1.2/) pattern** and, if the component is listed there, follow its specification as possible. +- Identify accessibility **variants**, the `role` ([ARIA roles](https://www.w3.org/TR/wai-aria-1.1/#role_definitions)) of the component, its `slots` and `aria-*` props. +- Describe the **keyboard navigation**: Tab Oder and Arrow Key Navigation. Describe any other keyboard **shortcuts** used +- Specify texts for **state change announcements** - [ARIA live regions + ](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Live_Regions) (number of available items in dropdown, error messages, confirmations, ...) +- Identify UI parts that appear on **hover or focus** and specify keyboard and screen reader interaction with them +- List cases when **focus** needs to be **trapped** in sections of the UI (for dialogs and popups or for hierarchical navigation) +- List cases when **focus** needs to be **moved programatically** (if parts of the UI are appearing/disappearing or other cases) diff --git a/packages/react-components/recipes/etc/recipes.api.md b/packages/react-components/recipes/etc/recipes.api.md new file mode 100644 index 00000000000000..e5e6edaab37ca8 --- /dev/null +++ b/packages/react-components/recipes/etc/recipes.api.md @@ -0,0 +1,9 @@ +## API Report File for "@fluentui/recipes" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts + +// (No @packageDocumentation comment for this package) + +``` diff --git a/packages/react-components/recipes/jest.config.js b/packages/react-components/recipes/jest.config.js new file mode 100644 index 00000000000000..3c3a931643c150 --- /dev/null +++ b/packages/react-components/recipes/jest.config.js @@ -0,0 +1,21 @@ +// @ts-check + +/** + * @type {import('@jest/types').Config.InitialOptions} + */ +module.exports = { + displayName: 'recipes', + preset: '../../../jest.preset.js', + transform: { + '^.+\\.tsx?$': [ + 'ts-jest', + { + tsconfig: '/tsconfig.spec.json', + isolatedModules: true, + }, + ], + }, + coverageDirectory: './coverage', + setupFilesAfterEnv: ['./config/tests.js'], + snapshotSerializers: ['@griffel/jest-serializer'], +}; diff --git a/packages/react-components/recipes/just.config.ts b/packages/react-components/recipes/just.config.ts new file mode 100644 index 00000000000000..b7b2c9a33bf435 --- /dev/null +++ b/packages/react-components/recipes/just.config.ts @@ -0,0 +1,5 @@ +import { preset, task } from '@fluentui/scripts-tasks'; + +preset(); + +task('build', 'build:react-components').cached?.(); diff --git a/packages/react-components/recipes/package.json b/packages/react-components/recipes/package.json new file mode 100644 index 00000000000000..fb9f516cb9101f --- /dev/null +++ b/packages/react-components/recipes/package.json @@ -0,0 +1,68 @@ +{ + "name": "@fluentui/recipes", + "version": "0.0.0", + "private": true, + "description": "New fluentui react package", + "main": "lib-commonjs/index.js", + "module": "lib/index.js", + "typings": "./dist/index.d.ts", + "sideEffects": false, + "files": [ + "*.md", + "dist/*.d.ts", + "lib", + "lib-commonjs" + ], + "repository": { + "type": "git", + "url": "https://github.com/microsoft/fluentui" + }, + "license": "MIT", + "scripts": { + "build": "just-scripts build", + "clean": "just-scripts clean", + "generate-api": "just-scripts generate-api", + "lint": "just-scripts lint", + "test": "jest --passWithNoTests", + "type-check": "just-scripts type-check" + }, + "devDependencies": { + "@fluentui/eslint-plugin": "*", + "@fluentui/react-conformance": "*", + "@fluentui/react-conformance-griffel": "*", + "@fluentui/scripts-api-extractor": "*", + "@fluentui/scripts-tasks": "*", + "@fluentui/react-storybook-addon": "*", + "@fluentui/react-storybook-addon-export-to-sandbox": "*" + }, + "dependencies": { + "@fluentui/react-provider": "^9.16.1", + "@fluentui/react-theme": "^9.1.19", + "@fluentui/react-text": "^9.4.19", + "@fluentui/react-utilities": "^9.18.9", + "@griffel/react": "^1.5.22", + "@swc/helpers": "^0.5.1", + "@fluentui/react-icons": "^2.0.239" + }, + "peerDependencies": { + "@types/react": ">=16.14.0 <19.0.0", + "@types/react-dom": ">=16.9.0 <19.0.0", + "react": ">=16.14.0 <19.0.0", + "react-dom": ">=16.14.0 <19.0.0" + }, + "exports": { + ".": { + "types": "./dist/index.d.ts", + "node": "./lib-commonjs/index.js", + "import": "./lib/index.js", + "require": "./lib-commonjs/index.js" + }, + "./package.json": "./package.json" + }, + "beachball": { + "disallowedChangeTypes": [ + "major", + "prerelease" + ] + } +} diff --git a/packages/react-components/recipes/project.json b/packages/react-components/recipes/project.json new file mode 100644 index 00000000000000..d762b2101004fe --- /dev/null +++ b/packages/react-components/recipes/project.json @@ -0,0 +1,8 @@ +{ + "name": "@fluentui/recipes", + "$schema": "../../../node_modules/nx/schemas/project-schema.json", + "projectType": "library", + "sourceRoot": "packages/react-components/recipes/src", + "tags": ["platform:web", "vNext", "type:stories"], + "implicitDependencies": [] +} diff --git a/packages/react-components/recipes/src/index.ts b/packages/react-components/recipes/src/index.ts new file mode 100644 index 00000000000000..cb0ff5c3b541f6 --- /dev/null +++ b/packages/react-components/recipes/src/index.ts @@ -0,0 +1 @@ +export {}; diff --git a/packages/react-components/recipes/src/recipes/media-object/MediaObject.stories.mdx b/packages/react-components/recipes/src/recipes/media-object/MediaObject.stories.mdx new file mode 100644 index 00000000000000..2eeb24eea12eb7 --- /dev/null +++ b/packages/react-components/recipes/src/recipes/media-object/MediaObject.stories.mdx @@ -0,0 +1,227 @@ +import LinkTo from '@storybook/addon-links/react'; +import { Meta } from '@storybook/addon-docs'; +import { TextPositionVariations, TextAlignmentVariations, FlexSkeleton, IconMediaObject } from './code-snippets'; +import { TemplateExample } from '../../templates'; + + + +--- + +# Media Object recipe + +### **Overview** + +A Media Object, is a pattern commonly used to display media content and a description of that content. +A typical example of this patter is displaying an Avatar of a person (the media content) next to their name (the description). Fluent UI's Persona is an example of this pattern. + +This recipe focuses on other uses of this patterns and will show you how to create a Media Object to display images, icons or any other media content you want to add. + +### **Ingredients** + +- @fluentui/react-icons +- @fluentui/react-image +- @fluentui/react-text + +> **\*\*Note:\*\*** This recipe is meant to provide a way to use custom media. If you want to use +> `Avatar` or `PresenceBadge` as media, please refer to our Persona component. + +## **Steps** + +There are two ways to build a Media Object, you can use a grid layout or a flex layout. We will cover both in this +recipe. There are upsides and downsides to both, but the main difference is that grid layout uses less DOM than the +flex option. + +### **Flex Layout** + +The main idea behind the flex layout is to have a container and two children, as shown below. The parent div will +contain the media and another div (text div) that will contain the text content. + + + + + +To achieve this layout, we will follow these steps: + +#### **Step 1: Creating our parent and text container styles.** + +As seen above, we will first need to create our parent div and our text containers. These containers will need to have +the `display: flex` css property and a `row` direction for the parent and a `column` direction for the text container, as shown in the `makeStyles` call below: + +```jsx +const useStyles = makeStyles({ + parent: { + display: 'flex', + flexDirection: 'row', + }, + textContainer: { + display: 'flex', + flexDirection: 'column', + }, +}); +``` + +#### **Step 2: Putting everything together.** + +After we have our styles, we just have to put our styles to work! Since we are using flex box we don't need to worry about dynamically placing items. Do take into account that using this approach will be heavier on the DOM than the grid approach. + +```jsx +import { Text, makeStyles } from '@fluentui/react-components'; + +// Our makeStyles call from above + +const MediaObject: React.FC<{ text?: string }> = ({ children, text }) => { + const styles = useStyles(); + + return ( +
+ {children} +
+ {text} +
+
+ ); +}; +``` + +After these steps you should be able to build something like the example below. + + + + + +### **Grid Layout** + +When using a grid for Media Object, we only need a parent div and we can pass the items directly into it. +As you can tell, the grid layout has one less layer of DOM compared to the flex layout. This is especially important +when the component is going to be used repeatedly in a list or the media or/and text have many layers themselves. + +To get started, we will follow these steps: + +#### **Step 1: Creating our grid.** + +To get the initial layout, we need to add these css properties to our parent div: + +- `display: grid`: This will create our grid layout and allow us to use the grid properties. +- `grid-auto-flow: column`: This will make the grid flow as a column and allow our media to be on the left and our text on the right. + +Resulting in a `makeStyles` call like this: + +```jsx +const useStyles = makeStyles({ + parent: { + display: 'grid', + gridAutoFlow: 'column', + }, +}); +``` + +#### **Step 2: Adding our media styles.** + +Our next step is to add our media styles. Unlike the flex layout, we will need to add some css properties to our media because we are using `grid-auto-flow: column`. To make our media take all the rows on the left, we will need to make our media span the number of rows the text will take. If we have 4 lines of text, we will need to add `grid-row-start: span 4`. While this could be done dynamically using line names, this is the simplest way to achieve a media object with grid. This will give us the following `makeStyles` call: + +```jsx +const useStyles = makeStyles({ + // parent styles + media: { + gridRowStart: 'span 4', + }, +}); +``` + +#### **Step 3: Putting everything together.** + +After these steps we should be able to add our media and text, and get the desired layout shown above. We should have a component that looks like this: + +```jsx +import { Text, makeStyles } from '@fluentui/react-components'; + +// Our makeStyles call from above + +const MediaObject: React.FC<{ text?: string }> = ({ children, text }) => { + const styles = useStyles(); + + return ( +
+
{children}
+ {text} +
+ ); +}; +``` + +#### **Step 3 (_optional_): Dynamically place media and text.** + +If you want to dynamically place your media and text, you can use `grid-template-columns` to achieve this. For the basic layout, we need to add the css property `grid-template-columns: max-content [middle] auto`. This will create two columns and a line name called middle. The max-content column will be the media which will help only use the space needed for the media and the auto column will be the text so we can wrap it when there is not enough space in our container. This will give us the following `makeStyles` call: + +```jsx +const useStyles = makeStyles({ + parent: { + display: 'grid', + gridTemplateColumns: 'max-content [middle] auto', + }, +}); +``` + +#### **Step 4 (_optional_): Let your media and text know where to start and end.** + +Now that we have our template columns, we need to let our text know where to start and our media where to end. The media must end at the middle line and the text must start at the middle line. This has the pro of not having to know how many rows of text we will have, but the downside is that we have to let the media and text know where to start or end. We should a `makeStyles` call like this: + +```jsx +const useStyles = makeStyles({ + // parent styles + media: { + gridColumnEnd: 'middle', + }, + text: { + gridColumnStart: 'middle', + }, +}); +``` + +#### **Step 5 (_optional_): Putting everything together.** + +After our styles are ready to be used, we can put everything together and get the desired layout shown above. We should have a component that looks like this: + +```jsx +import { Text, makeStyles } from '@fluentui/react-components'; + +// Our makeStyles call from above + +const MediaObject: React.FC<{ text?: string }> = ({ children, text }) => { + const styles = useStyles(); + + return ( +
+
{children}
+ {text} +
+ ); +}; +``` + +## **Variants** + +There are a few common variants that you might want to use when building a Media Object. These are some of them: + +- ### **Text Alignment Variations** + +There are text alignment variations that might be useful when building an application. These can be ones below where the +first one is after the media, the second one is below, and the last one is before. + + + + + +- ### **Text Vertical Alignment Variations** + +There are also vertical alignment variations, these include start and center as seen below. + + + + + +## **Best practices** + +- The higher up the text line, the more important it is. You should not apply higher weights to the lines underneath. +- When using `grid-template-columns` make sure the DOM makes sense and not differ from how the grid is placing them. Do + not have your DOM be ` ` when the rendered result looks like ` `. diff --git a/packages/react-components/recipes/src/recipes/media-object/code-snippets/MediaObject.tsx b/packages/react-components/recipes/src/recipes/media-object/code-snippets/MediaObject.tsx new file mode 100644 index 00000000000000..3bdf1ae3e9a5d5 --- /dev/null +++ b/packages/react-components/recipes/src/recipes/media-object/code-snippets/MediaObject.tsx @@ -0,0 +1,133 @@ +import * as React from 'react'; +import { Attach24Regular } from '@fluentui/react-icons'; +import { mergeClasses } from '@griffel/react'; +import { Text } from '@fluentui/react-text'; +import { useMediaObjectStyles, useSkeletonStyles, useExampleStyles } from './MediaObjectStyles.styles'; + +type MediaObjectTypes = { + media?: React.ReactElement; + text?: React.ReactElement; + textPosition?: 'before' | 'below' | 'after'; + textAlignment?: 'start' | 'center'; +}; + +const MediaObject: React.VoidFunctionComponent = ({ + media, + text, + textPosition = 'after', + textAlignment = 'start', +}) => { + const mediaObjectStyles = useMediaObjectStyles(); + + const mainClassName = mergeClasses( + mediaObjectStyles.main, + textPosition === 'below' && mediaObjectStyles.verticalMediaObject, + textAlignment === 'center' && mediaObjectStyles.centerMedia, + ); + + const textClassName = mergeClasses( + mediaObjectStyles.text, + textPosition === 'below' && mediaObjectStyles.centerTextPosition, + textPosition === 'before' && mediaObjectStyles.beforeTextPosition, + ); + + return ( +
+ {(textPosition === 'after' || textPosition === 'below') && media} +
{text}
+ {textPosition === 'before' && media} +
+ ); +}; + +const Legend: React.FC<{ colorClassName: string }> = ({ children, colorClassName }) => { + const skeletonStyles = useSkeletonStyles(); + return ( +
+
+ {children} +
+ ); +}; + +export const FlexSkeleton = () => { + const exampleStyles = useExampleStyles(); + const skeletonStyles = useSkeletonStyles(); + const mediaObjectStyles = useMediaObjectStyles(); + + return ( +
+
+
+
+
+ Parent div + Text div +
+
+ ); +}; + +export const IconMediaObject = () => ( + } + text={ + <> + + File.tsx + + 256 Gb + + } + /> +); + +export const TextPositionVariations = () => { + const exampleStyles = useExampleStyles(); + const positions: MediaObjectTypes['textPosition'][] = ['after', 'below', 'before']; + + return ( +
+ {positions.map(textPosition => ( + } + key={textPosition} + text={ + <> + + File.tsx + + 256 Gb + + } + /> + ))} +
+ ); +}; + +export const TextAlignmentVariations = () => { + const exampleStyles = useExampleStyles(); + const alignments: MediaObjectTypes['textAlignment'][] = ['start', 'center']; + + return ( +
+ {alignments.map(alignment => ( + } + text={ + <> + + File.tsx + + 256 Gb + + } + /> + ))} +
+ ); +}; diff --git a/packages/react-components/recipes/src/recipes/media-object/code-snippets/MediaObjectStyles.styles.ts b/packages/react-components/recipes/src/recipes/media-object/code-snippets/MediaObjectStyles.styles.ts new file mode 100644 index 00000000000000..72cbf510720d99 --- /dev/null +++ b/packages/react-components/recipes/src/recipes/media-object/code-snippets/MediaObjectStyles.styles.ts @@ -0,0 +1,76 @@ +import { makeStyles, shorthands } from '@griffel/react'; + +export const useExampleStyles = makeStyles({ + multiExample: { + display: 'flex', + justifyContent: 'center', + rowGap: '60px', + columnGap: '60px', + }, +}); + +export const useSkeletonStyles = makeStyles({ + blue: { + backgroundColor: '#4f5e8f', + }, + purple: { + backgroundColor: '#e1c7fc', + }, + legendContainer: { + display: 'flex', + flexDirection: 'column-reverse', + rowGap: '10px', + columnGap: '10px', + ...shorthands.padding('4px'), + }, + legend: { + display: 'flex', + rowGap: '5px', + columnGap: '5px', + }, + legendColor: { + alignSelf: 'center', + width: '10px', + height: '10px', + }, +}); + +export const useMediaObjectStyles = makeStyles({ + main: { + display: 'flex', + flexDirection: 'row', + rowGap: '4px', + columnGap: '4px', + }, + text: { + display: 'flex', + flexDirection: 'column', + rowGap: '4px', + columnGap: '4px', + }, + emptyMedia: { + ...shorthands.padding('20px', '20px', '20px', '80px'), + }, + emptyText: { + width: '100px', + height: '50px', + }, + + centerMedia: { + alignItems: 'center', + }, + + verticalMediaObject: { + display: 'flex', + flexDirection: 'column', + rowGap: '4px', + columnGap: '4px', + alignItems: 'center', + }, + centerTextPosition: { + alignItems: 'center', + }, + beforeTextPosition: { + alignItems: 'flex-end', + }, +}); diff --git a/packages/react-components/recipes/src/recipes/media-object/code-snippets/index.ts b/packages/react-components/recipes/src/recipes/media-object/code-snippets/index.ts new file mode 100644 index 00000000000000..d1f240176219dd --- /dev/null +++ b/packages/react-components/recipes/src/recipes/media-object/code-snippets/index.ts @@ -0,0 +1 @@ +export * from './MediaObject'; diff --git a/packages/react-components/recipes/src/templates/Example.styles.ts b/packages/react-components/recipes/src/templates/Example.styles.ts new file mode 100644 index 00000000000000..5b7098670b0935 --- /dev/null +++ b/packages/react-components/recipes/src/templates/Example.styles.ts @@ -0,0 +1,19 @@ +import { tokens } from '@fluentui/react-theme'; +import { makeStyles, shorthands } from '@griffel/react'; + +export const useExampleStyles = makeStyles({ + root: { + ...shorthands.border('1px', 'solid', tokens.colorNeutralStroke1), + ...shorthands.borderRadius('16px'), + ...shorthands.padding('30px'), + ...shorthands.margin('6px'), + }, + innerContainer: { + display: 'flex', + backgroundColor: 'rgb(250, 250, 250)', + ...shorthands.padding('48px', '24px', '48px', '24px'), + }, + centered: { + justifyContent: 'center', + }, +}); diff --git a/packages/react-components/recipes/src/templates/Example.tsx b/packages/react-components/recipes/src/templates/Example.tsx new file mode 100644 index 00000000000000..46132fa6e05d1d --- /dev/null +++ b/packages/react-components/recipes/src/templates/Example.tsx @@ -0,0 +1,19 @@ +import * as React from 'react'; +import { FluentProvider } from '@fluentui/react-provider'; +import { webLightTheme } from '@fluentui/react-theme'; +import { mergeClasses } from '@griffel/react'; +import { useExampleStyles } from './Example.styles'; + +export const TemplateExample: React.FC<{ centered?: boolean }> = ({ children, centered }) => { + const exampleStyles = useExampleStyles(); + + const innerContainerClassName = mergeClasses(exampleStyles.innerContainer, centered && exampleStyles.centered); + + return ( + +
+
{children}
+
+
+ ); +}; diff --git a/packages/react-components/recipes/src/templates/index.ts b/packages/react-components/recipes/src/templates/index.ts new file mode 100644 index 00000000000000..99e30127539411 --- /dev/null +++ b/packages/react-components/recipes/src/templates/index.ts @@ -0,0 +1 @@ +export * from './Example'; diff --git a/packages/react-components/recipes/tsconfig.json b/packages/react-components/recipes/tsconfig.json new file mode 100644 index 00000000000000..1941a041d46c19 --- /dev/null +++ b/packages/react-components/recipes/tsconfig.json @@ -0,0 +1,25 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "target": "ES2019", + "noEmit": true, + "isolatedModules": true, + "importHelpers": true, + "jsx": "react", + "noUnusedLocals": true, + "preserveConstEnums": true + }, + "include": [], + "files": [], + "references": [ + { + "path": "./tsconfig.lib.json" + }, + { + "path": "./tsconfig.spec.json" + }, + { + "path": "./.storybook/tsconfig.json" + } + ] +} diff --git a/packages/react-components/recipes/tsconfig.lib.json b/packages/react-components/recipes/tsconfig.lib.json new file mode 100644 index 00000000000000..6f90cf95c005bd --- /dev/null +++ b/packages/react-components/recipes/tsconfig.lib.json @@ -0,0 +1,22 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "noEmit": false, + "lib": ["ES2019", "dom"], + "declaration": true, + "declarationDir": "../../../dist/out-tsc/types", + "outDir": "../../../dist/out-tsc", + "inlineSources": true, + "types": ["static-assets", "environment"] + }, + "exclude": [ + "./src/testing/**", + "**/*.spec.ts", + "**/*.spec.tsx", + "**/*.test.ts", + "**/*.test.tsx", + "**/*.stories.ts", + "**/*.stories.tsx" + ], + "include": ["./src/**/*.ts", "./src/**/*.tsx"] +} diff --git a/packages/react-components/recipes/tsconfig.spec.json b/packages/react-components/recipes/tsconfig.spec.json new file mode 100644 index 00000000000000..911456fe4b4d91 --- /dev/null +++ b/packages/react-components/recipes/tsconfig.spec.json @@ -0,0 +1,17 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "CommonJS", + "outDir": "dist", + "types": ["jest", "node"] + }, + "include": [ + "**/*.spec.ts", + "**/*.spec.tsx", + "**/*.test.ts", + "**/*.test.tsx", + "**/*.d.ts", + "./src/testing/**/*.ts", + "./src/testing/**/*.tsx" + ] +} diff --git a/tools/workspace-plugin/src/generators/recipe-generator/index.spec.ts b/tools/workspace-plugin/src/generators/recipe-generator/index.spec.ts index 8011bb60a2fbac..0f7a93b1bbb184 100644 --- a/tools/workspace-plugin/src/generators/recipe-generator/index.spec.ts +++ b/tools/workspace-plugin/src/generators/recipe-generator/index.spec.ts @@ -5,20 +5,20 @@ import path from 'path'; import generator from './index'; import { RecipeGeneratorGeneratorSchema } from './schema'; -const recipesRoot = 'apps/recipes-react-components/src/recipes'; +const recipesRoot = 'packages/react-components/recipes/src/recipes'; const recipeName = 'Hello World'; const recipePackageName = 'hello-world'; const setup = (tree: Tree) => { const generateApp = () => { - const paths = { root: 'apps/recipes-react-components' }; + const paths = { root: 'packages/react-components/recipes' }; writeJson(tree, path.join(paths.root, 'package.json'), { - name: '@proj/recipes-react-components', + name: '@proj/recipes', private: true, }); tree.write(path.join(paths.root, 'src/recipes/.gitkeep'), ''); - addProjectConfiguration(tree, '@proj/recipes-react-components', { + addProjectConfiguration(tree, '@proj/recipes', { root: paths.root, sourceRoot: path.join(paths.root, 'src'), projectType: 'application', diff --git a/tools/workspace-plugin/src/generators/recipe-generator/index.ts b/tools/workspace-plugin/src/generators/recipe-generator/index.ts index 881971e5f4e13e..145a5c6ce47e16 100644 --- a/tools/workspace-plugin/src/generators/recipe-generator/index.ts +++ b/tools/workspace-plugin/src/generators/recipe-generator/index.ts @@ -15,7 +15,7 @@ export default async function (tree: Tree, schema: RecipeGeneratorGeneratorSchem function normalizeOptions(tree: Tree, schema: RecipeGeneratorGeneratorSchema) { const workspaceConfig = getWorkspaceConfig(tree); const recipesProject = getProjectConfig(tree, { - packageName: `@${workspaceConfig.npmScope}/recipes-react-components`, + packageName: `@${workspaceConfig.npmScope}/recipes`, }); const recipesRoot = joinPathFragments(recipesProject.paths.sourceRoot, 'recipes'); diff --git a/tsconfig.base.all.json b/tsconfig.base.all.json index 78b29ead1a1fb1..fd35ade6fe9e64 100644 --- a/tsconfig.base.all.json +++ b/tsconfig.base.all.json @@ -172,7 +172,8 @@ "@fluentui/theme-designer": ["packages/react-components/theme-designer/src/index.ts"], "@fluentui/tokens": ["packages/tokens/src/index.ts"], "@fluentui/workspace-plugin": ["tools/workspace-plugin/src/index.ts"], - "@fluentui/react-swatch-picker": ["packages/react-components/react-swatch-picker/src/index.ts"] + "@fluentui/react-swatch-picker": ["packages/react-components/react-swatch-picker/src/index.ts"], + "@fluentui/recipes": ["packages/react-components/recipes/src/index.ts"] } } } diff --git a/tsconfig.base.json b/tsconfig.base.json index b22f5b3dea3706..97ec18db2868ed 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -105,6 +105,7 @@ "@fluentui/react-tree": ["packages/react-components/react-tree/src/index.ts"], "@fluentui/react-utilities": ["packages/react-components/react-utilities/src/index.ts"], "@fluentui/react-virtualizer": ["packages/react-components/react-virtualizer/src/index.ts"], + "@fluentui/recipes": ["packages/react-components/recipes/src/index.ts"], "@fluentui/theme-designer": ["packages/react-components/theme-designer/src/index.ts"], "@fluentui/tokens": ["packages/tokens/src/index.ts"], "@fluentui/workspace-plugin": ["tools/workspace-plugin/src/index.ts"]