[add] Appearance module

Close #1597
This commit is contained in:
Jaap Frolich
2020-05-06 18:14:00 -07:00
committed by Nicolas Gallagher
parent cecacbc3ac
commit 324995ec2b
7 changed files with 162 additions and 0 deletions
@@ -6,6 +6,7 @@ module.exports = {
Animated: true,
AppRegistry: true,
AppState: true,
Appearance: true,
BackHandler: true,
Button: true,
CheckBox: true,
@@ -64,5 +65,6 @@ module.exports = {
processColor: true,
render: true,
unmountComponentAtNode: true,
useColorScheme: true,
useWindowDimensions: true
};
@@ -0,0 +1,63 @@
import { Meta, Preview } from '@storybook/addon-docs/blocks';
import * as Stories from './examples';
<Meta title="APIs|Appearance" />
# Appearance
The Appearance module exposes information about the user's appearance
preferences, such as their preferred color scheme (light or dark). In
`react-native-web` this is achieved using the `prefers-color-scheme` media query.
## Methods
### getColorScheme()
You can use the Appearance module to determine if the user prefers a dark color
scheme:
```js
const colorScheme = Appearance.getColorScheme();
if (colorScheme === 'dark') {
// Use dark color scheme
}
```
Although the color scheme is available immediately, this may change (e.g.
scheduled color scheme change at sunrise or sunset). Any rendering logic or
styles that depend on the user preferred color scheme should try to call this
function on every render, rather than caching the value.
## Hooks
### useColorScheme
The `useColorScheme` React hook provides and subscribes to color scheme updates
from the Appearance module. The return value indicates the current user
preferred color scheme. The value may be updated later, either through direct
user action (e.g. theme selection in device settings) or on a schedule (e.g.
light and dark themes that follow the day/night cycle).
Supported color schemes:
- `'light'`: The user prefers a light color theme.
- `'dark'`: The user prefers a dark color theme.
- `null`: The user has not indicated a preferred color theme.
```js
import * as React from 'react';
import { Text, useColorScheme } from 'react-native';
const MyComponent = () => {
const colorScheme = useColorScheme();
return <Text>Your color scheme is: {colorScheme}</Text>;
};
```
This produces:
<Preview withSource="none">
<Story name="colorScheme">
<Stories.colorSchemeText />
</Story>
</Preview>
@@ -0,0 +1,7 @@
import * as React from 'react';
import { Text, useColorScheme } from 'react-native';
export default function ColorSchemeText() {
const colorScheme = useColorScheme();
return <Text>Your color scheme is: {colorScheme}</Text>;
}
@@ -0,0 +1 @@
export { default as colorSchemeText } from './ColorSchemeText';
@@ -0,0 +1,60 @@
/**
* Copyright (c) Nicolas Gallagher.
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
import { canUseDOM } from 'fbjs/lib/ExecutionEnvironment';
export type ColorSchemeName = 'light' | 'dark';
export type AppearancePreferences = {|
colorScheme: ColorSchemeName
|};
type AppearanceListener = (preferences: AppearancePreferences) => void;
type DOMAppearanceListener = (ev: MediaQueryListEvent) => any;
function getQuery(): MediaQueryList | null {
return canUseDOM && window.matchMedia != null
? window.matchMedia('(prefers-color-scheme: dark)')
: null;
}
const query = getQuery();
const listenerMapping = new WeakMap<AppearanceListener, DOMAppearanceListener>();
const Appearance = {
getColorScheme(): ColorSchemeName {
return query && query.matches ? 'dark' : 'light';
},
addChangeListener(listener: AppearanceListener): void {
let mappedListener = listenerMapping.get(listener);
if (!mappedListener) {
mappedListener = ({ matches }: MediaQueryListEvent) => {
listener({ colorScheme: matches ? 'dark' : 'light' });
};
listenerMapping.set(listener, mappedListener);
}
if (query) {
query.addListener(mappedListener);
}
},
removeChangeListener(listener: AppearanceListener): void {
const mappedListener = listenerMapping.get(listener);
if (mappedListener) {
if (query) {
query.removeListener(mappedListener);
}
listenerMapping.delete(listener);
}
}
};
export default Appearance;
@@ -0,0 +1,27 @@
/**
* Copyright (c) Nicolas Gallagher.
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
import * as React from 'react';
import type { ColorSchemeName } from '../Appearance';
import Appearance from '../Appearance';
export default function useColorScheme(): ColorSchemeName {
const [colorScheme, setColorScheme] = React.useState(Appearance.getColorScheme());
React.useEffect(() => {
function listener(appearance) {
setColorScheme(appearance.colorScheme);
}
Appearance.addChangeListener(listener);
return () => Appearance.removeChangeListener(listener);
});
return colorScheme;
}
+2
View File
@@ -9,6 +9,7 @@ export { default as NativeModules } from './exports/NativeModules';
export { default as AccessibilityInfo } from './exports/AccessibilityInfo';
export { default as Alert } from './exports/Alert';
export { default as Animated } from './exports/Animated';
export { default as Appearance } from './exports/Appearance';
export { default as AppRegistry } from './exports/AppRegistry';
export { default as AppState } from './exports/AppState';
export { default as BackHandler } from './exports/BackHandler';
@@ -73,4 +74,5 @@ export { default as TVEventHandler } from './exports/TVEventHandler';
export { default as DeviceEventEmitter } from './exports/DeviceEventEmitter';
// hooks
export { default as useColorScheme } from './exports/useColorScheme';
export { default as useWindowDimensions } from './exports/useWindowDimensions';