Skip to content

Latest commit

 

History

History
298 lines (206 loc) · 20.4 KB

CONTRIBUTING.md

File metadata and controls

298 lines (206 loc) · 20.4 KB

Contributing

Pre-requisites

This guide assumes you:

  • Have read through the React Native Docs. In particular:
    • Understand classes vs function components (we use the latter) and hooks. Here's a good video that explains function components and hooks for traditional OOP developers.
    • Understand Native Modules.
  • Have a local fork of FluentUI React Native and have run the test app.

Understanding the Repository Structure

There are some specific quirks to this repository that one should be familiar with.

When configuring or maintaining a package, start with its AGENTS.md and README.md; package-owned recipes and shared agent guidance live in its indexed references/ directory. Follow the package-owned guidance standard.

Component generations

The repository contains three generations of components. Identify the generation from a component's imports and structure before changing it; the classification guide describes how.

  • v0: components built on foundation-compose. These are maintenance-only.
  • v1: components built on @fluentui-react-native/framework compose or compressible, usually exported with a V1 suffix from packages/components and packages/experimental. These are maintenance-only.
  • Agentic: components in packages/agentic/components, built from reviewed contracts with framework-base patterns and design tokens. All new components use this package.

Understanding the framework

Our framework has a few key concepts that you should be familiar with.

Slots

The "Slot" pattern is an abstraction pattern used to compose higher order components. The pattern allows us to divide the props of one component into multiple "slots". An individual slot represents your inner component, in essence the actual entries that you will put in your render tree. Using the slot pattern lets you abstract away specifically what gets rendered in the tree and in advanced user scenarios allow you to switch out or flatten slots. For example, you might choose to replace whatever we put in the default "icon" slot for a button with an SVG, or a spinner. You could replace the root slot of a radio group with a focus trapzone if the radio button is a required part of the form. The props are assigned to whatever happens to get rendered for that slot. It is also built to help with style creation for the slots to help keep object references to the produced style objects consistent and use cached results where possible.

One caveat is that if a component simply wraps a single native component, then it only has one slot.

Tokens

FluentUI React Native (and eventually all of FluentUI) uses the design token system to handle styling / customization. There's extended documentation about this in the repo, but here is a simple overview.

Tokens are things you set at design time, via theme, or via customizing the control. An example might be "brandColor", where each app has it's own color token it sets on all if its controls.

Props are just normal properties that you can set on a JSX element. An example might be "isAnimating".

Tokens can also be props (which is specified in the compose framework with the "TokensThatAreAlsoProps" field of the control) Tokens help us achieve simpler customization for complex higher order components, and also help with memoization (AKA rendering the minimal set of changes every time the component is re-rendered).

Creating a new component

New components go in packages/agentic/components: a styled higher-order component in src/components/<name>, or an unstyled building block in src/primitives/<name>. Do not create new v0 or v1 component packages under packages/components or packages/experimental; those generations accept maintenance only. Read the package's contributor instructions and use Button and Icon as the canonical examples.

Define the contract

  1. A higher-order component starts with a reviewed contract: SPEC.md plus spec/source.json, spec/tokens.yaml, spec/accessibility.md, spec/interaction.md, and spec/usage.md. Follow the contracts reference for provenance, review state, and source adapters.
  2. A primitive instead keeps a colocated CONTRACT.md that describes its public API and renderer branches.
  3. Run yarn workspace @fluentui-react-native/components check:spec-contracts to validate the contract.

Add the source code

A higher-order component uses these files, in dependency order:

  1. <component>.types.ts: public props, slots, and resolved state.
  2. use<Component>.ts: defaults, derived state, interaction, accessibility, and slot construction.
  3. <component>.styles.ts and use<Component>Styles.ts: token-based styles and slot style application.
  4. render<Component>.tsx: pure slot rendering. Put /** @jsxImportSource @fluentui-react-native/framework-base */ on the first line of files that render slots; see packages/framework-base/references/component-patterns.md.
  5. <component>.ts: assembly and displayName.

Expose the native root ref through the React 19 ref prop rather than forwardRef. Export the component and its public types explicitly from src/index.ts, or from src/primitives/index.ts for a primitive; do not use export *.

Test your component

  1. Colocate runtime tests, type tests, and snapshots with the implementation.
  2. Add Storybook stories next to the component. Desktop behavior tests are wdio functions in the stories and run on macOS, Windows, and Win32 through the agentic Storybook app.
  3. Run the package's declared scripts, such as yarn workspace @fluentui-react-native/components test, plus build, lint, and format. While iterating, run only your component's tests from its package, for example yarn test button.
  4. Before opening a pull request, run yarn clean-lage-cache and then yarn lage buildci from the repository root.

Maintaining v0 and v1 components

Fixes to existing v0 and v1 components keep their current framework, public API, and tests:

  1. FluentTester test pages are located under apps/tester-core/src/TestComponents. Register a page in apps/tester-core/src/testPages.ts, whose platforms array lists the platforms where it appears.
  2. End-to-end tests live in apps/E2E/src/<Component>. See Authoring an E2E Test, and reach out to Samuel Freiberg with questions about E2E testing.
  3. If a component contains a native module, add its podspec path to the FluentTester Podfile. The React Native community CLI does not support autolinking transitive dependencies. Then run yarn && yarn build from the root, and run pod install from apps/fluent-tester/macos for macOS or apps/legacy-mobile/ios for iOS.

Adding native code to your new component

New modern native components and TurboModules belong in @fluentui-react-native/native-core rather than a new standalone package. The sections below describe the native wrappers used by existing v0 and v1 packages.

Through the power of Native Modules, we are able to create components that are comprised of native platform code, rather than JS. This is particularly useful if you want platform specific behavior, or if you want a component that feels much more aligned to it's specific platform. The downside is you must implement the Native module for every platform you wish to support. It's worth investigating whether you truly need a native module, or if a more cross platform JS implementation is the better approach.

There are a few caveats to know of adding a native module to a FluentUI React Native component:

  • Your component will probably only have one slot. We have not yet explored creating a native component with multiple re-composable slots, and have adopted the pattern of having one "root" slot that simply holds the native component
  • Use codegenNativeComponent to ensure that a JS config is provided, which will be required when moving to the new architecture.
  • You will need to decide what is a token and what is a property, and how you want to create your component's API surface and map it to the native view's API.
  • If you want the default values of your component's props / tokens to come from the Native Module, you can use the constantsToExport API, and pass it to slotProps in your component. You can see an example of this in experimental-avatar.

Creating new native apple components

This section is specifically focused on creating new components for Apple platforms. Most if not all of these are wrapped controls from FluentUI Apple.

If you are creating a new component from scratch (AKA your wrapped FluentUI Apple control currently has no analog in FluentUI React Native), you have the most leeway to design your API. In general, you will probably want to expose each property from the native swift control as either a token or prop (or both) to JS.

If you want to add a native implementation to an existing component, you may be better off making a duplicate "experimental" component that contains the native implementation, as using a Native Module limits you to one slot, where an existing JS component may have multiple slots. Examples of duplicate native implementation include "experimental-avatar" and "experimental-native-button".

An example of how we would create Shimmer with the compose framework if all the swift properties on the native control were tokens would look like so. This is how it's currently implemented

export type ShimmerTokens = {
  // add the native swift properties here
}
// the native control on the inside needs to receive these
export type NativeShimmerProps = ShimmerTokens & ViewProps;
// the shimmer control only should get these via customization
export type ShimmerProps = ViewProps
// tokensThatAreAlsoProps is false and can be omitted because nothing is shared
// type looks like this:
props: ShimmerProps;
slotProps: { root: NativeShimmerProps };
tokens: ShimmerTokens;
// buildProps would look like this:
root: buildProps(tokens => ({ ...tokens }), [])
// this is because the tokens aren't depending on any props
// which means they only change when the theme is updated

If instead, some of the native swift properties were tokens AND props, it would look something like this:

const tokensThatAreAlsoProps: (keyof ShimmerTokens)[] = [/* list all the tokens here */];

// in the body of the component then add
tokenProps,

// then buildProps becomes:
  root: buildProps(tokens => ({...tokens}), tokenProps)

To add a native module that wraps a FluentUI Apple control:

  1. Create a podspec file at the root of the your new component's directory.
    • This is the file cocoapods will read to figure out what native files to import when it compiles the client app that is using your component
    • We can read the metadata from our package.json (See the existing podspecs for how to do this)
    • We specify MicrosoftFluentUI as dependency, which ensures the source code for FluentUI Apple is pulled in while compiling this component. Consider using a subspec (i.e MicrosoftFluentUI/Avatar_ios) if possible.
  2. Create the ios/ and macos/ subdirectories (whichever platforms you need) in your components directory
  3. Inside each new directory you just created, you will have at a minimum of 3 files (2 if you skip Swift and only use objective c)
    1. FRN<new-component>ViewManager.h / m
      • This is the Objective-C file needed because Swift does not support macros and React Native requires them to map JS props to the native properties of the control. (Macros like RCT_EXPORT_VIEW_PROPERTY and RCT_EXPORT_METHOD). The .h file should at the minumum import <React/RCTViewManager.h>
    2. <new-component>ViewManager.swift
      • This Swift file imports FluentUI Apple, and creates a subclass of RCTViewManager to instantiate and return your FluentUI Apple control. Objective-C methods like requiresMainQueueSetup and constantsToExport can be overridden here. It's important to note that in order for properties and methods to be available to React Native, they must add the @objc decorator to it's declaration.

Create new native Windows components

This section is specifically focused on creating new Windows components using React Native for Windows.

If you are creating a new component from scratch, you have the most leeway to design your API. In general, you will probably want to expose each property from the native WinUI control as either a token or prop (or both) to JS.

