Skip to content

Commit

Permalink
Article updates + dark theme and a11y improvements
Browse files Browse the repository at this point in the history
  • Loading branch information
DaniGuardiola committed Jun 16, 2024
1 parent a40a63c commit 2811ff9
Show file tree
Hide file tree
Showing 7 changed files with 129 additions and 31 deletions.
74 changes: 74 additions & 0 deletions src/components/Blitz.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import clsx from "clsx";
import { type ComponentProps, splitProps } from "solid-js";

type BlitzProps = ComponentProps<"iframe"> & {
blitzId: string;
file?: string;
/**
* @default "preview"
*/
view?: "editor" | "preview" | "both";

params?: {
ctl?: boolean;
devtoolsheight?: number;
hidedevtools?: boolean;
hideExplorer?: boolean;
hideNavigation?: boolean;
initialpath?: string;
showSidebar?: boolean;
startScript?: string;
terminalHeight?: number;
theme?: "light" | "dark";
};
};

const DEFAULTS = {
view: "preview",
hideNavigation: true,
};

function toParamValue(value: string | number | boolean) {
if (typeof value === "boolean") return value ? "1" : "0";
return value.toString();
}

export function Blitz(props: BlitzProps) {
const [blitzProps, htmlProps] = splitProps(props, [
"blitzId",
"file",
"view",
"params",
]);
const searchParams = () => {
const params = new URLSearchParams({ embed: "1" });

// Set defaults.
Object.entries(DEFAULTS).forEach(([key, value]) =>
params.set(key, toParamValue(value))
);

// Set user-defined values.
if (blitzProps.file) params.set("file", blitzProps.file);
if (blitzProps.view) params.set("view", blitzProps.view);
if (blitzProps.params) {
for (const [key, value] of Object.entries(blitzProps.params)) {
params.set(key, toParamValue(value));
}
}
return params;
};
return (
<iframe
loading="lazy"
src={`https://stackblitz.com/edit/${
blitzProps.blitzId
}?${searchParams()}`}
{...htmlProps}
class={clsx(
"w-[calc(100%+2rem)] min-h-[32rem] -mx-4 my-[1.25em] rounded-lg",
htmlProps.class
)}
/>
);
}
2 changes: 1 addition & 1 deletion src/components/MDXContent/code.sass
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
// code block (```code```)
& pre
@apply article-rounded
@apply flex font-fira-code -mx-4 my-[1.25em] px-4 py-6 bg-gray-900 text-black overflow-x-auto
@apply flex font-fira-code -mx-4 my-[1.25em] px-4 py-6 bg-neutral-800 text-black overflow-x-auto

& .code-container
@apply grow
Expand Down
6 changes: 1 addition & 5 deletions src/components/MDXContent/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -83,11 +83,7 @@ function Pre(props: ComponentProps<"pre">) {
<pre
{...props}
onScroll={(event) => setScrollXDebounced(event.currentTarget.scrollLeft)}
style={
typeof props.style === "string"
? `--scroll-x:${scrollX()}px;${props.style}`
: { ...props.style, "--scroll-x": `${scrollX()}px` }
}
style={{ "--scroll-x": `${scrollX()}px` }}
/>
);
}
Expand Down
8 changes: 4 additions & 4 deletions src/components/MDXContent/styles.sass
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
@apply lg:content-["#"] absolute left-[-.9em]

& a
@apply text-dark-invert
@apply text-dark-invert no-underline hover:underline

& + *
@apply mt-0
Expand All @@ -39,7 +39,7 @@
@apply bg-white dark:bg-black mx-auto my-[1.8em] rounded lg:rounded-lg shadow

& a
@apply text-accent hover:underline focus-ring rounded-sm focus-scroll-target
@apply text-accent underline underline-offset-4 decoration-1 hover:decoration-2 focus-ring rounded-sm focus-scroll-target

& img
@apply transition-shadow hover:shadow-lg
Expand All @@ -50,7 +50,7 @@

& blockquote
@apply article-rounded
@apply relative overflow-hidden bg-gray-200 dark:bg-neutral-900 font-roboto-slab -mx-4 py-4 px-8 my-[1.25em]
@apply relative overflow-hidden bg-gray-200 dark:bg-neutral-800 font-roboto-slab -mx-4 py-4 px-8 my-[1.25em]

&::before
@apply content-[""] absolute inset-y-0 left-0 w-2 bg-gray-500 dark:bg-neutral-800
Expand All @@ -65,7 +65,7 @@
@apply mb-[-1em]

& hr
@apply my-[3em] border-t border-gray-200
@apply my-[3em] border-t border-black/10 dark:border-white/20

& ul
@apply list-disc
Expand Down
4 changes: 2 additions & 2 deletions src/root.sass
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@

@apply bg-gray-100
&.dark
@apply bg-neutral-950
@apply bg-neutral-900

