@fountain-ui/lab 2.0.0-beta.82 → 2.0.0-beta.83
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/commonjs/BottomSheet/BottomSheetNative.js +69 -16
- package/build/commonjs/BottomSheet/BottomSheetNative.js.map +1 -1
- package/build/commonjs/BottomSheet/BottomSheetProps.js.map +1 -1
- package/build/commonjs/BottomSheet/BottomSheetWeb.js +81 -12
- package/build/commonjs/BottomSheet/BottomSheetWeb.js.map +1 -1
- package/build/module/BottomSheet/BottomSheetNative.js +65 -16
- package/build/module/BottomSheet/BottomSheetNative.js.map +1 -1
- package/build/module/BottomSheet/BottomSheetProps.js.map +1 -1
- package/build/module/BottomSheet/BottomSheetWeb.js +79 -14
- package/build/module/BottomSheet/BottomSheetWeb.js.map +1 -1
- package/build/typescript/BottomSheet/BottomSheetProps.d.ts +13 -10
- package/package.json +2 -2
- package/src/BottomSheet/BottomSheetNative.tsx +80 -20
- package/src/BottomSheet/BottomSheetProps.ts +16 -12
- package/src/BottomSheet/BottomSheetWeb.tsx +95 -14
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":[],"sources":["BottomSheetProps.ts"],"sourcesContent":["import React from 'react';\nimport type { ComponentProps } from '@fountain-ui/core';\n\nexport default interface BottomSheetProps extends ComponentProps<{\n /**\n * Opacity for BackdropComponent\n * @default 0.5\n */\n backdropOpacity?: number;\n\n /**\n * BottomSheet children, usually the included sub-components.\n */\n children?: React.ReactNode;\n\n /**\n * Enable dynamic sizing for content size.\n * @default true\n */\n enableDynamicSizing?: boolean;\n\n /**\n * Area to be fixed on the top of the bottom sheet.\n */\n header?: React.ReactNode;\n\n /**\n * Top element for displaying additional information on the bottom sheet.\n */\n topElement?: React.ReactNode;\n\n /**\n * Snap index. You could also provide -1 to bottom sheet in closed state.\n */\n index: number;\n\n /**\n * Maximum height(normalized value) of dialog\n * ex. 30% => 0.3 / 90% => 0.9\n * @default 0.9\n */\n maxHeightNormalizedRatio?: number;\n\n /**\n * Callback fired when the index is changed.\n * Important! Use memoized value.\n */\n onChange?: (newIndex: number) => void;\n\n /**\n * Points for the bottom sheet to snap to, points should be sorted from bottom to top.\n * Important! Use memoized value.\n * Only number type or string type(~% format) can be used.\n * @default []\n */\n snapPoints?: Array<number | string>;\n
|
|
1
|
+
{"version":3,"names":[],"sources":["BottomSheetProps.ts"],"sourcesContent":["import React from 'react';\nimport type { ComponentProps } from '@fountain-ui/core';\n\nexport default interface BottomSheetProps extends ComponentProps<{\n /**\n * Opacity for BackdropComponent\n * @default 0.5\n */\n backdropOpacity?: number;\n\n /**\n * Border radius for bottom sheet\n */\n borderRadius?: number;\n\n /**\n * BottomSheet children, usually the included sub-components.\n */\n children?: React.ReactNode;\n\n /**\n * Enable dynamic sizing for content size.\n * @default true\n */\n enableDynamicSizing?: boolean;\n\n /**\n * Area to be fixed on the top of the bottom sheet.\n */\n header?: React.ReactNode;\n\n /**\n * If set to true, a border will be applied to the header area when the bottom sheet content is scrollable.\n * This visually separates the header from the scrollable content.\n */\n enableScrollableHeaderBorder?: boolean;\n\n /**\n * Area to be fixed on the bottom of the bottom sheet.\n */\n stickyBottomElement?: React.ReactNode;\n\n /**\n * Top element for displaying additional information on the bottom sheet.\n */\n topElement?: React.ReactNode;\n\n /**\n * Snap index. You could also provide -1 to bottom sheet in closed state.\n */\n index: number;\n\n /**\n * Maximum height(normalized value) of dialog\n * ex. 30% => 0.3 / 90% => 0.9\n * @default 0.9\n */\n maxHeightNormalizedRatio?: number;\n\n /**\n * Callback fired when the index is changed.\n * Important! Use memoized value.\n */\n onChange?: (newIndex: number) => void;\n\n /**\n * Points for the bottom sheet to snap to, points should be sorted from bottom to top.\n * Important! Use memoized value.\n * Only number type or string type(~% format) can be used.\n * @default []\n */\n snapPoints?: Array<number | string>;\n}> {}\n"],"mappings":""}
|
|
@@ -1,11 +1,21 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { ScrollView } from 'react-native';
|
|
3
|
-
import { Column, Modal, Paper, StyleSheet,
|
|
1
|
+
import React, { useLayoutEffect, useMemo, useState } from 'react';
|
|
2
|
+
import { ScrollView, useWindowDimensions, View } from 'react-native';
|
|
3
|
+
import { Column, css, Modal, Paper, StyleSheet, useElevationStyle, useTheme } from '@fountain-ui/core';
|
|
4
4
|
import AnimatedY from '../AnimatedY';
|
|
5
5
|
import useDynamicSnapPoints from './useDynamicSnapPoints';
|
|
6
6
|
|
|
7
|
+
const createHeightLayoutHandler = setHeight => {
|
|
8
|
+
return event => {
|
|
9
|
+
const {
|
|
10
|
+
height
|
|
11
|
+
} = event.nativeEvent.layout;
|
|
12
|
+
setHeight(height);
|
|
13
|
+
};
|
|
14
|
+
};
|
|
15
|
+
|
|
7
16
|
const useStyles = function () {
|
|
8
17
|
const theme = useTheme();
|
|
18
|
+
const stickyBottomElementShadow = useElevationStyle(8);
|
|
9
19
|
return {
|
|
10
20
|
root: {
|
|
11
21
|
justifyContent: 'flex-end',
|
|
@@ -28,25 +38,51 @@ const useStyles = function () {
|
|
|
28
38
|
position: 'absolute',
|
|
29
39
|
bottom: 0,
|
|
30
40
|
width: '100%'
|
|
31
|
-
}
|
|
41
|
+
},
|
|
42
|
+
headerContainer: {
|
|
43
|
+
backgroundColor: theme.palette.paper.default
|
|
44
|
+
},
|
|
45
|
+
headerBorder: {
|
|
46
|
+
borderBottomWidth: 0.5,
|
|
47
|
+
borderBottomColor: theme.palette.divider
|
|
48
|
+
},
|
|
49
|
+
stickyBottomElement: {
|
|
50
|
+
position: 'absolute',
|
|
51
|
+
bottom: 0,
|
|
52
|
+
left: 0,
|
|
53
|
+
right: 0,
|
|
54
|
+
backgroundColor: theme.palette.paper.default
|
|
55
|
+
},
|
|
56
|
+
stickyBottomElementShadow
|
|
32
57
|
};
|
|
33
58
|
};
|
|
34
59
|
|
|
35
60
|
export default function BottomSheet(props) {
|
|
36
61
|
const {
|
|
37
62
|
backdropOpacity,
|
|
63
|
+
borderRadius,
|
|
38
64
|
children,
|
|
39
65
|
enableDynamicSizing = true,
|
|
40
66
|
header,
|
|
67
|
+
stickyBottomElement,
|
|
41
68
|
topElement,
|
|
42
69
|
index,
|
|
43
70
|
maxHeightNormalizedRatio = 0.9,
|
|
44
71
|
onChange,
|
|
45
72
|
snapPoints = [],
|
|
46
|
-
|
|
47
|
-
disableDefaultShadow = false
|
|
73
|
+
enableScrollableHeaderBorder = false
|
|
48
74
|
} = props;
|
|
49
75
|
const styles = useStyles();
|
|
76
|
+
const {
|
|
77
|
+
height: windowHeight
|
|
78
|
+
} = useWindowDimensions();
|
|
79
|
+
const [topElementHeight, setTopElementHeight] = useState(0);
|
|
80
|
+
const [stickyBottomElementHeight, setStickyBottomElementHeight] = useState(0);
|
|
81
|
+
const [contentHeight, setContentHeight] = useState(0);
|
|
82
|
+
const [isScrollable, setIsScrollable] = useState(false);
|
|
83
|
+
const maxDynamicContentSize = Math.round(windowHeight * maxHeightNormalizedRatio) - topElementHeight;
|
|
84
|
+
const handleTopElementLayout = createHeightLayoutHandler(setTopElementHeight);
|
|
85
|
+
const handleStickyBottomElementLayout = createHeightLayoutHandler(setStickyBottomElementHeight);
|
|
50
86
|
|
|
51
87
|
const handleClose = () => {
|
|
52
88
|
if (onChange) {
|
|
@@ -64,9 +100,27 @@ export default function BottomSheet(props) {
|
|
|
64
100
|
snapPoints
|
|
65
101
|
});
|
|
66
102
|
const translateY = highestSnapPoint - (convertedSnapPoints[index] ?? 0);
|
|
67
|
-
const
|
|
68
|
-
|
|
103
|
+
const adjustedContentHeight = useMemo(() => {
|
|
104
|
+
const adjustedHighestSnapPoint = highestSnapPoint + stickyBottomElementHeight;
|
|
105
|
+
return Math.min(maxDynamicContentSize, adjustedHighestSnapPoint);
|
|
106
|
+
}, [highestSnapPoint, stickyBottomElementHeight]);
|
|
107
|
+
const contentStyles = [styles.paper, borderRadius ? {
|
|
108
|
+
borderTopLeftRadius: borderRadius,
|
|
109
|
+
borderTopRightRadius: borderRadius
|
|
110
|
+
} : {}, {
|
|
111
|
+
height: adjustedContentHeight,
|
|
112
|
+
maxHeight: maxDynamicContentSize
|
|
69
113
|
}];
|
|
114
|
+
const headerStyles = [styles.headerContainer, isScrollable && enableScrollableHeaderBorder ? styles.headerBorder : {}];
|
|
115
|
+
const stickyBottomElementStyles = [styles.stickyBottomElement, isScrollable ? styles.stickyBottomElementShadow : {}];
|
|
116
|
+
useLayoutEffect(() => {
|
|
117
|
+
if (contentHeight === 0 || highestSnapPoint === 0) {
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const adjustedHighestSnapPoint = highestSnapPoint + stickyBottomElementHeight;
|
|
122
|
+
setIsScrollable(adjustedHighestSnapPoint > maxDynamicContentSize);
|
|
123
|
+
}, [contentHeight, highestSnapPoint, stickyBottomElementHeight, maxDynamicContentSize]);
|
|
70
124
|
return /*#__PURE__*/React.createElement(Modal, {
|
|
71
125
|
backdropOpacity: backdropOpacity,
|
|
72
126
|
onClose: handleClose,
|
|
@@ -76,15 +130,26 @@ export default function BottomSheet(props) {
|
|
|
76
130
|
style: styles.animated,
|
|
77
131
|
translateY: translateY
|
|
78
132
|
}, topElement ? /*#__PURE__*/React.createElement(Column, null, /*#__PURE__*/React.createElement(Column, {
|
|
133
|
+
onLayout: handleTopElementLayout,
|
|
79
134
|
style: styles.topElementLocation
|
|
80
135
|
}, topElement)) : null, /*#__PURE__*/React.createElement(Paper, {
|
|
81
|
-
elevation:
|
|
82
|
-
style:
|
|
83
|
-
colorValue: disableDefaultBackgroundColor ? '#ffffff00' : undefined
|
|
136
|
+
elevation: 12,
|
|
137
|
+
style: contentStyles
|
|
84
138
|
}, /*#__PURE__*/React.createElement(ScrollView, {
|
|
85
|
-
onContentSizeChange:
|
|
86
|
-
|
|
87
|
-
|
|
139
|
+
onContentSizeChange: (contentWidth, contentHeight) => {
|
|
140
|
+
setContentHeight(contentHeight);
|
|
141
|
+
handleContentSizeChange(contentWidth, contentHeight);
|
|
142
|
+
},
|
|
143
|
+
stickyHeaderIndices: header ? [0] : undefined,
|
|
144
|
+
style: {
|
|
145
|
+
paddingBottom: stickyBottomElementHeight
|
|
146
|
+
}
|
|
147
|
+
}, header ? /*#__PURE__*/React.createElement(View, {
|
|
148
|
+
style: headerStyles
|
|
149
|
+
}, header) : null, children), stickyBottomElement ? /*#__PURE__*/React.createElement(Column, {
|
|
150
|
+
style: stickyBottomElementStyles,
|
|
151
|
+
onLayout: handleStickyBottomElementLayout
|
|
152
|
+
}, stickyBottomElement) : null)));
|
|
88
153
|
}
|
|
89
154
|
;
|
|
90
155
|
//# sourceMappingURL=BottomSheetWeb.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["React","ScrollView","Column","Modal","Paper","StyleSheet","css","useTheme","AnimatedY","useDynamicSnapPoints","useStyles","theme","root","justifyContent","zIndex","dialog","animated","alignSelf","maxWidth","width","paper","borderTopLeftRadius","shape","roundnessExtra","borderTopRightRadius","borderBottomLeftRadius","borderBottomRightRadius","flexGrow","overflow","topElementLocation","position","bottom","BottomSheet","props","backdropOpacity","children","enableDynamicSizing","header","topElement","index","maxHeightNormalizedRatio","onChange","snapPoints","disableDefaultBackgroundColor","disableDefaultShadow","styles","handleClose","convertedSnapPoints","handleContentSizeChange","highestSnapPoint","translateY","paperStyles","height","absoluteFill","undefined"],"sources":["BottomSheetWeb.tsx"],"sourcesContent":["import React from 'react';\nimport { ScrollView } from 'react-native';\nimport { Column, Modal, Paper, StyleSheet, css, useTheme } from '@fountain-ui/core';\nimport { NamedStylesStringUnion, UseStyles } from '@fountain-ui/styles';\nimport AnimatedY from '../AnimatedY';\nimport type BottomSheetProps from './BottomSheetProps';\nimport useDynamicSnapPoints from './useDynamicSnapPoints';\n\ntype BottomSheetStyles = NamedStylesStringUnion<'root' | 'animated' | 'paper' | 'topElementLocation'>;\n\nconst useStyles: UseStyles<BottomSheetStyles> = function (): BottomSheetStyles {\n const theme = useTheme();\n\n return {\n root: {\n justifyContent: 'flex-end',\n zIndex: theme.zIndex.dialog,\n },\n animated: {\n alignSelf: 'center',\n maxWidth: 720,\n width: '100%',\n },\n paper: {\n borderTopLeftRadius: theme.shape.roundnessExtra,\n borderTopRightRadius: theme.shape.roundnessExtra,\n borderBottomLeftRadius: 0,\n borderBottomRightRadius: 0,\n flexGrow: 1,\n overflow: 'hidden',\n },\n topElementLocation: {\n position: 'absolute',\n bottom: 0,\n width: '100%',\n },\n };\n};\n\nexport default function BottomSheet(props: BottomSheetProps) {\n const {\n backdropOpacity,\n children,\n enableDynamicSizing = true,\n header,\n topElement,\n index,\n maxHeightNormalizedRatio = 0.9,\n onChange,\n snapPoints = [],\n disableDefaultBackgroundColor = false,\n disableDefaultShadow = false,\n } = props;\n\n const styles = useStyles();\n\n const handleClose = () => {\n if (onChange) {\n onChange(-1);\n }\n };\n\n const {\n convertedSnapPoints,\n handleContentSizeChange,\n highestSnapPoint,\n } = useDynamicSnapPoints({\n enableDynamicSizing,\n maxHeightNormalizedRatio,\n snapPoints,\n });\n\n const translateY = highestSnapPoint - (convertedSnapPoints[index] ?? 0);\n\n const paperStyles = [\n styles.paper,\n { height: highestSnapPoint },\n ];\n\n return (\n <Modal\n backdropOpacity={backdropOpacity}\n onClose={handleClose}\n visible={index >= 0}\n style={css([StyleSheet.absoluteFill, styles.root])}\n >\n <AnimatedY\n style={styles.animated}\n translateY={translateY}\n >\n {topElement ? (\n <Column>\n <Column style={styles.topElementLocation}>\n {topElement}\n </Column>\n </Column>\n ) : null}\n\n <Paper\n elevation={disableDefaultShadow ? 0 : 12}\n style={paperStyles}\n colorValue={disableDefaultBackgroundColor ? '#ffffff00' : undefined}\n >\n <ScrollView\n onContentSizeChange={handleContentSizeChange}\n stickyHeaderIndices={header ? [0] : undefined}\n >\n {header}\n\n {children}\n </ScrollView>\n </Paper>\n </AnimatedY>\n </Modal>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"names":["React","useLayoutEffect","useMemo","useState","ScrollView","useWindowDimensions","View","Column","css","Modal","Paper","StyleSheet","useElevationStyle","useTheme","AnimatedY","useDynamicSnapPoints","createHeightLayoutHandler","setHeight","event","height","nativeEvent","layout","useStyles","theme","stickyBottomElementShadow","root","justifyContent","zIndex","dialog","animated","alignSelf","maxWidth","width","paper","borderTopLeftRadius","shape","roundnessExtra","borderTopRightRadius","borderBottomLeftRadius","borderBottomRightRadius","flexGrow","overflow","topElementLocation","position","bottom","headerContainer","backgroundColor","palette","default","headerBorder","borderBottomWidth","borderBottomColor","divider","stickyBottomElement","left","right","BottomSheet","props","backdropOpacity","borderRadius","children","enableDynamicSizing","header","topElement","index","maxHeightNormalizedRatio","onChange","snapPoints","enableScrollableHeaderBorder","styles","windowHeight","topElementHeight","setTopElementHeight","stickyBottomElementHeight","setStickyBottomElementHeight","contentHeight","setContentHeight","isScrollable","setIsScrollable","maxDynamicContentSize","Math","round","handleTopElementLayout","handleStickyBottomElementLayout","handleClose","convertedSnapPoints","handleContentSizeChange","highestSnapPoint","translateY","adjustedContentHeight","adjustedHighestSnapPoint","min","contentStyles","maxHeight","headerStyles","stickyBottomElementStyles","absoluteFill","contentWidth","undefined","paddingBottom"],"sources":["BottomSheetWeb.tsx"],"sourcesContent":["import React, { useLayoutEffect, useMemo, useState } from 'react';\nimport { LayoutChangeEvent, ScrollView, useWindowDimensions, View } from 'react-native';\nimport { Column, css, Modal, Paper, StyleSheet, useElevationStyle, useTheme } from '@fountain-ui/core';\nimport { NamedStylesStringUnion, UseStyles } from '@fountain-ui/styles';\nimport AnimatedY from '../AnimatedY';\nimport type BottomSheetProps from './BottomSheetProps';\nimport useDynamicSnapPoints from './useDynamicSnapPoints';\n\nconst createHeightLayoutHandler = (setHeight: React.Dispatch<React.SetStateAction<number>>) => {\n return (event: LayoutChangeEvent) => {\n const { height } = event.nativeEvent.layout;\n setHeight(height);\n };\n}\n\ntype BottomSheetStyles = NamedStylesStringUnion<'root' | 'animated' | 'paper' | 'topElementLocation' | 'headerContainer' | 'headerBorder' | 'stickyBottomElement' | 'stickyBottomElementShadow'>;\n\nconst useStyles: UseStyles<BottomSheetStyles> = function (): BottomSheetStyles {\n const theme = useTheme();\n const stickyBottomElementShadow = useElevationStyle(8);\n\n return {\n root: {\n justifyContent: 'flex-end',\n zIndex: theme.zIndex.dialog,\n },\n animated: {\n alignSelf: 'center',\n maxWidth: 720,\n width: '100%',\n },\n paper: {\n borderTopLeftRadius: theme.shape.roundnessExtra,\n borderTopRightRadius: theme.shape.roundnessExtra,\n borderBottomLeftRadius: 0,\n borderBottomRightRadius: 0,\n flexGrow: 1,\n overflow: 'hidden',\n },\n topElementLocation: {\n position: 'absolute',\n bottom: 0,\n width: '100%',\n },\n headerContainer: {\n backgroundColor: theme.palette.paper.default,\n },\n headerBorder: {\n borderBottomWidth: 0.5,\n borderBottomColor: theme.palette.divider,\n },\n stickyBottomElement: {\n position: 'absolute',\n bottom: 0,\n left: 0,\n right: 0,\n backgroundColor: theme.palette.paper.default,\n },\n stickyBottomElementShadow,\n };\n};\n\nexport default function BottomSheet(props: BottomSheetProps) {\n const {\n backdropOpacity,\n borderRadius,\n children,\n enableDynamicSizing = true,\n header,\n stickyBottomElement,\n topElement,\n index,\n maxHeightNormalizedRatio = 0.9,\n onChange,\n snapPoints = [],\n enableScrollableHeaderBorder = false,\n } = props;\n\n const styles = useStyles();\n\n const { height: windowHeight } = useWindowDimensions();\n\n const [topElementHeight, setTopElementHeight] = useState(0);\n const [stickyBottomElementHeight, setStickyBottomElementHeight] = useState(0);\n const [contentHeight, setContentHeight] = useState(0);\n const [isScrollable, setIsScrollable] = useState(false);\n\n const maxDynamicContentSize = Math.round(windowHeight * maxHeightNormalizedRatio) - topElementHeight;\n\n const handleTopElementLayout = createHeightLayoutHandler(setTopElementHeight);\n const handleStickyBottomElementLayout = createHeightLayoutHandler(setStickyBottomElementHeight);\n\n const handleClose = () => {\n if (onChange) {\n onChange(-1);\n }\n };\n\n const {\n convertedSnapPoints,\n handleContentSizeChange,\n highestSnapPoint,\n } = useDynamicSnapPoints({\n enableDynamicSizing,\n maxHeightNormalizedRatio,\n snapPoints,\n });\n\n const translateY = highestSnapPoint - (convertedSnapPoints[index] ?? 0);\n\n const adjustedContentHeight = useMemo(() => {\n const adjustedHighestSnapPoint = highestSnapPoint + stickyBottomElementHeight;\n\n return Math.min(maxDynamicContentSize, adjustedHighestSnapPoint);\n }, [highestSnapPoint, stickyBottomElementHeight]);\n\n const contentStyles = [\n styles.paper,\n borderRadius ? { borderTopLeftRadius: borderRadius, borderTopRightRadius: borderRadius } : {},\n { height: adjustedContentHeight, maxHeight: maxDynamicContentSize },\n ];\n\n const headerStyles = [\n styles.headerContainer,\n isScrollable && enableScrollableHeaderBorder ? styles.headerBorder : {},\n ];\n\n const stickyBottomElementStyles = [\n styles.stickyBottomElement,\n isScrollable ? styles.stickyBottomElementShadow : {},\n ];\n\n useLayoutEffect(() => {\n if (contentHeight === 0 || highestSnapPoint === 0) {\n return;\n }\n\n const adjustedHighestSnapPoint = highestSnapPoint + stickyBottomElementHeight;\n setIsScrollable(adjustedHighestSnapPoint > maxDynamicContentSize);\n }, [contentHeight, highestSnapPoint, stickyBottomElementHeight, maxDynamicContentSize]);\n\n return (\n <Modal\n backdropOpacity={backdropOpacity}\n onClose={handleClose}\n visible={index >= 0}\n style={css([StyleSheet.absoluteFill, styles.root])}\n >\n <AnimatedY\n style={styles.animated}\n translateY={translateY}\n >\n {topElement ? (\n <Column>\n <Column\n onLayout={handleTopElementLayout}\n style={styles.topElementLocation}\n >\n {topElement}\n </Column>\n </Column>\n ) : null}\n\n <Paper\n elevation={12}\n style={contentStyles}\n >\n <ScrollView\n onContentSizeChange={(contentWidth: number, contentHeight: number) => {\n setContentHeight(contentHeight);\n handleContentSizeChange(contentWidth, contentHeight);\n }}\n stickyHeaderIndices={header ? [0] : undefined}\n style={{ paddingBottom: stickyBottomElementHeight }}\n >\n {header ? (\n <View style={headerStyles}>\n {header}\n </View>\n ): null}\n\n {children}\n </ScrollView>\n\n {stickyBottomElement ? (\n <Column\n style={stickyBottomElementStyles}\n onLayout={handleStickyBottomElementLayout}\n >\n {stickyBottomElement}\n </Column>\n ): null}\n </Paper>\n </AnimatedY>\n </Modal>\n );\n};\n"],"mappings":"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"}
|
|
@@ -6,6 +6,10 @@ export default interface BottomSheetProps extends ComponentProps<{
|
|
|
6
6
|
* @default 0.5
|
|
7
7
|
*/
|
|
8
8
|
backdropOpacity?: number;
|
|
9
|
+
/**
|
|
10
|
+
* Border radius for bottom sheet
|
|
11
|
+
*/
|
|
12
|
+
borderRadius?: number;
|
|
9
13
|
/**
|
|
10
14
|
* BottomSheet children, usually the included sub-components.
|
|
11
15
|
*/
|
|
@@ -19,6 +23,15 @@ export default interface BottomSheetProps extends ComponentProps<{
|
|
|
19
23
|
* Area to be fixed on the top of the bottom sheet.
|
|
20
24
|
*/
|
|
21
25
|
header?: React.ReactNode;
|
|
26
|
+
/**
|
|
27
|
+
* If set to true, a border will be applied to the header area when the bottom sheet content is scrollable.
|
|
28
|
+
* This visually separates the header from the scrollable content.
|
|
29
|
+
*/
|
|
30
|
+
enableScrollableHeaderBorder?: boolean;
|
|
31
|
+
/**
|
|
32
|
+
* Area to be fixed on the bottom of the bottom sheet.
|
|
33
|
+
*/
|
|
34
|
+
stickyBottomElement?: React.ReactNode;
|
|
22
35
|
/**
|
|
23
36
|
* Top element for displaying additional information on the bottom sheet.
|
|
24
37
|
*/
|
|
@@ -45,15 +58,5 @@ export default interface BottomSheetProps extends ComponentProps<{
|
|
|
45
58
|
* @default []
|
|
46
59
|
*/
|
|
47
60
|
snapPoints?: Array<number | string>;
|
|
48
|
-
/**
|
|
49
|
-
* Disable default backgroundColor.
|
|
50
|
-
* @default false
|
|
51
|
-
*/
|
|
52
|
-
disableDefaultBackgroundColor?: boolean;
|
|
53
|
-
/**
|
|
54
|
-
* Disable default shadow.
|
|
55
|
-
* @default false
|
|
56
|
-
*/
|
|
57
|
-
disableDefaultShadow?: boolean;
|
|
58
61
|
}> {
|
|
59
62
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fountain-ui/lab",
|
|
3
|
-
"version": "2.0.0-beta.
|
|
3
|
+
"version": "2.0.0-beta.83",
|
|
4
4
|
"private": false,
|
|
5
5
|
"author": "Fountain-UI Team",
|
|
6
6
|
"description": "Incubator for Fountain-UI React components.",
|
|
@@ -70,5 +70,5 @@
|
|
|
70
70
|
"publishConfig": {
|
|
71
71
|
"access": "public"
|
|
72
72
|
},
|
|
73
|
-
"gitHead": "
|
|
73
|
+
"gitHead": "dfa417385224924740cfd4438d32a3fbff88cb9c"
|
|
74
74
|
}
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { Animated, Platform, useWindowDimensions, View } from 'react-native';
|
|
3
|
-
import { Column, useAnimatedValue, ExtendedStyle, isNotAndroid12 } from '@fountain-ui/core';
|
|
4
|
-
import { useTheme } from '@fountain-ui/styles';
|
|
2
|
+
import { Animated, LayoutChangeEvent, Platform, useWindowDimensions, View, ViewStyle } from 'react-native';
|
|
5
3
|
import {
|
|
6
4
|
BottomSheetBackdrop,
|
|
7
5
|
BottomSheetBackdropProps,
|
|
@@ -9,31 +7,48 @@ import {
|
|
|
9
7
|
BottomSheetModalProvider,
|
|
10
8
|
BottomSheetScrollView,
|
|
11
9
|
} from '@gorhom/bottom-sheet';
|
|
10
|
+
import { Column, ExtendedStyle, isNotAndroid12, useAnimatedValue, useElevationStyle } from '@fountain-ui/core';
|
|
11
|
+
import { useTheme } from '@fountain-ui/styles';
|
|
12
12
|
import type BottomSheetProps from './BottomSheetProps';
|
|
13
13
|
import TransparentBackdrop from './TransparentBackdrop';
|
|
14
14
|
|
|
15
15
|
const NoHandle = () => null;
|
|
16
16
|
|
|
17
|
+
const createHeightLayoutHandler = (setHeight: React.Dispatch<React.SetStateAction<number>>) => {
|
|
18
|
+
return (event: LayoutChangeEvent) => {
|
|
19
|
+
const { height } = event.nativeEvent.layout;
|
|
20
|
+
setHeight(height);
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
17
24
|
export default function BottomSheet(props: BottomSheetProps) {
|
|
18
25
|
const {
|
|
19
26
|
backdropOpacity = 0.5,
|
|
27
|
+
borderRadius: borderRadiusProp,
|
|
20
28
|
children,
|
|
21
29
|
enableDynamicSizing = true,
|
|
22
30
|
header,
|
|
31
|
+
stickyBottomElement,
|
|
23
32
|
topElement,
|
|
24
33
|
index,
|
|
25
34
|
maxHeightNormalizedRatio = 0.9,
|
|
26
35
|
onChange,
|
|
27
36
|
snapPoints = [],
|
|
28
|
-
|
|
29
|
-
disableDefaultShadow = false,
|
|
37
|
+
enableScrollableHeaderBorder = false,
|
|
30
38
|
} = props;
|
|
31
39
|
|
|
32
40
|
const indexRef = React.useRef<number>(-1);
|
|
33
41
|
const bottomSheetRef = React.useRef<BottomSheetModal | null>(null);
|
|
34
42
|
|
|
35
43
|
const { height: windowHeight } = useWindowDimensions();
|
|
36
|
-
const
|
|
44
|
+
const [isScrollable, setIsScrollable] = React.useState(false);
|
|
45
|
+
const [topElementHeight, setTopElementHeight] = React.useState(0);
|
|
46
|
+
const [stickyBottomElementHeight, setStickyBottomElementHeight] = React.useState(0);
|
|
47
|
+
|
|
48
|
+
const maxDynamicContentSize = Math.round(windowHeight * maxHeightNormalizedRatio) - topElementHeight;
|
|
49
|
+
|
|
50
|
+
const handleTopElementLayout = createHeightLayoutHandler(setTopElementHeight);
|
|
51
|
+
const handleStickyBottomElementLayout = createHeightLayoutHandler(setStickyBottomElementHeight);
|
|
37
52
|
|
|
38
53
|
const handleChange = React.useCallback((newIndex: number) => {
|
|
39
54
|
indexRef.current = newIndex;
|
|
@@ -64,25 +79,27 @@ export default function BottomSheet(props: BottomSheetProps) {
|
|
|
64
79
|
|
|
65
80
|
const theme = useTheme();
|
|
66
81
|
const shadow = theme.shadow[12];
|
|
82
|
+
const borderRadius = borderRadiusProp ?? theme.shape.roundnessExtra;
|
|
67
83
|
const modalStyle = {
|
|
68
84
|
backgroundColor: '#ffffff00',
|
|
69
|
-
borderTopLeftRadius:
|
|
70
|
-
borderTopRightRadius:
|
|
71
|
-
...
|
|
85
|
+
borderTopLeftRadius: borderRadius,
|
|
86
|
+
borderTopRightRadius: borderRadius,
|
|
87
|
+
...Platform.select<object>({
|
|
72
88
|
android: shadow?.elevation,
|
|
73
89
|
ios: shadow?.shadow,
|
|
74
90
|
web: shadow?.boxShadow,
|
|
75
|
-
})
|
|
91
|
+
}),
|
|
76
92
|
};
|
|
93
|
+
const backgroundColor = theme.palette.paper.default;
|
|
77
94
|
const backgroundStyle = {
|
|
78
|
-
backgroundColor
|
|
79
|
-
borderTopLeftRadius:
|
|
80
|
-
borderTopRightRadius:
|
|
95
|
+
backgroundColor,
|
|
96
|
+
borderTopLeftRadius: borderRadius,
|
|
97
|
+
borderTopRightRadius: borderRadius,
|
|
81
98
|
};
|
|
82
|
-
const contentWrapperStyle = {
|
|
99
|
+
const contentWrapperStyle: ViewStyle = {
|
|
83
100
|
flex: 1,
|
|
84
|
-
borderTopLeftRadius:
|
|
85
|
-
borderTopRightRadius:
|
|
101
|
+
borderTopLeftRadius: borderRadius,
|
|
102
|
+
borderTopRightRadius: borderRadius,
|
|
86
103
|
overflow: 'hidden',
|
|
87
104
|
};
|
|
88
105
|
|
|
@@ -115,6 +132,33 @@ export default function BottomSheet(props: BottomSheetProps) {
|
|
|
115
132
|
}).start();
|
|
116
133
|
};
|
|
117
134
|
|
|
135
|
+
const handleContentSizeChange = (_: number, contentHeight: number) => {
|
|
136
|
+
setIsScrollable(contentHeight > maxDynamicContentSize);
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
const headerStyle = {
|
|
140
|
+
backgroundColor,
|
|
141
|
+
...(isScrollable && enableScrollableHeaderBorder ? {
|
|
142
|
+
borderBottomWidth: 0.5,
|
|
143
|
+
borderBottomColor: theme.palette.divider,
|
|
144
|
+
}: {})
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const childrenStyle = {
|
|
148
|
+
backgroundColor,
|
|
149
|
+
paddingBottom: stickyBottomElementHeight,
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const stickyBottomElementShadow = useElevationStyle(8);
|
|
153
|
+
|
|
154
|
+
const stickyBottomElementStyle = {
|
|
155
|
+
backgroundColor: theme.palette.paper.default,
|
|
156
|
+
position: 'absolute',
|
|
157
|
+
width: '100%',
|
|
158
|
+
bottom: 0,
|
|
159
|
+
...(isScrollable ? stickyBottomElementShadow : {}),
|
|
160
|
+
}
|
|
161
|
+
|
|
118
162
|
return (
|
|
119
163
|
<BottomSheetModalProvider>
|
|
120
164
|
<BottomSheetModal
|
|
@@ -136,22 +180,38 @@ export default function BottomSheet(props: BottomSheetProps) {
|
|
|
136
180
|
>
|
|
137
181
|
{topElement ? (
|
|
138
182
|
<Animated.View style={topElementAnimationStyle}>
|
|
139
|
-
<Column
|
|
183
|
+
<Column
|
|
184
|
+
onLayout={handleTopElementLayout}
|
|
185
|
+
style={topElementLocationStyle}
|
|
186
|
+
>
|
|
140
187
|
{topElement}
|
|
141
188
|
</Column>
|
|
142
189
|
</Animated.View>
|
|
143
190
|
) : null}
|
|
144
191
|
|
|
145
|
-
{/* @ts-ignore */}
|
|
146
192
|
<View style={contentWrapperStyle}>
|
|
147
193
|
<BottomSheetScrollView
|
|
148
194
|
bounces={false}
|
|
149
195
|
stickyHeaderIndices={header ? [0] : undefined}
|
|
196
|
+
onContentSizeChange={handleContentSizeChange}
|
|
150
197
|
>
|
|
151
|
-
{
|
|
198
|
+
<View style={headerStyle}>
|
|
199
|
+
{header}
|
|
200
|
+
</View>
|
|
152
201
|
|
|
153
|
-
{
|
|
202
|
+
<View style={childrenStyle}>
|
|
203
|
+
{children}
|
|
204
|
+
</View>
|
|
154
205
|
</BottomSheetScrollView>
|
|
206
|
+
|
|
207
|
+
{stickyBottomElement ? (
|
|
208
|
+
<View
|
|
209
|
+
style={stickyBottomElementStyle}
|
|
210
|
+
onLayout={handleStickyBottomElementLayout}
|
|
211
|
+
>
|
|
212
|
+
{stickyBottomElement}
|
|
213
|
+
</View>
|
|
214
|
+
): null}
|
|
155
215
|
</View>
|
|
156
216
|
</BottomSheetModal>
|
|
157
217
|
</BottomSheetModalProvider>
|
|
@@ -8,6 +8,11 @@ export default interface BottomSheetProps extends ComponentProps<{
|
|
|
8
8
|
*/
|
|
9
9
|
backdropOpacity?: number;
|
|
10
10
|
|
|
11
|
+
/**
|
|
12
|
+
* Border radius for bottom sheet
|
|
13
|
+
*/
|
|
14
|
+
borderRadius?: number;
|
|
15
|
+
|
|
11
16
|
/**
|
|
12
17
|
* BottomSheet children, usually the included sub-components.
|
|
13
18
|
*/
|
|
@@ -24,6 +29,17 @@ export default interface BottomSheetProps extends ComponentProps<{
|
|
|
24
29
|
*/
|
|
25
30
|
header?: React.ReactNode;
|
|
26
31
|
|
|
32
|
+
/**
|
|
33
|
+
* If set to true, a border will be applied to the header area when the bottom sheet content is scrollable.
|
|
34
|
+
* This visually separates the header from the scrollable content.
|
|
35
|
+
*/
|
|
36
|
+
enableScrollableHeaderBorder?: boolean;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Area to be fixed on the bottom of the bottom sheet.
|
|
40
|
+
*/
|
|
41
|
+
stickyBottomElement?: React.ReactNode;
|
|
42
|
+
|
|
27
43
|
/**
|
|
28
44
|
* Top element for displaying additional information on the bottom sheet.
|
|
29
45
|
*/
|
|
@@ -54,16 +70,4 @@ export default interface BottomSheetProps extends ComponentProps<{
|
|
|
54
70
|
* @default []
|
|
55
71
|
*/
|
|
56
72
|
snapPoints?: Array<number | string>;
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* Disable default backgroundColor.
|
|
60
|
-
* @default false
|
|
61
|
-
*/
|
|
62
|
-
disableDefaultBackgroundColor?: boolean;
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Disable default shadow.
|
|
66
|
-
* @default false
|
|
67
|
-
*/
|
|
68
|
-
disableDefaultShadow?: boolean;
|
|
69
73
|
}> {}
|
|
@@ -1,15 +1,23 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { ScrollView } from 'react-native';
|
|
3
|
-
import { Column, Modal, Paper, StyleSheet,
|
|
1
|
+
import React, { useLayoutEffect, useMemo, useState } from 'react';
|
|
2
|
+
import { LayoutChangeEvent, ScrollView, useWindowDimensions, View } from 'react-native';
|
|
3
|
+
import { Column, css, Modal, Paper, StyleSheet, useElevationStyle, useTheme } from '@fountain-ui/core';
|
|
4
4
|
import { NamedStylesStringUnion, UseStyles } from '@fountain-ui/styles';
|
|
5
5
|
import AnimatedY from '../AnimatedY';
|
|
6
6
|
import type BottomSheetProps from './BottomSheetProps';
|
|
7
7
|
import useDynamicSnapPoints from './useDynamicSnapPoints';
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
const createHeightLayoutHandler = (setHeight: React.Dispatch<React.SetStateAction<number>>) => {
|
|
10
|
+
return (event: LayoutChangeEvent) => {
|
|
11
|
+
const { height } = event.nativeEvent.layout;
|
|
12
|
+
setHeight(height);
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
type BottomSheetStyles = NamedStylesStringUnion<'root' | 'animated' | 'paper' | 'topElementLocation' | 'headerContainer' | 'headerBorder' | 'stickyBottomElement' | 'stickyBottomElementShadow'>;
|
|
10
17
|
|
|
11
18
|
const useStyles: UseStyles<BottomSheetStyles> = function (): BottomSheetStyles {
|
|
12
19
|
const theme = useTheme();
|
|
20
|
+
const stickyBottomElementShadow = useElevationStyle(8);
|
|
13
21
|
|
|
14
22
|
return {
|
|
15
23
|
root: {
|
|
@@ -34,26 +42,54 @@ const useStyles: UseStyles<BottomSheetStyles> = function (): BottomSheetStyles {
|
|
|
34
42
|
bottom: 0,
|
|
35
43
|
width: '100%',
|
|
36
44
|
},
|
|
45
|
+
headerContainer: {
|
|
46
|
+
backgroundColor: theme.palette.paper.default,
|
|
47
|
+
},
|
|
48
|
+
headerBorder: {
|
|
49
|
+
borderBottomWidth: 0.5,
|
|
50
|
+
borderBottomColor: theme.palette.divider,
|
|
51
|
+
},
|
|
52
|
+
stickyBottomElement: {
|
|
53
|
+
position: 'absolute',
|
|
54
|
+
bottom: 0,
|
|
55
|
+
left: 0,
|
|
56
|
+
right: 0,
|
|
57
|
+
backgroundColor: theme.palette.paper.default,
|
|
58
|
+
},
|
|
59
|
+
stickyBottomElementShadow,
|
|
37
60
|
};
|
|
38
61
|
};
|
|
39
62
|
|
|
40
63
|
export default function BottomSheet(props: BottomSheetProps) {
|
|
41
64
|
const {
|
|
42
65
|
backdropOpacity,
|
|
66
|
+
borderRadius,
|
|
43
67
|
children,
|
|
44
68
|
enableDynamicSizing = true,
|
|
45
69
|
header,
|
|
70
|
+
stickyBottomElement,
|
|
46
71
|
topElement,
|
|
47
72
|
index,
|
|
48
73
|
maxHeightNormalizedRatio = 0.9,
|
|
49
74
|
onChange,
|
|
50
75
|
snapPoints = [],
|
|
51
|
-
|
|
52
|
-
disableDefaultShadow = false,
|
|
76
|
+
enableScrollableHeaderBorder = false,
|
|
53
77
|
} = props;
|
|
54
78
|
|
|
55
79
|
const styles = useStyles();
|
|
56
80
|
|
|
81
|
+
const { height: windowHeight } = useWindowDimensions();
|
|
82
|
+
|
|
83
|
+
const [topElementHeight, setTopElementHeight] = useState(0);
|
|
84
|
+
const [stickyBottomElementHeight, setStickyBottomElementHeight] = useState(0);
|
|
85
|
+
const [contentHeight, setContentHeight] = useState(0);
|
|
86
|
+
const [isScrollable, setIsScrollable] = useState(false);
|
|
87
|
+
|
|
88
|
+
const maxDynamicContentSize = Math.round(windowHeight * maxHeightNormalizedRatio) - topElementHeight;
|
|
89
|
+
|
|
90
|
+
const handleTopElementLayout = createHeightLayoutHandler(setTopElementHeight);
|
|
91
|
+
const handleStickyBottomElementLayout = createHeightLayoutHandler(setStickyBottomElementHeight);
|
|
92
|
+
|
|
57
93
|
const handleClose = () => {
|
|
58
94
|
if (onChange) {
|
|
59
95
|
onChange(-1);
|
|
@@ -72,11 +108,37 @@ export default function BottomSheet(props: BottomSheetProps) {
|
|
|
72
108
|
|
|
73
109
|
const translateY = highestSnapPoint - (convertedSnapPoints[index] ?? 0);
|
|
74
110
|
|
|
75
|
-
const
|
|
111
|
+
const adjustedContentHeight = useMemo(() => {
|
|
112
|
+
const adjustedHighestSnapPoint = highestSnapPoint + stickyBottomElementHeight;
|
|
113
|
+
|
|
114
|
+
return Math.min(maxDynamicContentSize, adjustedHighestSnapPoint);
|
|
115
|
+
}, [highestSnapPoint, stickyBottomElementHeight]);
|
|
116
|
+
|
|
117
|
+
const contentStyles = [
|
|
76
118
|
styles.paper,
|
|
77
|
-
{
|
|
119
|
+
borderRadius ? { borderTopLeftRadius: borderRadius, borderTopRightRadius: borderRadius } : {},
|
|
120
|
+
{ height: adjustedContentHeight, maxHeight: maxDynamicContentSize },
|
|
121
|
+
];
|
|
122
|
+
|
|
123
|
+
const headerStyles = [
|
|
124
|
+
styles.headerContainer,
|
|
125
|
+
isScrollable && enableScrollableHeaderBorder ? styles.headerBorder : {},
|
|
126
|
+
];
|
|
127
|
+
|
|
128
|
+
const stickyBottomElementStyles = [
|
|
129
|
+
styles.stickyBottomElement,
|
|
130
|
+
isScrollable ? styles.stickyBottomElementShadow : {},
|
|
78
131
|
];
|
|
79
132
|
|
|
133
|
+
useLayoutEffect(() => {
|
|
134
|
+
if (contentHeight === 0 || highestSnapPoint === 0) {
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const adjustedHighestSnapPoint = highestSnapPoint + stickyBottomElementHeight;
|
|
139
|
+
setIsScrollable(adjustedHighestSnapPoint > maxDynamicContentSize);
|
|
140
|
+
}, [contentHeight, highestSnapPoint, stickyBottomElementHeight, maxDynamicContentSize]);
|
|
141
|
+
|
|
80
142
|
return (
|
|
81
143
|
<Modal
|
|
82
144
|
backdropOpacity={backdropOpacity}
|
|
@@ -90,25 +152,44 @@ export default function BottomSheet(props: BottomSheetProps) {
|
|
|
90
152
|
>
|
|
91
153
|
{topElement ? (
|
|
92
154
|
<Column>
|
|
93
|
-
<Column
|
|
155
|
+
<Column
|
|
156
|
+
onLayout={handleTopElementLayout}
|
|
157
|
+
style={styles.topElementLocation}
|
|
158
|
+
>
|
|
94
159
|
{topElement}
|
|
95
160
|
</Column>
|
|
96
161
|
</Column>
|
|
97
162
|
) : null}
|
|
98
163
|
|
|
99
164
|
<Paper
|
|
100
|
-
elevation={
|
|
101
|
-
style={
|
|
102
|
-
colorValue={disableDefaultBackgroundColor ? '#ffffff00' : undefined}
|
|
165
|
+
elevation={12}
|
|
166
|
+
style={contentStyles}
|
|
103
167
|
>
|
|
104
168
|
<ScrollView
|
|
105
|
-
onContentSizeChange={
|
|
169
|
+
onContentSizeChange={(contentWidth: number, contentHeight: number) => {
|
|
170
|
+
setContentHeight(contentHeight);
|
|
171
|
+
handleContentSizeChange(contentWidth, contentHeight);
|
|
172
|
+
}}
|
|
106
173
|
stickyHeaderIndices={header ? [0] : undefined}
|
|
174
|
+
style={{ paddingBottom: stickyBottomElementHeight }}
|
|
107
175
|
>
|
|
108
|
-
{header
|
|
176
|
+
{header ? (
|
|
177
|
+
<View style={headerStyles}>
|
|
178
|
+
{header}
|
|
179
|
+
</View>
|
|
180
|
+
): null}
|
|
109
181
|
|
|
110
182
|
{children}
|
|
111
183
|
</ScrollView>
|
|
184
|
+
|
|
185
|
+
{stickyBottomElement ? (
|
|
186
|
+
<Column
|
|
187
|
+
style={stickyBottomElementStyles}
|
|
188
|
+
onLayout={handleStickyBottomElementLayout}
|
|
189
|
+
>
|
|
190
|
+
{stickyBottomElement}
|
|
191
|
+
</Column>
|
|
192
|
+
): null}
|
|
112
193
|
</Paper>
|
|
113
194
|
</AnimatedY>
|
|
114
195
|
</Modal>
|