Skip to content
Closed
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
3 changes: 3 additions & 0 deletions packages/components/Button/src/Button.styling.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ export const stylingSettings: UseStylingOptions<ButtonProps, ButtonSlotProps, Bu
...borderStyles.from(tokens, theme),
...layoutStyles.from(tokens, theme),
},
android_ripple: {
color: 'red', //todo
},
}),
['backgroundColor', 'width', ...borderStyles.keys, ...layoutStyles.keys],
),
Expand Down
20 changes: 12 additions & 8 deletions packages/components/Button/src/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { compose, mergeProps, withSlots, UseSlots } from '@fluentui-react-native
import { useButton } from './useButton';
import { Icon } from '@fluentui-react-native/icon';
import { createIconProps, IPressableState } from '@fluentui-react-native/interactive-hooks';
import Ripple from '../../../experimental/Ripple/src/Ripple';

/**
* A function which determines if a set of styles should be applied to the compoent given the current state and props of the button.
Expand Down Expand Up @@ -41,6 +42,7 @@ export const Button = compose<ButtonType>({
root: Pressable,
icon: Icon,
content: Text,
ripple: Ripple,
},
useRender: (userProps: ButtonProps, useSlots: UseSlots<ButtonType>) => {
const button = useButton(userProps);
Expand Down Expand Up @@ -72,14 +74,16 @@ export const Button = compose<ButtonType>({
const label = accessibilityLabel ?? childText;

return (
<Slots.root {...mergedProps} accessibilityLabel={label}>
{loading && <ActivityIndicator />}
{shouldShowIcon && iconPosition === 'before' && <Slots.icon {...iconProps} />}
{React.Children.map(children, (child) =>
typeof child === 'string' ? <Slots.content key="content">{child}</Slots.content> : child,
)}
{shouldShowIcon && iconPosition === 'after' && <Slots.icon {...iconProps} />}
</Slots.root>
<Slots.ripple>
<Slots.root {...mergedProps} accessibilityLabel={label}>
{loading && <ActivityIndicator />}
{shouldShowIcon && iconPosition === 'before' && <Slots.icon {...iconProps} />}
{React.Children.map(children, (child) =>
typeof child === 'string' ? <Slots.content key="content">{child}</Slots.content> : child,
)}
{shouldShowIcon && iconPosition === 'after' && <Slots.icon {...iconProps} />}
</Slots.root>
</Slots.ripple>
);
};
},
Expand Down
1 change: 1 addition & 0 deletions packages/components/Button/src/Button.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ export interface ButtonSlotProps {
root: React.PropsWithRef<PressablePropsExtended>;
icon: IconProps;
content: TextProps;
ripple?: { children: React.PropsWithRef<PressablePropsExtended> }; // Is this valid?
}

export interface ButtonType {
Expand Down
3 changes: 3 additions & 0 deletions packages/components/Button/src/FAB/FAB.styling.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ export const stylingSettings: UseStylingOptions<ButtonCoreProps, FABSlotProps, B
...layoutStyles.from(tokens, theme),
...shadowStyles.from(tokens, theme),
},
android_ripple: {
color: 'red', //todo
},
elevation: tokens.elevation,
}),
['backgroundColor', 'width', 'elevation', ...borderStyles.keys, ...layoutStyles.keys, ...shadowStyles.keys],
Expand Down
2 changes: 2 additions & 0 deletions packages/components/Button/src/FAB/FAB.types.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { ButtonSlotProps, ButtonCoreTokens, ButtonCoreProps } from '../Button.types';
import { ShadowProps } from '@fluentui-react-native/experimental-shadow';
import { PressablePropsExtended } from '@fluentui-react-native/interactive-hooks';

export const fabName = 'FAB';

export interface FABSlotProps extends ButtonSlotProps {
shadow?: ShadowProps;
ripple?: { children: React.PropsWithRef<PressablePropsExtended> }; // Is this valid?
}

export interface FABProps extends ButtonCoreProps {
Expand Down
8 changes: 5 additions & 3 deletions packages/components/Button/src/FAB/FABCore.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/** @jsx withSlots */
import * as React from 'react';
import { Platform, View } from 'react-native';
import { Platform, Pressable } from 'react-native';
import { fabName, FABProps, FABType } from './FAB.types';
import { TextV1 as Text } from '@fluentui-react-native/text';
import { stylingSettings } from './FAB.styling';
Expand All @@ -9,6 +9,7 @@ import { useButton } from '../useButton';
import { Icon } from '@fluentui-react-native/icon';
import { createIconProps, IPressableState } from '@fluentui-react-native/interactive-hooks';
import { Shadow } from '@fluentui-react-native/experimental-shadow';
import { Ripple } from '../../../../experimental/Ripple/src/Ripple';