html.dark
--text-accent: #1d9267
--dark-invert: #e1e1e1
--dark-invert: #d2d2d2
--black-invert: 255 255 255
--subtle-invert: #909090
--subtle-white-invert: #ffffffd1
Expand Down
8 changes: 5 additions & 3 deletions src/root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,10 +74,12 @@ function ThemeToggle() {
function Header() {
return (
<header
class="fixed z-20 inset-x-0 top-0 bg-white/95 dark:bg-black/95 select-none transition-[height] flex items-center overflow-hidden"
class="fixed z-20 inset-x-0 top-0 select-none transition-[height,background-color] flex items-center overflow-hidden"
classList={{
"h-[5rem] sm:h-[11.25rem]": !headerScrolled(),
"h-[3.5rem] sm:h-[4.5rem]": headerScrolled(),
"h-[5rem] sm:h-[11.25rem] bg-white/95 dark:bg-neutral-900":
!headerScrolled(),
"h-[3.5rem] sm:h-[4.5rem] bg-white/80 dark:bg-neutral-900/80":
headerScrolled(),
"shadow-[0_2px_4px_rgba(0,0,0,.25)]": !scrolledAtTop(),
}}
>
Expand Down
58 changes: 42 additions & 16 deletions src/routes/article/the-open-closed-component-part-1/index.mdx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
date: 2024/6/13
title: The open/closed component
description: A powerful pattern to build extensible UI components that do more with less.
description: A powerful pattern for building extensible UI components that achieve more with less.
topics:
- design-systems
- js
Expand All @@ -11,6 +11,7 @@ imageUrl: /open-graph/the-open-closed-component-part-1.png
---

import { TheOpenClosedComponentIndex } from "~/components/ArticleSeriesIndex/TheOpenClosedComponentIndex";
import { Blitz } from "~/components/Blitz";

<TheOpenClosedComponentIndex />

Expand Down Expand Up @@ -81,7 +82,7 @@ The resolved value that's passed to the `<button />` element will be `"my-class"

Let's take a quick detour to explain what happens when you pass the same prop (like `className`) multiple times to a component, including as part of prop spreading (e.g. `<div {...props} />`).

If you have a good grasp of this, feel free to skip ahead.
If you already understand these concepts, feel free to skip ahead.

> Passing props to a component results in the same behavior as declaring properties in an object.
>
Expand Down Expand Up @@ -205,7 +206,7 @@ function Button({ variant, ...props }) {
This strategy is great, but we can't merge every prop. The amount of props that can be merged is actually rather small: `className`, `style`, `ref`, and event handlers.
While we can't merge non-mergeable props (duh!), we can let external props override the internal ones. With this approach, the values passed internally can be thought of as "defaults" for the underlying element or component. Defaults that the user can override if they need to.
While we can't merge non-mergeable props (duh!), we can let external props override the internal ones. With this approach, the values passed internally can be thought of as "defaults" for the underlying element or component. Defaults that the user can override if needed.
# What is an open/closed component?
Expand All @@ -215,7 +216,7 @@ Here's how [Diego](https://haz.dev/) defined it:
> The open/closed (...) component:
>
> A component that is based on the open/closed principle, which states that software entities (classes, modules, functions, etc.) should be **open for extension**, but **closed for modification**.
> A component that is based on the open/closed principle, which states that "software entities (classes, modules, functions, etc.) should be **open for extension**, but **closed for modification**".
Let's digest this:
Expand All @@ -238,7 +239,7 @@ For example, if you create a custom button, users can interact with both the but
![invert || A diagram representing the idea above with an example of a Button component](button-sprinkles.png)
By following the open/closed principle, more components can be created on top of existing components, creating a hierarchy of components that build on each other.
By following the open/closed principle, more components can be created on top of existing components, forming a hierarchy of components that build on each other.
> If this sounds familiar, that's because it's just good old [object-oriented programming](https://en.wikipedia.org/wiki/Object-oriented_programming)! The [open/closed principle](https://en.wikipedia.org/wiki/Open%E2%80%93closed_principle) is an OOP concept.
Expand Down Expand Up @@ -296,23 +297,48 @@ In other words, the components **must be open/closed**! These two libraries (as
This unlocks amazing composition patterns, like the following example [from the Radix docs](https://www.radix-ui.com/primitives/docs/guides/composition#composing-multiple-primitives):
```jsx {2-6}
<Dialog.Root>
<Tooltip.Root>
<Tooltip.Trigger asChild>
<Dialog.Trigger asChild>
<RadixDialog.Root>
<RadixTooltip.Root>
<RadixTooltip.Trigger asChild>
<RadixDialog.Trigger asChild>
<MyButton>Open dialog</MyButton>
</Dialog.Trigger>
</Tooltip.Trigger>
</RadixDialog.Trigger>
</RadixTooltip.Trigger>
<RadixTooltip.Portal>...</RadixTooltip.Portal>
</RadixTooltip.Root>

<RadixDialog.Portal>...</RadixDialog.Portal>
</RadixDialog.Root>
```
Not only that but since both libraries follow the same principles, you can mix and match components without any issues!
Below, I've rewritten the example replacing the Radix tooltip and the custom button with Ariakit components.
```jsx {2-6}
<RadixDialog.Root>
<Ariakit.TooltipProvider>
<Ariakit.TooltipAnchor
render={
<RadixDialog.Trigger asChild>
<Ariakit.Button>Open dialog</Ariakit.Button>
</RadixDialog.Trigger>
}
></Ariakit.TooltipAnchor>
<Tooltip.Portal>...</Tooltip.Portal>
</Tooltip.Root>
</Ariakit.TooltipProvider>

<Dialog.Portal>...</Dialog.Portal>
</Dialog.Root>
<RadixDialog.Portal>...</RadixDialog.Portal>
</RadixDialog.Root>
```
Radix is low-level, so it can be a little verbose. In your own component library, with the right abstractions, composition can become even simpler!
Here's a live demo of Radix and Ariakit working together:
<Blitz blitzId="vitejs-vite-zchpdx" file="src/App.tsx" />
Both Radix and Ariakit are fairly low-level, so they can be a little verbose. In your own component library, with the right abstractions, composition can become even simpler!
For example, this is [a real example](https://atlas.guide.co/?path=/story/stories-menu--trigger-with-tooltip) from a component library I build in the past:
This is [a real example](https://atlas.guide.co/?path=/story/stories-menu--trigger-with-tooltip) from a component library I built in the past:
```jsx {1-5}
<Menu.Root>
Expand Down

0 comments on commit 2811ff9

Please sign in to comment.