Skip to content

Commit

Permalink
Add full-height popover component.
Browse files Browse the repository at this point in the history
  • Loading branch information
jameswilddev committed Nov 17, 2021
1 parent 0e0cecd commit 9f79c21
Show file tree
Hide file tree
Showing 6 changed files with 4,870 additions and 0 deletions.
256 changes: 256 additions & 0 deletions components/createFullHeightPopoverComponent/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,256 @@
import * as React from "react";
import { View, ViewStyle, Text, StyleSheet } from "react-native";
import type { ControlStyle } from "../../types/ControlStyle";
import { useRefresh } from "../../hooks/useRefresh";
import { Hitbox } from "../Hitbox";
import { SimpleModal } from "../SimpleModal";
import {
createControlPlaceholderTextStyleInstance,
createControlStateStyleInstance,
createControlStyleInstance,
createControlTextStyleInstance,
createFullHeightPopoverStateStyleInstance,
} from "../helpers";

/**
* Creates a new React component which displays a button which can be pressed to
* show an element in a pop-over which fills the display vertically.
* @param controlStyle The styling to use.
* @returns The created React component.
*/
export const createFullHeightPopoverComponent = (
controlStyle: ControlStyle
): React.FunctionComponent<{
/**
* The text shown in the button. When null, the placeholder is shown instead.
*/
readonly label: null | string;

/**
* The placeholder text shown in the button when there is no text.
*/
readonly placeholder: string;

/**
* When true, the button cannot be pressed and the body is not shown.
*/
readonly disabled: boolean;

/**
* When true, the control is styled as though it is valid. It is otherwise
* styles as though it is invalid.
*/
readonly valid: boolean;
}> => {
const styles = StyleSheet.create({
blurredValidHitbox: createControlStyleInstance(
controlStyle,
controlStyle.blurredValid
),
blurredInvalidHitbox: createControlStateStyleInstance(
controlStyle,
controlStyle.blurredInvalid
),
focusedValidHitbox: createControlStateStyleInstance(
controlStyle,
controlStyle.focusedValid
),
focusedInvalidHitbox: createControlStateStyleInstance(
controlStyle,
controlStyle.focusedInvalid
),
disabledValidHitbox: createControlStateStyleInstance(
controlStyle,
controlStyle.disabledValid
),
disabledInvalidHitbox: createControlStateStyleInstance(
controlStyle,
controlStyle.disabledInvalid
),
disabledValidText: createControlTextStyleInstance(
controlStyle,
controlStyle.disabledValid
),
disabledInvalidText: createControlTextStyleInstance(
controlStyle,
controlStyle.disabledInvalid
),
blurredValidText: createControlTextStyleInstance(
controlStyle,
controlStyle.blurredValid
),
blurredInvalidText: createControlTextStyleInstance(
controlStyle,
controlStyle.blurredInvalid
),
focusedValidText: createControlTextStyleInstance(
controlStyle,
controlStyle.focusedValid
),
focusedInvalidText: createControlTextStyleInstance(
controlStyle,
controlStyle.focusedInvalid
),
disabledValidPlaceholderText: createControlPlaceholderTextStyleInstance(
controlStyle,
controlStyle.disabledValid
),
disabledInvalidPlaceholderText: createControlPlaceholderTextStyleInstance(
controlStyle,
controlStyle.disabledInvalid
),
blurredValidPlaceholderText: createControlPlaceholderTextStyleInstance(
controlStyle,
controlStyle.blurredValid
),
blurredInvalidPlaceholderText: createControlPlaceholderTextStyleInstance(
controlStyle,
controlStyle.blurredInvalid
),
focusedValidPlaceholderText: createControlPlaceholderTextStyleInstance(
controlStyle,
controlStyle.focusedValid
),
focusedInvalidPlaceholderText: createControlPlaceholderTextStyleInstance(
controlStyle,
controlStyle.focusedInvalid
),
validView: createFullHeightPopoverStateStyleInstance(
controlStyle.focusedValid
),
invalidView: createFullHeightPopoverStateStyleInstance(
controlStyle.focusedInvalid
),
});

return ({ label, placeholder, disabled, valid, children }) => {
const refresh = useRefresh();

const state = React.useRef<{
open: boolean;
layout: null | {
readonly pageX: number;
readonly width: number;
};
}>({
open: false,
layout: null,
});

// Ensure that the drop-down does not re-open itself if it is disabled while
// open, then re-enabled.
if (disabled) {
state.current.open = false;
}

let additionalModalViewStyle: null | ViewStyle;

if (!disabled && state.current.open && state.current.layout !== null) {
additionalModalViewStyle = {
left: state.current.layout.pageX,
width: state.current.layout.width,
};
} else {
additionalModalViewStyle = null;
}

const inline = (
<Hitbox
style={
disabled
? valid
? styles.disabledValidHitbox
: styles.disabledInvalidHitbox
: additionalModalViewStyle === null
? valid
? styles.blurredValidHitbox
: styles.blurredInvalidHitbox
: valid
? styles.focusedValidHitbox
: styles.focusedInvalidHitbox
}
onMeasure={(x, y, width, height, pageX, pageY) => {
x;
y;
height;
pageY;

if (
state.current.layout === null ||
pageX !== state.current.layout.pageX ||
width !== state.current.layout.width
) {
state.current.layout = {
pageX,
width,
};

refresh();
}
}}
onPress={() => {
state.current.open = true;

refresh();
}}
disabled={disabled}
>
<Text
style={
disabled
? valid
? label === null
? styles.disabledValidPlaceholderText
: styles.disabledValidText
: label === null
? styles.disabledInvalidPlaceholderText
: styles.disabledInvalidText
: additionalModalViewStyle === null
? valid
? label === null
? styles.blurredValidPlaceholderText
: styles.blurredValidText
: label === null
? styles.blurredInvalidPlaceholderText
: styles.blurredInvalidText
: valid
? label === null
? styles.focusedValidPlaceholderText
: styles.focusedValidText
: label === null
? styles.focusedInvalidPlaceholderText
: styles.focusedInvalidText
}
>
{label ?? placeholder}
</Text>
</Hitbox>
);

if (additionalModalViewStyle === null) {
return inline;
} else {
return (
<React.Fragment>
{inline}
<SimpleModal
onClose={() => {
state.current.open = false;

refresh();
}}
>
<View
style={[
valid ? styles.validView : styles.invalidView,
additionalModalViewStyle,
]}
>
{children}
</View>
</SimpleModal>
</React.Fragment>
);
}
};
};
96 changes: 96 additions & 0 deletions components/createFullHeightPopoverComponent/readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
# `react-native-app-helpers/createFullHeightPopoverComponent`

