Skip to content

Commit

Permalink
feat(blade): added bottomsheet body zero padding option (#1346)
Browse files Browse the repository at this point in the history
* feat: web implementation

* chore: add native padding

* chore: update snapshot

* fix: react native bottomsheet fix

* chore: update snapshot and test

* chore: update

* chore: review updates

* Create many-parrots-guess.md

* Update .changeset/many-parrots-guess.md

Co-authored-by: Saurabh Daware <[email protected]>

---------

Co-authored-by: Saurabh Daware <[email protected]>
  • Loading branch information
anuraghazra and saurabhdaware committed Jun 27, 2023
1 parent d160755 commit dd0695d
Show file tree
Hide file tree
Showing 17 changed files with 1,139 additions and 30 deletions.
5 changes: 5 additions & 0 deletions .changeset/many-parrots-guess.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@razorpay/blade": minor
---

feat(blade): added bottomsheet body zero padding option
14 changes: 12 additions & 2 deletions packages/blade/src/components/BottomSheet/BottomSheet.native.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ const _BottomSheet = ({
const [headerHeight, setHeaderHeight] = React.useState(0);
const [footerHeight, setFooterHeight] = React.useState(0);
const [contentHeight, setContentHeight] = React.useState(0);
const [hasBodyPadding, setHasBodyPadding] = React.useState(true);
const [isHeaderEmpty, setIsHeaderEmpty] = React.useState(false);
const initialSnapPoint = React.useRef<number>(0);
const totalHeight = React.useMemo(() => {
return headerHeight + footerHeight + contentHeight;
Expand Down Expand Up @@ -168,6 +170,10 @@ const _BottomSheet = ({
const renderHandle = React.useCallback((): React.ReactElement => {
return (
<BaseBox
position={isHeaderEmpty ? 'absolute' : 'relative'}
top="spacing.0"
left="spacing.0"
right="spacing.0"
onLayout={({ nativeEvent }) => {
setHeaderHeight(nativeEvent.layout.height);
}}
Expand All @@ -178,8 +184,9 @@ const _BottomSheet = ({
{header.current}
</BaseBox>
);
}, [zIndex]);
}, [isHeaderEmpty, zIndex]);

const isHeaderFloating = !hasBodyPadding && isHeaderEmpty;
const contextValue = React.useMemo<BottomSheetContextProps>(
() => ({
isInBottomSheet: true,
Expand All @@ -195,8 +202,11 @@ const _BottomSheet = ({
scrollRef: () => {},
bind: {} as never,
defaultInitialFocusRef,
isHeaderFloating,
setHasBodyPadding,
setIsHeaderEmpty,
}),
[_isOpen, contentHeight, footerHeight, handleOnClose, headerHeight],
[_isOpen, contentHeight, footerHeight, handleOnClose, headerHeight, isHeaderFloating],
);

// Hack: We need to <Portal> the GorhomBottomSheet to the root of the react-native app
Expand Down
81 changes: 81 additions & 0 deletions packages/blade/src/components/BottomSheet/BottomSheet.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* eslint-disable jsx-a11y/media-has-caption */
/* eslint-disable jsx-a11y/no-autofocus */
/* eslint-disable react/no-unescaped-entities */
/* eslint-disable @typescript-eslint/no-explicit-any */
Expand Down Expand Up @@ -42,6 +43,7 @@ import { OTPInput } from '~components/Input/OTPInput';
import { Link } from '~components/Link';
import { Sandbox } from '~src/_helpers/storybook/Sandbox';
import StoryPageWrapper from '~src/_helpers/storybook/StoryPageWrapper';
import { isReactNative } from '~utils';

const Page = (): React.ReactElement => {
return (
Expand Down Expand Up @@ -671,6 +673,85 @@ const InitialFocusTemplate: ComponentStory<typeof BottomSheetComponent> = () =>

export const InitialFocus = InitialFocusTemplate.bind({});

const HeadingBanner = (): React.ReactElement => {
if (isReactNative()) {
return (
<Box position="relative" height="250px" overflow="hidden">
<Box
position="absolute"
top="spacing.0"
left="spacing.0"
width="100%"
height="100%"
backgroundColor="surface.background.level1.highContrast"
/>
<Box position="absolute" bottom="spacing.4" left="spacing.5">
<Heading color="surface.text.normal.highContrast">
All-in-one Escrow management platform
</Heading>
</Box>
</Box>
);
}

return (
<Box position="relative" height="250px" overflow="hidden">
<Box position="absolute" top="-150px" left="spacing.0">
<video
autoPlay
style={{ objectFit: 'cover' }}
width="100%"
height="100%"
src="https://cdn.razorpay.com/static/assets/razorpay.com/x/escrow-accounts/hero-illustration.mp4"
/>
</Box>
<Box position="absolute" bottom="spacing.4" left="spacing.5">
<Heading color="surface.text.normal.highContrast">
All-in-one Escrow management platform
</Heading>
</Box>
</Box>
);
};

const ZeroPaddingTemplate: ComponentStory<typeof BottomSheetComponent> = () => {
const [isOpen, setIsOpen] = React.useState(false);

return (
<BaseBox>
<Button onClick={() => setIsOpen(true)}>Open</Button>
<BottomSheetComponent
isOpen={isOpen}
onDismiss={() => {
setIsOpen(false);
}}
>
<BottomSheetHeader />
<BottomSheetBody padding="spacing.0">
<Box display="flex" flexDirection="column">
<HeadingBanner />
<Box padding="spacing.5" display="flex" flexDirection="column">
<Text>
We bring together Escrow account, Banks, Trusteeship services & Automation - all in
ONE place to deliver a seamless user experience for you. Work with our experts to
ensure your escrow money transfers are always compliant, safe & effortless.
</Text>
<Text marginTop="spacing.3" type="muted">
100% secure | Instant payouts | Unbeatable pricing
</Text>
</Box>
</Box>
</BottomSheetBody>
<BottomSheetFooter>
<Button isFullWidth>Talk To Our Escrow Experts</Button>
</BottomSheetFooter>
</BottomSheetComponent>
</BaseBox>
);
};

export const ZeroPadding = ZeroPaddingTemplate.bind({});

const SnapPointsTemplate: ComponentStory<typeof BottomSheetComponent> = () => {
const fruites = [
'Apple',
Expand Down
13 changes: 12 additions & 1 deletion packages/blade/src/components/BottomSheet/BottomSheet.web.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { BottomSheetBody } from './BottomSheetBody';
import type { SnapPoints } from './utils';
import { computeMaxContent, computeSnapPointBounds } from './utils';
import { BottomSheetBackdrop } from './BottomSheetBackdrop';
import type { BottomSheetContextProps } from './BottomSheetContext';
import { BottomSheetContext, useBottomSheetAndDropdownGlue } from './BottomSheetContext';
import { ComponentIds } from './componentIds';
import type { BottomSheetProps } from './types';
Expand Down Expand Up @@ -82,6 +83,8 @@ const _BottomSheet = ({
const [headerHeight, setHeaderHeight] = React.useState(0);
const [footerHeight, setFooterHeight] = React.useState(0);
const [grabHandleHeight, setGrabHandleHeight] = React.useState(0);
const [hasBodyPadding, setHasBodyPadding] = React.useState(true);
const [isHeaderEmpty, setIsHeaderEmpty] = React.useState(false);

const bottomSheetAndDropdownGlue = useBottomSheetAndDropdownGlue();
const [positionY, _setPositionY] = React.useState(0);
Expand Down Expand Up @@ -362,7 +365,8 @@ const _BottomSheet = ({
transitionDuration: theme.motion.duration.moderate,
});

const contextValue = React.useMemo(
const isHeaderFloating = !hasBodyPadding && isHeaderEmpty;
const contextValue: BottomSheetContextProps = React.useMemo(
() => ({
isInBottomSheet: true,
isOpen: Boolean(isVisible),
Expand All @@ -374,9 +378,12 @@ const _BottomSheet = ({
setContentHeight,
setFooterHeight,
setHeaderHeight,
setHasBodyPadding,
setIsHeaderEmpty,
scrollRef,
bind,
defaultInitialFocusRef,
isHeaderFloating,
}),
[
isVisible,
Expand All @@ -388,9 +395,12 @@ const _BottomSheet = ({
setContentHeight,
setFooterHeight,
setHeaderHeight,
setHasBodyPadding,
setIsHeaderEmpty,
scrollRef,
bind,
defaultInitialFocusRef,
isHeaderFloating,
],
);

Expand Down Expand Up @@ -445,6 +455,7 @@ const _BottomSheet = ({
<BaseBox height="100%" display="flex" flexDirection="column">
<BottomSheetGrabHandle
ref={grabHandleRef}
isHeaderFloating={isHeaderFloating}
{...metaAttribute({ name: ComponentIds.BottomSheetGrabHandle })}
{...bind()}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,23 @@ import React from 'react';
import { BottomSheetScrollView } from '@gorhom/bottom-sheet';
import { ComponentIds } from './componentIds';
import { useBottomSheetContext } from './BottomSheetContext';
import type { BottomSheetBodyProps } from './types';
import BaseBox from '~components/Box/BaseBox';
import { assignWithoutSideEffects } from '~src/utils/assignWithoutSideEffects';
import { isValidAllowedChildren } from '~utils';
import { componentIds } from '~components/ActionList/componentIds';
import { size } from '~tokens/global';

const _BottomSheetBody = ({ children }: { children: React.ReactNode }): React.ReactElement => {
const { footerHeight, setContentHeight } = useBottomSheetContext();
const _BottomSheetBody = ({
children,
padding = 'spacing.5',
}: BottomSheetBodyProps): React.ReactElement => {
const {
footerHeight,
setContentHeight,
setHasBodyPadding,
isHeaderFloating,
} = useBottomSheetContext();
const [bottomSheetHasActionList, setBottomSheetHasActionList] = React.useState<
boolean | undefined
>(undefined);
Expand All @@ -24,6 +34,13 @@ const _BottomSheetBody = ({ children }: { children: React.ReactNode }): React.Re
});
}, [children]);

React.useEffect(() => {
if (padding === 'spacing.0') {
setHasBodyPadding(false);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [padding]);

if (bottomSheetHasActionList === undefined) return <></>;
// If we are rendering ActionList, then we don't wrap the ActionList with extra wrappers
// This is to ensure that GorhomBottomSheetSectionList work as expected, adding extra wrappers breaks gorhom's rendering
Expand All @@ -36,14 +53,14 @@ const _BottomSheetBody = ({ children }: { children: React.ReactNode }): React.Re
onContentSizeChange={(_width, height) => {
setContentHeight(height);
}}
style={{ marginBottom: footerHeight }}
style={{ marginBottom: footerHeight, borderRadius: isHeaderFloating ? size[16] : 0 }}
>
<BaseBox flexShrink={1} flexGrow={1} overflow="hidden">
<BaseBox
paddingLeft={bottomSheetHasActionList ? 'spacing.3' : 'spacing.5'}
paddingRight={bottomSheetHasActionList ? 'spacing.3' : 'spacing.5'}
paddingTop={bottomSheetHasActionList ? 'spacing.3' : 'spacing.5'}
paddingBottom={bottomSheetHasActionList ? 'spacing.3' : 'spacing.5'}
paddingLeft={bottomSheetHasActionList ? 'spacing.3' : padding}
paddingRight={bottomSheetHasActionList ? 'spacing.3' : padding}
paddingTop={bottomSheetHasActionList ? 'spacing.3' : padding}
paddingBottom={bottomSheetHasActionList ? 'spacing.3' : padding}
overflow="hidden"
>
{children}
Expand Down
23 changes: 17 additions & 6 deletions packages/blade/src/components/BottomSheet/BottomSheetBody.web.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import React from 'react';
import { ComponentIds } from './componentIds';
import { useBottomSheetContext } from './BottomSheetContext';
import type { BottomSheetBodyProps } from './types';
import { useIsomorphicLayoutEffect } from '~src/hooks/useIsomorphicLayoutEffect';
import BaseBox from '~components/Box/BaseBox';
import { assignWithoutSideEffects } from '~src/utils/assignWithoutSideEffects';
Expand All @@ -19,8 +20,11 @@ const bodyStyles: React.CSSProperties = {
touchAction: 'none',
};

const _BottomSheetBody = ({ children }: { children: React.ReactNode }): React.ReactElement => {
const { scrollRef, setContentHeight, isOpen, bind } = useBottomSheetContext();
const _BottomSheetBody = ({
children,
padding = 'spacing.5',
}: BottomSheetBodyProps): React.ReactElement => {
const { scrollRef, setContentHeight, setHasBodyPadding, isOpen, bind } = useBottomSheetContext();
const contentRef = React.useRef<HTMLDivElement>(null);
const [bottomSheetHasActionList, setBottomSheetHasActionList] = React.useState<boolean>(false);

Expand All @@ -38,6 +42,13 @@ const _BottomSheetBody = ({ children }: { children: React.ReactNode }): React.Re
});
}, [children]);

React.useEffect(() => {
if (padding === 'spacing.0') {
setHasBodyPadding(false);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [padding]);

return (
<BaseBox
{...metaAttribute({
Expand All @@ -53,10 +64,10 @@ const _BottomSheetBody = ({ children }: { children: React.ReactNode }): React.Re
{...bind?.({ isContentDragging: true })}
>
<BaseBox
paddingLeft={bottomSheetHasActionList ? 'spacing.3' : 'spacing.5'}
paddingRight={bottomSheetHasActionList ? 'spacing.3' : 'spacing.5'}
paddingTop={bottomSheetHasActionList ? 'spacing.3' : 'spacing.5'}
paddingBottom={bottomSheetHasActionList ? 'spacing.3' : 'spacing.5'}
paddingLeft={bottomSheetHasActionList ? 'spacing.3' : padding}
paddingRight={bottomSheetHasActionList ? 'spacing.3' : padding}
paddingTop={bottomSheetHasActionList ? 'spacing.3' : padding}
paddingBottom={bottomSheetHasActionList ? 'spacing.3' : padding}
ref={contentRef}
overflow="auto"
>
Expand Down
16 changes: 13 additions & 3 deletions packages/blade/src/components/BottomSheet/BottomSheetCommon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ const BottomSheetEmptyHeader = React.forwardRef<BladeElementRef, BottomSheetEmpt
},
ref,
) => {
const { close } = useBottomSheetContext();
const { close, isHeaderFloating } = useBottomSheetContext();
const { theme } = useTheme();
const webOnlyEventHandlers: Record<string, any> = isReactNative()
? {}
Expand All @@ -52,14 +52,23 @@ const BottomSheetEmptyHeader = React.forwardRef<BladeElementRef, BottomSheetEmpt
onPointerUp,
};

let topOffset: 'spacing.5' | 'spacing.0' | undefined = isHeaderFloating
? 'spacing.5'
: undefined;
if (isReactNative()) {
topOffset = 'spacing.0';
}

return (
<BaseBox
position="relative"
position={isHeaderFloating ? 'absolute' : 'relative'}
// bottomsheet empty header suppose to be 28px in height
// the grab handle height is 20px & here we are adding 8px
// total = 28px
height={makeSize(size[8])}
touchAction="none"
top={topOffset}
right="spacing.0"
{...webOnlyEventHandlers}
>
<BaseBox
Expand All @@ -70,13 +79,14 @@ const BottomSheetEmptyHeader = React.forwardRef<BladeElementRef, BottomSheetEmpt
// the bottomsheet handle has a height of 16px + 4px padding
// we need to make put the close button at 16px from top so adjusting the 4px
// cannot use position=fixed because RN won't support it
top={makeSpace(-size[4])}
top={isHeaderFloating ? 'spacing.0' : makeSpace(-size[4])}
right="spacing.5"
width={makeSize(size[28])}
height={makeSize(size[28])}
flexShrink={0}
backgroundColor={theme.colors.surface.background.level2.lowContrast}
borderRadius="max"
zIndex={100}
>
<IconButton
ref={ref}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,12 @@ type BottomSheetContextProps = {
headerHeight: number;
contentHeight: number;
footerHeight: number;
isHeaderFloating: boolean;
setContentHeight: React.Dispatch<React.SetStateAction<number>>;
setHeaderHeight: React.Dispatch<React.SetStateAction<number>>;
setFooterHeight: React.Dispatch<React.SetStateAction<number>>;
setHasBodyPadding: React.Dispatch<React.SetStateAction<boolean>>;
setIsHeaderEmpty: React.Dispatch<React.SetStateAction<boolean>>;
/**
* Closes the bottomsheet
*/
Expand Down Expand Up @@ -52,9 +55,12 @@ const BottomSheetContext = React.createContext<BottomSheetContextProps>({
headerHeight: 0,
contentHeight: 0,
footerHeight: 0,
isHeaderFloating: false,
setContentHeight: () => {},
setHeaderHeight: () => {},
setFooterHeight: () => {},
setHasBodyPadding: () => {},
setIsHeaderEmpty: () => {},
close: () => {},
scrollRef: null,
bind: null,
Expand Down
Loading

0 comments on commit dd0695d

Please sign in to comment.