Skip to content

Commit 16c2152

Browse files
committed
Convert ProjectSummaryItem to use Tailwind
Rework project page. Add class props to BigHeading and OptionalAnchor. Add utility functions for trim-* styles.
1 parent e4302ab commit 16c2152

File tree

5 files changed

+135
-168
lines changed

5 files changed

+135
-168
lines changed

src/components/BigHeading.astro

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
interface Props {
33
anchor?: "left" | "right";
44
size?: "lg" | "md" | "sm";
5+
class?: string;
56
}
67
78
const Sizes = {
@@ -15,13 +16,14 @@ const Sizes = {
1516
},
1617
sm: {
1718
Heading: "h4",
18-
headingFontSize: "text-2xl",
19+
headingFontSize: "text-3xl",
1920
},
2021
};
2122
2223
const {
2324
anchor = "left",
2425
size = "lg",
26+
class: className,
2527
} = Astro.props;
2628
const { Heading, headingFontSize } = Sizes[size];
2729
---
@@ -34,7 +36,9 @@ const { Heading, headingFontSize } = Sizes[size];
3436
"uppercase",
3537
headingFontSize,
3638
"leading-[0.8]",
39+
"trim-start",
3740
"mb-0",
41+
className,
3842
// anchor === "left" && "text-right",
3943
]}
4044
>

src/components/OptionalAnchor.astro

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,13 @@
22
interface Props {
33
href?: string;
44
title?: string;
5+
class?: string;
56
}
67
7-
const { href, title } = Astro.props;
8+
const { href, title, class: className } = Astro.props;
89
---
910

1011
{href
11-
? <a href={href} title={title}><slot></slot></a>
12+
? <a href={href} title={title} class={className}><slot></slot></a>
1213
: <slot></slot>
1314
}

src/components/ProjectSummaryItem.astro

