<Spinner win32> - Add TrackSvg and implement platform specific design (#2747)
* add tracker svg for win32 * Change files * define useSpinner hook * add sizes as tokens
This commit is contained in:
Родитель
eaa485222e
Коммит
05bd73ded2
|
@ -0,0 +1,92 @@
|
|||
import * as React from 'react';
|
||||
import { View /*Switch */ } from 'react-native';
|
||||
|
||||
//import type { SpinnerStatus } from '@fluentui-react-native/spinner';
|
||||
import { Spinner } from '@fluentui-react-native/spinner';
|
||||
import { Stack } from '@fluentui-react-native/stack';
|
||||
import { TextV1 as Text } from '@fluentui-react-native/text';
|
||||
|
||||
import { E2ETestingSpinner } from './SpinnerE2ETest';
|
||||
import { SPINNER_TESTPAGE } from '../../../../E2E/src/Spinner/consts';
|
||||
import { stackStyle, commonTestStyles as commonStyles } from '../Common/styles';
|
||||
import type { TestSection, PlatformStatus } from '../Test';
|
||||
import { Test } from '../Test';
|
||||
|
||||
const BasicSpinnerTest: React.FunctionComponent = () => {
|
||||
return (
|
||||
<Stack style={stackStyle}>
|
||||
<View style={commonStyles.root}>
|
||||
<View style={commonStyles.settings}></View>
|
||||
<Spinner />
|
||||
</View>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
const SpinnerSizeTest: React.FunctionComponent = () => {
|
||||
return (
|
||||
<Stack style={stackStyle}>
|
||||
<View>
|
||||
<View>
|
||||
<Text>tiny</Text>
|
||||
<Spinner size="tiny" />
|
||||
</View>
|
||||
<View>
|
||||
<Text>x-small</Text>
|
||||
<Spinner size="x-small" />
|
||||
</View>
|
||||
<View>
|
||||
<Text>small</Text>
|
||||
<Spinner size="small" />
|
||||
</View>
|
||||
<View>
|
||||
<Text>medium</Text>
|
||||
<Spinner size="medium" />
|
||||
</View>
|
||||
<View>
|
||||
<Text>large</Text>
|
||||
<Spinner size="large" />
|
||||
</View>
|
||||
<View>
|
||||
<Text>x-large</Text>
|
||||
<Spinner size="x-large" />
|
||||
</View>
|
||||
<View>
|
||||
<Text>huge</Text>
|
||||
<Spinner size="huge" />
|
||||
</View>
|
||||
</View>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
const spinnerSections: TestSection[] = [
|
||||
{
|
||||
name: 'Basic Spinner Test',
|
||||
testID: SPINNER_TESTPAGE,
|
||||
component: BasicSpinnerTest,
|
||||
},
|
||||
{
|
||||
name: 'Spinner Size Test',
|
||||
component: SpinnerSizeTest,
|
||||
},
|
||||
{
|
||||
name: 'Spinner for E2E Testing',
|
||||
component: () => <E2ETestingSpinner />,
|
||||
},
|
||||
];
|
||||
|
||||
export const SpinnerTest: React.FunctionComponent = () => {
|
||||
const status: PlatformStatus = {
|
||||
win32Status: 'Experimental',
|
||||
uwpStatus: 'Backlog',
|
||||
iosStatus: 'Beta',
|
||||
macosStatus: 'Backlog',
|
||||
androidStatus: 'Backlog',
|
||||
};
|
||||
|
||||
const description =
|
||||
'Spinner is a visual representation that data is being loaded. It is implemented with a View wrapping an Animated SVG. The View is to ensure that AccessibilityRole works. AccessibilityRole currently does not work on SVGs.';
|
||||
|
||||
return <Test name="Spinner Test" description={description} sections={spinnerSections} status={status}></Test>;
|
||||
};
|
|
@ -253,7 +253,7 @@ export const tests: TestDescription[] = [
|
|||
name: 'Spinner V1',
|
||||
component: SpinnerTest,
|
||||
testPageButton: Constants.HOMEPAGE_SPINNER_BUTTON,
|
||||
platforms: ['android'],
|
||||
platforms: ['android', 'win32'],
|
||||
},
|
||||
{
|
||||
name: 'Stroke Width Tokens',
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"type": "minor",
|
||||
"comment": "add tracker svg for win32",
|
||||
"packageName": "@fluentui-react-native/spinner",
|
||||
"email": "email not defined",
|
||||
"dependentChangeType": "patch"
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"type": "minor",
|
||||
"comment": "add tracker svg for win32",
|
||||
"packageName": "@fluentui-react-native/tester",
|
||||
"email": "email not defined",
|
||||
"dependentChangeType": "patch"
|
||||
}
|
|
@ -42,13 +42,6 @@ The `Spinner` is an outline of a circle which animates around itself, to visuall
|
|||
|
||||
## Variants
|
||||
|
||||
Label postion decides where will the label place with respect to Spinner :
|
||||
|
||||
- If `labelPosition` is equal to "above", then the Label appears be vertically above with respect to Spinner.
|
||||
- If `labelPosition` is equal to "below", then the Label appears be vertically below with respect to Spinner.
|
||||
- If `labelPosition` is equal to "before", then the Label appears be horizontally before with respect to Spinner.
|
||||
- If `labelPosition` is equal to "after", then the Label appears be horizontally after with respect to Spinner.
|
||||
|
||||
#### Status
|
||||
|
||||
Users can control whether `Spinner` is animating or not :
|
||||
|
@ -60,6 +53,8 @@ Users can control whether `Spinner` is animating or not :
|
|||
|
||||
Users can control the `Spinner` size from the configuration below :
|
||||
|
||||
##### Mobile
|
||||
|
||||
| Size | Diameter (height/width) | Line Thickness |
|
||||
| -------- | ----------------------- | ---------------- |
|
||||
| xx-small | 12 (iconSize120) | 1 (stokeWidth10) |
|
||||
|
@ -68,6 +63,18 @@ Users can control the `Spinner` size from the configuration below :
|
|||
| large | 32 | 3 (stokeWidth30) |
|
||||
| x-large | 40 (iconSize360) | 4 (stokeWidth40) |
|
||||
|
||||
##### Win32
|
||||
|
||||
| Size | Diameter (height/width) | Line Thickness |
|
||||
| ------- | ----------------------- | ---------------- |
|
||||
| tiny | 20 (iconSize200) | 2 (stokeWidth20) |
|
||||
| x-small | 24 (iconSize240) | 2 (stokeWidth20) |
|
||||
| small | 28 (iconSize280) | 2 (stokeWidth20) |
|
||||
| medium | 32 (iconSize320) | 3 (stokeWidth30) |
|
||||
| large | 36 (iconSize360) | 3 (stokeWidth30) |
|
||||
| x-large | 40 (iconSize400) | 3 (stokeWidth30) |
|
||||
| huge | 44 (iconSize440) | 4 (stokeWidth40) |
|
||||
|
||||
#### Label Postion
|
||||
|
||||
Label postion decides where will the label place with respect to Spinner :
|
||||
|
@ -86,10 +93,10 @@ The `Spinner` uses 5 slots on win32 and 2 slots on mobile platforms:
|
|||
Win32 Slots
|
||||
|
||||
- `root` [View] The outer container of the component
|
||||
- `track` [Svg]The container for the spinner svg .
|
||||
- `tail` [Svg] The svg of the tail that will act as animated spinner
|
||||
- `track` [trackSvg]The container for the spinner svg .
|
||||
- `tail` [tailSvg] The svg of the tail that will act as animated spinner
|
||||
- `tailContainer` [RCTNativeAnimatedSpinner] The Container for the tail of the spinner
|
||||
- `label` [TextV1] If specified, renders the name of the passed value as text.
|
||||
- `label` [Text] If specified, renders the name of the passed value as text.
|
||||
|
||||
Mobile Slots
|
||||
|
||||
|
@ -101,7 +108,7 @@ Mobile Slots
|
|||
Below is the set of props `Spinner` supports.
|
||||
|
||||
```tsx
|
||||
export interface SpinnerProps extends ViewProps, SpinnerTokens {
|
||||
export interface SpinnerProps extends ViewProps {
|
||||
/**
|
||||
* Spinner appearnace
|
||||
* @defaultValue 'primary'
|
||||
|
@ -114,6 +121,11 @@ export interface SpinnerProps extends ViewProps, SpinnerTokens {
|
|||
* Note: This is not supported on mobile platforms
|
||||
*/
|
||||
labelPosition?: SpinnerLabelPosition;
|
||||
/**
|
||||
* Spinner label
|
||||
* Note: This is not supported on mobile platforms
|
||||
*/
|
||||
label?: string;
|
||||
/**
|
||||
* Spinner size
|
||||
* @defaultValue 'medium'
|
||||
|
@ -124,11 +136,6 @@ export interface SpinnerProps extends ViewProps, SpinnerTokens {
|
|||
* @defaultValue 'active'
|
||||
*/
|
||||
status?: SpinnerStatus;
|
||||
/**
|
||||
* Spinner label
|
||||
* Note: This is not supported on mobile platforms
|
||||
*/
|
||||
label?: string;
|
||||
/**
|
||||
* Spinner hidden when not animating or not hidden
|
||||
* @defaultValue 'true'
|
||||
|
@ -136,12 +143,23 @@ export interface SpinnerProps extends ViewProps, SpinnerTokens {
|
|||
*/
|
||||
hidesWhenStopped?: boolean;
|
||||
}
|
||||
|
||||
export interface SpinnerSvgProps extends SpinnerTokens {
|
||||
/**
|
||||
* The height and width of the viewBox are internal props used by the SVG to size themselves and
|
||||
* set up their viewBox to establish coordinate space for DPI scaling purposes.
|
||||
*/
|
||||
viewBoxHeight: number;
|
||||
viewBoxWidth: number;
|
||||
}
|
||||
```
|
||||
|
||||
### Styling Tokens
|
||||
|
||||
Tokens can be used to customize the styling of the control by using the customize function on the `Spinner`. For more information on using the customize API, please see [this page](https://github.com/microsoft/fluentui-react-native/blob/main/packages/framework/composition/README.md). The `Spinner` has the following tokens:
|
||||
|
||||
#### Shared Tokens
|
||||
|
||||
```tsx
|
||||
export interface SpinnerTokens {
|
||||
/**
|
||||
|
@ -158,5 +176,10 @@ export interface SpinnerTokens {
|
|||
* @defaultValue 'medium'
|
||||
*/
|
||||
size?: SpinnerSize;
|
||||
/**
|
||||
* Spinner appearnace
|
||||
* @defaultValue 'true'
|
||||
*/
|
||||
inverted?: SpinnerTokens;
|
||||
}
|
||||
```
|
||||
|
|
|
@ -5,11 +5,13 @@ import { Animated, Easing, View } from 'react-native';
|
|||
|
||||
import { compose, mergeProps, withSlots } from '@fluentui-react-native/framework';
|
||||
import type { UseSlots } from '@fluentui-react-native/framework';
|
||||
import { Svg, Path } from 'react-native-svg';
|
||||
import { Path, Svg } from 'react-native-svg';
|
||||
|
||||
import { diameterSizeMap, lineThicknessSizeMap, stylingSettings } from './Spinner.styling';
|
||||
import { stylingSettings } from './Spinner.styling';
|
||||
import type { SpinnerProps, SpinnerType } from './Spinner.types';
|
||||
import { spinnerName } from './Spinner.types';
|
||||
import { diameterSizeMap, lineThicknessSizeMap } from './SpinnerTokens.win32';
|
||||
import { useSpinner } from './useSpinner';
|
||||
|
||||
const getSpinnerPath = (diameter: number, width: number, color: ColorValue) => {
|
||||
const start = {
|
||||
|
@ -31,7 +33,8 @@ export const Spinner = compose<SpinnerType>({
|
|||
svg: AnimatedSvg,
|
||||
},
|
||||
useRender: (props: SpinnerProps, useSlots: UseSlots<SpinnerType>) => {
|
||||
const Slots = useSlots(props);
|
||||
const spinnerProps = useSpinner(props);
|
||||
const Slots = useSlots(spinnerProps);
|
||||
const status = props.status !== undefined ? props.status : 'active';
|
||||
const hidesWhenStopped = props.hidesWhenStopped != undefined ? props.hidesWhenStopped : true;
|
||||
const hideOpacity = status === 'inactive' && hidesWhenStopped == true ? 0 : 1;
|
||||
|
@ -74,11 +77,7 @@ export const Spinner = compose<SpinnerType>({
|
|||
outputRange: ['0deg', '359deg'],
|
||||
});
|
||||
|
||||
const path = getSpinnerPath(
|
||||
diameterSizeMap[Slots.root({}).props.size],
|
||||
lineThicknessSizeMap[Slots.root({}).props.size],
|
||||
Slots.root({}).props.trackColor,
|
||||
);
|
||||
const path = getSpinnerPath(diameterSizeMap[spinnerProps.size], lineThicknessSizeMap[spinnerProps.size], spinnerProps.trackColor);
|
||||
|
||||
// perspective is needed for animations to work on Android. See https://reactnative.dev/docs/animations#bear-in-mind
|
||||
const animatedSvgProps = {
|
||||
|
|
|
@ -5,21 +5,6 @@ import type { SpinnerProps, SpinnerSlotProps, SpinnerTokens } from './Spinner.ty
|
|||
import { spinnerName } from './Spinner.types';
|
||||
import { defaultSpinnerTokens } from './SpinnerTokens';
|
||||
|
||||
export const diameterSizeMap: { [key: string]: number } = {
|
||||
'xx-small': 12,
|
||||
'x-small': 16,
|
||||
medium: 24,
|
||||
large: 32,
|
||||
'x-large': 40,
|
||||
};
|
||||
export const lineThicknessSizeMap: { [key: string]: number } = {
|
||||
'xx-small': 1,
|
||||
'x-small': 1,
|
||||
medium: 2,
|
||||
large: 3,
|
||||
'x-large': 4,
|
||||
};
|
||||
|
||||
export const stylingSettings: UseStylingOptions<SpinnerProps, SpinnerSlotProps, SpinnerTokens> = {
|
||||
tokens: [defaultSpinnerTokens, spinnerName],
|
||||
tokensThatAreAlsoProps: 'all',
|
||||
|
@ -29,21 +14,19 @@ export const stylingSettings: UseStylingOptions<SpinnerProps, SpinnerSlotProps,
|
|||
trackColor: tokens.trackColor,
|
||||
size: tokens.size,
|
||||
lineThickness: tokens.size != 'medium' ? tokens.size : tokens.size,
|
||||
accessibilityRole: 'progressbar',
|
||||
accessible: true,
|
||||
style: {
|
||||
width: diameterSizeMap[tokens.size],
|
||||
height: diameterSizeMap[tokens.size],
|
||||
width: tokens.width,
|
||||
height: tokens.height,
|
||||
},
|
||||
}),
|
||||
['trackColor', 'size'],
|
||||
),
|
||||
svg: buildProps(
|
||||
(tokens: SpinnerTokens) => ({
|
||||
width: diameterSizeMap[tokens.size],
|
||||
height: diameterSizeMap[tokens.size],
|
||||
width: tokens.width,
|
||||
height: tokens.height,
|
||||
}),
|
||||
['size'],
|
||||
['width', 'height'],
|
||||
),
|
||||
},
|
||||
};
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
import type { UseStylingOptions } from '@fluentui-react-native/framework';
|
||||
import { buildProps } from '@fluentui-react-native/framework';
|
||||
|
||||
import type { SpinnerProps, SpinnerSlotProps, SpinnerTokens } from './Spinner.types.win32';
|
||||
import { spinnerName } from './Spinner.types.win32';
|
||||
import { defaultSpinnerTokens } from './SpinnerTokens';
|
||||
|
||||
export const stylingSettings: UseStylingOptions<SpinnerProps, SpinnerSlotProps, SpinnerTokens> = {
|
||||
tokens: [defaultSpinnerTokens, spinnerName],
|
||||
tokensThatAreAlsoProps: 'all',
|
||||
slotProps: {
|
||||
root: buildProps(
|
||||
(tokens: SpinnerTokens) => ({
|
||||
style: {
|
||||
width: tokens.width,
|
||||
height: tokens.height,
|
||||
},
|
||||
}),
|
||||
['width', 'height'],
|
||||
),
|
||||
track: buildProps(
|
||||
(tokens: SpinnerTokens) => ({
|
||||
size: tokens.size,
|
||||
trackColor: tokens.trackColor,
|
||||
viewBoxWidth: tokens.width,
|
||||
viewBoxHeight: tokens.height,
|
||||
}),
|
||||
['size', 'trackColor', 'width', 'height'],
|
||||
),
|
||||
},
|
||||
};
|
|
@ -1,35 +1,28 @@
|
|||
/** @jsx withSlots */
|
||||
import { View } from 'react-native';
|
||||
import { Animated, View } from 'react-native';
|
||||
|
||||
import type { UseSlots } from '@fluentui-react-native/framework';
|
||||
import { compose, mergeProps, withSlots } from '@fluentui-react-native/framework';
|
||||
import { TextV1 as Text } from '@fluentui-react-native/text';
|
||||
import { Svg } from 'react-native-svg';
|
||||
|
||||
import { RCTNativeAnimatedSpinner } from './consts.win32';
|
||||
import type { SpinnerProps, SpinnerType } from './Spinner.types';
|
||||
import { spinnerName } from './Spinner.types';
|
||||
import { useSpinner } from './useSpinner';
|
||||
|
||||
/* TODO: Implement Spinner with following slots */
|
||||
export const AnimatedSvg = Animated.createAnimatedComponent(Svg);
|
||||
export const Spinner = compose<SpinnerType>({
|
||||
displayName: spinnerName,
|
||||
slots: {
|
||||
root: View,
|
||||
track: Svg,
|
||||
tail: Svg,
|
||||
tailContainer: RCTNativeAnimatedSpinner,
|
||||
label: Text,
|
||||
svg: AnimatedSvg,
|
||||
},
|
||||
useRender: (props: SpinnerProps, useSlots: UseSlots<SpinnerType>) => {
|
||||
const Slots = useSlots(props);
|
||||
const spinnerProps = useSpinner(props);
|
||||
const Slots = useSlots(spinnerProps);
|
||||
|
||||
return (rest: SpinnerProps) => {
|
||||
const { ...mergedProps } = mergeProps(props, rest);
|
||||
return (
|
||||
<Slots.root {...mergedProps}>
|
||||
<Slots.label />
|
||||
</Slots.root>
|
||||
);
|
||||
const { ...mergedProps } = mergeProps(spinnerProps, rest);
|
||||
return <Slots.root {...mergedProps}></Slots.root>;
|
||||
};
|
||||
},
|
||||
});
|
||||
|
|
|
@ -0,0 +1,106 @@
|
|||
import type { Animated, ViewProps } from 'react-native';
|
||||
|
||||
import type { SvgProps } from 'react-native-svg';
|
||||
|
||||
export const spinnerName = 'Spinner';
|
||||
/**
|
||||
* Specifies the possible appearance of the Spinner.
|
||||
*/
|
||||
export type SpinnerAppearance = 'primary' | 'inverted';
|
||||
/**
|
||||
* Specifies the possible label position of the Spinner.
|
||||
*/
|
||||
export type SpinnerLabelPosition = 'above' | 'below' | 'before' | 'after';
|
||||
/**
|
||||
* Specifies the possible sizes of the Spinner.
|
||||
*/
|
||||
export type SpinnerSize = 'tiny' | 'xx-small' | 'x-small' | 'small' | 'medium' | 'large' | 'x-large' | 'huge';
|
||||
/**
|
||||
* Specifies the possible status of the Spinner.
|
||||
*/
|
||||
export type SpinnerStatus = 'active' | 'inactive';
|
||||
|
||||
export interface SpinnerTokens {
|
||||
/**
|
||||
* Spinner element color
|
||||
*/
|
||||
trackColor?: string;
|
||||
/**
|
||||
* Spinner element color
|
||||
* Note: This is not supported on mobile platforms
|
||||
*/
|
||||
tailColor?: string;
|
||||
/**
|
||||
* Size of the Spinner view
|
||||
* @defaultValue 'medium'
|
||||
*/
|
||||
size?: SpinnerSize;
|
||||
/**
|
||||
* Spinner appearnace
|
||||
* @defaultValue 'false'
|
||||
*/
|
||||
inverted?: SpinnerTokens;
|
||||
width?: number;
|
||||
height?: number;
|
||||
/**
|
||||
* Sizes of the Spinner
|
||||
*/
|
||||
'x-small'?: SpinnerTokens;
|
||||
small?: SpinnerTokens;
|
||||
medium?: SpinnerTokens;
|
||||
large?: SpinnerTokens;
|
||||
'x-large'?: SpinnerTokens;
|
||||
/* win32 specific */
|
||||
tiny?: SpinnerTokens;
|
||||
huge?: SpinnerTokens;
|
||||
/* mobile specific */
|
||||
'xx-small'?: SpinnerTokens;
|
||||
}
|
||||
|
||||
export interface SpinnerProps extends ViewProps, SpinnerTokens {
|
||||
/**
|
||||
* Spinner appearnace
|
||||
* @defaultValue 'primary'
|
||||
* Note: This is not supported on mobile platforms
|
||||
*/
|
||||
appearance?: SpinnerAppearance;
|
||||
/**
|
||||
* Spinner label position
|
||||
* @defaultValue 'after'
|
||||
* Note: This is not supported on mobile platforms
|
||||
*/
|
||||
labelPosition?: SpinnerLabelPosition;
|
||||
/**
|
||||
* Spinner label
|
||||
* Note: This is not supported on mobile platforms
|
||||
*/
|
||||
label?: string;
|
||||
/**
|
||||
* Spinner size
|
||||
* @defaultValue 'medium'
|
||||
*/
|
||||
size?: SpinnerSize;
|
||||
/**
|
||||
* Spinner animating or not
|
||||
* @defaultValue 'active'
|
||||
*/
|
||||
status?: SpinnerStatus;
|
||||
/**
|
||||
* Spinner hidden when not animating or not hidden
|
||||
* @defaultValue 'true'
|
||||
* @platform android
|
||||
*/
|
||||
hidesWhenStopped?: boolean;
|
||||
}
|
||||
|
||||
export type SpinnerState = SpinnerProps;
|
||||
|
||||
export interface SpinnerSlotProps {
|
||||
root: SpinnerProps;
|
||||
svg?: Animated.AnimatedProps<SvgProps>;
|
||||
}
|
||||
export interface SpinnerType {
|
||||
props: SpinnerProps;
|
||||
slotProps: SpinnerSlotProps;
|
||||
tokens: SpinnerTokens;
|
||||
}
|
|
@ -1,97 +1,12 @@
|
|||
import type { Animated, TextProps, ViewProps } from 'react-native';
|
||||
|
||||
import type { SvgProps } from 'react-native-svg';
|
||||
|
||||
export const spinnerName = 'Spinner';
|
||||
/**
|
||||
* Specifies the possible appearance of the Spinner.
|
||||
*/
|
||||
export type SpinnerAppearance = 'primary' | 'inverted';
|
||||
/**
|
||||
* Specifies the possible label position of the Spinner.
|
||||
*/
|
||||
export type SpinnerLabelPosition = 'above' | 'below' | 'before' | 'after';
|
||||
/**
|
||||
* Specifies the possible sizes of the Spinner.
|
||||
*/
|
||||
export type SpinnerSize = 'tiny' | 'xx-small' | 'x-small' | 'small' | 'medium' | 'large' | 'x-large' | 'huge';
|
||||
/**
|
||||
* Specifies the possible status of the Spinner.
|
||||
*/
|
||||
export type SpinnerStatus = 'active' | 'inactive';
|
||||
|
||||
export interface SpinnerTokens {
|
||||
/**
|
||||
* Spinner element color
|
||||
*/
|
||||
trackColor?: string;
|
||||
/**
|
||||
* Spinner element color
|
||||
* Note: This is not supported on mobile platforms
|
||||
*/
|
||||
tailColor?: string;
|
||||
/**
|
||||
* Size of the Spinner view
|
||||
* @defaultValue 'medium'
|
||||
*/
|
||||
size?: SpinnerSize;
|
||||
}
|
||||
|
||||
export interface SpinnerProps extends ViewProps, SpinnerTokens {
|
||||
/**
|
||||
* Spinner appearnace
|
||||
* @defaultValue 'primary'
|
||||
* Note: This is not supported on mobile platforms
|
||||
*/
|
||||
appearance?: SpinnerAppearance;
|
||||
/**
|
||||
* Spinner label position
|
||||
* @defaultValue 'after'
|
||||
* Note: This is not supported on mobile platforms
|
||||
*/
|
||||
labelPosition?: SpinnerLabelPosition;
|
||||
/**
|
||||
* Spinner size
|
||||
* @defaultValue 'medium'
|
||||
*/
|
||||
size?: SpinnerSize;
|
||||
/**
|
||||
* Spinner animating or not
|
||||
* @defaultValue 'active'
|
||||
*/
|
||||
status?: SpinnerStatus;
|
||||
/**
|
||||
* Spinner label
|
||||
* Note: This is not supported on mobile platforms
|
||||
*/
|
||||
label?: string;
|
||||
/**
|
||||
* Spinner hidden when not animating or not hidden
|
||||
* @defaultValue 'true'
|
||||
* @platform android
|
||||
*/
|
||||
hidesWhenStopped?: boolean;
|
||||
}
|
||||
|
||||
export interface SpinnerSvgProps extends SpinnerTokens {
|
||||
/**
|
||||
* The height and width of the viewBox are internal props used by the SVG to size themselves and
|
||||
* set up their viewBox to establish coordinate space for DPI scaling purposes.
|
||||
*/
|
||||
viewBoxHeight: number;
|
||||
viewBoxWidth: number;
|
||||
}
|
||||
|
||||
export interface SpinnerSlotProps {
|
||||
root: SpinnerProps; //SpinnerProps extends ViewProps which is required for win32 native module.
|
||||
track?: SpinnerSvgProps;
|
||||
tail?: SpinnerSvgProps;
|
||||
tailContainer?: SpinnerSvgProps;
|
||||
label?: TextProps;
|
||||
svg?: Animated.AnimatedProps<SvgProps>;
|
||||
}
|
||||
export interface SpinnerType {
|
||||
props: SpinnerProps;
|
||||
slotProps: SpinnerSlotProps;
|
||||
tokens: SpinnerTokens;
|
||||
}
|
||||
export {
|
||||
spinnerName,
|
||||
SpinnerAppearance,
|
||||
SpinnerLabelPosition,
|
||||
SpinnerSize,
|
||||
SpinnerStatus,
|
||||
SpinnerState,
|
||||
SpinnerTokens,
|
||||
SpinnerProps,
|
||||
SpinnerSlotProps,
|
||||
SpinnerType,
|
||||
} from './Spinner.types.shared';
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
import type { TextProps } from 'react-native';
|
||||
|
||||
import type { SpinnerProps, SpinnerTokens } from './Spinner.types.shared';
|
||||
|
||||
export { SpinnerProps, SpinnerTokens };
|
||||
export { spinnerName, SpinnerAppearance, SpinnerLabelPosition, SpinnerSize, SpinnerStatus } from './Spinner.types.shared';
|
||||
|
||||
export interface SpinnerSvgProps extends SpinnerTokens {
|
||||
/**
|
||||
* The height and width of the viewBox are internal props used by the SVG to size themselves and
|
||||
* set up their viewBox to establish coordinate space for DPI scaling purposes.
|
||||
*/
|
||||
viewBoxHeight?: number;
|
||||
viewBoxWidth?: number;
|
||||
}
|
||||
|
||||
export interface SpinnerSlotProps {
|
||||
root: SpinnerProps; //SpinnerProps extends ViewProps which is required for win32 native module.
|
||||
track?: SpinnerSvgProps;
|
||||
tail?: SpinnerSvgProps;
|
||||
tailContainer?: SpinnerSvgProps;
|
||||
label?: TextProps;
|
||||
}
|
||||
export interface SpinnerType {
|
||||
props: SpinnerProps;
|
||||
slotProps: SpinnerSlotProps;
|
||||
tokens: SpinnerTokens;
|
||||
}
|
|
@ -0,0 +1,77 @@
|
|||
/** @jsx withSlots */
|
||||
import type { ColorValue } from 'react-native';
|
||||
import { View } from 'react-native';
|
||||
|
||||
import type { UseSlots } from '@fluentui-react-native/framework';
|
||||
import { compose, mergeProps, withSlots } from '@fluentui-react-native/framework';
|
||||
import { TextV1 as Text } from '@fluentui-react-native/text';
|
||||
import { Path, Svg } from 'react-native-svg';
|
||||
import type { SvgProps } from 'react-native-svg';
|
||||
|
||||
import { RCTNativeAnimatedSpinner } from './consts.win32';
|
||||
import { stylingSettings } from './Spinner.styling.win32';
|
||||
import { spinnerName } from './Spinner.types';
|
||||
import type { SpinnerProps, SpinnerType, SpinnerSvgProps } from './Spinner.types.win32';
|
||||
import { diameterSizeMap, lineThicknessSizeMap, getDefaultSize } from './SpinnerTokens.win32';
|
||||
import { useSpinner } from './useSpinner';
|
||||
// TODO: getTailPath, tailSvg
|
||||
|
||||
const getTrackPath = (diameter: number, width: number, color: ColorValue) => {
|
||||
const start = {
|
||||
x: width / 2,
|
||||
y: diameter / 2 + width / 2,
|
||||
};
|
||||
|
||||
const path = `M${start.x} ${start.y} a${diameter / 2} ${diameter / 2} 0 1 0 ${diameter} 0 a${diameter / 2} ${
|
||||
diameter / 2
|
||||
} 0 1 0 -${diameter} 0}`;
|
||||
return <Path d={path} stroke={color} strokeWidth={width} strokeLinecap="round" fillOpacity={0} />;
|
||||
};
|
||||
|
||||
/* Track is a full circle with a transparent fill */
|
||||
const trackSvg: React.FunctionComponent<SpinnerSvgProps> = (props: SpinnerSvgProps) => {
|
||||
const { size, trackColor } = props;
|
||||
const svgProps: SvgProps = {
|
||||
style: {
|
||||
height: diameterSizeMap[size],
|
||||
width: diameterSizeMap[size],
|
||||
},
|
||||
};
|
||||
const path = getTrackPath(diameterSizeMap[size] - lineThicknessSizeMap[size], lineThicknessSizeMap[size], trackColor);
|
||||
|
||||
return <Svg {...svgProps}>{path}</Svg>;
|
||||
};
|
||||
|
||||
export const spinnerLookup = (layer: string, userProps: SpinnerProps): boolean => {
|
||||
return (
|
||||
userProps[layer] ||
|
||||
layer === userProps['appearance'] ||
|
||||
layer === userProps['size'] ||
|
||||
(!userProps['size'] && layer === getDefaultSize())
|
||||
);
|
||||
};
|
||||
|
||||
export const Spinner = compose<SpinnerType>({
|
||||
displayName: spinnerName,
|
||||
...stylingSettings,
|
||||
slots: {
|
||||
root: View,
|
||||
track: trackSvg,
|
||||
tail: Svg,
|
||||
tailContainer: RCTNativeAnimatedSpinner,
|
||||
label: Text,
|
||||
},
|
||||
useRender: (props: SpinnerProps, useSlots: UseSlots<SpinnerType>) => {
|
||||
const spinnerProps = useSpinner(props);
|
||||
const Slots = useSlots(spinnerProps, (layer) => spinnerLookup(layer, spinnerProps));
|
||||
|
||||
return (final: SpinnerProps) => {
|
||||
const { ...mergedProps } = mergeProps(spinnerProps, final);
|
||||
return (
|
||||
<Slots.root {...mergedProps}>
|
||||
<Slots.track />
|
||||
</Slots.root>
|
||||
);
|
||||
};
|
||||
},
|
||||
});
|
|
@ -8,6 +8,4 @@ import type { SpinnerTokens } from './Spinner.types';
|
|||
export const defaultSpinnerTokens: TokenSettings<SpinnerTokens, Theme> = () =>
|
||||
({
|
||||
trackColor: Appearance.getColorScheme() === 'light' ? globalTokens.color.grey56 : globalTokens.color.grey72,
|
||||
lineThickness: 'medium',
|
||||
size: 'medium',
|
||||
} as SpinnerTokens);
|
||||
|
|
|
@ -1,13 +1,50 @@
|
|||
import { Appearance } from 'react-native';
|
||||
|
||||
import type { Theme } from '@fluentui-react-native/framework';
|
||||
import type { TokenSettings } from '@fluentui-react-native/use-styling';
|
||||
|
||||
import type { SpinnerTokens } from './Spinner.types';
|
||||
|
||||
export const defaultSpinnerTokens: TokenSettings<SpinnerTokens, Theme> = () =>
|
||||
/* Mobile sizes */
|
||||
export const diameterSizeMap: { [key: string]: number } = {
|
||||
'xx-small': 12,
|
||||
'x-small': 16,
|
||||
medium: 24,
|
||||
large: 32,
|
||||
'x-large': 40,
|
||||
};
|
||||
export const lineThicknessSizeMap: { [key: string]: number } = {
|
||||
'xx-small': 1,
|
||||
'x-small': 1,
|
||||
medium: 2,
|
||||
large: 3,
|
||||
'x-large': 4,
|
||||
};
|
||||
|
||||
export const defaultSpinnerTokens: TokenSettings<SpinnerTokens, Theme> = (t: Theme) =>
|
||||
({
|
||||
trackColor: Appearance.getColorScheme() === 'light' ? '#BDBDBD' : '#666666',
|
||||
lineThickness: 'medium',
|
||||
size: 'medium',
|
||||
xxSmall: {
|
||||
size: 'xx-small',
|
||||
width: diameterSizeMap['xx-small'],
|
||||
height: diameterSizeMap['xx-small'],
|
||||
},
|
||||
small: {
|
||||
size: 'x-small',
|
||||
width: diameterSizeMap['x-small'],
|
||||
height: diameterSizeMap['x-small'],
|
||||
},
|
||||
medium: {
|
||||
size: 'medium',
|
||||
width: diameterSizeMap['medium'],
|
||||
height: diameterSizeMap['medium'],
|
||||
},
|
||||
large: {
|
||||
size: 'large',
|
||||
width: diameterSizeMap['large'],
|
||||
height: diameterSizeMap['large'],
|
||||
},
|
||||
xlarge: {
|
||||
size: 'x-large',
|
||||
width: diameterSizeMap['x-large'],
|
||||
height: diameterSizeMap['x-large'],
|
||||
},
|
||||
trackColor: t.colors.brandStroke2,
|
||||
} as SpinnerTokens);
|
||||
|
|
|
@ -0,0 +1,82 @@
|
|||
import type { Theme, Variant } from '@fluentui-react-native/framework';
|
||||
import type { TokenSettings } from '@fluentui-react-native/use-styling';
|
||||
|
||||
import type { SpinnerSize, SpinnerTokens } from './Spinner.types.win32';
|
||||
|
||||
export const diameterSizeMap: { [key: string]: number } = {
|
||||
tiny: 20,
|
||||
'x-small': 24,
|
||||
small: 28,
|
||||
medium: 32,
|
||||
large: 36,
|
||||
'x-large': 40,
|
||||
huge: 44,
|
||||
};
|
||||
export const lineThicknessSizeMap: { [key: string]: number } = {
|
||||
tiny: 2,
|
||||
'x-small': 2,
|
||||
small: 2,
|
||||
medium: 3,
|
||||
large: 3,
|
||||
'x-large': 3,
|
||||
huge: 4,
|
||||
};
|
||||
|
||||
export const textStyleMap: { [key: string]: Variant } = {
|
||||
tiny: 'body1',
|
||||
'x-small': 'body1',
|
||||
small: 'body1',
|
||||
medium: 'subtitle2',
|
||||
large: 'subtitle2',
|
||||
'x-large': 'subtitle2',
|
||||
huge: 'subtitle1',
|
||||
};
|
||||
|
||||
export const getDefaultSize = (): SpinnerSize => {
|
||||
return 'medium';
|
||||
};
|
||||
|
||||
export const defaultSpinnerTokens: TokenSettings<SpinnerTokens, Theme> = (t: Theme) =>
|
||||
({
|
||||
tiny: {
|
||||
size: 'tiny',
|
||||
width: diameterSizeMap['tiny'],
|
||||
height: diameterSizeMap['tiny'],
|
||||
},
|
||||
'x-small': {
|
||||
size: 'x-small',
|
||||
width: diameterSizeMap['x-small'],
|
||||
height: diameterSizeMap['x-small'],
|
||||
},
|
||||
small: {
|
||||
size: 'small',
|
||||
width: diameterSizeMap['small'],
|
||||
height: diameterSizeMap['small'],
|
||||
},
|
||||
medium: {
|
||||
size: 'medium',
|
||||
width: diameterSizeMap['medium'],
|
||||
height: diameterSizeMap['medium'],
|
||||
},
|
||||
large: {
|
||||
size: 'large',
|
||||
width: diameterSizeMap['large'],
|
||||
height: diameterSizeMap['large'],
|
||||
},
|
||||
'x-large': {
|
||||
size: 'x-large',
|
||||
width: diameterSizeMap['x-large'],
|
||||
height: diameterSizeMap['x-large'],
|
||||
},
|
||||
huge: {
|
||||
size: 'huge',
|
||||
width: diameterSizeMap['huge'],
|
||||
height: diameterSizeMap['huge'],
|
||||
},
|
||||
tailColor: t.colors.brandStroke1,
|
||||
trackColor: t.colors.brandStroke2,
|
||||
inverted: {
|
||||
tailColor: t.colors.neutralStroke2,
|
||||
trackColor: t.colors.neutralBackgroundInverted,
|
||||
},
|
||||
} as SpinnerTokens);
|
|
@ -1,4 +1,5 @@
|
|||
export { Spinner } from './Spinner';
|
||||
export { spinnerName } from './Spinner.types';
|
||||
export {
|
||||
SpinnerTokens,
|
||||
SpinnerProps,
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
import type { SpinnerProps, SpinnerState } from './Spinner.types';
|
||||
|
||||
export const useSpinner = (props: SpinnerProps): SpinnerState => {
|
||||
return {
|
||||
accessible: true,
|
||||
accessibilityRole: 'progressbar',
|
||||
size: 'medium',
|
||||
...props,
|
||||
};
|
||||
};
|
Загрузка…
Ссылка в новой задаче