import type { ReactIsland } from "@ethicdevs/react-monolith";
import React from "react";
import type { Organization, Repository } from "@prisma/client";
import type { WithThemeSchemeProp } from "../types";
import { AppRoute } from "../routes.defs";
import { Card } from "../components/Card.styled";
import { Chip } from "../components/Chip";
import { Grid } from "../components/Grid";
import { buildRouteLink } from "../utils/shared";
import { breakpoints, NamedColors } from "../utils/style";
export interface RepositoriesListProps {
repositories: (Repository & { parentOrg: Organization })[];
}
const RepositoriesList: ReactIsland<
RepositoriesListProps & WithThemeSchemeProp
> = ({ repositories, themeScheme }) => {
return (
<>
<style>{`
.cards {
display: grid;
grid-template-columns: 1fr; /* mobile default (1 column) */
width: 100%;
max-width: 1200px; /* prevents cards becoming too wide when no sidebar */
margin: 0 auto;
box-sizing: border-box;
gap: 16px;
}
/* Tablet fallback using container width (if no container queries) */
@media (min-width: 600px) {
.cards {
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
}
}
/* Desktop: force 3 columns */
@media (min-width: 1100px) {
.cards {
grid-template-columns: repeat(3, 1fr);
}
}
/* Container query (preferred) — adapts based on .cards container width, not viewport */
@container (min-width: 600px) {
.cards {
display: grid;
grid-template-columns: repeat(2, minmax(300px, 1fr)); // auto-fit
}
.cards > .card {
min-width: 300px;
width: 100%;
}
}
@container (min-width: 928px) {
.cards {
display: flex;
justify-content: stretch;
align-items: flex-start;
}
.cards > .card {
flex: .33;
min-width: 33%;
}
}
@container (min-width: 1100px) {
.cards {
grid-template-columns: repeat(3, 1fr);
}
}
/* Required: enable container for .cards */
.cards {
container-type: inline-size;
}
.card {
min-width: 300px;
width: 100%;
flex: 0 0 33%;
max-height: 320px;
min-height: 200px;
gap: 8px;
}
@container repositories (min-width: ${breakpoints.sml}) {
.card {
flex: .3;
min-width: calc(33% - 8px);
}
}
@container repositories (max-width: ${breakpoints.sml}) {
.card {
flex: .5;
min-width: calc(50% - 8px);
}
}
@media only screen and (max-width: ${breakpoints.s}) {
.card {
flex: 1;
min-width: calc(100% - 8px);
}
}
`}</style>
<Grid.Row
fluid
gap={4}
alignItems={"stretch"}
justifyContent={"stretch"}
style={{ marginTop: 16 }}
className="cards"
>
{repositories.map((repo) => (
<Card key={repo.id} themeScheme={themeScheme} className="card">
<Grid.Row fluid nowrap>
<h1 style={{ margin: 0, flex: 1 }}>
<a
href={buildRouteLink(AppRoute.REPOSITORY_DETAILS, {
orgSlug: repo.parentOrg.slug,
repoSlug: repo.slug,
})}
>
{repo.parentOrg.displayName || repo.parentOrg.slug}
{" / "}
{repo.displayName || repo.slug}
{}
</a>
</h1>
{repo.isFork && (
<p style={{ margin: 0, marginTop: 8 }}>
<code>[fork]</code>
</p>
)}
</Grid.Row>
<Grid.Col fluid gap={8}>
<p style={{ flex: 1, margin: 0 }}>{repo.shortDescription}</p>
{repo.lastPushedAt != null && (
<p
style={{
margin: 0,
fontSize: 14,
color: NamedColors.TEXT_MUTED[themeScheme],
}}
>
Last push: {new Date(repo.lastPushedAt).toLocaleString()}
</p>
)}
<Chip themeScheme={themeScheme}>
{repo.visibility.toLowerCase()}
</Chip>
</Grid.Col>
</Card>
))}
</Grid.Row>
</>
);
};
RepositoriesList.displayName = "RepositoriesList";
export default RepositoriesList;