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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 18 additions & 18 deletions apps/fluent-tester/macos/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ PODS:
- DoubleConversion (1.1.6)
- FBLazyVector (0.74.30)
- fmt (9.1.0)
- FRNAvatar (0.21.4):
- FRNAvatar (0.21.11):
- MicrosoftFluentUI (= 0.13.1)
- React
- FRNCallout (0.27.2):
- FRNCallout (0.27.9):
- DoubleConversion
- glog
- RCT-Folly (= 2024.01.01.00)
Expand All @@ -28,13 +28,13 @@ PODS:
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
- Yoga
- FRNCheckbox (0.17.9):
- FRNCheckbox (0.17.16):
- React
- FRNMenuButton (0.13.19):
- FRNMenuButton (0.13.26):
- React
- FRNRadioButton (0.21.16):
- FRNRadioButton (0.21.23):
- React
- FRNVibrancyView (0.3.0):
- FRNVibrancyView (0.3.4):
- React
- glog (0.3.5)
- MicrosoftFluentUI (0.13.1):
Expand Down Expand Up @@ -123,7 +123,7 @@ PODS:
- fmt (= 9.1.0)
- glog
- RCTDeprecation (0.74.30)
- RCTFocusZone (0.21.0):
- RCTFocusZone (0.21.7):
- React
- RCTRequired (0.74.30)
- RCTTypeSafety (0.74.30):
Expand Down Expand Up @@ -1262,11 +1262,11 @@ DEPENDENCIES:
- FBLazyVector (from `../../../node_modules/react-native-macos/Libraries/FBLazyVector`)
- fmt (from `../../../node_modules/react-native-macos/third-party-podspecs/fmt.podspec`)
- FRNAvatar (from `../../../packages/experimental/Avatar/FRNAvatar.podspec`)
- "FRNCallout (from `../../../node_modules/@fluentui-react-native/callout`)"
- "FRNCallout (from `../node_modules/@fluentui-react-native/callout`)"
- FRNCheckbox (from `../../../packages/experimental/Checkbox/FRNCheckbox.podspec`)
- FRNMenuButton (from `../../../packages/components/MenuButton/FRNMenuButton.podspec`)
- FRNRadioButton (from `../../../packages/components/RadioGroup/FRNRadioButton.podspec`)
- "FRNVibrancyView (from `../../../node_modules/@fluentui-react-native/vibrancy-view`)"
- "FRNVibrancyView (from `../node_modules/@fluentui-react-native/vibrancy-view`)"
- glog (from `../../../node_modules/react-native-macos/third-party-podspecs/glog.podspec`)
- RCT-Folly (from `../../../node_modules/react-native-macos/third-party-podspecs/RCT-Folly.podspec`)
- RCT-Folly/Fabric (from `../../../node_modules/react-native-macos/third-party-podspecs/RCT-Folly.podspec`)
Expand Down Expand Up @@ -1341,15 +1341,15 @@ EXTERNAL SOURCES:
FRNAvatar:
:path: "../../../packages/experimental/Avatar/FRNAvatar.podspec"
FRNCallout:
:path: "../../../node_modules/@fluentui-react-native/callout"
:path: "../node_modules/@fluentui-react-native/callout"
FRNCheckbox:
:path: "../../../packages/experimental/Checkbox/FRNCheckbox.podspec"
FRNMenuButton:
:path: "../../../packages/components/MenuButton/FRNMenuButton.podspec"
FRNRadioButton:
:path: "../../../packages/components/RadioGroup/FRNRadioButton.podspec"
FRNVibrancyView:
:path: "../../../node_modules/@fluentui-react-native/vibrancy-view"
:path: "../node_modules/@fluentui-react-native/vibrancy-view"
glog:
:podspec: "../../../node_modules/react-native-macos/third-party-podspecs/glog.podspec"
RCT-Folly:
Expand Down Expand Up @@ -1466,17 +1466,17 @@ SPEC CHECKSUMS:
DoubleConversion: 5b92c4507c560bb62e7aa1acdf2785ea3ff08b3b
FBLazyVector: 0aa0591844f7fe4736f3aba70d30232edbd21eb5
fmt: 03574da4b7ba40de39da59677ca66610ce8c4a02
FRNAvatar: ec4d219c71bfd3d74306a1fcf94f71393b63359f
FRNCallout: 1989375a3f3f704d7f85a560d979c222a5d2dc5c
FRNCheckbox: 80e3700277629ce802b1f07123e564f954d0e9b1
FRNMenuButton: 43dd93252a1a6a0a3f4546b6d8021c2a572bf586
FRNRadioButton: 8260e87a2df63c5a67ba23ce87dc8234ef137643
FRNVibrancyView: 975f7e8ea14999015c3224743057418574833afa
FRNAvatar: 1eccbe629f3034e2caa11f62e16db120ac3c7836
FRNCallout: 3eca65ff4ee29de3881a8402842b230626024ff6
FRNCheckbox: 1d87e81b71e6706b6e94e69ab6923e50755c4a29
FRNMenuButton: d87749093d3418d9c9377144f9e17e09af707335
FRNRadioButton: 8b6c2d6c2d5c513ce493568ac0597d114359cb57
FRNVibrancyView: 0fb5d289c2a4b934596fb26d81166151d66fa28c
glog: ba31c1afa7dcf1915a109861bccdb4421be6175b
MicrosoftFluentUI: dde98d8ed3fc306d9ddd0a6f0bc0c1f24fe5275e
RCT-Folly: f47da9a444aae485a0528b3bccf0336156009d60
RCTDeprecation: 6c1d8fdaf3e34933c33a56531bd984bc2d22ef9e
RCTFocusZone: 50bf108af173c92cb8c4a776c17c37d010db31bc
RCTFocusZone: 999b4c2acb2193fd189f9ef6d1e970c2a6676250
RCTRequired: 5266165e3b6c7ca1554c5a75fb4c1ebe1bc60b53
RCTTypeSafety: ced894df76a17b8f7331d24e2efa862a7a616e89
React: 620dbf1e10232c8517a8b89d0def5b29e04ad24e
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "minor",
"comment": "implement both new and old render patterns for classic and jsx-runtimes",
"packageName": "@fluentui-react-native/framework-base",
"email": "[email protected]",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "none",
"comment": "update documentation",
"packageName": "@fluentui-react-native/immutable-merge",
"email": "[email protected]",
"dependentChangeType": "none"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "none",
"comment": "update documentation",
"packageName": "@fluentui-react-native/memo-cache",
"email": "[email protected]",
"dependentChangeType": "none"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "none",
"comment": "update documentation",
"packageName": "@fluentui-react-native/merge-props",
"email": "[email protected]",
"dependentChangeType": "none"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "patch",
"comment": "rework jsx helpers for both new and old runtimes",
"packageName": "@fluentui-react-native/tester",
"email": "[email protected]",
"dependentChangeType": "patch"
}
21 changes: 10 additions & 11 deletions packages/framework-base/README.md
Original file line number Diff line number Diff line change
@@ -1,20 +1,19 @@
# Framework Base package
# `@fluentui-react-native/framework-base`

This package provides core types and helpers used by both the old and new versions of the framework.
This package provides core implementations and types to support both the legacy and current frameworks.

Several previously standalone packages have had their implementations moved into this package. This allows them to share certain typings and helpers without having to work around circular dependency issues. The moved packages are:

- `@fluentui-react-native/immutable-merge`
- `@fluentui-react-native/memo-cache`
- `@fluentui-react-native/merge-props`
- [`@fluentui-react-native/immutable-merge`](./src/immutable-merge/README.md)
- [`@fluentui-react-native/memo-cache`](./src/memo-cache/README.md)
- [`@fluentui-react-native/merge-props`](./src/merge-props/README.md)

The functionality in these packages can be imported either by the base entry point for the package, or by using dedicated exports. The previous packages will continue to exist for the time being but are now just references to their individual exports. Note that export maps require special handling for metro bundling (with the exception of the jsx-runtime export) so the export maps are primarily for use
in JS/web projects.
The functionality in these packages is now exposed as part of this package.

## Type Helpers
## Component Patterns

- TODO: There are a number of issues with the way types are handled in the larger fluentui-react-native project, helpers and core types will be added here to help solve inference issues, avoid hard typecasts, and help the project eventually move to typescript 5.x.
The shared patterns for rendering components, as well as the JSX handlers have been centralized in this package. More information can be found [here](./src/component-patterns/README.md).

## JSX Helpers
## Type Helpers

- TODO: Both classic and the new jsx-runtime helpers will eventually come out of this package and be shared between old and new frameworks. This will be necessary to improve typing across the board.
- TODO: There are a number of issues with the way types are handled in the larger fluentui-react-native project, helpers and core types will be added here to help solve inference issues, avoid hard typecasts, and help the project eventually move to typescript 5.x.
5 changes: 5 additions & 0 deletions packages/framework-base/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@
"import": "./lib/index.js",
"require": "./lib-commonjs/index.js",
"types": "./lib/index.d.ts"
},
"./jsx-runtime": {
"import": "./lib/jsx-runtime.js",
"require": "./lib-commonjs/jsx-runtime.js",
"types": "./lib/jsx-runtime.d.ts"
}
},
"scripts": {
Expand Down
39 changes: 39 additions & 0 deletions packages/framework-base/src/component-patterns/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# `fluentui-react-native` - Common component patterns

These are the base component patterns shared across the deprecated or v0 framework (found under packages/deprecated), and the newer framework (found under packages/framework). This also includes the custom JSX handlers required to render them properly.

There are two main patterns exposed here: direct rendering and staged rendering.

## Direct Rendering

The direct rendering pattern allows a component to be called directly, rather than creating a new entry in the DOM.

As an example, if you want to create a wrapper around a component called `MyText` that has `italicize` as one of its props, that always wants to set that value to true. You could define:

```ts
const MyNewText: React.FunctionComponent<MyTextProps> = (props) => {
return <MyText {...props, italicize: true} />;
}
```

When this is rendered, there is an entry for `MyNewText` which contains a `MyText` (another entry), which might contains `Text` (for react-native usage). The direct rendering pattern is one where a component can denote that it is safe to be called directly as a function, instead operating as a prop transform that gets applied to the underlying component.

- For the above to be safe, `MyNewText` should NOT use hooks. In the case of any conditional rendering logic this will break the rule of hooks.

There are two types of implementations in this folder:

- `DirectComponent` - a functional component that marks itself as direct with a `_callDirect: true` attached property. This will then be called as a normal function component, with children included as part of props.
- `LegacyDirectComponent` - the pattern currently used in this library that should be moved away from. In this case `_canCompose: true` is set as an attached property, and the function component will be called with children split from props.

The internal logic of the JSX rendering helpers will handle both patterns. In the case of the newer `DirectComponent` pattern, the component will still work, even without any jsx hooks, whereas the `LegacyDirectComponent` pattern will have a somewhat undefined behavior with regards to children.

## Staged Rendering

The issue with the direct component pattern above, is that hooks are integral to writing functional components. The staged rendering pattern is designed to help with this. In this case a component is implemented in two stages, the prep stage where hooks are called, and the rendering stage where the tree is emitted.

As above there is a newer and older version of the pattern.

- `StagedComponent` - the newer version of the pattern, where the returned component function expects children as part of props.
- `StagedRender` - the older version, where children are split out and JSX hooks are required to render correctly.

Note that while the newer patterns work without any JSX hooks, the hooks will enable the element flattening.
54 changes: 54 additions & 0 deletions packages/framework-base/src/component-patterns/render.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import React from 'react';
import * as ReactJSX from 'react/jsx-runtime';
import type { RenderType, RenderResult, DirectComponent, LegacyDirectComponent } from './render.types';

export type CustomRender = () => RenderResult;

function asDirectComponent<TProps>(type: RenderType): DirectComponent<TProps> | undefined {
if (typeof type === 'function' && (type as DirectComponent<TProps>)._callDirect) {
return type as DirectComponent<TProps>;
}
return undefined;
}

function asLegacyDirectComponent<TProps>(type: RenderType): LegacyDirectComponent<TProps> | undefined {
if (typeof type === 'function' && (type as LegacyDirectComponent<TProps>)._canCompose) {
return type as LegacyDirectComponent<TProps>;
}
return undefined;
}

export function renderForJsxRuntime<TProps>(
type: React.ElementType,
props: React.PropsWithChildren<TProps>,
key?: React.Key,
jsxFn: typeof ReactJSX.jsx = ReactJSX.jsx,
): RenderResult {
const legacyDirect = asLegacyDirectComponent(type);
if (legacyDirect) {
const { children, ...rest } = props;
const newProps = { ...rest, key };
return legacyDirect(newProps, ...React.Children.toArray(children)) as RenderResult;
}
const directComponent = asDirectComponent<TProps>(type);
if (directComponent) {
const newProps = { ...props, key };
return directComponent(newProps);
}
return jsxFn(type, props, key);
}

export function renderForClassicRuntime<TProps>(type: RenderType, props: TProps, ...children: React.ReactNode[]): RenderResult {
const legacyDirect = asLegacyDirectComponent(type);
if (legacyDirect) {
return legacyDirect(props, ...children) as RenderResult;
}
const directComponent = asDirectComponent(type);
if (directComponent) {
const newProps = { ...props, children };
return directComponent(newProps);
}
return React.createElement(type, props, ...children);
}

export const renderSlot = renderForClassicRuntime;
114 changes: 114 additions & 0 deletions packages/framework-base/src/component-patterns/render.types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import type React from 'react';
import type ReactJSX from 'react/jsx-runtime';

/**
* Base types for rendering components in a react application, extracted from react types
*/
export type RenderResult = ReturnType<typeof ReactJSX.jsx>;
export type RenderType = Parameters<typeof ReactJSX.jsx>[0] | string;

/**
* The standard element type inputs for react and react-native. This might be View or Button, or it might be 'div' in web. Effectively
* it is what react accepts for React.createElement
*/
export type NativeReactType = RenderType;

/**
* DIRECT RENDERING
*
* This is a pattern where a function can by called directly to render a component, bypassing creating additional layers of the
* rendering tree. This is useful for higher order components that mainly need to do simple prop manipulation but want to
* compartmentalize the logic.
*
* Note that for this to be safe, hooks cannot be used in the function. This is the reason why function component is redefined,
* to help linting tools catch bad usage.
*
* The newer DirectComponent type should be used, as it will handle children consistently.
*/

/**
* type of the render function, not a FunctionComponent to help prevent hook usage
*/
export type DirectComponentFunction<TProps> = (props: TProps) => RenderResult;

/**
* The full component definition that has the attached properties to allow the jsx handlers to render it directly.
*/
export type DirectComponent<TProps> = DirectComponentFunction<TProps> & {
displayName?: string;
_callDirect?: boolean;
};

/**
* Legacy slot function type, this allows the rendering handlers to bypass the normal JSX rendering and call the function
* directly. This expects the function to have children as the last argument of the call which isn't consistent with standard
* react usage, where children are passed as a prop. If writing new components use the DirectComponent type instead.
* @deprecated use DirectComponent instead
*/
export type LegacyDirectComponent<TProps> = React.FunctionComponent<TProps> & {
_canCompose?: boolean;
};

/**
* Legacy type name used for consistency with old rendering patterns.
*/
export type SlotFn<TProps> = LegacyDirectComponent<TProps>;

/**
* MULTI-STAGE RENDERING
*
* The above direct rendering pattern is useful for simple components, but it does not allow for hooks or complex logic. The staged render pattern allows
* for a component to be rendered in two stages, allowing for hooks to be used in the first stage and then the second stage to be a simple render function that can
* be called directly.
*
* In code that respects the pattern the first stage will be called with props (though children will not be present) and will return a function that will be called
* with additional props, this time with children present. This allows for the first stage to handle all the logic and hooks, while the second stage can be a simple render function
* that can leverage direct rendering if supported.
*
* The component itself will be a FunctionComponent, but it will have an attached property that is the staged render function. This allows the component to be used in two
* parts via the useSlot hook, or to be used directly in JSX/TSX as a normal component.
*/

/**
* This is an updated version of the staged render that handles children and types more consistently. Generally children
* will be passed as part of the props for component rendering, it is inconsistent to have them as a variable argument.
*
* The `children` prop will be automatically inferred and typed correctly by the prop type. Hooks are still expected
*/
export type TwoStageRender<TProps> = (props: TProps) => React.ComponentType<React.PropsWithChildren<TProps>>;

/**
* Component type for a component that can be rendered in two stages, with the attached render function.
*/
export type StagedComponent<TProps> = React.FunctionComponent<TProps> & {
_twoStageRender?: TwoStageRender<TProps>;
};

/**
* The final rendering of the props in a staged render. This is the function component signature that matches that of
* React.createElement, children (if present) will be part of the variable args at the end.
*/
export type FinalRender<TProps> = (props: TProps, ...children: React.ReactNode[]) => JSX.Element | null;

/**
* Signature for a staged render function.
* @deprecated Use TwoStageRender instead
*/
export type StagedRender<TProps> = (props: TProps, ...args: any[]) => FinalRender<TProps>;

/**
* Signature for a component that uses the staged render pattern.
* @deprecated Use TwoStageRender instead
*/
export type ComposableFunction<TProps> = React.FunctionComponent<TProps> & { _staged?: StagedRender<TProps> };

/**
* A type aggregating all the custom types that can be used in the render process.
* @internal only used in this package, should not be exported
*/
export type AnyCustomType<TProps> =
| React.FunctionComponent<TProps>
| DirectComponent<TProps>
| StagedComponent<TProps>
| ComposableFunction<TProps>
| LegacyDirectComponent<TProps>;
Loading