Skip to content

Commit

Permalink
Port <IndividualResource> to App Router style [#1066]
Browse files Browse the repository at this point in the history
* Refactor <Name>, <ResourceLinkWrapper>, and <ResourceLink> into
  <IndividualResourceLink>, and migrate into distinct file; in the
  future, <GroupLink> will also be refactored into that file, which is
  reflected in the `group-and-resource-links` file name
* Refactor <IconContainer> into distinct file, rework to avoid need to
  pass non-serializable prop
* Refactor <IndividualResource> and associated components from Styled
  to CSS Module
* Refactor <IndividualResource> component to be passed `gapWidth`
  prop, which supports cleaner separation of concerns with where that
  constant is defined and used
* Refactor <TooltipWrapper> into a distinct file, because it doesn't
  need to be a React Client component
  • Loading branch information
genehack committed Mar 6, 2025
1 parent 282af83 commit 87dd7e8
Show file tree
Hide file tree
Showing 7 changed files with 276 additions and 179 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
.baseLink {
color: #5097BA;
text-decoration: none;
}
.baseLink:hover {
color: #31586c;
}

.resourceLink {
font-size: 16px;
font-family: monospace;
white-space: pre; /* don't collapse back-to-back spaces */
}
56 changes: 56 additions & 0 deletions static-site/components/list-resources/group-and-resource-links.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
"use client";

import React, { useContext, useState } from "react";

import { InternalError } from "../error-boundary";

import { SetModalResourceContext } from "./modal";

import { Resource } from "./types";

import styles from "./group-and-resource-links.module.css";

export function IndividualResourceLink({
resource,
topOfColumn,
}: {
resource: Resource;
topOfColumn: boolean;
}): React.ReactElement | null {
const setModalResource = useContext(SetModalResourceContext);
if (!setModalResource) {
throw new InternalError("Context not provided!");
}

const [hovered, setHovered] = useState(false);

function onClick(e: React.MouseEvent): void {
if (e.shiftKey) {
setModalResource && setModalResource(resource);
e.preventDefault(); // child elements (e.g. <a>) shouldn't receive the click
}
}

// we already checked for this in the caller (probably?) but this pacifies the typechecker
if (!resource.displayName) {
return null;
}

return (
<div onClick={onClick}>
<a
className={`${styles.baseLink} ${styles.resourceLink}`}
href={resource.url}
onMouseOut={(): void => setHovered(false)}
onMouseOver={(): void => setHovered(true)}
rel="noreferrer"
target="_blank"
>
{"• "}
{hovered || topOfColumn
? resource.displayName.hovered
: resource.displayName.default}
</a>
</div>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.iconWrapper {
align-items: center;
display: flex;
gap: 3px;
}
77 changes: 77 additions & 0 deletions static-site/components/list-resources/icon-container.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
"use client";

import React, { useState } from "react";

import { IconType } from "react-icons";
import { MdFormatListBulleted, MdHistory } from "react-icons/md";

import { InternalError } from "../error-boundary";

import styles from "./icon-container.module.css";

/**
* React Client Component that renders the icon for the provided
* `iconName` inside a styled `<div>`.
*
* Currently supports `"history"` and `"bullet-list"` as `iconName`
* values; if provided with something else, will throw an
* `InternalError`
*
* @param color - (optional) string value for CSS `color`
* @param handleClick - (optional) callback for `onClick` handler
* @param hoverColor - (optional) string value for CSS `color` when
* element is hovered over
* @param iconName - name of icon to display
* @param text to display below icon
*/
export default function IconContainer({
color,
handleClick,
hoverColor,
iconName,
text,
}: {
color?: string;
handleClick?: () => void;
hoverColor?: string;
iconName: string;
text: string;
}): React.ReactElement {
const [hovered, setHovered] = useState(false);

const defaultColor = "#aaa";
const defaultHoverColor = "rgb(79, 75, 80)";
const hasOnClick = typeof handleClick === "function";

const col = hovered ? hoverColor || defaultHoverColor : color || defaultColor;
const cursor = hasOnClick ? "pointer" : "auto";
const iconProps = { size: "1.2em", color: col };

let Icon: IconType;
switch (iconName) {
case "history":
Icon = MdHistory;
break;
case "bullet-list":
Icon = MdFormatListBulleted;
break;
default:
throw new InternalError(`${iconName} is not a valid icon name`);
}

return (
<div
className={styles.iconWrapper}
onClick={hasOnClick ? handleClick : undefined}
onMouseOut={() => setHovered(false)}
onMouseOver={() => setHovered(true)}
style={{
color: col,
cursor,
}}
>
<Icon {...iconProps} />
{text}
</div>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
.flexRow {
align-items: center;
display: flex;
flex-direction: row;
justify-content: flex-start;
}

.individualResourceContainer {
color: #4f4b50;
overflow: hidden;
padding: 3px;
}
Loading

0 comments on commit 87dd7e8

Please sign in to comment.