Skip to content

Commit

Permalink
update
Browse files Browse the repository at this point in the history
  • Loading branch information
sebald committed Jun 12, 2024
1 parent 50d722b commit fe6e0a7
Show file tree
Hide file tree
Showing 6 changed files with 186 additions and 15 deletions.
12 changes: 12 additions & 0 deletions src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,15 @@
.ch-mini-browser .ch-frame-content {
flex-basis: auto;
}

.ch-codeblock .ch-code-button {
display: none;
}

.ch-codeblock:hover .ch-code-button {
display: block;
}

.ch-code-multiline-mark > .ch-code-button {
display: none !important;
}
22 changes: 22 additions & 0 deletions src/routes/compound-component/_components/Link.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import type { ComponentProps } from 'react';
import { cn } from '@marigold/system';

const VARIANTS = {
default: '',
cta: 'flex items-center h-7 rounded bg-gradient-to-tr from-purple-700 to-purple-500 px-2 text-white shadow transition-transform hover:scale-110 no-underline',
};

export interface LinkProps extends ComponentProps<'a'> {
variant?: keyof typeof VARIANTS;
}

export const Link = ({
className,
variant = 'default',
children,
...props
}: LinkProps) => (
<a {...props} className={cn(VARIANTS[variant], className)}>
{children}
</a>
);
4 changes: 3 additions & 1 deletion src/routes/compound-component/_components/Tabs/TabList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,7 @@ export interface TabListProps {
}

export const TabList = ({ children }: TabListProps) => (
<div className="flex gap-4 border-b border-secondary-300">{children}</div>
<div className="flex items-center gap-4 border-b border-secondary-300">
{children}
</div>
);
60 changes: 60 additions & 0 deletions src/routes/compound-component/_components/TabsActionExample.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { MarigoldProvider } from '@marigold/components';
import theme from '@marigold/theme-core';

import { Tabs } from './Tabs';
import { Link } from './Link';

const App = () => (
<MarigoldProvider theme={theme} className="h-[340px]">
<Tabs defaultActiveTab="description">
<Tabs.List>
<Tabs.Item id="description">Description</Tabs.Item>
<Tabs.Item id="locations">Locations</Tabs.Item>
<Tabs.Item id="merchandise">Merchandise</Tabs.Item>
<Link
variant="cta"
className="ml-auto"
href="https://www.reservix.de/"
target="_blank"
rel="noreferrer"
>
Buy Tickets!
</Link>
</Tabs.List>
<Tabs.Panel id="description">
<div className="prose">
<strong>DJ Wobblemeister's Wobble Extravaganza</strong>
<p className="m-0">
Join us for an unforgettable night with DJ Wobblemeister, the
maestro of wobble beats and king of quirky dance moves. Expect an
evening filled with thumping bass, wobbly rhythms, and an
unparalleled light show. It's going to be a wobbly good time!
</p>
</div>
</Tabs.Panel>
<Tabs.Panel id="locations">
<ul className="prose m-0">
<li>Wobbleville - June 20th</li>
<li>Bass City - June 25th</li>
<li>Quirkytown - July 1st</li>
<li>Beatsburgh - July 5th</li>
</ul>
</Tabs.Panel>
<Tabs.Panel id="merchandise">
<div className="prose">
<p className="mt-0">
Grab your exclusive DJ Wobblemeister merchandise at the concert!
</p>
<ul>
<li>Wobble Hats - $25</li>
<li>Quirky T-Shirts - $30</li>
<li>Bass Boosted Hoodies - $50</li>
<li>Glow-in-the-dark Wobble Wristbands - $10</li>
</ul>
</div>
</Tabs.Panel>
</Tabs>
</MarigoldProvider>
);

