Reputation: 187
I'm struggling to figure out how to code a header/menu in my React/Tailwind app.
Here are the requirements:
What is the cleanest/most elegant approach to implement this with Tailwind?
I thought about separating Header/Navigation/NavigationItem into their own components, but the issue with this is that the Header needs to be aware of the NavigationItem components so that it can pass a isMenuOpen parameter to tell them to animate-in/out when the menu opens/closes.
It looks like this:
const App = () => {
const [isMenuOpen, setIsMenuOpen] = useState(false)
const handleMenuToggle = (isMenuOpen) => {
setIsMenuOpen(isMenuOpen)
}
return (
<Header onMenuToggle={handleMenuToggle}>
<Navigation appearance="primary">
<NavigationItem appearance="primary" index={0} isMenuOpen={isMenuOpen}>
Menu Item #1
</NavigationItem>
<NavigationItem appearance="primary" index={1} isMenuOpen={isMenuOpen}>
Menu Item #2
</NavigationItem>
<NavigationItem appearance="primary" index={2} isMenuOpen={isMenuOpen}>
Menu Item #3
</NavigationItem>
<NavigationItem appearance="primary" index={3} isMenuOpen={isMenuOpen}>
Menu Item #4
</NavigationItem>
</Navigation>
<Navigation appearance="secondary">
<NavigationItem appearance="secondary" index={4} isMenuOpen={isMenuOpen}>
Facebook
</NavigationItem>
<NavigationItem appearance="secondary" index={5} isMenuOpen={isMenuOpen}>
Twitter
</NavigationItem>
</Navigation>
</Header>
)
}
This isn't elegant and the components are too heavily tied to each other.
Another option was to do it purely in CSS with a class like .header.is-open .navigation-item {}
but this feels like it goes against the Tailwind patterns.
Is there another way to do this?
Upvotes: 0
Views: 476
Reputation: 42228
I thought about separating Header/Navigation/NavigationItem into their own components, but the issue with this is that the Header needs to be aware of the NavigationItem components so that it can pass a isMenuOpen parameter to tell them to animate-in/out when the menu opens/closes.
I don't see any reason why you can't pass isMenuOpen
down as a prop. We can also clean up a lot of the duplication of similar JSX elements. The only difference from one NavigationItem
to the next is the children
content and the index
, which increases by 1 each time.
We can create a MenuSection
component that loops through items and applies the same props to all, incrementing the index.
const MenuSection = ({ appearance, isMenuOpen, startIndex = 0, menuItems }) => {
return (
<Navigation appearance={appearance}>
{menuItems.map((node, i) => (
<NavigationItem
key={i}
appearance={appearance}
index={i + startIndex}
isMenuOpen={isMenuOpen}
>
{node}
</NavigationItem>
))}
</Navigation>
);
};
This menuItems
prop is an array
of anything which can be used as the children
property of the NavigationItem
. So the elements can be a string
or an element.
If we try to use this in App
then it gets weird because we have to set the startIndex
on the second MenuSection
based on the count of items in the first. Does the index for the secondary navigation really need to continue from where the primary left off? If it does then we should extract one level deeper so that we can look at the length
property of the first list.
const MenuPair = ({ isMenuOpen, primaryItems, secondaryItems }) => {
return (
<>
<MenuSection
isMenuOpen={isMenuOpen}
appearance="primary"
startIndex={0}
menuItems={primaryItems}
/>
<MenuSection
isMenuOpen={isMenuOpen}
appearance="secondary"
startIndex={primaryItems.length}
menuItems={secondaryItems}
/>
</>
);
};
const App = () => {
const [isMenuOpen, setIsMenuOpen] = useState(false);
const handleMenuToggle = (isMenuOpen) => {
setIsMenuOpen(isMenuOpen);
};
return (
<Header onMenuToggle={handleMenuToggle}>
<MenuPair
isMenuOpen={isMenuOpen}
primaryItems={[
"Menu Item #1",
"Menu Item #2",
"Menu Item #3",
"Menu Item #4"
]}
secondaryItems={["Facebook", "Twitter"]}
/>
</Header>
);
};
For this 6-item menu we haven't shortened the code by abstracting pieces into their own components, but at least we've ensured that the index
property will always index properly without having to hard-code the numbers.
Upvotes: 0