To add a native Windows module:

  1. Follow these instructions for creating a new C++/WinRT native windows module library. Complete all steps through the end of Making your module ready for consumption in an app.
    • When creating Views and ViewManagers for your module, Windows components such as Expander and Windows modules outside of the FluentUI React Native repository such as the datetimepicker are helpful resources
  2. Follow the steps for creating a new component in FluentUI React Native.
    • Other Windows components such as the Expander will be helpful with this step.
  3. Copy the windows folder from the local native component library created in Step 1 into the root of the new componenet's directory.
  4. Testing the component locally
    1. Follow steps for Option 1 of testing the module
      • Be sure to run the autolinking command from the component's root directory (npx react-native autolink-windows)
    2. Check that the NuGet packages for the test application and component line up. i.e. If the component uses WinUI 2.6, the test application should as well.
      • Right-click on the solution within VS. Select Manage NuGet Packages for Solution…. Look at differences under the consolidate tab.
      • After this step, you may need to remove unused references for .xcsproj files
    3. Add your native module references within the App.js file.
      • Import { NewComponent } from @fluentui-react-native/experimental-<new-component>;
    4. Start metro via command line
      • Navigate to windows folder (packages\experimental\<new-component>\windows)
      • Run yarn start
    5. Run application

Creating new native Android Components

This section is specifically focused on creating new components for Android platforms.

If you are creating a new component from scratch, you have the most leeway to design your API. In general, you will probably want to expose each property from the native Android control as either a token or prop (or both) to JS.

To add a new control to the FURN component library: creating a new component

To add a native module that wraps a FluentUI Android control:

  1. Create the android/src/main/java/com/microsoft/fnandroid/ subdirectory in your components directory

  2. Inside the new directory you just created, add the following files. In all of the newly created files, add your package name at the top of the file: package com.microsoft.fnandroid.(new-component)

    a. (new-component)ViewManager.kt: This Kotlin file imports FluentUI Android, and creates a subclass of RCTViewManager to instantiate and return your FluentUI Android control.

    • Implement the createViewInstance method

    • Expose view property setters using @ReactProp (or @ReactPropGroup) It's important to note that in order for properties and methods to be available to React Native, they must add the @ReactMethod decorator to it's declaration.

    b. (new-component)Module.kt: This file will contain your native module class. Your module class will extend the ReactContextBaseJavaModule

    c. (new-component)Package.kt

    • Add the ViewManager in createViewManagers of the applications package
    • Add the Module in createNativeModules of the applications package
  3. In directory android/src/main, add AndroidManifest.xml and add the package name

  4. Autolink Native Module

    a. Gradle Build Init plugin

    • Run gradle init inside android directory
    • Select type of project to generate: Basic
    • Select build script DSL: Groovy

    b. Include dependencies for android build environment

    • Edit the generated build.gradle file
    • Add dependencies for kotlin, maven, react-native, etc
    • Add dependency for FluentUIAndroid

    c. Add @fluentui-react-native/ package under "dependencies" and "depcheck"/"ignoreMatches" in the relevant tester host package.json (apps/legacy-mobile for iOS/Android and apps/fluent-tester for macOS/Windows) so native modules are available to each host.

Creating a pull request

Thanks for your interest in contributing to the fluentui-react-native! We welcome all contributions. Here's information on how to prepare your change for a pull request.

Code of Conduct

This project has adopted the Microsoft Open Source Code of Conduct. For more information see the Code of Conduct FAQ or contact opencode@microsoft.com with any additional questions or comments.

Prerequisites

Changesets

This repo manages semantic versioning and publishing using Changesets. When contributing, make sure to run the following before making a pull request:

  1. yarn changeset will take you through a command line wizard to generate changeset files
  2. Make sure to push the newly generated changeset file (.changeset/*.md)

Repository release tooling uses Changesets CLI v3 with Node.js 22.x (22.19 or later), 24.x, or 26+, and the repository-pinned Yarn version. Changelogs use oxfmt; run yarn format from the repository root to format changes. yarn changeset:version requires an unreleased changeset and fails when none are present. The GitHub Actions workflow only creates version-update pull requests; package publishing remains in Azure Pipelines.

Testing changes

Before you create a pull request, test your changes with the FluentUI Tester on the platforms that are affected by your change. For more information on the FluentUI Tester, please follow instructions in the FluentUI Tester readme.

Contributor License Agreement

This project welcomes contributions and suggestions. Most contributions require you to agree to a Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us the rights to use your contribution. For details, visit https://cla.opensource.microsoft.com.

When you submit a pull request, a CLA bot will automatically determine whether you need to provide a CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the instructions provided by the bot. You will only need to do this once across all repos using our CLA.

Additional Considerations

Update Documentation

We welcome having documentation for our packages and components! Please add or update any relevant documentation for your changes.

Accessibility

Please test your change for keyboard navigation as well as compatibility with Voiceover and screen readers.

Internationalization

Consider if your change needs to have special handling for right-to-left languages, or other internationalization considerations.