Skip to content

Fix: Jobs list and child elements don't use html list #130

@thienautran

Description

@thienautran

description of the issue

Currently the job list does not use semantic html and only renders a wrapper div. This component should use ul or ol for the list itself, and li for the job cards. This considers that all elements on a given page are contextually related, and should be represented programmatically in addition to stylistically, see WCAG 1.3.1

Proposed change

Two aspects need to be modified here. The JobList component, and the JobCard component. The job list needs to render a ul element here as the ordering of the list items doesn't play into each item's importance. Each JobCard should render a li element as a part of the job list.

JobList: replace div with ul
frontend/src/components/jobs/job-list.tsx

<ul className="space-y-4 pr-1">
          {jobs.map((job) => (
            <div
              key={job.id}
              onClick={() => {
                setSelectedJob(job);
                // Only open modal on mobile
                if (window.innerWidth < 1024) {
                  setIsModalOpen(true);
                }
              }}
              className="cursor-pointer"
            >
              <JobCard
                job={job}
                isSelected={selectedJob?.id === job.id}
                isSponsor={job.highlight}
              />
            </div>
          ))}
        </ul>

JobCard: wrap Box with li
/frontend/src/components/jobs/details/job-card.tsx

<li>
      <Box
        bg={isSelected ? "selected" : "secondary"}
        bd="2px solid selected"
        className={`p-4 rounded-xl transition-colors flex flex-col justify-between`}
      >
        {/* Top section - company info */}
        <div className="flex flex-col gap-2">
          <div className={"flex justify-between"}>
            <div className={"flex flex-1 min-w-0"}>
              <CompanyLogo
                name={job.company.name}
                applicationUrl={job.application_url}
                logo={job.company.logo}
                className="mr-2 lg:h-14 lg:w-14 h-10 w-10"
              />
              <div
                className={
                  "flex justify-center flex-col flex-1 min-w-0 space-y-0.5"
                }
              >
                <span className="text-sm lg:text-md font-bold line-clamp-2 leading-tight pr-2">
                  {job.title}
                </span>
                <span className="text-xs line-clamp-1 leading-tight">
                  {job.company.name}
                </span>
              </div>
            </div>
            <span className={"text-xs flex-shrink-0"}>
              {getTimeAgo(job.created_at)}
            </span>
          </div>
          <div
            dangerouslySetInnerHTML={{
              __html: DOMPurify.sanitize(washedDescription),
            }}
            className="text-xs line-clamp-3 lg:line-clamp-3 mb-2"
          />
        </div>

        {/* Bottom section - badges */}
        <div className="flex gap-2 mt-auto">
          {/* Show a yellow "Sponsored" badge if this is a sponsor card */}
          {isSponsor && <Badge text="Sponsored" color="accent"></Badge>}
          {job.type && <Badge text={formatCapString(job.type)} />}
          {job.working_rights?.[0] && (
            <Badge
              text={
                job.working_rights.includes("INTERNATIONAL")
                  ? "International"
                  : "Citizen/PR"
              }
            />
          )}
          {!isSponsor && job.industry_field && (
            <Badge text={formatCapString(job.industry_field)} />
          )}
          {job.locations && job.locations.length > 0 && (
            <Badge
              className={"hidden lg:inline"}
              text={`${job.locations
                .slice(0, 2)
                .map((loc) => formatCapString(loc))
                .join(", ")}${job.locations.length > 2 ? ", ..." : ""}`}
            />
          )}
        </div>
      </Box>
    </li>

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions