From 364c4b60f9f6706a4b85d915b3d335fda99f56c2 Mon Sep 17 00:00:00 2001 From: Anna Dingler <47195059+amdingler@users.noreply.github.com> Date: Fri, 13 Aug 2021 09:45:21 -0400 Subject: [PATCH] Add Expander native component (WinUI 2.6) (#756) ### Platforms Impacted - [ ] iOS - [ ] macOS - [ ] win32 (Office) - [x] windows - [ ] android ### Description of changes Adding a native windows Expander control (WinUI 2.6) to FURN. - packages/experimental/Expander/windows: Contains native module code for the Expander control - packages/experimental/Expander/src: Contains code to compose the native view into a component Notable design choices: - packages/experimental/Expander/windows/ReactNativeExpander/ExpanderView.idl: ExpanderView inherits from a ContentPresenter, and the Expander control is displayed from within the ContentPresenter control. This is because we are unable to inherit from the Expander control directly. Future work: - Resolve layout issues between Yoga and XAML layout systems within the RNW framework. Conflicting layout systems caused the following issues and work arounds in the Expander implementation: - Expander requires a fixed collapsed and expanded height - height must be exposed as props to allow the proper collapsed/expanded height changes. If the user tries to use the height prop, it is overridden. Can potentially fix this using token states to update the Expander height. - Expander does not account for height changes caused by window resizing - User cannot interact with non-native components within the header. In this scenario, the Expander will expand instead. - Remove implicit ordering of Expander header and content. The user should be able to specify which child element is the header and which is the content. ### Verification Tested on a [React Native for Windows v0.64.x tester app](https://github.com/amdingler/expander-tester). Expander was tested for light mode, dark mode, and keyboard accessibility. **Basic Expander** https://user-images.githubusercontent.com/47195059/127239274-c1378b17-2429-410e-9afd-d769772e7c35.mp4 **Full Expander Functionality** https://user-images.githubusercontent.com/47195059/127241306-3870077d-9a22-4f2c-85e7-3cdc2d329d5c.mp4 **Dark Mode** Dark_Mode_Expander ### Pull request checklist This PR has considered (when applicable): - [ ] Automated Tests - [x] Documentation and examples - [x] Keyboard Accessibility - [ ] Voiceover - [ ] Internationalization and Right-to-left Layouts --- apps/fluent-tester/package.json | 1 + .../TestComponents/Expander/ExpanderTest.tsx | 125 +++++ .../TestComponents/Expander/consts.ts | 2 + .../TestComponents/Expander/index.ts | 2 + .../src/FluentTester/testPages.ts | 6 + .../src/FluentTester/testPages.windows.ts | 6 + ...-ded57293-a3eb-4c0e-a8ee-6486ec59beef.json | 7 + ...-fa6c5c21-a7e4-43ac-a631-f831de6b8c6b.json | 7 + packages/experimental/Expander/CHANGELOG.json | 4 + packages/experimental/Expander/CHANGELOG.md | 5 + packages/experimental/Expander/README.md | 440 ++++++++++++++++++ .../experimental/Expander/babel.config.js | 1 + packages/experimental/Expander/just.config.js | 3 + packages/experimental/Expander/package.json | 44 ++ .../experimental/Expander/src/Expander.tsx | 47 ++ .../Expander/src/Expander.types.ts | 161 +++++++ packages/experimental/Expander/src/index.ts | 2 + packages/experimental/Expander/tsconfig.json | 7 + .../experimental/Expander/windows/.gitignore | 92 ++++ .../Expander/windows/NuGet.Config | 14 + .../Expander/windows/ReactNativeExpander.sln | 187 ++++++++ .../ReactNativeExpander/ExpanderView.cpp | 265 +++++++++++ .../ReactNativeExpander/ExpanderView.h | 34 ++ .../ReactNativeExpander/ExpanderView.idl | 12 + .../ExpanderViewManager.cpp | 146 ++++++ .../ReactNativeExpander/ExpanderViewManager.h | 49 ++ .../ReactNativeExpander/PropertySheet.props | 16 + .../ReactNativeExpander.def | 3 + .../ReactNativeExpander.vcxproj | 167 +++++++ .../ReactNativeExpander.vcxproj.filters | 24 + .../ReactPackageProvider.cpp | 19 + .../ReactPackageProvider.h | 18 + .../ReactPackageProvider.idl | 9 + .../ReactNativeExpander/packages.config | 5 + .../windows/ReactNativeExpander/pch.cpp | 1 + .../windows/ReactNativeExpander/pch.h | 26 ++ yarn.lock | 17 + 37 files changed, 1974 insertions(+) create mode 100644 apps/fluent-tester/src/FluentTester/TestComponents/Expander/ExpanderTest.tsx create mode 100644 apps/fluent-tester/src/FluentTester/TestComponents/Expander/consts.ts create mode 100644 apps/fluent-tester/src/FluentTester/TestComponents/Expander/index.ts create mode 100644 change/@fluentui-react-native-experimental-expander-ded57293-a3eb-4c0e-a8ee-6486ec59beef.json create mode 100644 change/@fluentui-react-native-tester-fa6c5c21-a7e4-43ac-a631-f831de6b8c6b.json create mode 100644 packages/experimental/Expander/CHANGELOG.json create mode 100644 packages/experimental/Expander/CHANGELOG.md create mode 100644 packages/experimental/Expander/README.md create mode 100644 packages/experimental/Expander/babel.config.js create mode 100644 packages/experimental/Expander/just.config.js create mode 100644 packages/experimental/Expander/package.json create mode 100644 packages/experimental/Expander/src/Expander.tsx create mode 100644 packages/experimental/Expander/src/Expander.types.ts create mode 100644 packages/experimental/Expander/src/index.ts create mode 100644 packages/experimental/Expander/tsconfig.json create mode 100644 packages/experimental/Expander/windows/.gitignore create mode 100644 packages/experimental/Expander/windows/NuGet.Config create mode 100644 packages/experimental/Expander/windows/ReactNativeExpander.sln create mode 100644 packages/experimental/Expander/windows/ReactNativeExpander/ExpanderView.cpp create mode 100644 packages/experimental/Expander/windows/ReactNativeExpander/ExpanderView.h create mode 100644 packages/experimental/Expander/windows/ReactNativeExpander/ExpanderView.idl create mode 100644 packages/experimental/Expander/windows/ReactNativeExpander/ExpanderViewManager.cpp create mode 100644 packages/experimental/Expander/windows/ReactNativeExpander/ExpanderViewManager.h create mode 100644 packages/experimental/Expander/windows/ReactNativeExpander/PropertySheet.props create mode 100644 packages/experimental/Expander/windows/ReactNativeExpander/ReactNativeExpander.def create mode 100644 packages/experimental/Expander/windows/ReactNativeExpander/ReactNativeExpander.vcxproj create mode 100644 packages/experimental/Expander/windows/ReactNativeExpander/ReactNativeExpander.vcxproj.filters create mode 100644 packages/experimental/Expander/windows/ReactNativeExpander/ReactPackageProvider.cpp create mode 100644 packages/experimental/Expander/windows/ReactNativeExpander/ReactPackageProvider.h create mode 100644 packages/experimental/Expander/windows/ReactNativeExpander/ReactPackageProvider.idl create mode 100644 packages/experimental/Expander/windows/ReactNativeExpander/packages.config create mode 100644 packages/experimental/Expander/windows/ReactNativeExpander/pch.cpp create mode 100644 packages/experimental/Expander/windows/ReactNativeExpander/pch.h diff --git a/apps/fluent-tester/package.json b/apps/fluent-tester/package.json index a4048c89e3..d46ad12153 100644 --- a/apps/fluent-tester/package.json +++ b/apps/fluent-tester/package.json @@ -35,6 +35,7 @@ "@fluentui-react-native/experimental-button": "0.8.9", "@fluentui-react-native/experimental-native-button": ">=0.7.10 <1.0.0", "@fluentui-react-native/experimental-native-date-picker": ">=0.3.3 <1.0.0", + "@fluentui-react-native/experimental-expander": "0.1.0", "@fluentui-react-native/experimental-shimmer": "0.5.4", "@fluentui-react-native/experimental-text": ">=0.6.9 <1.0.0", "@fluentui-react-native/framework": ">=0.5.28 <1.0.0", diff --git a/apps/fluent-tester/src/FluentTester/TestComponents/Expander/ExpanderTest.tsx b/apps/fluent-tester/src/FluentTester/TestComponents/Expander/ExpanderTest.tsx new file mode 100644 index 0000000000..9001f2b74b --- /dev/null +++ b/apps/fluent-tester/src/FluentTester/TestComponents/Expander/ExpanderTest.tsx @@ -0,0 +1,125 @@ +import * as React from 'react'; +import { Expander } from '@fluentui-react-native/experimental-expander'; +import { Text } from '@fluentui/react-native'; +import { Stack } from '@fluentui-react-native/stack'; +import { stackStyle, commonTestStyles as commonStyles } from '../Common/styles'; +import { Test, TestSection, PlatformStatus } from '../Test'; +import { EXPANDER_TESTPAGE } from './consts'; +import { View, Switch } from 'react-native'; + +const expanderTest: React.FunctionComponent = () => { + /** This test page has not yet been tested and does not currently build because + * the react-native-test-app does not yet support WinUI 2.6 + * Filed issue in react-native-test-app: https://github.com/microsoft/react-native-test-app/issues/444 + */ + const CustomizedExpander = Expander.customize({ + headerBackground: '#9c9c9c', + headerForeground: '#ffffff', + contentBackground:'#c3c3c3', + chevronBackground: '#ff7f7f', + chevronForeground: '#ffffff', + chevronPointerOverBackground: '#b5ffb2', + chevronPointerOverForeground: '#bfbdbd', + chevronPressedBackground: '#ffb2f4', + chevronPressedForeground: '#912a2a', + headerForegroundPointerOver: '#27f238', + headerForegroundPressed: '#f227eb', + headerBorderBrush: '#f22727', + headerBorderPointerOverBrush: '#27f238', + headerBorderPressedBrush: '#f227eb', + contentBorderBrush: '#f227eb', + headerBorderThickness: 2, + chevronBorderBrush: '#f22727', + chevronBorderPointerOverBrush: '#27f238', + chevronBorderPressedBrush: '#f227eb', + chevronBorderThickness: 2 + }); + + const [switchValue, setSwitchValue] = React.useState(false); + const [expanderText, setExpanderText] = React.useState('Initial state'); + const onExpanding = () => { + setExpanderText('Expanding event changed title'); + } + const onCollapsing = () => { + setExpanderText('Collapsing event changed title'); + } + + return ( + + expanded=true, expandDirection=up, and event functionality + + {expanderText} + Text in the content + + enabled = false + + User cannot interact with this control + Content that you should not see + + Multiple components in header and content + + + + Line one + Line two + + + + + First line of text + Second line of text + + + Customized Expander + + Text in the header + Text in the content + + + ); +}; + +const basicExpander: React.FunctionComponent = () => { + return ( + + + + Text in the header + Text in the content + + + + ); +}; + +const expanderSections: TestSection[] = [ + { + name: 'BaseExpander', + testID: EXPANDER_TESTPAGE, + component: basicExpander, + }, + { + name: 'Expander', + testID: EXPANDER_TESTPAGE, + component: expanderTest, + }, +]; + +export const ExpanderTest: React.FunctionComponent = () => { + const status: PlatformStatus = { + win32Status: 'Backlog', + uwpStatus: 'Beta', + iosStatus: 'Backlog', + macosStatus: 'Backlog', + androidStatus: 'Backlog', + }; + + const description = + 'Expander is a content control that displays components in the header and content. The control has an expanded and collapsed size. Expander is a native control implemented with WinUI 2.6 Expander.'; + + return ; +}; diff --git a/apps/fluent-tester/src/FluentTester/TestComponents/Expander/consts.ts b/apps/fluent-tester/src/FluentTester/TestComponents/Expander/consts.ts new file mode 100644 index 0000000000..fc05fb1252 --- /dev/null +++ b/apps/fluent-tester/src/FluentTester/TestComponents/Expander/consts.ts @@ -0,0 +1,2 @@ +export const HOMEPAGE_EXPANDER_BUTTON = 'Homepage_Expander_Button'; +export const EXPANDER_TESTPAGE = 'Expander_TestPage'; \ No newline at end of file diff --git a/apps/fluent-tester/src/FluentTester/TestComponents/Expander/index.ts b/apps/fluent-tester/src/FluentTester/TestComponents/Expander/index.ts new file mode 100644 index 0000000000..3d3a7ebf97 --- /dev/null +++ b/apps/fluent-tester/src/FluentTester/TestComponents/Expander/index.ts @@ -0,0 +1,2 @@ +export * from './ExpanderTest'; +export * from './consts'; \ No newline at end of file diff --git a/apps/fluent-tester/src/FluentTester/testPages.ts b/apps/fluent-tester/src/FluentTester/testPages.ts index cec28bf235..3b84af1c72 100644 --- a/apps/fluent-tester/src/FluentTester/testPages.ts +++ b/apps/fluent-tester/src/FluentTester/testPages.ts @@ -21,6 +21,7 @@ import { ContextualMenuTest, HOMEPAGE_CONTEXTUALMENU_BUTTON } from './TestCompon import { ActivityIndicatorTest, HOMEPAGE_ACTIVITYINDICATOR_BUTTON } from './TestComponents/ActivityIndicator'; import { MenuButtonTest, HOMEPAGE_MENU_BUTTON } from './TestComponents/MenuButton'; import { HOMEPAGE_TOKEN_BUTTON, TokenTest } from './TestComponents/Tokens'; +import { ExpanderTest, HOMEPAGE_EXPANDER_BUTTON } from './TestComponents/Expander'; export const tests: TestDescription[] = [ { @@ -133,4 +134,9 @@ export const tests: TestDescription[] = [ component: TokenTest, testPage: HOMEPAGE_TOKEN_BUTTON, }, + { + name: 'Expander Test', + component: ExpanderTest, + testPage: HOMEPAGE_EXPANDER_BUTTON, + }, ]; diff --git a/apps/fluent-tester/src/FluentTester/testPages.windows.ts b/apps/fluent-tester/src/FluentTester/testPages.windows.ts index 7cea0cea9d..ed9b8a44e3 100644 --- a/apps/fluent-tester/src/FluentTester/testPages.windows.ts +++ b/apps/fluent-tester/src/FluentTester/testPages.windows.ts @@ -11,6 +11,7 @@ import { HOMEPAGE_SEPARATOR_BUTTON, SeparatorTest } from './TestComponents/Separ import { HOMEPAGE_TEXT_BUTTON, TextTest } from './TestComponents/Text'; import { HOMEPAGE_THEME_BUTTON, ThemeTest } from './TestComponents/Theme'; import { HOMEPAGE_TOKEN_BUTTON, TokenTest } from './TestComponents/Tokens'; +import { ExpanderTest, HOMEPAGE_EXPANDER_BUTTON } from './TestComponents/Expander'; export const tests = [ { @@ -66,4 +67,9 @@ export const tests = [ component: TokenTest, testPage: HOMEPAGE_TOKEN_BUTTON, }, + { + name: 'Expander Test', + component: ExpanderTest, + testPage: HOMEPAGE_EXPANDER_BUTTON, + }, ]; diff --git a/change/@fluentui-react-native-experimental-expander-ded57293-a3eb-4c0e-a8ee-6486ec59beef.json b/change/@fluentui-react-native-experimental-expander-ded57293-a3eb-4c0e-a8ee-6486ec59beef.json new file mode 100644 index 0000000000..d82c1e1546 --- /dev/null +++ b/change/@fluentui-react-native-experimental-expander-ded57293-a3eb-4c0e-a8ee-6486ec59beef.json @@ -0,0 +1,7 @@ +{ + "type": "minor", + "comment": "Add native Expander component", + "packageName": "@fluentui-react-native/experimental-expander", + "email": "amdingler694@gmail.com", + "dependentChangeType": "patch" +} diff --git a/change/@fluentui-react-native-tester-fa6c5c21-a7e4-43ac-a631-f831de6b8c6b.json b/change/@fluentui-react-native-tester-fa6c5c21-a7e4-43ac-a631-f831de6b8c6b.json new file mode 100644 index 0000000000..f40bcd5330 --- /dev/null +++ b/change/@fluentui-react-native-tester-fa6c5c21-a7e4-43ac-a631-f831de6b8c6b.json @@ -0,0 +1,7 @@ +{ + "type": "patch", + "comment": "Add Expander component test", + "packageName": "@fluentui-react-native/tester", + "email": "amdingler694@gmail.com", + "dependentChangeType": "patch" +} diff --git a/packages/experimental/Expander/CHANGELOG.json b/packages/experimental/Expander/CHANGELOG.json new file mode 100644 index 0000000000..2106ecee98 --- /dev/null +++ b/packages/experimental/Expander/CHANGELOG.json @@ -0,0 +1,4 @@ +{ + "name": "@fluentui-react-native/experimental-expander", + "entries": [] +} diff --git a/packages/experimental/Expander/CHANGELOG.md b/packages/experimental/Expander/CHANGELOG.md new file mode 100644 index 0000000000..461ec58c49 --- /dev/null +++ b/packages/experimental/Expander/CHANGELOG.md @@ -0,0 +1,5 @@ +# Change Log - @fluentui-react-native/experimental-native-button + +This log was last generated on Thu, 11 Mar 2021 19:48:16 GMT and should not be manually modified. + + diff --git a/packages/experimental/Expander/README.md b/packages/experimental/Expander/README.md new file mode 100644 index 0000000000..bdaf23e7c6 --- /dev/null +++ b/packages/experimental/Expander/README.md @@ -0,0 +1,440 @@ +# @fluentui-react-native/experimental-expander + +This package provides a FluentUI React Native implementation of the [WinUI 2.6 Expander control](https://docs.microsoft.com/en-us/windows/winui/api/microsoft.ui.xaml.controls.expander?view=winui-2.6). + +This component is only supported on Windows. + +## Getting started + +`$ npm install @fluentui-react-native/experimental-expander --save` + +or + +`$ yarn add @fluentui-react-native/experimental-expander` + +After the package has been installed, run the following command to link the package. + +`npx react-native autolink-windows` + +## Usage + +Import Expander from @fluentui-react-native/experimental-expander + +`import { Expander } from @fluentui-react-native/experimental-expander` + +Add `Expander` as follows: + +```javascript + + Text in the header + Text in the content + +``` + +The first child element of Expander will be assigned to the header, and the second child element will be assigned to the content. If you would like multiple components in the header or the content, wrap them in `View` as follows: + +```javascript + + + Text in the header + Second line of text in the header + + + Text in the content + Second line of text in the content + + +``` + +### Props + +- [`expandDirection`](#expanddirection) +- [`expanded`](#expanded) +- [`enabled`](#enabled) +- [`height`](#height) +- [`expandedHeight`](#expandedheight) +- [`collapsedHeight`](#collapsedheight) +- [`onCollapsing`](#oncollapsing) +- [`onExpanding`](#onexpanding) + +### Tokens + +- [`width`](#width) +- [`contentHorizontalAlignment`](#contenthorizontalalignment) +- [`contentVerticalAlignment`](#contentverticalalignment) +- [`headerBackground`](#headerbackground) +- [`headerForeground`](#headerforeground) +- [`headerForegroundPointerOver`](#headerforegroundpointerover) +- [`headerForegroundPressed`](#headerforegroundpressed) +- [`headerBorderBrush`](#headerborderbrush) +- [`headerBorderPointerOverBrush`](#headerborderpointeroverbrush) +- [`headerBorderPressedBrush`](#headerborderpressedbrush) +- [`headerDisabledForeground`](#headerdisabledforeground) +- [`headerDisabledBorderBrush`](#headerdisabledborderbrush) +- [`headerBorderThickness`](#headerborderthickness) +- [`contentBackground`](#contentbackground) +- [`contentBorderBrush`](#contentborderbrush) +- [`chevronBackground`](#chevronbackground) +- [`chevronForeground`](#chevronforeground) +- [`chevronPointerOverBackground`](#chevronpointeroverbackground) +- [`chevronPointerOverForeground`](#chevronpointeroverforeground) +- [`chevronPressedBackground`](#chevronpressedbackground) +- [`chevronPressedForeground`](#chevronpressedforeground) +- [`chevronBorderThickness`](#chevronborderthickness) +- [`chevronBorderBrush`](#chevronborderbrush) +- [`chevronBorderPointerOverBrush`](#chevronborderpointeroverbrush) +- [`chevronBorderPressedBrush`](#chevronborderpressedbrush) + +### Important notes + +- `collapsedHeight` and `expandedHeight` must be set for Expander to display correctly. There is no need to set the `height` prop. +- Non-native components within the header will not be interactable (i.e. if there is a `Button` in the header, and you press it, the Expander will expand, and the `Button` functionality will not be performed). We recommend using [react-native-xaml](https://github.com/asklar/react-native-xaml) controls in the header if you would like an interactive control that does not have a native implementation. + +--- + +# Reference + +## Props + +### `expandDirection` + +Specifies the direction that the Expander should expand. + +* 'down': Expands downward. This is the default. +* 'up': Expands upward. + +| Type | Required | +| ------------------ | -------- | +| enum('up', 'down') | No | + +--- + +### `expanded` + +Specifies if the Expander is expanded. Default is `false`. + +| Type | Required | +| ------- | -------- | +| boolean | No | + +--- + +### `enabled` + +If set to false, the Expander will be disabled, i.e. the user will not be able to expand it. + +| Type | Required | +| ------- | -------- | +| boolean | No | + +--- + +### `height` + +Sets the height of the Expander. In order to maintain expand/collapse functionality, do not set this value. Instead, use `collapsedHeight` and `expandedHeight`. + +| Type | Required | +| ------ | -------- | +| number | No | + +--- + +### `expandedHeight` + +Sets the height of the Expander while it is expanded. + +| Type | Required | +| ------ | --------- | +| number | Yes | + +--- + +### `collapsedHeight` + +Sets the height of the Expander while it is collapsed. + +| Type | Required | +| ------ | --------- | +| number | Yes | + +--- + +### `onCollapsing` + +Callback for when the Expander begins to collapse. + +| Type | Required | +| -------- | -------- | +| function | No | + +--- + +### `onExpanding` + +Callback for when the Expander begins to expand. + +| Type | Required | +| -------- | -------- | +| function | No | + +--- + +## Tokens + +### `width` + +Sets the width of the Expander. + +| Type | Required | +| ------ | -------- | +| number | No | + +--- + +### `contentHorizontalAlignment` + +Sets the horizontal alignment of the Expander's content. + +* 'center': The content is aligned in the center of the Expander. +* 'left': The content is aligned to the left of the Expander. +* 'right': The content is aligned to the right of the Expander. +* 'stretch': The content will stretch to fit the Expander. + +| Type | Required | +| ------------------------------------------ | -------- | +| enum('center', 'left', 'right', 'stretch') | No | + +--- + +### `contentVerticalAlignment` + +Sets the vertical alignment of the Expander's content. + +* 'bottom': The content is aligned to the bottom of the Expander. +* 'center': The content is aligned in the center of the Expander. +* 'stretch': The content will stretch to fit the Expander. +* 'top': The content is aligned to the top of the Expander. + +| Type | Required | +| ------------------------------------------ | -------- | +| enum('bottom', 'center', 'stretch', 'top') | No | + +--- + +### `headerBackground` + +Header background color. Input value should be hexadecimal string or a [predefined color name](https://docs.microsoft.com/en-us/windows/winui/api/microsoft.ui.colors?view=winui-3.0) string. + +| Type | Required | +| ------ | -------- | +| string | No | + +--- + +### `headerForeground` + +Header foreground color at rest. Input value should be hexadecimal string or a [predefined color name](https://docs.microsoft.com/en-us/windows/winui/api/microsoft.ui.colors?view=winui-3.0) string. + +| Type | Required | +| ------ | -------- | +| string | No | + +--- + +### `headerForegroundPointerOver` + +Header foreground color on pointer over. Input value should be hexadecimal string or a [predefined color name](https://docs.microsoft.com/en-us/windows/winui/api/microsoft.ui.colors?view=winui-3.0) string. + +| Type | Required | +| ------ | -------- | +| string | No | + +--- + +### `headerForegroundPressed` + +Header foreground color when pressed. Input value should be hexadecimal string or a [predefined color name](https://docs.microsoft.com/en-us/windows/winui/api/microsoft.ui.colors?view=winui-3.0) string. + +| Type | Required | +| ------ | -------- | +| string | No | + +--- + +### `headerBorderBrush` + +Header border color at rest. Input value should be hexadecimal string or a [predefined color name](https://docs.microsoft.com/en-us/windows/winui/api/microsoft.ui.colors?view=winui-3.0) string. + +| Type | Required | +| ------ | -------- | +| string | No | + +--- + +### `headerBorderPointerOverBrush` + +Header border color on pointer over. Input value should be hexadecimal string or a [predefined color name](https://docs.microsoft.com/en-us/windows/winui/api/microsoft.ui.colors?view=winui-3.0) string. + +| Type | Required | +| ------ | -------- | +| string | No | + +--- + +### `headerBorderPressedBrush` + +Header border color when pressed. Input value should be hexadecimal string or a [predefined color name](https://docs.microsoft.com/en-us/windows/winui/api/microsoft.ui.colors?view=winui-3.0) string. + +| Type | Required | +| ------ | -------- | +| string | No | + +--- + +### `headerDisabledForeground` + +Header foreground color when disabled. Input value should be hexadecimal string or a [predefined color name](https://docs.microsoft.com/en-us/windows/winui/api/microsoft.ui.colors?view=winui-3.0) string. + +| Type | Required | +| ------ | -------- | +| string | No | + +--- + +### `headerDisabledBorderBrush` + +Header border color when disabled. Input value should be hexadecimal string or a [predefined color name](https://docs.microsoft.com/en-us/windows/winui/api/microsoft.ui.colors?view=winui-3.0) string. + +| Type | Required | +| ------ | -------- | +| string | No | + +--- + +### `headerBorderThickness` + +Header border thickness. + +| Type | Required | +| ------ | -------- | +| number | No | + +--- + +### `contentBackground` + +Content background color. Input value should be hexadecimal string or a [predefined color name](https://docs.microsoft.com/en-us/windows/winui/api/microsoft.ui.colors?view=winui-3.0) string. + +| Type | Required | +| ------ | -------- | +| string | No | + +--- + +### `contentBorderBrush` + +Content border color. Input value should be hexadecimal string or a [predefined color name](https://docs.microsoft.com/en-us/windows/winui/api/microsoft.ui.colors?view=winui-3.0) string. + +| Type | Required | +| ------ | -------- | +| string | No | + +--- + +### `chevronBackground` + +Chevron background color at rest. Input value should be hexadecimal string or a [predefined color name](https://docs.microsoft.com/en-us/windows/winui/api/microsoft.ui.colors?view=winui-3.0) string. + +| Type | Required | +| ------ | -------- | +| string | No | + +--- + +### `chevronForeground` + +Chevron foreground color at rest. Input value should be hexadecimal string or a [predefined color name](https://docs.microsoft.com/en-us/windows/winui/api/microsoft.ui.colors?view=winui-3.0) string. + +| Type | Required | +| ------ | -------- | +| string | No | + +--- + +### `chevronPointerOverBackground` + +Chevron background color on pointer over. Input value should be hexadecimal string or a [predefined color name](https://docs.microsoft.com/en-us/windows/winui/api/microsoft.ui.colors?view=winui-3.0) string. + +| Type | Required | +| ------ | -------- | +| string | No | + +--- + +### `chevronPointerOverForeground` + +Chevron foreground color on pointer over. Input value should be hexadecimal string or a [predefined color name](https://docs.microsoft.com/en-us/windows/winui/api/microsoft.ui.colors?view=winui-3.0) string. + +| Type | Required | +| ------ | -------- | +| string | No | + +--- + +### `chevronPressedBackground` + +Chevron background color when pressed. Input value should be hexadecimal string or a [predefined color name](https://docs.microsoft.com/en-us/windows/winui/api/microsoft.ui.colors?view=winui-3.0) string. + +| Type | Required | +| ------ | -------- | +| string | No | + +--- + +### `chevronPressedForeground` + +Chevron foreground color when pressed. Input value should be hexadecimal string or a [predefined color name](https://docs.microsoft.com/en-us/windows/winui/api/microsoft.ui.colors?view=winui-3.0) string. + +| Type | Required | +| ------ | -------- | +| string | No | + +--- + +### `chevronBorderThickness` + +Chevron border thickness. + +| Type | Required | +| ------ | -------- | +| number | No | + +--- + +### `chevronBorderBrush` + +Chevron border color at rest. Input value should be hexadecimal string or a [predefined color name](https://docs.microsoft.com/en-us/windows/winui/api/microsoft.ui.colors?view=winui-3.0) string. + +| Type | Required | +| ------ | -------- | +| string | No | + +--- + +### `chevronBorderPointerOverBrush` + +Chevron border color on pointer over. Input value should be hexadecimal string or a [predefined color name](https://docs.microsoft.com/en-us/windows/winui/api/microsoft.ui.colors?view=winui-3.0) string. + +| Type | Required | +| ------ | -------- | +| string | No | + +--- + +### `chevronBorderPressedBrush` + +Chevron border color when pressed. Input value should be hexadecimal string or a [predefined color name](https://docs.microsoft.com/en-us/windows/winui/api/microsoft.ui.colors?view=winui-3.0) string. + +| Type | Required | +| ------ | -------- | +| string | No | diff --git a/packages/experimental/Expander/babel.config.js b/packages/experimental/Expander/babel.config.js new file mode 100644 index 0000000000..e7dd7c3c35 --- /dev/null +++ b/packages/experimental/Expander/babel.config.js @@ -0,0 +1 @@ +module.exports = require('@uifabricshared/build-native/babel.config'); diff --git a/packages/experimental/Expander/just.config.js b/packages/experimental/Expander/just.config.js new file mode 100644 index 0000000000..4bd28548c5 --- /dev/null +++ b/packages/experimental/Expander/just.config.js @@ -0,0 +1,3 @@ +const { preset } = require('@uifabricshared/build-native'); + +preset(); diff --git a/packages/experimental/Expander/package.json b/packages/experimental/Expander/package.json new file mode 100644 index 0000000000..62af3d1784 --- /dev/null +++ b/packages/experimental/Expander/package.json @@ -0,0 +1,44 @@ +{ + "name": "@fluentui-react-native/experimental-expander", + "version": "0.1.0", + "description": "A cross-platform Native Expander component using the Fluent Design System. Currently only implemented on windows", + "license": "MIT", + "author": "Microsoft ", + "homepage": "https://github.com/microsoft/fluentui-react-native", + "main": "src/index.ts", + "module": "src/index.ts", + "typings": "lib/index.d.ts", + "onPublish": { + "main": "lib-commonjs/index.js", + "module": "lib/index.js" + }, + "scripts": { + "build": "fluentui-scripts build", + "just": "fluentui-scripts", + "clean": "fluentui-scripts clean", + "lint": "fluentui-scripts eslint", + "depcheck": "fluentui-scripts depcheck", + "test": "fluentui-scripts jest", + "update-snapshots": "fluentui-scripts jest -u", + "verify-api": "fluentui-scripts verify-api-extractor", + "update-api": "fluentui-scripts update-api-extractor", + "windows": "react-native run-windows" + }, + "dependencies": { + "@fluentui-react-native/component-cache": "^1.2.0", + "@fluentui-react-native/framework": "0.5.15" + }, + "devDependencies": { + "@types/react-native": "^0.63.0", + "@uifabricshared/build-native": "^0.1.1", + "@uifabricshared/eslint-config-rules": "^0.1.1", + "react": "16.13.1", + "react-native": "^0.63.4", + "react-native-windows": "^0.63.0" + }, + "peerDependencies": { + "react": ">=16.13.1", + "react-native": ">=0.63.4", + "react-native-windows": ">=0.63.0" + } +} diff --git a/packages/experimental/Expander/src/Expander.tsx b/packages/experimental/Expander/src/Expander.tsx new file mode 100644 index 0000000000..64f11b811a --- /dev/null +++ b/packages/experimental/Expander/src/Expander.tsx @@ -0,0 +1,47 @@ +/** @jsx withSlots */ +import * as React from 'react'; +import { expanderName, ExpanderType, ExpanderProps, ExpanderViewProps } from './Expander.types'; +import { compose, mergeProps, withSlots, UseSlots, buildProps } from '@fluentui-react-native/framework'; +import { ensureNativeComponent } from '@fluentui-react-native/component-cache'; + +const ExpanderComponent = ensureNativeComponent('MSFExpanderView'); +function delay(ms: number) { + return new Promise( resolve => setTimeout(resolve, ms) ); +} + +export const Expander = compose({ + displayName: expanderName, + tokens: [{}, expanderName], + slotProps: { + root: buildProps( + (tokens) => ({...tokens}) + ), + }, + slots: { root: ExpanderComponent }, + render: (userProps: ExpanderProps, useSlots: UseSlots) => { + const Root = useSlots(userProps).root; + const [expandedState, setExpandedState] = React.useState(userProps.expanded); + const expanderHeight = expandedState? userProps.expandedHeight : userProps.collapsedHeight; + + const _onCollapsing = async () => { + userProps.onCollapsing?.(); + // Need to delay the height change so that the animation runs + await delay(175); + setExpandedState(false); + } + + const _onExpanding = () => { + setExpandedState(true); + userProps.onExpanding?.(); + } + + return (rest: ExpanderViewProps, ...children: React.ReactNode[]) => + + {children} + ; + }, +}); diff --git a/packages/experimental/Expander/src/Expander.types.ts b/packages/experimental/Expander/src/Expander.types.ts new file mode 100644 index 0000000000..140d5d19cd --- /dev/null +++ b/packages/experimental/Expander/src/Expander.types.ts @@ -0,0 +1,161 @@ +import { ColorValue } from 'react-native'; + +export const expanderName = 'Expander'; + +/* + * General notes: + * The Expander header will be consumed as the first child element of the Expander + * The Expander content will be consumed as the second child element of the Expander + * To include more complex layouts for the header or content, wrap multiple header/content elements inside of a + */ + +export interface ExpanderProps { + /* + * Direction to expand the Expander. + */ + expandDirection?: ExpandDirection; + /* + * Determines if the expander is currently expanded + */ + expanded?: boolean; + /* + * Is Expander control enabled for the user + */ + enabled?: boolean; + /* + * Height of the Expander + */ + height?: number; + /* + * Height for the Expander when expanded + */ + expandedHeight: number; + /* + * Height for the Expander when collapsed + */ + collapsedHeight: number; + /* + * A callback to call on Expander collapsed event + */ + onCollapsing?: () => void; + /* + * A callback to call on Expander expanding event + */ + onExpanding?: () => void; +} + +export interface ExpanderTokens { + /* + * Width of the expander + */ + width?: number; + /* + * Horizontal alignment of the expander's content + */ + contentHorizontalAlignment?: HorizontalAlignment; + /* + * Vertical alignment of the expander's content + */ + contentVerticalAlignment?: VerticalAlignment; + /* + * Header background color + */ + headerBackground?: ColorValue; + /* + * Header foreground color at rest + */ + headerForeground?: ColorValue; + /* + * Header foreground color on pointer over + */ + headerForegroundPointerOver?: ColorValue; + /* + * Header foreground color when pressed + */ + headerForegroundPressed?: ColorValue; + /* + * Header border color at rest + */ + headerBorderBrush?: ColorValue; + /* + * Header border color on pointer over + */ + headerBorderPointerOverBrush?: ColorValue; + /* + * Header border color when pressed + */ + headerBorderPressedBrush?: ColorValue; + /* + * Header foreground color when disabled + */ + headerDisabledForeground?: ColorValue; + /* + * Header border color when disabled + */ + headerDisabledBorderBrush?: ColorValue; + /* + * Header border thickness + */ + headerBorderThickness?: Number; + /* + * Content background color + */ + contentBackground?: ColorValue; + /* + * Content border color + */ + contentBorderBrush?: ColorValue; + /* + * Chevron background color at rest + */ + chevronBackground?: ColorValue; + /* + * Chevron foreground color at rest + */ + chevronForeground?: ColorValue; + /* + * Chevron background color on pointer over + */ + chevronPointerOverBackground?: ColorValue; + /* + * Chevron foreground color on pointer over + */ + chevronPointerOverForeground?: ColorValue; + /* + * Chevron background color when pressed + */ + chevronPressedBackground?: ColorValue; + /* + * Chevron foreground color when pressed + */ + chevronPressedForeground?: ColorValue; + /* + * Chevron border thickness + */ + chevronBorderThickness?: Number; + /* + * Chevron border color at rest + */ + chevronBorderBrush?: ColorValue; + /* + * Chevron border color on pointer over + */ + chevronBorderPointerOverBrush?: ColorValue; + /* + * Chevron border color when pressed + */ + chevronBorderPressedBrush?: ColorValue; +} + +export type ExpandDirection = 'up' | 'down'; +export type VerticalAlignment = 'bottom' | 'center' | 'stretch' | 'top'; +export type HorizontalAlignment = 'center' | 'left' | 'right' | 'stretch'; +export type ExpanderViewProps = ExpanderProps & ExpanderTokens; + +export interface ExpanderType { + props: ExpanderProps; + tokens: ExpanderTokens; + slotProps: { + root: ExpanderViewProps + } +} diff --git a/packages/experimental/Expander/src/index.ts b/packages/experimental/Expander/src/index.ts new file mode 100644 index 0000000000..1352e61d5b --- /dev/null +++ b/packages/experimental/Expander/src/index.ts @@ -0,0 +1,2 @@ +export * from './Expander'; +export * from './Expander.types'; diff --git a/packages/experimental/Expander/tsconfig.json b/packages/experimental/Expander/tsconfig.json new file mode 100644 index 0000000000..fd8ecb574e --- /dev/null +++ b/packages/experimental/Expander/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "@uifabricshared/build-native/tsconfig.json", + "compilerOptions": { + "outDir": "lib" + }, + "include": ["src"] +} diff --git a/packages/experimental/Expander/windows/.gitignore b/packages/experimental/Expander/windows/.gitignore new file mode 100644 index 0000000000..4ea0c7b5a3 --- /dev/null +++ b/packages/experimental/Expander/windows/.gitignore @@ -0,0 +1,92 @@ +*AppPackages* +*BundleArtifacts* + +#OS junk files +[Tt]humbs.db +*.DS_Store + +#Visual Studio files +*.[Oo]bj +*.user +*.aps +*.pch +*.vspscc +*.vssscc +*_i.c +*_p.c +*.ncb +*.suo +*.tlb +*.tlh +*.bak +*.[Cc]ache +*.ilk +*.log +*.lib +*.sbr +*.sdf +*.opensdf +*.opendb +*.unsuccessfulbuild +ipch/ +[Oo]bj/ +[Bb]in +[Dd]ebug*/ +[Rr]elease*/ +Ankh.NoLoad + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +#MonoDevelop +*.pidb +*.userprefs + +#Tooling +_ReSharper*/ +*.resharper +[Tt]est[Rr]esult* +*.sass-cache + +#Project files +[Bb]uild/ + +#Subversion files +.svn + +# Office Temp Files +~$* + +# vim Temp Files +*~ + +#NuGet +packages/ +*.nupkg + +#ncrunch +*ncrunch* +*crunch*.local.xml + +# visual studio database projects +*.dbmdl + +#Test files +*.testsettings + +#Other files +*.DotSettings +.vs/ +*project.lock.json + +#Files generated by the VS build +**/Generated Files/** + diff --git a/packages/experimental/Expander/windows/NuGet.Config b/packages/experimental/Expander/windows/NuGet.Config new file mode 100644 index 0000000000..37da5d50a4 --- /dev/null +++ b/packages/experimental/Expander/windows/NuGet.Config @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/packages/experimental/Expander/windows/ReactNativeExpander.sln b/packages/experimental/Expander/windows/ReactNativeExpander.sln new file mode 100644 index 0000000000..3427750b60 --- /dev/null +++ b/packages/experimental/Expander/windows/ReactNativeExpander.sln @@ -0,0 +1,187 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.29215.179 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ReactNativeExpander", "ReactNativeExpander\ReactNativeExpander.vcxproj", "{51BB85F5-7BC7-4A61-8AF8-49B05CEF0623}" + ProjectSection(ProjectDependencies) = postProject + {F7D32BD0-2749-483E-9A0D-1635EF7E3136} = {F7D32BD0-2749-483E-9A0D-1635EF7E3136} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Folly", "..\node_modules\react-native-windows\Folly\Folly.vcxproj", "{A990658C-CE31-4BCC-976F-0FC6B1AF693D}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ReactCommon", "..\node_modules\react-native-windows\ReactCommon\ReactCommon.vcxproj", "{A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}" + ProjectSection(ProjectDependencies) = postProject + {A990658C-CE31-4BCC-976F-0FC6B1AF693D} = {A990658C-CE31-4BCC-976F-0FC6B1AF693D} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Chakra", "..\node_modules\react-native-windows\Chakra\Chakra.vcxitems", "{C38970C0-5FBF-4D69-90D8-CBAC225AE895}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Microsoft.ReactNative", "..\node_modules\react-native-windows\Microsoft.ReactNative\Microsoft.ReactNative.vcxproj", "{F7D32BD0-2749-483E-9A0D-1635EF7E3136}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "JSI.Shared", "..\node_modules\react-native-windows\JSI\Shared\JSI.Shared.vcxitems", "{0CC28589-39E4-4288-B162-97B959F8B843}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "JSI.Universal", "..\node_modules\react-native-windows\JSI\Universal\JSI.Universal.vcxproj", "{A62D504A-16B8-41D2-9F19-E2E86019E5E4}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Microsoft.ReactNative.Cxx", "..\node_modules\react-native-windows\Microsoft.ReactNative.Cxx\Microsoft.ReactNative.Cxx.vcxitems", "{DA8B35B3-DA00-4B02-BDE6-6A397B3FD46B}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Common", "..\node_modules\react-native-windows\Common\Common.vcxproj", "{FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ReactNative", "ReactNative", "{5EA20F54-880A-49F3-99FA-4B3FE54E8AB1}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Microsoft.ReactNative.Shared", "..\node_modules\react-native-windows\Shared\Shared.vcxitems", "{2049DBE9-8D13-42C9-AE4B-413AE38FFFD0}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Mso", "..\node_modules\react-native-windows\Mso\Mso.vcxitems", "{84E05BFA-CBAF-4F0D-BFB6-4CE85742A57E}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Include", "..\node_modules\react-native-windows\include\Include.vcxitems", "{EF074BA1-2D54-4D49-A28E-5E040B47CD2E}" +EndProject +Global + GlobalSection(SharedMSBuildProjectFiles) = preSolution + ..\node_modules\react-native-windows\JSI\Shared\JSI.Shared.vcxitems*{0cc28589-39e4-4288-b162-97b959f8b843}*SharedItemsImports = 9 + ..\node_modules\react-native-windows\Shared\Shared.vcxitems*{2049dbe9-8d13-42c9-ae4b-413ae38fffd0}*SharedItemsImports = 9 + ..\node_modules\react-native-windows\Mso\Mso.vcxitems*{84e05bfa-cbaf-4f0d-bfb6-4ce85742a57e}*SharedItemsImports = 9 + ..\node_modules\react-native-windows\JSI\Shared\JSI.Shared.vcxitems*{a62d504a-16b8-41d2-9f19-e2e86019e5e4}*SharedItemsImports = 4 + ..\node_modules\react-native-windows\Chakra\Chakra.vcxitems*{c38970c0-5fbf-4d69-90d8-cbac225ae895}*SharedItemsImports = 9 + ..\node_modules\react-native-windows\Microsoft.ReactNative.Cxx\Microsoft.ReactNative.Cxx.vcxitems*{da8b35b3-da00-4b02-bde6-6a397b3fd46b}*SharedItemsImports = 9 + ..\node_modules\react-native-windows\include\Include.vcxitems*{ef074ba1-2d54-4d49-a28e-5e040b47cd2e}*SharedItemsImports = 9 + ..\node_modules\react-native-windows\Chakra\Chakra.vcxitems*{f7d32bd0-2749-483e-9a0d-1635ef7e3136}*SharedItemsImports = 4 + ..\node_modules\react-native-windows\Microsoft.ReactNative.Cxx\Microsoft.ReactNative.Cxx.vcxitems*{f7d32bd0-2749-483e-9a0d-1635ef7e3136}*SharedItemsImports = 4 + ..\node_modules\react-native-windows\Mso\Mso.vcxitems*{f7d32bd0-2749-483e-9a0d-1635ef7e3136}*SharedItemsImports = 4 + ..\node_modules\react-native-windows\Shared\Shared.vcxitems*{f7d32bd0-2749-483e-9a0d-1635ef7e3136}*SharedItemsImports = 4 + EndGlobalSection + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|ARM = Debug|ARM + Debug|ARM64 = Debug|ARM64 + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|ARM = Release|ARM + Release|ARM64 = Release|ARM64 + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {51BB85F5-7BC7-4A61-8AF8-49B05CEF0623}.Debug|ARM.ActiveCfg = Debug|ARM + {51BB85F5-7BC7-4A61-8AF8-49B05CEF0623}.Debug|ARM.Build.0 = Debug|ARM + {51BB85F5-7BC7-4A61-8AF8-49B05CEF0623}.Debug|ARM.Deploy.0 = Debug|ARM + {51BB85F5-7BC7-4A61-8AF8-49B05CEF0623}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {51BB85F5-7BC7-4A61-8AF8-49B05CEF0623}.Debug|ARM64.Build.0 = Debug|ARM64 + {51BB85F5-7BC7-4A61-8AF8-49B05CEF0623}.Debug|ARM64.Deploy.0 = Debug|ARM64 + {51BB85F5-7BC7-4A61-8AF8-49B05CEF0623}.Debug|x64.ActiveCfg = Debug|x64 + {51BB85F5-7BC7-4A61-8AF8-49B05CEF0623}.Debug|x64.Build.0 = Debug|x64 + {51BB85F5-7BC7-4A61-8AF8-49B05CEF0623}.Debug|x64.Deploy.0 = Debug|x64 + {51BB85F5-7BC7-4A61-8AF8-49B05CEF0623}.Debug|x86.ActiveCfg = Debug|Win32 + {51BB85F5-7BC7-4A61-8AF8-49B05CEF0623}.Debug|x86.Build.0 = Debug|Win32 + {51BB85F5-7BC7-4A61-8AF8-49B05CEF0623}.Debug|x86.Deploy.0 = Debug|Win32 + {51BB85F5-7BC7-4A61-8AF8-49B05CEF0623}.Release|ARM.ActiveCfg = Release|ARM + {51BB85F5-7BC7-4A61-8AF8-49B05CEF0623}.Release|ARM.Build.0 = Release|ARM + {51BB85F5-7BC7-4A61-8AF8-49B05CEF0623}.Release|ARM.Deploy.0 = Release|ARM + {51BB85F5-7BC7-4A61-8AF8-49B05CEF0623}.Release|ARM64.ActiveCfg = Release|ARM64 + {51BB85F5-7BC7-4A61-8AF8-49B05CEF0623}.Release|ARM64.Build.0 = Release|ARM64 + {51BB85F5-7BC7-4A61-8AF8-49B05CEF0623}.Release|ARM64.Deploy.0 = Release|ARM64 + {51BB85F5-7BC7-4A61-8AF8-49B05CEF0623}.Release|x64.ActiveCfg = Release|x64 + {51BB85F5-7BC7-4A61-8AF8-49B05CEF0623}.Release|x64.Build.0 = Release|x64 + {51BB85F5-7BC7-4A61-8AF8-49B05CEF0623}.Release|x64.Deploy.0 = Release|x64 + {51BB85F5-7BC7-4A61-8AF8-49B05CEF0623}.Release|x86.ActiveCfg = Release|Win32 + {51BB85F5-7BC7-4A61-8AF8-49B05CEF0623}.Release|x86.Build.0 = Release|Win32 + {51BB85F5-7BC7-4A61-8AF8-49B05CEF0623}.Release|x86.Deploy.0 = Release|Win32 + {A990658C-CE31-4BCC-976F-0FC6B1AF693D}.Debug|ARM.ActiveCfg = Debug|ARM + {A990658C-CE31-4BCC-976F-0FC6B1AF693D}.Debug|ARM.Build.0 = Debug|ARM + {A990658C-CE31-4BCC-976F-0FC6B1AF693D}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {A990658C-CE31-4BCC-976F-0FC6B1AF693D}.Debug|ARM64.Build.0 = Debug|ARM64 + {A990658C-CE31-4BCC-976F-0FC6B1AF693D}.Debug|x64.ActiveCfg = Debug|x64 + {A990658C-CE31-4BCC-976F-0FC6B1AF693D}.Debug|x64.Build.0 = Debug|x64 + {A990658C-CE31-4BCC-976F-0FC6B1AF693D}.Debug|x86.ActiveCfg = Debug|Win32 + {A990658C-CE31-4BCC-976F-0FC6B1AF693D}.Debug|x86.Build.0 = Debug|Win32 + {A990658C-CE31-4BCC-976F-0FC6B1AF693D}.Release|ARM.ActiveCfg = Release|ARM + {A990658C-CE31-4BCC-976F-0FC6B1AF693D}.Release|ARM.Build.0 = Release|ARM + {A990658C-CE31-4BCC-976F-0FC6B1AF693D}.Release|ARM64.ActiveCfg = Release|ARM64 + {A990658C-CE31-4BCC-976F-0FC6B1AF693D}.Release|ARM64.Build.0 = Release|ARM64 + {A990658C-CE31-4BCC-976F-0FC6B1AF693D}.Release|x64.ActiveCfg = Release|x64 + {A990658C-CE31-4BCC-976F-0FC6B1AF693D}.Release|x64.Build.0 = Release|x64 + {A990658C-CE31-4BCC-976F-0FC6B1AF693D}.Release|x86.ActiveCfg = Release|Win32 + {A990658C-CE31-4BCC-976F-0FC6B1AF693D}.Release|x86.Build.0 = Release|Win32 + {A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}.Debug|ARM.ActiveCfg = Debug|ARM + {A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}.Debug|ARM.Build.0 = Debug|ARM + {A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}.Debug|ARM64.Build.0 = Debug|ARM64 + {A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}.Debug|x64.ActiveCfg = Debug|x64 + {A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}.Debug|x64.Build.0 = Debug|x64 + {A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}.Debug|x86.ActiveCfg = Debug|Win32 + {A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}.Debug|x86.Build.0 = Debug|Win32 + {A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}.Release|ARM.ActiveCfg = Release|ARM + {A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}.Release|ARM.Build.0 = Release|ARM + {A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}.Release|ARM64.ActiveCfg = Release|ARM64 + {A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}.Release|ARM64.Build.0 = Release|ARM64 + {A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}.Release|x64.ActiveCfg = Release|x64 + {A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}.Release|x64.Build.0 = Release|x64 + {A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}.Release|x86.ActiveCfg = Release|Win32 + {A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}.Release|x86.Build.0 = Release|Win32 + {F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Debug|ARM.ActiveCfg = Debug|ARM + {F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Debug|ARM.Build.0 = Debug|ARM + {F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Debug|ARM64.Build.0 = Debug|ARM64 + {F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Debug|x64.ActiveCfg = Debug|x64 + {F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Debug|x64.Build.0 = Debug|x64 + {F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Debug|x86.ActiveCfg = Debug|Win32 + {F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Debug|x86.Build.0 = Debug|Win32 + {F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Release|ARM.ActiveCfg = Release|ARM + {F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Release|ARM.Build.0 = Release|ARM + {F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Release|ARM64.ActiveCfg = Release|ARM64 + {F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Release|ARM64.Build.0 = Release|ARM64 + {F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Release|x64.ActiveCfg = Release|x64 + {F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Release|x64.Build.0 = Release|x64 + {F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Release|x86.ActiveCfg = Release|Win32 + {F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Release|x86.Build.0 = Release|Win32 + {A62D504A-16B8-41D2-9F19-E2E86019E5E4}.Debug|ARM.ActiveCfg = Debug|ARM + {A62D504A-16B8-41D2-9F19-E2E86019E5E4}.Debug|ARM.Build.0 = Debug|ARM + {A62D504A-16B8-41D2-9F19-E2E86019E5E4}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {A62D504A-16B8-41D2-9F19-E2E86019E5E4}.Debug|ARM64.Build.0 = Debug|ARM64 + {A62D504A-16B8-41D2-9F19-E2E86019E5E4}.Debug|x64.ActiveCfg = Debug|x64 + {A62D504A-16B8-41D2-9F19-E2E86019E5E4}.Debug|x64.Build.0 = Debug|x64 + {A62D504A-16B8-41D2-9F19-E2E86019E5E4}.Debug|x86.ActiveCfg = Debug|Win32 + {A62D504A-16B8-41D2-9F19-E2E86019E5E4}.Debug|x86.Build.0 = Debug|Win32 + {A62D504A-16B8-41D2-9F19-E2E86019E5E4}.Release|ARM.ActiveCfg = Release|ARM + {A62D504A-16B8-41D2-9F19-E2E86019E5E4}.Release|ARM.Build.0 = Release|ARM + {A62D504A-16B8-41D2-9F19-E2E86019E5E4}.Release|ARM64.ActiveCfg = Release|ARM64 + {A62D504A-16B8-41D2-9F19-E2E86019E5E4}.Release|ARM64.Build.0 = Release|ARM64 + {A62D504A-16B8-41D2-9F19-E2E86019E5E4}.Release|x64.ActiveCfg = Release|x64 + {A62D504A-16B8-41D2-9F19-E2E86019E5E4}.Release|x64.Build.0 = Release|x64 + {A62D504A-16B8-41D2-9F19-E2E86019E5E4}.Release|x86.ActiveCfg = Release|Win32 + {A62D504A-16B8-41D2-9F19-E2E86019E5E4}.Release|x86.Build.0 = Release|Win32 + {FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}.Debug|ARM.ActiveCfg = Debug|ARM + {FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}.Debug|ARM.Build.0 = Debug|ARM + {FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}.Debug|ARM64.Build.0 = Debug|ARM64 + {FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}.Debug|x64.ActiveCfg = Debug|x64 + {FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}.Debug|x64.Build.0 = Debug|x64 + {FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}.Debug|x86.ActiveCfg = Debug|Win32 + {FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}.Debug|x86.Build.0 = Debug|Win32 + {FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}.Release|ARM.ActiveCfg = Release|ARM + {FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}.Release|ARM.Build.0 = Release|ARM + {FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}.Release|ARM64.ActiveCfg = Release|ARM64 + {FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}.Release|ARM64.Build.0 = Release|ARM64 + {FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}.Release|x64.ActiveCfg = Release|x64 + {FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}.Release|x64.Build.0 = Release|x64 + {FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}.Release|x86.ActiveCfg = Release|Win32 + {FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}.Release|x86.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {A990658C-CE31-4BCC-976F-0FC6B1AF693D} = {5EA20F54-880A-49F3-99FA-4B3FE54E8AB1} + {A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD} = {5EA20F54-880A-49F3-99FA-4B3FE54E8AB1} + {C38970C0-5FBF-4D69-90D8-CBAC225AE895} = {5EA20F54-880A-49F3-99FA-4B3FE54E8AB1} + {F7D32BD0-2749-483E-9A0D-1635EF7E3136} = {5EA20F54-880A-49F3-99FA-4B3FE54E8AB1} + {0CC28589-39E4-4288-B162-97B959F8B843} = {5EA20F54-880A-49F3-99FA-4B3FE54E8AB1} + {A62D504A-16B8-41D2-9F19-E2E86019E5E4} = {5EA20F54-880A-49F3-99FA-4B3FE54E8AB1} + {DA8B35B3-DA00-4B02-BDE6-6A397B3FD46B} = {5EA20F54-880A-49F3-99FA-4B3FE54E8AB1} + {FCA38F3C-7C73-4C47-BE4E-32F77FA8538D} = {5EA20F54-880A-49F3-99FA-4B3FE54E8AB1} + {2049DBE9-8D13-42C9-AE4B-413AE38FFFD0} = {5EA20F54-880A-49F3-99FA-4B3FE54E8AB1} + {84E05BFA-CBAF-4F0D-BFB6-4CE85742A57E} = {5EA20F54-880A-49F3-99FA-4B3FE54E8AB1} + {EF074BA1-2D54-4D49-A28E-5E040B47CD2E} = {5EA20F54-880A-49F3-99FA-4B3FE54E8AB1} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {D43FAD39-F619-437D-BB40-04A3982ACB6A} + EndGlobalSection +EndGlobal diff --git a/packages/experimental/Expander/windows/ReactNativeExpander/ExpanderView.cpp b/packages/experimental/Expander/windows/ReactNativeExpander/ExpanderView.cpp new file mode 100644 index 0000000000..027f657322 --- /dev/null +++ b/packages/experimental/Expander/windows/ReactNativeExpander/ExpanderView.cpp @@ -0,0 +1,265 @@ +#include "pch.h" +#include "JSValueXaml.h" +#include "ExpanderView.h" +#include "ExpanderView.g.cpp" + +#include +#include + +namespace winrt { + using namespace Microsoft::ReactNative; + using namespace Windows::Foundation; + using namespace Windows::UI::Xaml::Media; +} + +namespace winrt::ReactNativeExpander::implementation { + + ExpanderView::ExpanderView(winrt::IReactContext const& reactContext) : m_reactContext(reactContext) { + auto expander = Microsoft::UI::Xaml::Controls::Expander(); + this->VerticalContentAlignment(xaml::VerticalAlignment::Top); + this->Content(expander); + + RegisterEvents(); + } + + void ExpanderView::RegisterEvents() { + auto expander = this->Content().try_as(); + + m_expanderCollapsingRevoker = expander.Collapsed(winrt::auto_revoke, + [ref = get_weak()](auto const& sender, auto const& args) { + if (auto self = ref.get()) { + self->OnCollapsing(sender, args); + } + }); + m_expanderExpandingRevoker = expander.Expanding(winrt::auto_revoke, + [ref = get_weak()](auto const& sender, auto const& args) { + if (auto self = ref.get()) { + self->OnExpanding(sender, args); + } + }); + } + + void ExpanderView::UpdateProperties(winrt::IJSValueReader const& reader) { + m_updating = true; + auto const& propertyMap = JSValueObject::ReadFrom(reader); + + if (auto expander = this->Content().try_as()) { + for (auto const& pair : propertyMap) { + auto const& propertyName = pair.first; + auto const& propertyValue = pair.second; + + if (propertyName == "expandDirection") { + if (propertyValue.IsNull()) { + expander.ClearValue(Microsoft::UI::Xaml::Controls::Expander::ExpandDirectionProperty()); + } + else { + auto direction = propertyValue.AsString(); + if (direction == "up") { + expander.ExpandDirection(Microsoft::UI::Xaml::Controls::ExpandDirection::Up); + } + else { + expander.ExpandDirection(Microsoft::UI::Xaml::Controls::ExpandDirection::Down); + } + } + } + else if (propertyName == "expanded") { + if (propertyValue.IsNull()) { + expander.ClearValue(Microsoft::UI::Xaml::Controls::Expander::IsExpandedProperty()); + } + else if (expander.IsExpanded() != propertyValue.AsBoolean()) { + expander.IsExpanded(propertyValue.AsBoolean()); + } + } + else if (propertyName == "enabled") { + if (propertyValue.IsNull()) { + expander.IsEnabled(true); + } + else { + expander.IsEnabled(propertyValue.AsBoolean()); + } + } + else if (propertyName == "width") { + if (!propertyValue.IsNull()) { + expander.Width(propertyValue.AsDouble()); + this->Width(propertyValue.AsDouble()); + } else { + expander.ClearValue(xaml::FrameworkElement::WidthProperty()); + this->ClearValue(xaml::FrameworkElement::WidthProperty()); + } + } + else if (propertyName == "height") { + if (!propertyValue.IsNull()) { + expander.Height(propertyValue.AsDouble()); + this->Height(propertyValue.AsDouble()); + } else { + expander.ClearValue(xaml::FrameworkElement::HeightProperty()); + this->ClearValue(xaml::FrameworkElement::HeightProperty()); + } + } + else if (propertyName == "contentHorizontalAlignment") { + if (!propertyValue.IsNull()) { + auto alignment = propertyValue.AsString(); + if (alignment == "center") { + expander.HorizontalContentAlignment(xaml::HorizontalAlignment::Center); + } + else if (alignment == "left") { + expander.HorizontalContentAlignment(xaml::HorizontalAlignment::Left); + } + else if (alignment == "right") { + expander.HorizontalContentAlignment(xaml::HorizontalAlignment::Right); + } + else if (alignment == "stretch") { + expander.HorizontalContentAlignment(xaml::HorizontalAlignment::Stretch); + } + } else { + expander.ClearValue(xaml::Controls::Control::HorizontalContentAlignmentProperty()); + } + } + else if (propertyName == "contentVerticalAlignment") { + if (!propertyValue.IsNull()) { + auto alignment = propertyValue.AsString(); + if (alignment == "bottom") { + expander.VerticalContentAlignment(xaml::VerticalAlignment::Bottom); + } + else if (alignment == "center") { + expander.VerticalContentAlignment(xaml::VerticalAlignment::Center); + } + else if (alignment == "stretch") { + expander.VerticalContentAlignment(xaml::VerticalAlignment::Stretch); + } + else if (alignment == "top") { + expander.VerticalContentAlignment(xaml::VerticalAlignment::Top); + } + } else { + expander.ClearValue(xaml::Controls::Control::VerticalContentAlignmentProperty()); + } + } + else if (propertyName == "headerBackground") { + this->SetResourceColor(L"ExpanderHeaderBackground", propertyValue); + } + else if (propertyName == "headerForeground") { + this->SetResourceColor(L"ExpanderHeaderForeground", propertyValue); + } + else if (propertyName == "headerBorderThickness") { + auto resDict = expander.Resources(); + if (!propertyValue.IsNull()) { + auto thickness = xaml::ThicknessHelper::FromUniformLength(propertyValue.AsDouble()); + resDict.Insert(winrt::box_value(L"ExpanderHeaderBorderThickness"), winrt::box_value(thickness)); + } else { + resDict.Remove(winrt::box_value(L"ExpanderHeaderBorderThickness")); + } + m_updateResDict = true; + } + else if (propertyName == "headerForegroundPointerOver") { + this->SetResourceColor(L"ExpanderHeaderForegroundPointerOver", propertyValue); + } + else if (propertyName == "headerForegroundPressed") { + this->SetResourceColor(L"ExpanderHeaderForegroundPressed", propertyValue); + } + else if (propertyName == "headerBorderBrush") { + this->SetResourceColor(L"ExpanderHeaderBorderBrush", propertyValue); + } + else if (propertyName == "headerBorderPointerOverBrush") { + this->SetResourceColor(L"ExpanderHeaderBorderPointerOverBrush", propertyValue); + } + else if (propertyName == "headerBorderPressedBrush") { + this->SetResourceColor(L"ExpanderHeaderBorderPressedBrush", propertyValue); + } + else if (propertyName == "headerDisabledForeground") { + this->SetResourceColor(L"ExpanderHeaderDisabledForeground", propertyValue); + } + else if (propertyName == "headerDisabledBorderBrush") { + this->SetResourceColor(L"ExpanderHeaderDisabledBorderBrush", propertyValue); + } + else if (propertyName == "contentBackground") { + this->SetResourceColor(L"ExpanderContentBackground", propertyValue); + } + else if (propertyName == "contentBorderBrush") { + this->SetResourceColor(L"ExpanderContentBorderBrush", propertyValue); + } + else if (propertyName == "chevronBackground") { + this->SetResourceColor(L"ExpanderChevronBackground", propertyValue); + } + else if (propertyName == "chevronForeground") { + this->SetResourceColor(L"ExpanderChevronForeground", propertyValue); + } + else if (propertyName == "chevronPointerOverBackground") { + this->SetResourceColor(L"ExpanderChevronPointerOverBackground", propertyValue); + } + else if (propertyName == "chevronPointerOverForeground") { + this->SetResourceColor(L"ExpanderChevronPointerOverForeground", propertyValue); + } + else if (propertyName == "chevronPressedBackground") { + this->SetResourceColor(L"ExpanderChevronPressedBackground", propertyValue); + } + else if (propertyName == "chevronPressedForeground") { + this->SetResourceColor(L"ExpanderChevronPressedForeground", propertyValue); + } + else if (propertyName == "chevronBorderThickness") { + auto resDict = expander.Resources(); + if (!propertyValue.IsNull()) { + auto thickness = xaml::ThicknessHelper::FromUniformLength(propertyValue.AsDouble()); + resDict.Insert(winrt::box_value(L"ExpanderChevronBorderThickness"), winrt::box_value(thickness)); + } else { + resDict.Remove(winrt::box_value(L"ExpanderChevronBorderThickness")); + } + m_updateResDict = true; + } + else if (propertyName == "chevronBorderBrush") { + this->SetResourceColor(L"ExpanderChevronBorderBrush", propertyValue); + } + else if (propertyName == "chevronBorderPointerOverBrush") { + this->SetResourceColor(L"ExpanderChevronBorderPointerOverBrush", propertyValue); + } + else if (propertyName == "chevronBorderPressedBrush") { + this->SetResourceColor(L"ExpanderChevronBorderPressedBrush", propertyValue); + } + } + if (m_updateResDict) { + auto currTheme = expander.ActualTheme(); + if (currTheme == xaml::ElementTheme::Dark) { + expander.RequestedTheme(xaml::ElementTheme::Light); + } + else { + expander.RequestedTheme(xaml::ElementTheme::Dark); + } + expander.RequestedTheme(currTheme); + m_updateResDict = false; + } + } + m_updating = false; + } + + void ExpanderView::SetResourceColor(hstring key, Microsoft::ReactNative::JSValue const& value) { + if (auto expander = this->Content().try_as()) { + auto resDict = expander.Resources(); + if (!value.IsNull()) { + auto color = value.To(); + resDict.Insert(winrt::box_value(key), color); + } else { + resDict.Remove(winrt::box_value(key)); + } + m_updateResDict = true; + } + } + + void ExpanderView::OnCollapsing(winrt::Windows::Foundation::IInspectable const& sender, + Microsoft::UI::Xaml::Controls::ExpanderCollapsedEventArgs const& args) { + if (!m_updating) { + m_reactContext.DispatchEvent( + *this, + L"topCollapsing", + [&](winrt::Microsoft::ReactNative::IJSValueWriter const& eventDataWriter) noexcept {}); + } + } + + void ExpanderView::OnExpanding(winrt::Windows::Foundation::IInspectable const& sender, + Microsoft::UI::Xaml::Controls::ExpanderExpandingEventArgs const& args) { + if (!m_updating) { + m_reactContext.DispatchEvent( + *this, + L"topExpanding", + [&](winrt::Microsoft::ReactNative::IJSValueWriter const& eventDataWriter) noexcept {}); + } + } +} diff --git a/packages/experimental/Expander/windows/ReactNativeExpander/ExpanderView.h b/packages/experimental/Expander/windows/ReactNativeExpander/ExpanderView.h new file mode 100644 index 0000000000..5c1a4735ad --- /dev/null +++ b/packages/experimental/Expander/windows/ReactNativeExpander/ExpanderView.h @@ -0,0 +1,34 @@ +#pragma once + +#include "ExpanderView.g.h" +#include "winrt/Microsoft.ReactNative.h" +#include "NativeModules.h" + +namespace winrt::ReactNativeExpander::implementation { + +class ExpanderView : public ExpanderViewT { +public: + ExpanderView(Microsoft::ReactNative::IReactContext const& reactContext); + void UpdateProperties(Microsoft::ReactNative::IJSValueReader const& reader); + +private: + Microsoft::ReactNative::IReactContext m_reactContext{ nullptr }; + bool m_updating{ false }; + bool m_updateResDict{ false }; + + // The Expander Collapsed event is misnamed. The event is fired when Expander begins collapsing + Microsoft::UI::Xaml::Controls::Expander::Collapsed_revoker m_expanderCollapsingRevoker{}; + Microsoft::UI::Xaml::Controls::Expander::Expanding_revoker m_expanderExpandingRevoker{}; + + void SetResourceColor(hstring key, Microsoft::ReactNative::JSValue const& value); + void OnCollapsing(winrt::Windows::Foundation::IInspectable const& sender, + Microsoft::UI::Xaml::Controls::ExpanderCollapsedEventArgs const& args); + void OnExpanding(winrt::Windows::Foundation::IInspectable const& sender, + Microsoft::UI::Xaml::Controls::ExpanderExpandingEventArgs const& args); + void RegisterEvents(); +}; +} + +namespace winrt::ReactNativeExpander::factory_implementation { +struct ExpanderView : ExpanderViewT {}; +} diff --git a/packages/experimental/Expander/windows/ReactNativeExpander/ExpanderView.idl b/packages/experimental/Expander/windows/ReactNativeExpander/ExpanderView.idl new file mode 100644 index 0000000000..d0ded9fe35 --- /dev/null +++ b/packages/experimental/Expander/windows/ReactNativeExpander/ExpanderView.idl @@ -0,0 +1,12 @@ +// Native styling was not rendered properly when the ExpanderView inherits directly from the Expander control +// Inheriting from a ContentPresenter and assigning an Expander to the content renders correctly + +namespace ReactNativeExpander{ + + [default_interface] + runtimeclass ExpanderView : Windows.UI.Xaml.Controls.ContentPresenter { + ExpanderView(Microsoft.ReactNative.IReactContext context); + void UpdateProperties(Microsoft.ReactNative.IJSValueReader reader); + }; + +} \ No newline at end of file diff --git a/packages/experimental/Expander/windows/ReactNativeExpander/ExpanderViewManager.cpp b/packages/experimental/Expander/windows/ReactNativeExpander/ExpanderViewManager.cpp new file mode 100644 index 0000000000..7e56339484 --- /dev/null +++ b/packages/experimental/Expander/windows/ReactNativeExpander/ExpanderViewManager.cpp @@ -0,0 +1,146 @@ +#include "pch.h" +#include "ExpanderViewManager.h" +#include "NativeModules.h" +#include "ExpanderView.h" + +namespace winrt { + using namespace Microsoft::ReactNative; + using namespace Windows::Foundation::Collections; +} + +namespace winrt::ReactNativeExpander::implementation { + + ExpanderViewManager::ExpanderViewManager() {} + + // IViewManager + winrt::hstring ExpanderViewManager::Name() noexcept { + return L"MSFExpanderView"; + } + + xaml::FrameworkElement ExpanderViewManager::CreateView() noexcept { + return winrt::ReactNativeExpander::ExpanderView(m_reactContext); + } + + // IViewManagerWithReactContext + winrt::IReactContext ExpanderViewManager::ReactContext() noexcept { + return m_reactContext; + } + + void ExpanderViewManager::ReactContext(IReactContext reactContext) noexcept { + m_reactContext = reactContext; + } + + // IViewManagerWithNativeProperties + IMapView ExpanderViewManager::NativeProps() noexcept { + auto nativeProps = winrt::single_threaded_map(); + + nativeProps.Insert(L"expandDirection", ViewManagerPropertyType::String); + nativeProps.Insert(L"expanded", ViewManagerPropertyType::Boolean); + nativeProps.Insert(L"enabled", ViewManagerPropertyType::Boolean); + nativeProps.Insert(L"width", ViewManagerPropertyType::Number); + nativeProps.Insert(L"height", ViewManagerPropertyType::Number); + nativeProps.Insert(L"contentHorizontalAlignment", ViewManagerPropertyType::String); + nativeProps.Insert(L"contentVerticalAlignment", ViewManagerPropertyType::String); + nativeProps.Insert(L"headerBackground", ViewManagerPropertyType::Color); + nativeProps.Insert(L"headerForeground", ViewManagerPropertyType::Color); + nativeProps.Insert(L"headerForegroundPointerOver", ViewManagerPropertyType::Color); + nativeProps.Insert(L"headerForegroundPressed", ViewManagerPropertyType::Color); + nativeProps.Insert(L"headerBorderBrush", ViewManagerPropertyType::Color); + nativeProps.Insert(L"headerBorderPointerOverBrush", ViewManagerPropertyType::Color); + nativeProps.Insert(L"headerBorderPressedBrush", ViewManagerPropertyType::Color); + nativeProps.Insert(L"headerDisabledForeground", ViewManagerPropertyType::Color); + nativeProps.Insert(L"headerDisabledBorderBrush", ViewManagerPropertyType::Color); + nativeProps.Insert(L"headerBorderThickness", ViewManagerPropertyType::Number); + nativeProps.Insert(L"contentBackground", ViewManagerPropertyType::Color); + nativeProps.Insert(L"contentBorderBrush", ViewManagerPropertyType::Color); + nativeProps.Insert(L"chevronBackground", ViewManagerPropertyType::Color); + nativeProps.Insert(L"chevronForeground", ViewManagerPropertyType::Color); + nativeProps.Insert(L"chevronPointerOverBackground", ViewManagerPropertyType::Color); + nativeProps.Insert(L"chevronPointerOverForeground", ViewManagerPropertyType::Color); + nativeProps.Insert(L"chevronPressedBackground", ViewManagerPropertyType::Color); + nativeProps.Insert(L"chevronPressedForeground", ViewManagerPropertyType::Color); + nativeProps.Insert(L"chevronBorderThickness", ViewManagerPropertyType::Number); + nativeProps.Insert(L"chevronBorderBrush", ViewManagerPropertyType::Color); + nativeProps.Insert(L"chevronBorderPointerOverBrush", ViewManagerPropertyType::Color); + nativeProps.Insert(L"chevronBorderPressedBrush", ViewManagerPropertyType::Color); + + return nativeProps.GetView(); + } + + void ExpanderViewManager::UpdateProperties(xaml::FrameworkElement const& view, + IJSValueReader const& propertyMapReader) noexcept { + if (auto expanderView = view.try_as()) { + expanderView->UpdateProperties(propertyMapReader); + } + else { + OutputDebugStringW(L"Type deduction for ExpanderView failed."); + } + } + + // IViewManagerWithExportedEventTypeConstants + ConstantProviderDelegate ExpanderViewManager::ExportedCustomBubblingEventTypeConstants() noexcept { + return nullptr; + } + + ConstantProviderDelegate ExpanderViewManager::ExportedCustomDirectEventTypeConstants() noexcept { + return [](winrt::IJSValueWriter const& constantWriter) { + WriteCustomDirectEventTypeConstant(constantWriter, L"Collapsing"); + WriteCustomDirectEventTypeConstant(constantWriter, L"Expanding"); + }; + } + + // IViewManagerWithChildren + void ExpanderViewManager::ReplaceChild(winrt::Windows::UI::Xaml::FrameworkElement const& parent, winrt::Windows::UI::Xaml::UIElement const& oldChild, winrt::Windows::UI::Xaml::UIElement const& newChild) noexcept { + auto expander = parent.as().Content().as(); + if (oldChild == expander.Header()) { + expander.ClearValue(Microsoft::UI::Xaml::Controls::Expander::HeaderProperty()); + expander.Header(newChild); + } + else if (oldChild == expander.Content()) { + expander.ClearValue(xaml::Controls::ContentControl::ContentProperty()); + expander.Content(newChild); + } + } + + void ExpanderViewManager::AddView(winrt::Windows::UI::Xaml::FrameworkElement const& parent, winrt::Windows::UI::Xaml::UIElement const& child, int64_t index) noexcept { + auto expander = parent.as().Content().as(); + if (index == 0) { + if (auto currentHeader = expander.GetValue(Microsoft::UI::Xaml::Controls::Expander::HeaderProperty())) { + expander.Header(child); + expander.Content(currentHeader); + } + else { + expander.Header(child); + } + } + else if (index == 1) { + expander.Content(child); + } + else { + m_reactContext.CallJSFunction( + L"RCTLog", + L"logToConsole", + MakeJSValueArgWriter("warn", "React Native for Windows does not support nesting more than two components under .\nThe first component will be the Expander header, and the second will be the Expander content.")); + } + } + + void ExpanderViewManager::RemoveAllChildren(winrt::Windows::UI::Xaml::FrameworkElement const& parent) noexcept { + auto expander = parent.as().Content().as(); + expander.ClearValue(xaml::Controls::ContentControl::ContentProperty()); + expander.ClearValue(Microsoft::UI::Xaml::Controls::Expander::HeaderProperty()); + } + + void ExpanderViewManager::RemoveChildAt(winrt::Windows::UI::Xaml::FrameworkElement const& parent, int64_t index) noexcept { + auto expander = parent.as().Content().as(); + if (index == 0) { + expander.ClearValue(Microsoft::UI::Xaml::Controls::Expander::HeaderProperty()); + if (auto currentContent = expander.GetValue(xaml::Controls::ContentControl::ContentProperty())) { + expander.ClearValue(xaml::Controls::ContentControl::ContentProperty()); + expander.Header(currentContent); + } + } + else if (index == 1) { + expander.ClearValue(xaml::Controls::ContentControl::ContentProperty()); + } + } +} \ No newline at end of file diff --git a/packages/experimental/Expander/windows/ReactNativeExpander/ExpanderViewManager.h b/packages/experimental/Expander/windows/ReactNativeExpander/ExpanderViewManager.h new file mode 100644 index 0000000000..131e0f80ed --- /dev/null +++ b/packages/experimental/Expander/windows/ReactNativeExpander/ExpanderViewManager.h @@ -0,0 +1,49 @@ +#pragma once + +#include "winrt/Microsoft.ReactNative.h" +#include "NativeModules.h" + +namespace winrt::ReactNativeExpander::implementation { + +class ExpanderViewManager : public winrt::implements< + ExpanderViewManager, + winrt::Microsoft::ReactNative::IViewManager, + winrt::Microsoft::ReactNative::IViewManagerWithReactContext, + winrt::Microsoft::ReactNative::IViewManagerWithNativeProperties, + winrt::Microsoft::ReactNative::IViewManagerWithExportedEventTypeConstants, + winrt::Microsoft::ReactNative::IViewManagerWithChildren> { + + public: + ExpanderViewManager(); + + // IViewManager + winrt::hstring Name() noexcept; + winrt::Windows::UI::Xaml::FrameworkElement CreateView() noexcept; + + // IViewManagerWithReactContext + winrt::Microsoft::ReactNative::IReactContext ReactContext() noexcept; + void ReactContext(winrt::Microsoft::ReactNative::IReactContext reactContext) noexcept; + + // IViewManagerWithNativeProperties + winrt::Windows::Foundation::Collections:: + IMapView + NativeProps() noexcept; + + void UpdateProperties( + winrt::Windows::UI::Xaml::FrameworkElement const& view, + winrt::Microsoft::ReactNative::IJSValueReader const& propertyMapReader) noexcept; + + // IViewManagerWithExportedEventTypeConstants + winrt::Microsoft::ReactNative::ConstantProviderDelegate ExportedCustomBubblingEventTypeConstants() noexcept; + winrt::Microsoft::ReactNative::ConstantProviderDelegate ExportedCustomDirectEventTypeConstants() noexcept; + + // IViewManagerWithChildren + void ReplaceChild(winrt::Windows::UI::Xaml::FrameworkElement const& parent, winrt::Windows::UI::Xaml::UIElement const& oldChild, winrt::Windows::UI::Xaml::UIElement const& newChild) noexcept; + void AddView(winrt::Windows::UI::Xaml::FrameworkElement const& parent, winrt::Windows::UI::Xaml::UIElement const& child, int64_t index) noexcept; + void RemoveAllChildren(winrt::Windows::UI::Xaml::FrameworkElement const& parent) noexcept; + void RemoveChildAt(winrt::Windows::UI::Xaml::FrameworkElement const& parent, int64_t index) noexcept; + + private: + winrt::Microsoft::ReactNative::IReactContext m_reactContext{ nullptr }; +}; +} \ No newline at end of file diff --git a/packages/experimental/Expander/windows/ReactNativeExpander/PropertySheet.props b/packages/experimental/Expander/windows/ReactNativeExpander/PropertySheet.props new file mode 100644 index 0000000000..5942ba395b --- /dev/null +++ b/packages/experimental/Expander/windows/ReactNativeExpander/PropertySheet.props @@ -0,0 +1,16 @@ + + + + + + + + \ No newline at end of file diff --git a/packages/experimental/Expander/windows/ReactNativeExpander/ReactNativeExpander.def b/packages/experimental/Expander/windows/ReactNativeExpander/ReactNativeExpander.def new file mode 100644 index 0000000000..24e7c1235c --- /dev/null +++ b/packages/experimental/Expander/windows/ReactNativeExpander/ReactNativeExpander.def @@ -0,0 +1,3 @@ +EXPORTS +DllCanUnloadNow = WINRT_CanUnloadNow PRIVATE +DllGetActivationFactory = WINRT_GetActivationFactory PRIVATE diff --git a/packages/experimental/Expander/windows/ReactNativeExpander/ReactNativeExpander.vcxproj b/packages/experimental/Expander/windows/ReactNativeExpander/ReactNativeExpander.vcxproj new file mode 100644 index 0000000000..2595893554 --- /dev/null +++ b/packages/experimental/Expander/windows/ReactNativeExpander/ReactNativeExpander.vcxproj @@ -0,0 +1,167 @@ + + + + + true + true + true + {A9669358-656D-4AB1-9AE8-11EFECE2597D} + ReactNativeExpander + ReactNativeExpander + en-US + 16.0 + true + Windows Store + 10.0 + 10.0.19041.0 + 10.0.16299.0 + + + + $([MSBuild]::GetDirectoryNameOfFileAbove($(SolutionDir), 'node_modules\react-native-windows\package.json'))\node_modules\react-native-windows\ + + + + Debug + ARM + + + Debug + ARM64 + + + Debug + Win32 + + + Debug + x64 + + + Release + ARM + + + Release + ARM64 + + + Release + Win32 + + + Release + x64 + + + + DynamicLibrary + Unicode + false + + + true + true + + + false + true + false + + + + + + + + + + + + + + + + + + Use + pch.h + $(IntDir)pch.pch + Level4 + %(AdditionalOptions) /bigobj + 4453;28204 + _WINRT_DLL;%(PreprocessorDefinitions) + $(WindowsSDK_WindowsMetadata);$(AdditionalUsingDirectories) + + + Console + true + ReactNativeExpander.def + + + + + _DEBUG;%(PreprocessorDefinitions) + + + + + NDEBUG;%(PreprocessorDefinitions) + + + + + false + + + + + + + ReactPackageProvider.idl + + + + + + + + Create + + + ReactPackageProvider.idl + + + + + + + + + + + + + + + + + + This project references targets in your node_modules\react-native-windows folder that are missing. The missing file is {0}. + + + + + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + + \ No newline at end of file diff --git a/packages/experimental/Expander/windows/ReactNativeExpander/ReactNativeExpander.vcxproj.filters b/packages/experimental/Expander/windows/ReactNativeExpander/ReactNativeExpander.vcxproj.filters new file mode 100644 index 0000000000..01c183eed6 --- /dev/null +++ b/packages/experimental/Expander/windows/ReactNativeExpander/ReactNativeExpander.vcxproj.filters @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/experimental/Expander/windows/ReactNativeExpander/ReactPackageProvider.cpp b/packages/experimental/Expander/windows/ReactNativeExpander/ReactPackageProvider.cpp new file mode 100644 index 0000000000..5108a8de67 --- /dev/null +++ b/packages/experimental/Expander/windows/ReactNativeExpander/ReactPackageProvider.cpp @@ -0,0 +1,19 @@ +#include "pch.h" +#include "ReactPackageProvider.h" +#if __has_include("ReactPackageProvider.g.cpp") +#include "ReactPackageProvider.g.cpp" +#endif + +#include "ExpanderViewManager.h" + +using namespace winrt::Microsoft::ReactNative; + +namespace winrt::ReactNativeExpander::implementation +{ + +void ReactPackageProvider::CreatePackage(IReactPackageBuilder const &packageBuilder) noexcept +{ + packageBuilder.AddViewManager(L"ExpanderViewManager", []() { return winrt::make(); }); +} + +} // namespace winrt::ReactNativeExpander::implementation diff --git a/packages/experimental/Expander/windows/ReactNativeExpander/ReactPackageProvider.h b/packages/experimental/Expander/windows/ReactNativeExpander/ReactPackageProvider.h new file mode 100644 index 0000000000..45bf3f4c0e --- /dev/null +++ b/packages/experimental/Expander/windows/ReactNativeExpander/ReactPackageProvider.h @@ -0,0 +1,18 @@ +#pragma once +#include "ReactPackageProvider.g.h" + +using namespace winrt::Microsoft::ReactNative; + +namespace winrt::ReactNativeExpander::implementation +{ + struct ReactPackageProvider : ReactPackageProviderT + { + ReactPackageProvider() = default; + void CreatePackage(IReactPackageBuilder const &packageBuilder) noexcept; + }; +} // namespace winrt::ReactNativeExpander::implementation + +namespace winrt::ReactNativeExpander::factory_implementation +{ + struct ReactPackageProvider : ReactPackageProviderT {}; +} // namespace winrt::ReactNativeExpander::factory_implementation diff --git a/packages/experimental/Expander/windows/ReactNativeExpander/ReactPackageProvider.idl b/packages/experimental/Expander/windows/ReactNativeExpander/ReactPackageProvider.idl new file mode 100644 index 0000000000..ff2172c7fd --- /dev/null +++ b/packages/experimental/Expander/windows/ReactNativeExpander/ReactPackageProvider.idl @@ -0,0 +1,9 @@ +namespace ReactNativeExpander +{ + [webhosthidden] + [default_interface] + runtimeclass ReactPackageProvider : Microsoft.ReactNative.IReactPackageProvider + { + ReactPackageProvider(); + }; +} diff --git a/packages/experimental/Expander/windows/ReactNativeExpander/packages.config b/packages/experimental/Expander/windows/ReactNativeExpander/packages.config new file mode 100644 index 0000000000..72e86a7050 --- /dev/null +++ b/packages/experimental/Expander/windows/ReactNativeExpander/packages.config @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/packages/experimental/Expander/windows/ReactNativeExpander/pch.cpp b/packages/experimental/Expander/windows/ReactNativeExpander/pch.cpp new file mode 100644 index 0000000000..bcb5590be1 --- /dev/null +++ b/packages/experimental/Expander/windows/ReactNativeExpander/pch.cpp @@ -0,0 +1 @@ +#include "pch.h" diff --git a/packages/experimental/Expander/windows/ReactNativeExpander/pch.h b/packages/experimental/Expander/windows/ReactNativeExpander/pch.h new file mode 100644 index 0000000000..794450ca35 --- /dev/null +++ b/packages/experimental/Expander/windows/ReactNativeExpander/pch.h @@ -0,0 +1,26 @@ +#pragma once + +#define NOMINMAX + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +using namespace winrt::Windows::Foundation; diff --git a/yarn.lock b/yarn.lock index e3f8f3dbeb..ab1ee15964 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1210,6 +1210,23 @@ minimatch "^3.0.4" strip-json-comments "^3.1.1" +"@fluentui-react-native/framework@0.5.15": + version "0.5.15" + resolved "https://registry.yarnpkg.com/@fluentui-react-native/framework/-/framework-0.5.15.tgz#c0efdb5204d74bb1ae3a8f5e902242b84e312ce7" + integrity sha512-YI3h1pQYbj8ERn0GICe6RqpTq20RS3AHJheGfSTSfq6Bb39J75G3c9pvHh6ulJiQTn5mIyDDCWEt4q3LhF04zw== + dependencies: + "@fluentui-react-native/composition" ">=0.6.3 <1.0.0" + "@fluentui-react-native/default-theme" ">=0.7.1 <1.0.0" + "@fluentui-react-native/immutable-merge" "^1.1.2" + "@fluentui-react-native/memo-cache" "^1.1.2" + "@fluentui-react-native/merge-props" ">=0.3.2 <1.0.0" + "@fluentui-react-native/theme-types" ">=0.9.1 <1.0.0" + "@fluentui-react-native/tokens" ">=0.9.11 <1.0.0" + "@fluentui-react-native/use-slot" ">=0.1.2 <1.0.0" + "@fluentui-react-native/use-slots" ">=0.5.4 <1.0.0" + "@fluentui-react-native/use-styling" ">=0.6.2 <1.0.0" + "@fluentui-react-native/use-tokens" ">=0.1.1 <1.0.0" + "@fluentui/token-pipeline@^0.3.9": version "0.3.9" resolved "https://registry.yarnpkg.com/@fluentui/token-pipeline/-/token-pipeline-0.3.9.tgz#7db1c8c3b2242ba4bbd9a172b7ae8bfb1f22e138"