Creates a new React component which displays a button which can be pressed to
show an element in a pop-over which fills the display vertically.

This is particularly useful for searchable and creatable selects, where the
button may be obscured by the keyboard (and as much vertical height as possible
is desirable for the list of options).

## Usage

```tsx
import { createFullHeightPopoverComponent } from "react-native-app-helpers";

const ExampleFullHeightPopover = createDropDownComponent(
{
fontFamily: `Example Font Family`,
fontSize: 37,
paddingVertical: 12,
paddingHorizontal: 29,
blurredValid: {
textColor: `#FFEE00`,
placeholderColor: `#E7AA32`,
backgroundColor: `#32AE12`,
radius: 5,
border: {
width: 4,
color: `#FF00FF`,
},
},
blurredInvalid: {
textColor: `#99FE88`,
placeholderColor: `#CACA3A`,
backgroundColor: `#259284`,
radius: 10,
border: {
width: 6,
color: `#9A9A8E`,
},
},
focusedValid: {
textColor: `#55EA13`,
placeholderColor: `#273346`,
backgroundColor: `#CABA99`,
radius: 3,
border: {
width: 5,
color: `#646464`,
},
},
focusedInvalid: {
textColor: `#ABAADE`,
placeholderColor: `#47ADAD`,
backgroundColor: `#32AA88`,
radius: 47,
border: {
width: 12,
color: `#98ADAA`,
},
},
disabledValid: {
textColor: `#AE2195`,
placeholderColor: `#FFAAEE`,
backgroundColor: `#772728`,
radius: 100,
border: {
width: 14,
color: `#5E5E5E`,
},
},
disabledInvalid: {
textColor: `#340297`,
placeholderColor: `#233832`,
backgroundColor: `#938837`,
radius: 2,
border: {
width: 19,
color: `#573829`,
},
},
}
);

const ExampleScreen = () => (
<ExampleFullHeightPopover
button={<Text>Click or touch to open the pop-over.</Text>}
body={(position) => (
<Text>
This is shown in a column spanning the full height of the display when
the button is pressed.
</Text>
)}
disabled={false}
/>
);
```
Loading

0 comments on commit 9f79c21

Please sign in to comment.