export default App;
Original file line number Diff line number Diff line change
Expand Up @@ -8,23 +8,27 @@ import { Block } from '@/components/Container';
```

When comparing the two implementations, one notable difference is how the tabs are defined and organized.
In the implementation on the right, the tabs are passed as an array of objects directly to the `<Tabs>`
component, with each object containing a `title` and `content`property.

In the first implementation, the tabs are passed as an array of objects directly to the `<Tabs>` component,
with each object containing a `title` and `content` property. While this approach works for small examples,
it can become difficult to maintain as the content structure grows larger and more complex. Using a
"data object" for the tabs' content goes against the intended use of JSX, which is designed for
building component structures in a declarative way.

---

```tsx TabsCompoundComponent.tsx
// from ./TabsExample.tsx 8:45
```

On the other hand, the second implementation separates the tabs into `<Tabs.Item>` components within a
`<Tabs.List>` component and associates each tab with its respective content panel using `<Tabs.Panel>`
components.
In contrast, the second implementation, which uses compound components, organizes the tabs into `<Tabs.Item>`
components within a `<Tabs.List>` component and associates each tab with its respective content panel
using `<Tabs.Panel>` components. This approach provides better separation of concerns and encapsulates
the logic more effectively.

This implementation offers better separation of concerns and encapsulation of logic. It allows for more
explicit and readable code, as each component is responsible for its own specific functionality.
Additionally, the use of compound components makes it easier to understand the relationship between
the tabs and their content panels.
By splitting the logic into distinct components, the code becomes more readable and easier to manage. Each
component has a specific responsibility, making it clear how the tabs and their content are related. This
aligns much better with the principles of JSX and React's component-based architecture.

</CH.Scrollycoding>
</Block>
81 changes: 76 additions & 5 deletions src/routes/compound-component/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import SelectExample from './_components/SelectExample';
import TabsBasicExample from './_components/TabsBasicExample';
import TabsAnatomy from './_components/TabsAnatomy';
import TabsExample from './_components/TabsExample';
import TabsActionExample from './_components/TabsActionExample';

import TabsBaiscContent from './_components/tabs-basic.mdx';
import TabsCompoundComponentContent from './_components/tabs-compound-component.mdx';
Expand Down Expand Up @@ -185,16 +186,86 @@ for setting up this shared state, managing the state of the tabs, and providing

## Benefits of Compound Components

To begin with, let's compare the code of the two implementations and see how they
fare when used to build the "DJ Wobblemeister's Wobble Extravaganza" example.
Let's stat by comparing the code of the two implementations and see how we can use them to
build the "DJ Wobblemeister's Wobble Extravaganza" example from before.

</Content>

<CompareImplementation />

<Content>

Overall, the second implementation using compound components is cleaner, more maintainable compared to the first implementation.
One of the biggest strengths of the second implementation is its ease of adding new features
and customizing the component. This flexibility stems from the clear separation of concerns and
modular structure, making it straightforward to enhance and extend individual parts without
affecting the entire component.

For example, consider the following enhancements:

### Adding a "Buy Tickets" Button

Imagine you've built the tabs using the first implementation. A few weeks into the project, your
product manager comes up with the great idea to make it possible to buy tickets regardless of what
tab is currently shown. This should be a very prominent feature, ideally a "Buy Ticket" button
should placed next to the tab titles.

How would you do this?

With the first implementation, adding such a feature is challenging. You'd have to modify the `<Tabs>` component directly,
which involves adding more props and changing the internal logic. This feature would be essentially hard-coded into the component
as something like a "tabs action". Adding fratures like this can quickly lead to messy and hard-to-maintain code.

{/* prettier-ignore */}
```tsx
<Tabs tabs={[/* ... */]} action={<button onClick={/* ... */}>Buy</button>}/>
```

If the component is build with compound components, you don't have to touch the
existing components at all! Let's see how we could implement the action button.

In our case, we can use a pre-styled `<Link>` component. We put this link into the
`<Tabs.List>`, and we're done! It's as simple as that!

<CH.Code style={{ height: 500 }}>

```tsx App.tsx focus=14:22
// from ./_components/TabsActionExample.tsx
```

```tsx Link.tsx
// from ./_components/Link.tsx
```

</CH.Code>

Below you can see the end result. We were able to easily satisfy the product manager's
request without complicating our codebase.

The flexibility of compound components allowed us to insert the new link right next to
the tab titles within the `<Tabs.List>`, seamlessly integrating it with the rest of the
tab structure.

<Preview>
<TabsActionExample />
</Preview>

### Adding new Behavior

After using the tabs for a while, the product manager notices that the real content is
much longer than anticipated. Sometimes, they want to reference content from other tabs
within a tab. So, she wants to add a new functionality: the ability to easily switch
from one tab to another within the tab content.

However, this isn't feasible with the first implementation since there is no way to update
the state of the `<Tabs>` component. The state is entirely encapsulated within the `<Tabs>`
component, making it impossible to accommodate dynamic interactions like switching tabs
from within the content.

Lucky for us, we won't encounter this issue when using compound components.

</Content>

<Content>

---

Expand All @@ -211,8 +282,8 @@ complex and tightly coupled code.
downsides of this implementation: we have a very ugly "data object" with all information and JSX as property inside it. this makes it hard(er) to work with the ... why?
add a new feature that jumps to a certain tab, making this almost like a wizard (using TabsContext) or something.

!!! we can use the component without having to put everything in a "data object" first.
!!! When it is not a good idea to use compound components!?

colocation of content and title (we had this in the objects also?)
we demonstracted how powerful and convenient compound components can be for extending functionality and maintaining clean, scalable code.

</Content>

0 comments on commit fe6e0a7

Please sign in to comment.