-
Notifications
You must be signed in to change notification settings - Fork 143
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(blade): add TopNav component (#2257)
* feat: add topnav & tabnav * chore: update * feat: integrate with menu * refactor: minor changes * chore: add trailing/leading gap & fix types * fix: topnav padding issue * chore: add native files * chore: update snaps * chore: change props * chore: review comments * chore: fix left/right button alignment * chore: update * chore: update * feat: add support for scrollintoview * chore: check for overflow * chore: change scrollintoview block * chore: pass down background color from topnav to subcomps * docs: TopNav documentation (#2270) * docs: add stories * chore: update * chore: add code example for topnav * chore: add tabnav example * chore: update * chore: review comments * chore: update * chore: add kitchen sink * chore: add bg * test: add topnav tests (#2272) * test: add topnav basic tests * chore: add tabnav tests * chore: update * chore: update * chore: rename * chore: wire up explore links * chore: update color in docs * chore: add meta attributes * Create gorgeous-months-move.md * chore: fix tsc
- Loading branch information
1 parent
290dc63
commit fe9a0cf
Showing
34 changed files
with
4,189 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
"@razorpay/blade": minor | ||
--- | ||
|
||
feat(blade): add TopNav component |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
10 changes: 8 additions & 2 deletions
10
packages/blade/src/components/SideNav/__tests__/__snapshots__/SideNav.ssr.test.tsx.snap
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
13 changes: 13 additions & 0 deletions
13
packages/blade/src/components/TopNav/TabNav/TabNav.native.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
import { Text } from '~components/Typography'; | ||
import { throwBladeError } from '~utils/logger'; | ||
|
||
const TabNav = (_props: never): React.ReactElement => { | ||
throwBladeError({ | ||
message: 'TabNav is not yet implemented for native', | ||
moduleName: 'TabNav', | ||
}); | ||
|
||
return <Text>TabNav Component is not available for Native mobile apps.</Text>; | ||
}; | ||
|
||
export { TabNav }; |
181 changes: 181 additions & 0 deletions
181
packages/blade/src/components/TopNav/TabNav/TabNav.web.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,181 @@ | ||
import React from 'react'; | ||
import styled from 'styled-components'; | ||
import { useTopNavContext } from '../TopNavContext'; | ||
import { approximatelyEqual, MIXED_BG_COLOR, useHasOverflow } from './utils'; | ||
import { TabNavContext } from './TabNavContext'; | ||
import BaseBox from '~components/Box/BaseBox'; | ||
import type { StyledPropsBlade } from '~components/Box/styledProps'; | ||
import { getStyledProps } from '~components/Box/styledProps'; | ||
import { Button } from '~components/Button'; | ||
import { Divider } from '~components/Divider'; | ||
import { ChevronLeftIcon, ChevronRightIcon } from '~components/Icons'; | ||
import { makeMotionTime, makeSize } from '~utils'; | ||
import { size } from '~tokens/global'; | ||
import getIn from '~utils/lodashButBetter/get'; | ||
import type { BoxProps } from '~components/Box'; | ||
import { metaAttribute, MetaConstants } from '~utils/metaAttribute'; | ||
|
||
const GRADIENT_WIDTH = 54 as const; | ||
const GRADIENT_OFFSET = -8 as const; | ||
const OFFSET_BOTTOM = -12 as const; | ||
const SCROLL_AMOUNT = 200; | ||
|
||
type TabNavProps = { | ||
children: React.ReactNode; | ||
}; | ||
|
||
const ScrollableArea = styled(BaseBox)(() => { | ||
return { | ||
'&::-webkit-scrollbar': { display: 'none' }, | ||
}; | ||
}); | ||
|
||
const GradientOverlay = styled(BaseBox)<{ | ||
shouldShow?: boolean; | ||
variant: 'left' | 'right'; | ||
$color: BoxProps['backgroundColor']; | ||
}>(({ theme, shouldShow, variant, $color }) => { | ||
const color = getIn(theme.colors, $color as never, MIXED_BG_COLOR); | ||
|
||
return { | ||
position: 'absolute', | ||
[variant]: 0, | ||
pointerEvents: shouldShow ? 'auto' : 'none', | ||
transform: shouldShow ? 'scale(1)' : 'scale(0.5)', | ||
opacity: shouldShow ? 1 : 0, | ||
transitionTimingFunction: `${theme.motion.easing.standard.revealing}`, | ||
transitionDuration: `${makeMotionTime(theme.motion.duration.xquick)}`, | ||
transitionProperty: 'opacity, transform', | ||
zIndex: 1, | ||
':before': { | ||
content: "''", | ||
pointerEvents: 'none', | ||
position: 'absolute', | ||
[variant]: 0, | ||
top: makeSize(GRADIENT_OFFSET), | ||
bottom: makeSize(GRADIENT_OFFSET), | ||
width: makeSize(GRADIENT_WIDTH), | ||
background: `linear-gradient(to ${variant}, transparent 0%, ${color} 30%, ${color} 100%);`, | ||
}, | ||
}; | ||
}); | ||
|
||
const TabNav = ({ | ||
children, | ||
...styledProps | ||
}: TabNavProps & StyledPropsBlade): React.ReactElement => { | ||
const ref = React.useRef<HTMLDivElement>(null); | ||
const hasOverflow = useHasOverflow(ref); | ||
const [scrollStatus, setScrollStatus] = React.useState<'start' | 'end' | 'middle'>('start'); | ||
const { backgroundColor } = useTopNavContext(); | ||
|
||
// Check if the scroll is at start, end or middle | ||
const handleScrollStatus = React.useCallback( | ||
(e: React.UIEvent<HTMLDivElement, UIEvent>): void => { | ||
const target = e.target as HTMLDivElement; | ||
const isAtStart = target.scrollLeft === 0; | ||
const isAtEnd = approximatelyEqual( | ||
target.scrollLeft, | ||
target.scrollWidth - target.offsetWidth, | ||
); | ||
|
||
if (isAtStart) { | ||
setScrollStatus('start'); | ||
} else if (isAtEnd) { | ||
setScrollStatus('end'); | ||
} else { | ||
setScrollStatus('middle'); | ||
} | ||
}, | ||
[], | ||
); | ||
|
||
const scrollRight = (): void => { | ||
if (!ref.current) return; | ||
ref.current.scrollBy({ | ||
behavior: 'smooth', | ||
left: SCROLL_AMOUNT, | ||
}); | ||
}; | ||
|
||
const scrollLeft = (): void => { | ||
if (!ref.current) return; | ||
ref.current.scrollBy({ | ||
behavior: 'smooth', | ||
left: -SCROLL_AMOUNT, | ||
}); | ||
}; | ||
|
||
return ( | ||
<TabNavContext.Provider value={{ containerRef: ref, hasOverflow }}> | ||
<BaseBox | ||
as="nav" | ||
display="flex" | ||
width="100%" | ||
alignItems="center" | ||
position="relative" | ||
marginBottom={makeSize(OFFSET_BOTTOM)} | ||
{...getStyledProps(styledProps)} | ||
{...metaAttribute({ name: MetaConstants.TabNav })} | ||
> | ||
<GradientOverlay | ||
variant="left" | ||
$color={backgroundColor} | ||
shouldShow={hasOverflow && scrollStatus !== 'start'} | ||
> | ||
<Button | ||
size="xsmall" | ||
variant="tertiary" | ||
icon={ChevronLeftIcon} | ||
accessibilityLabel="Scroll Left" | ||
onClick={scrollLeft} | ||
/> | ||
</GradientOverlay> | ||
<ScrollableArea | ||
ref={ref} | ||
onScroll={handleScrollStatus} | ||
display="flex" | ||
width="100%" | ||
position="relative" | ||
whiteSpace="nowrap" | ||
gap="spacing.0" | ||
overflowY="hidden" | ||
overflowX="auto" | ||
> | ||
<BaseBox display="flex" flexDirection="row" width="max-content"> | ||
{React.Children.map(children, (child, index) => { | ||
return ( | ||
<> | ||
{index > 0 ? ( | ||
<Divider | ||
margin="auto" | ||
variant="muted" | ||
orientation="vertical" | ||
height={makeSize(size[16])} | ||
/> | ||
) : null} | ||
{child} | ||
</> | ||
); | ||
})} | ||
</BaseBox> | ||
</ScrollableArea> | ||
<GradientOverlay | ||
variant="right" | ||
$color={backgroundColor} | ||
shouldShow={hasOverflow && scrollStatus !== 'end'} | ||
> | ||
<Button | ||
size="xsmall" | ||
variant="tertiary" | ||
icon={ChevronRightIcon} | ||
accessibilityLabel="Scroll Right" | ||
onClick={scrollRight} | ||
/> | ||
</GradientOverlay> | ||
</BaseBox> | ||
</TabNavContext.Provider> | ||
); | ||
}; | ||
|
||
export { TabNav }; |
21 changes: 21 additions & 0 deletions
21
packages/blade/src/components/TopNav/TabNav/TabNavContext.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
import React from 'react'; | ||
import { throwBladeError } from '~utils/logger'; | ||
|
||
type TabNavContextProps = { | ||
containerRef: React.RefObject<HTMLDivElement>; | ||
hasOverflow: boolean; | ||
}; | ||
const TabNavContext = React.createContext<TabNavContextProps | null>(null); | ||
|
||
const useTabNavContext = (): TabNavContextProps => { | ||
const context = React.useContext(TabNavContext); | ||
if (!context) { | ||
throwBladeError({ | ||
message: 'useTabNavContext must be used within a TabNavProvider', | ||
moduleName: 'TabNav', | ||
}); | ||
} | ||
return context!; | ||
}; | ||
|
||
export { TabNavContext, useTabNavContext }; |
13 changes: 13 additions & 0 deletions
13
packages/blade/src/components/TopNav/TabNav/TabNavItem.native.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
import { Text } from '~components/Typography'; | ||
import { throwBladeError } from '~utils/logger'; | ||
|
||
const TabNavItem = (_props: never): React.ReactElement => { | ||
throwBladeError({ | ||
message: 'TabNavItem is not yet implemented for native', | ||
moduleName: 'TabNavItem', | ||
}); | ||
|
||
return <Text>TabNavItem Component is not available for Native mobile apps.</Text>; | ||
}; | ||
|
||
export { TabNavItem }; |
Oops, something went wrong.