Skip to content

Add optional social links to webring tableΒ #2

Description

@AJaccP

🧠 Context

The webring directory (/webring) currently has three columns β€” Name / Grad / Site β€” and a member schema of just name, url, year, inRing. This ticket lets a member optionally list social links (GitHub, LinkedIn, Instagram, X, Email) and shows them as icons in a new Links column.

The fields are all optional, so this is backward-compatible: existing member entries without links stay valid and the build won't break. Do not make any of them required.

Schema, rendering, and icons are one ticket on purpose β€” splitting them leaves half-wired states.

Files you'll touch:

  • src/content.config.ts (the webring collection schema)
  • src/content/webring/example-member.json
  • src/pages/webring.astro (the directory table)
  • src/icons/*.svg (new β€” the brand icon SVGs; see step 4)

Don't touch: anything under src/components/ for projects, the projects pages, or the Card files β€” this is webring-only.


πŸ› οΈ Implementation Plan

  1. Schema β€” in src/content.config.ts, add five optional URL fields to the webring collection:

    schema: z.object({
      name: z.string(),
      url: z.url(),
      year: z.number(), // graduation year
      inRing: z.boolean(),
      github: z.url().optional(),
      linkedin: z.url().optional(),
      email: z.email().optional(),
      instagram: z.url().optional(),
      x: z.url().optional(), // X (formerly Twitter)
    }),
  2. Example entry β€” add a couple of links to src/content/webring/example-member.json so the column isn't empty in dev (don't use links for a real student, example only):

    {
      "name": "Example Member",
      "url": "https://example.com",
      "year": 2025,
      "inRing": true,
      "github": "https://github.com/example",
      "linkedin": "https://www.linkedin.com/in/example"
    }
  3. Directory column β€” in src/pages/webring.astro, add a Links <th> to the header row and a matching <td>. Define the keys once in the frontmatter and render whichever are present. Start with text links (you'll swap them for icons in step 4):

    ---
    const socials = ['github', 'linkedin', 'email', 'instagram', 'x'] as const;
    ---
    
    <th class="py-2 font-normal">Links</th>
    
    <td class="py-3">
      <span class="flex gap-2">
        {
          socials.map((key) =>
            member.data[key] ? (
              <a
                href={member.data[key]} // email will likely need special handling since it needs "mailto:", not just the email value. Something like `key === 'email' ? mailto:${member.data[key]} : member.data[key]`
                class="text-muted hover:text-accent"
                target="_blank"
                rel="noopener"
              >
                {key}
              </a>
            ) : null,
          )
        }
      </span>
    </td>

email is handled differently from the URL links: its value is an address, so the link is href={mailto:${member.data.email}} (not the raw value), and its icon (see next step) is the lucide UI icon lucide:mail β€” "mail" is a generic glyph, not a brand, so lucide is fine here (no local SVG, unlike the brand icons). So when you build each link, special-case email for the mailto: href while the others use their value directly.

  1. Icons β€” replace the text words with brand icons. Don't use the lucide: set for these β€” lucide is deprecating its brand glyphs, so they're not a safe long-term source. Instead add the brand SVGs as local icons and render them through astro-icon.

    • Add the SVGs to src/icons/ (astro-icon's local-icon folder): github.svg, linkedin.svg, instagram.svg, x.svg. Grab them from Simple Icons (CC0 brand logos). Make sure each SVG inherits color β€” strip any hardcoded fill so it uses currentColor β€” so the icons pick up text-muted / hover:text-accent from the tokens. For email, use the lucide mail icon.
    • Reference them by bare filename. astro-icon renders a local icon by its name with no set prefix: <Icon name="github" /> loads src/icons/github.svg (unlike the lucide: prefix used elsewhere in the project).

    Map each social key to its icon and swap the text label for the icon. Because the link no longer has visible text, put the aria-label on the <a> (that becomes its screen-reader name) and mark the <Icon> aria-hidden (this hides the icon from screen readers only β€” it stays fully visible β€” so the link reads as just "GitHub" rather than announcing the SVG too):

    ---
    import { Icon } from 'astro-icon/components';
    
    const socials = [
      { key: 'github', label: 'GitHub', icon: 'github' },
      { key: 'linkedin', label: 'LinkedIn', icon: 'linkedin' },
      { key: 'email', label: 'Email', icon: 'lucide:mail' },
      { key: 'instagram', label: 'Instagram', icon: 'instagram' },
      { key: 'x', label: 'X', icon: 'x' },
    ] as const;
    ---
    
    <a
      href={member.data[key]} //special handling for email here as well, same as step 3
      aria-label={label}
      class="text-muted hover:text-accent"
      target="_blank"
      rel="noopener"
    >
      <Icon name={icon} class="h-4 w-4" aria-hidden="true" />
    </a>

βœ… Acceptance Criteria

  • Members can add any subset of github / linkedin / 'email' / instagram / x.
  • The directory shows a Links column with icons for whichever links exist.
  • A member with no links renders an empty (not broken) Links cell.
  • Icons are local brand SVGs in src/icons/ rendered via <Icon name="..." /> (astro-icon, no lucide: brand glyphs), accessible (aria-label on the link), and inherit color via currentColor.
  • The email link uses a mailto: href (not a plain URL) and the lucide:mail icon.
  • The table columns stay aligned (it's one <table>, not per-row grids).
  • pnpm format:check, pnpm check, and pnpm build all pass.

Metadata

Metadata

Assignees

Labels

Type

No type

Fields

No fields configured for issues without a type.

Projects

Status
In Progress

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions