โจ Watch on YouTube | ๐ GitHub | ๐ฎ Demo
It's quite a common UI pattern to add a delimiter between elements, such as a dot or a slash, to separate pieces of text or create divisions between sections on a page. Today, I would like to share abstract React components that are built on top of basic flexbox CSS, allowing you to implement such use cases in seconds.
Let's start with the StackSeparatedBy
component and its horizontal version, HStackSeparatedBy
. These components take a separator element and render it between their children. They are built on top of the Stack
component, which I have described in detail in this post. By using Children.toArray
, we can remove null
children. However, it's important to note that if one of the children is a component that returns null
, we won't be able to recognize it.
import React, { Fragment, ReactNode } from "react"
import { Stack, StackProps } from "./Stack"
import { isLast } from "lib/shared/utils/isLast"
export const dotSeparator = "โข"
export const slashSeparator = "/"
export interface StackSeparatedByProps extends StackProps {
separator: ReactNode
}
export const StackSeparatedBy = ({
children,
separator,
gap = 8,
wrap = "wrap",
...rest
}: StackSeparatedByProps) => {
const items = React.Children.toArray(children)
return (
<Stack wrap={wrap} gap={gap} {...rest}>
{items.map((child, index) => {
if (isLast(items, index)) {
return child
}
return (
<Fragment key={index}>
{child}
{separator}
</Fragment>
)
})}
</Stack>
)
}
export interface HStackSeparatedByProps
extends Omit<StackSeparatedByProps, "direction"> {}
export const HStackSeparatedBy = ({
alignItems = "center",
...props
}: HStackSeparatedByProps) => {
return <StackSeparatedBy direction="row" alignItems={alignItems} {...props} />
}
To add lines between sections, we have the SeparatedByLine
component. While it's also built on top of the Stack
component, it uses CSS to add a border-bottom
and padding-bottom
to all children except the last one. Here, we don't want to iterate over elements because sections are usually more complex components, and they might return null
. In such cases, we won't be able to recognize it because children would still be a component.
import styled, { css } from "styled-components"
import { VStack } from "./Stack"
import { getCSSUnit } from "./utils/getCSSUnit"
import { getColor } from "./theme/getters"
export const SeparatedByLine = styled(VStack)`
> *:not(:last-child) {
border-bottom: 1px solid ${getColor("backgroundGlass2")};
padding-bottom: ${({ gap = 0 }) => getCSSUnit(gap)};
}
`
Top comments (2)
The link to the base component is broken :(
And it might be worth including a link to the utils.isLast too.
Without being able to view the base code, I am interested in why the horizontal version is a wrapper around the vertical component rather than just a different extension of the base component; particularly how the Omits<> construct is used to override the base direction as this is now two abstract levels away. Just from a clean code perspective would it make more sense to omit direction from the StackSeparatedByProps and then include it explicitly in both derived components
@aarone4 thanks for pointing out the broken link, here's the blog-post about the stack: radzion.com/blog/stacks
and source code: github.com/RodionChachura/reactkit...