Lines changed: 70 additions & 116 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,22 @@ interface Props {
1616
1717
const ThumbnailWidth = 165;
1818
const Thumbnail2X = 2 * ThumbnailWidth;
19+
const IconClass = `
20+
inline-block
21+
mr-1
22+
min-w-[1.5em]
23+
text-center
24+
data-[icon^=fa:github]:top-[-.1em]
25+
data-[icon^=fa:github]:relative
26+
`;
27+
const ThumbnailClass = `
28+
aspect-square
29+
object-contain
30+
object-top
31+
w-4col
32+
h-auto
33+
max-w-none
34+
`;
1935
2036
const {
2137
project: {
@@ -40,125 +56,63 @@ const localImageMetadata = thumbnail && !isExternal
4056
4157
---
4258

43-
<li class="project">
44-
<article>
45-
<OptionalAnchor href={url}>
46-
{thumbnail && isExternal ? (
47-
<img class="thumbnail" src={thumbnail} alt={name} width={ThumbnailWidth} />
48-
) : localImageMetadata ? (
49-
<Image
50-
class="thumbnail"
51-
src={localImageMetadata}
52-
alt={name}
53-
widths={[ThumbnailWidth, Thumbnail2X]}
54-
sizes={`(max-width: 768px) ${ThumbnailWidth}px, ${ThumbnailWidth}px`}
55-
/>
56-
) : (
57-
<DefaultThumbnail size={ThumbnailWidth} />
58-
)}
59-
</OptionalAnchor>
60-
61-
<section>
62-
<h3>
63-
<OptionalAnchor href={url}>
64-
{name}
65-
</OptionalAnchor>
66-
</h3>
67-
68-
{/* descriptionHTML is formatted as a <p> tag */}
69-
<Fragment set:html={descriptionHTML} />
59+
<li class="flex flex-row items-start gap-1u mb-2u">
60+
<OptionalAnchor href={url}>
61+
{thumbnail && isExternal ? (
62+
<img class={ThumbnailClass} src={thumbnail} alt={name} width={ThumbnailWidth} />
63+
) : localImageMetadata ? (
64+
<Image
65+
class={ThumbnailClass}
66+
src={localImageMetadata}
67+
alt={name}
68+
widths={[ThumbnailWidth, Thumbnail2X]}
69+
sizes={`(max-width: 768px) ${ThumbnailWidth}px, ${ThumbnailWidth}px`}
70+
/>
71+
) : (
72+
<DefaultThumbnail size={ThumbnailWidth} />
73+
)}
74+
</OptionalAnchor>
75+
76+
<article class="flex-1 prose">
77+
<h3 class="text-3xl font-semibold font-condensed mb-1u not-prose trim-start">
78+
<OptionalAnchor href={url} class="text-text-base! decoration-text-base/20! hover:decoration-text-base!">
79+
{name}
80+
</OptionalAnchor>
81+
</h3>
82+
83+
{/* descriptionHTML is formatted as a <p> tag */}
84+
<Fragment set:html={descriptionHTML} />
85+
86+
{technologies &&
87+
<h4>Tech stack</h4>
88+
<p>{technologies.join(", ")}</p>
89+
}
7090

71-
{technologies &&
72-
<h4>Tech stack</h4>
73-
<p>{technologies.join(", ")}</p>
91+
<ul class="*:my-3 not-prose" role="list">
92+
{(slack && status !== "completed") &&
93+
<li>
94+
<Icon name={"fa:slack"} class={IconClass} aria-label="Slack icon" />
95+
Join: <a href={slack.url}>{slack.name}</a>
96+
</li>
7497
}
7598

76-
<div class="links">
77-
{(slack && status !== "completed") &&
78-
<p>
79-
<Icon name={"fa:slack"} class="icon" /> Join: <a href={slack.url}>{slack.name}</a>
80-
</p>
81-
}
82-
83-
{repos?.length &&
84-
<>
85-
{repos.map((url) => (
86-
<p>
87-
<Icon name={"fa:github"} class="icon" /> Contribute: <a href={url}>{url.replace("https://github.com/", "")}</a>
88-
</p>
89-
))}
90-
</>
91-
}
99+
{repos?.length &&
100+
<>
101+
{repos.map((url: string) => (
102+
<li>
103+
<Icon name={"fa:github"} class={IconClass} aria-label="GitHub icon" />
104+
Contribute: <a href={url}>{url.replace(/https?:\/\/(www\.)?github\.com\//, "")}</a>
105+
</li>
106+
))}
107+
</>
108+
}
92109

93-
{website &&
94-
<p>
95-
<Icon name={"fa:external-link"} class="icon" /> Visit: <a href={website}>{cleanWebsite(website)}</a>
96-
</p>
97-
}
98-
</div>
99-
</section>
110+
{website &&
111+
<li>
112+
<Icon name={"fa:external-link"} class={IconClass} aria-label="External link icon" />
113+
Visit: <a href={website}>{cleanWebsite(website)}</a>
114+
</li>
115+
}
116+
</ul>
100117
</article>
101118
</li>
102-
103-
<style>
104-
li.project {
105-
margin: var(--pico-block-spacing-vertical) 0 0 0;
106-
}
107-
108-
article {
109-
display: flex;
110-
flex-direction: row;
111-
align-items: flex-start;
112-
gap: var(--pico-block-spacing-horizontal);
113-
}
114-
115-
img.thumbnail {
116-
aspect-ratio: 1;
117-
object-fit: contain;
118-
object-position: center top;
119-
width: 165px;
120-
height: 165px;
121-
max-width: unset;
122-
}
123-
124-
section {
125-
margin-bottom: 0;
126-
}
127-
128-
h4 {
129-
margin-bottom: calc(.5 * var(--pico-typography-spacing-vertical));
130-
}
131-
132-
.links {
133-
margin-top: calc(1.75 * var(--pico-typography-spacing-vertical));
134-
}
135-
136-
.icon {
137-
display: inline-block;
138-
min-width: 1.5rem;
139-
text-align: center;
140-
}
141-
142-
.icon[data-icon="fa:github"] {
143-
top: -.15rem;
144-
position: relative;
145-
}
146-
147-
@media only screen and (max-width: 768px) {
148-
article {
149-
flex-direction: column;
150-
gap: calc(.5 * var(--pico-block-spacing-vertical));
151-
}
152-
153-
img.thumbnail {
154-
aspect-ratio: auto;
155-
height: auto;
156-
}
157-
158-
.links {
159-
a {
160-
word-break: break-all;
161-
}
162-
}
163-
}
164-
</style>

src/pages/projects/index.astro

Lines changed: 34 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,21 @@
11
---
22
import BaseLayout from "@/layouts/BaseLayout.astro";
3+
import type { CollectionEntry } from "astro:content";
34
import ProjectSummaryItem from "@/components/ProjectSummaryItem.astro";
5+
import BigHeading from "@/components/BigHeading.astro";
46
import { getProjects } from "@/content";
7+
// add the groupBy polyfill if needed
58
import "@/utils/groupBy";
69
10+
const Sections = [
11+
["active", "Active"],
12+
["inactive", "Inactive"],
13+
["completed", "Completed"],
14+
];
15+
716
const projects = await getProjects();
8-
const projectsByStatus = Object.groupBy(projects, (project) => project.data.status);
17+
const projectsByStatus: Record<string, CollectionEntry<"projects">[]> =
18+
Object.groupBy(projects, (project) => project.data.status);
919
const meta = {
1020
description: "Civic tech projects by, and for, San Francisco residents",
1121
};
@@ -15,42 +25,28 @@ const meta = {
1525
header="Projects"
1626
meta={meta}
1727
>
18-
<p>All projects built by SF Civic Tech are:</p>
19-
20-
<ul>
21-
<li>Open source</li>
22-
<li>Volunteer-driven</li>
23-
<li>Not-for-profit</li>
24-
<li>In service of the public good</li>
25-
</ul>
26-
27-
{projectsByStatus.active?.length &&
28-
<h2>Active projects</h2>
29-
30-
<ul role="list">
31-
{projectsByStatus.active.map((project) => (
32-
<ProjectSummaryItem project={project} />
33-
))}
34-
</ul>
35-
}
36-
37-
{projectsByStatus.inactive?.length &&
38-
<h2>Inactive projects</h2>
39-
40-
<ul role="list">
41-
{projectsByStatus.inactive?.map((project) => (
42-
<ProjectSummaryItem project={project} />
43-
))}
44-
</ul>
45-
}
46-
47-
{projectsByStatus.completed?.length &&
48-
<h2>Completed projects</h2>
49-
50-
<ul role="list">
51-
{projectsByStatus.completed?.map((project) => (
52-
<ProjectSummaryItem project={project} />
53-
))}
28+
<div class="prose max-w-none">
29+
<p>All projects built by SF Civic Tech are:</p>
30+
31+
<ul>
32+
<li>Open source</li>
33+
<li>Volunteer-driven</li>
34+
<li>Not-for-profit</li>
35+
<li>In service of the public good</li>
5436
</ul>
55-
}
37+
</div>
38+
39+
{Sections.map(([key, title]) => projectsByStatus[key]?.length > 0 &&
40+
<section class="mt-3u">
41+
<BigHeading size="md">
42+
<b>{title}</b> projects
43+
</BigHeading>
44+
45+
<ul role="list" class="mt-2u">
46+
{projectsByStatus[key].map((project) =>
47+
<ProjectSummaryItem project={project} />
48+
)}
49+
</ul>
50+
</section>
51+
)}
5652
</BaseLayout>

src/styles/global.css

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -130,19 +130,31 @@
130130
.prose {
131131
line-height: revert;
132132

133-
/*
134-
a {
135-
*/
136133
a:where(a:not([role=button])), [role=link] {
137134
color: var(--color-sf-red);
138-
@apply underline
139-
font-semibold
140-
transition
141-
duration-300
142-
ease-out
143-
underline-offset-[.2rem]
144-
decoration-primary/20
145-
hover:decoration-primary;
135+
@apply underline
136+
font-semibold
137+
transition
138+
duration-300
139+
ease-out
140+
underline-offset-[.2rem]
141+
decoration-primary/20
142+
hover:decoration-primary;
146143
}
147144
}
145+
146+
.trim-start {
147+
text-box-trim: trim-start;
148+
text-box-edge: cap alphabetic;
149+
}
150+
151+
.trim-end {
152+
text-box-trim: trim-end;
153+
text-box-edge: cap alphabetic;
154+
}
155+
156+
.trim-both {
157+
text-box-trim: trim-both;
158+
text-box-edge: cap alphabetic;
159+
}
148160
}

0 commit comments

Comments
 (0)