/**
* A function which determines if a set of styles should be applied to the compoent given the current state and props of the button.
Expand All @@ -28,10 +29,11 @@ export const FAB = compose<FABType>({
displayName: fabName,
...stylingSettings,
slots: {
root: View,
root: Pressable,
icon: Icon,
content: Text,
shadow: Shadow,
ripple: Ripple,
},
useRender: (userProps: FABProps, useSlots: UseSlots<FABType>) => {
const { icon, ...rest } = userProps;
Expand Down Expand Up @@ -78,7 +80,7 @@ export const FAB = compose<FABType>({
if (hasShadow) {
return <Slots.shadow>{fabWithoutShadow}</Slots.shadow>;
} else {
return fabWithoutShadow;
return <Slots.ripple>{fabWithoutShadow}</Slots.ripple>;
}
};
},
Expand Down
3 changes: 3 additions & 0 deletions packages/experimental/Ripple/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = {
extends: ['@fluentui-react-native/eslint-config-rules'],
};
Empty file.
Empty file.
9 changes: 9 additions & 0 deletions packages/experimental/Ripple/SPEC.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Ripple

A `Ripple` is a component that can be used to add Fluent Ripple to existing Pressable components.

// Pressable Ripple does not support curved edges currently
// To get it working it needs to wrapped in a view where border is set
// This component was being considered to allow for this change to be done easily
// Props need to be split between the enclosing and inner views
// Code is hugely borrowed from Shadow component.
1 change: 1 addition & 0 deletions packages/experimental/Ripple/babel.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = require('@fluentui-react-native/scripts/babel.config');
2 changes: 2 additions & 0 deletions packages/experimental/Ripple/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
const { configureReactNativeJest } = require('@fluentui-react-native/scripts');
module.exports = configureReactNativeJest('android');
2 changes: 2 additions & 0 deletions packages/experimental/Ripple/just.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
const { preset } = require('@fluentui-react-native/scripts');
preset();
57 changes: 57 additions & 0 deletions packages/experimental/Ripple/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
{
"name": "@fluentui-react-native/experimental-ripple",
"version": "0.0.1",
"description": "A component that provides Ripple animation for Android",
"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",
"clean": "fluentui-scripts clean",
"depcheck": "fluentui-scripts depcheck",
"just": "fluentui-scripts",
"lint": "fluentui-scripts eslint",
"test": "fluentui-scripts jest",
"update-snapshots": "fluentui-scripts jest -u",
"prettier": "fluentui-scripts prettier",
"prettier-fix": "fluentui-scripts prettier --fix true"
},
"repository": {
"type": "git",
"url": "https://github.com/microsoft/fluentui-react-native.git",
"directory": "packages/experimental/Ripple"
},
"dependencies": {
"@fluentui-react-native/framework": "0.8.13",
"@fluentui-react-native/pressable": "0.9.24",
"@fluentui-react-native/theme-types": "0.22.0"
},
"devDependencies": {
"@fluentui-react-native/eslint-config-rules": "^0.1.1",
"@fluentui-react-native/scripts": "^0.1.1",
"@fluentui-react-native/test-tools": ">=0.1.1 <1.0.0",
"react": "17.0.2",
"react-native": "^0.68.0"
},
"peerDependencies": {
"react": "17.0.2",
"react-native": "^0.68.0"
},
"author": "",
"license": "MIT",
"rnx-kit": {
"reactNativeVersion": "^0.68",
"reactNativeDevVersion": "^0.68",
"kitType": "library",
"capabilities": [
"core",
"core-android",
"core-ios",
"react"
]
}
}
92 changes: 92 additions & 0 deletions packages/experimental/Ripple/src/Ripple.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import * as React from 'react';
import { Platform, Pressable, View, ViewStyle } from 'react-native';
import { rippleName } from './Ripple.types';
import { memoize, stagedComponent } from '@fluentui-react-native/framework';
import { IViewProps } from '@fluentui-react-native/adapters';

export const Ripple = stagedComponent(() => {
return (_final: IViewProps, children: React.ReactNode) => {
if (Platform.OS != 'android') {
return <>{children}</>;
}
const childrenArray = React.Children.toArray(children) as React.ReactElement[];
const child = childrenArray[0];

if (__DEV__) {
if (childrenArray.length !== 1) {
console.warn('Ripple must only have one child');
}
if (child.type !== Pressable) {
console.warn('Should be used to fix curved corners for Pressable only');
}
}

const { style: childStyle, ...restOfChildProps } = child.props;
const [rippleStyleProps, innerStyleProps] = extractOuterStylePropsAndroid(childStyle);

restOfChildProps.style = innerStyleProps;
const childWithOuterRipple = React.cloneElement(child, restOfChildProps);

return (
<View style={rippleStyleProps}>
{/* RN Pressable needs to be wrapped with a root view to support curved edges */}
{childWithOuterRipple}
</View>
);
};
});

export const extractOuterStylePropsAndroid = memoize((style: ViewStyle = {}): [outerStyleProps: ViewStyle, innerStyleProps: ViewStyle] => {
const {
margin,
marginTop,
marginBottom,
marginLeft,
marginRight,
marginStart,
marginEnd,
marginVertical,
marginHorizontal,
start,
end,
left,
right,
top,
bottom,
display,
opacity,
borderRadius,
...restOfProps
} = style;

return [
{
margin,
marginTop,
marginBottom,
marginLeft,
marginRight,
marginStart,
marginEnd,
marginVertical,
marginHorizontal,
start,
end,
left,
right,
top,
bottom,
display,
opacity,
flexDirection: 'row',
alignSelf: 'baseline',
borderRadius,
overflow: 'hidden',
},
{ ...restOfProps },
];
});

Ripple.displayName = rippleName;

export default Ripple;
1 change: 1 addition & 0 deletions packages/experimental/Ripple/src/Ripple.types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const rippleName = 'Ripple';
Loading