Skip to content

Commit 4b00fd4

Browse files
authored
Story 2204: Library Item Component (#2270)
1 parent bd45b49 commit 4b00fd4

6 files changed

Lines changed: 468 additions & 0 deletions

File tree

core/views.py

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1252,6 +1252,7 @@ def get_context_data(self, **kwargs):
12521252
build_library_intro_context,
12531253
get_commit_data_by_release_for_library,
12541254
commit_data_to_stats_bars,
1255+
patch_commit_authors,
12551256
)
12561257

12571258
CODE_DEMO_BEAST = """int main()
@@ -1989,4 +1990,82 @@ def get_context_data(self, **kwargs):
19891990
else:
19901991
context["example_library_not_found"] = library_slug
19911992
context["example_library_slug"] = library_slug
1993+
1994+
demo_library_items = []
1995+
demo_libs_qs = (
1996+
Library.objects.filter(
1997+
slug__in=[
1998+
"accumulators",
1999+
"algorithm",
2000+
"align",
2001+
"any",
2002+
"array",
2003+
"asio",
2004+
"assign",
2005+
"atomic",
2006+
"beast",
2007+
"bimap",
2008+
"bind",
2009+
]
2010+
)
2011+
.prefetch_related("categories", "authors")
2012+
.order_by("name")
2013+
)
2014+
# Collect one author per demo library and patch CommitAuthor data
2015+
demo_authors = {}
2016+
for lib in demo_libs_qs:
2017+
author = (
2018+
lib.authors.exclude(email__startswith="deleted-")
2019+
.exclude(github_username="")
2020+
.first()
2021+
) or lib.authors.exclude(email__startswith="deleted-").first()
2022+
if author:
2023+
demo_authors[lib.pk] = author
2024+
patch_commit_authors(list(demo_authors.values()))
2025+
2026+
for lib in demo_libs_qs:
2027+
lv = (
2028+
LibraryVersion.objects.filter(version=latest, library=lib).first()
2029+
if latest
2030+
else None
2031+
)
2032+
cats = [
2033+
{"label": cat.name, "url": "#", "variant": "neutral"}
2034+
for cat in lib.categories.all()[:3]
2035+
]
2036+
author = demo_authors.get(lib.pk)
2037+
demo_library_items.append(
2038+
{
2039+
"library_name": lib.display_name_short,
2040+
"library_url": reverse(
2041+
"library-detail",
2042+
kwargs={
2043+
"version_slug": "latest",
2044+
"library_slug": lib.slug,
2045+
},
2046+
),
2047+
"description": lib.description or "",
2048+
"categories": cats,
2049+
"cpp_version": (
2050+
lv.get_cpp_standard_minimum_display()
2051+
if lv and lv.cpp_standard_minimum
2052+
else "C++03"
2053+
),
2054+
"author": {
2055+
"name": author.display_name if author else "Unknown",
2056+
"role": "Contributor",
2057+
"avatar_url": author.get_avatar_url() if author else "",
2058+
"badge_url": f"{badge_img}/badge-first-place.png",
2059+
},
2060+
"doc_url": reverse(
2061+
"library-detail",
2062+
kwargs={
2063+
"version_slug": "latest",
2064+
"library_slug": lib.slug,
2065+
},
2066+
),
2067+
}
2068+
)
2069+
context["demo_library_items"] = demo_library_items
2070+
19922071
return context

static/css/v3/components.css

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,3 +46,4 @@
4646
@import "./post-card.css";
4747
@import "./event-card.css";
4848
@import "./badge-button.css";
49+
@import "./library-item.css";

static/css/v3/library-item.css

Lines changed: 301 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,301 @@
1+
/* ==========================================================================
2+
Library Item — list row and card variants
3+
========================================================================== */
4+
5+
/* Demo only: provides a contrasting background for the list variant in
6+
Lookbook/examples so the item surface colours are visible. Remove when
7+
the component is used on a real page with its own background. */
8+
.v3-examples-section__example-box:has(.library-item-list) {
9+
background: var(--color-surface-mid);
10+
}
11+
12+
/* ---------- Containers ---------- */
13+
14+
.library-item-list {
15+
list-style: none;
16+
margin: 0;
17+
padding: 0;
18+
display: grid;
19+
grid-template-columns: 160px 2fr 1fr 1fr 1fr auto;
20+
column-gap: var(--space-xl);
21+
max-width: 1408px;
22+
width: 100%;
23+
}
24+
25+
.library-item-card-grid {
26+
display: grid;
27+
grid-template-columns: repeat(4, 1fr);
28+
gap: var(--space-large);
29+
}
30+
31+
@media (max-width: 1024px) {
32+
.library-item-card-grid {
33+
grid-template-columns: repeat(3, 1fr);
34+
}
35+
}
36+
37+
/* ---------- Shared element styles ---------- */
38+
39+
.library-item__name {
40+
font-family: var(--font-display);
41+
font-size: var(--font-size-base);
42+
font-weight: var(--font-weight-medium);
43+
line-height: var(--line-height-tight);
44+
letter-spacing: var(--letter-spacing-display-regular);
45+
color: var(--color-text-primary);
46+
text-decoration: none;
47+
}
48+
49+
a.library-item__name:hover {
50+
text-decoration: underline;
51+
}
52+
53+
.library-item p {
54+
margin: 0;
55+
padding: 0;
56+
}
57+
58+
.library-item__description {
59+
font-size: var(--font-size-small);
60+
font-weight: var(--font-weight-regular);
61+
line-height: var(--line-height-default);
62+
letter-spacing: var(--letter-spacing-tight);
63+
color: var(--color-text-secondary);
64+
}
65+
66+
.library-item .btn-icon-library {
67+
padding: 0;
68+
border: none;
69+
background: none;
70+
width: var(--space-large);
71+
}
72+
73+
.library-item .btn-icon-library .btn-icon svg {
74+
width: 100%;
75+
height: 100%;
76+
}
77+
78+
/* Version tag visibility — desktop shows the tag inside .library-item__actions,
79+
mobile/card shows the tag inline with category tags. */
80+
.library-item__cpp-version {
81+
display: none;
82+
}
83+
84+
.library-item__cpp-version--desktop {
85+
display: inline-flex;
86+
width: fit-content;
87+
justify-self: center;
88+
}
89+
90+
/* ==========================================================================
91+
List variant
92+
========================================================================== */
93+
94+
/* ---------- Desktop ---------- */
95+
96+
.library-item--list {
97+
display: grid;
98+
grid-column: 1 / -1;
99+
grid-template-columns: subgrid;
100+
align-items: start;
101+
row-gap: var(--space-large);
102+
column-gap: var(--space-large);
103+
padding: var(--space-large);
104+
padding-top: var(--space-default);
105+
background: var(--color-surface-weak);
106+
border-bottom: 1px solid var(--color-stroke-weak);
107+
}
108+
109+
.library-item--list .library-item__header {
110+
display: contents;
111+
}
112+
113+
.library-item--list .library-item__tags {
114+
display: flex;
115+
flex-wrap: wrap;
116+
align-items: center;
117+
justify-self: start;
118+
gap: var(--space-s);
119+
}
120+
121+
.library-item--list .library-item__contributor {
122+
display: flex;
123+
align-items: start;
124+
gap: var(--space-default);
125+
}
126+
127+
.library-item--list .library-item__actions {
128+
display: contents;
129+
}
130+
131+
/* Right-align the doc button with extra spacing from version tag */
132+
.library-item--list .library-item__actions > .btn-icon-library {
133+
justify-self: end;
134+
margin-left: var(--space-xl);
135+
}
136+
137+
/* ---------- Tablet (≤ 1024px) ---------- */
138+
139+
@media (max-width: 1024px) {
140+
.library-item-list {
141+
grid-template-columns: auto 2fr 1fr 1fr min-content auto;
142+
/* Figma specifies --space-large here, but long descriptions cause overflow
143+
at tablet widths. Using --space-default until content constraints are
144+
defined. */
145+
column-gap: var(--space-default);
146+
}
147+
148+
.library-item--list .user-profile {
149+
align-items: normal;
150+
}
151+
152+
.library-item--list .library-item__actions > .btn-icon-library {
153+
margin-left: var(--space-default);
154+
}
155+
}
156+
157+
/* ---------- Mobile (≤ 767px) — switches to card-like layout ---------- */
158+
159+
@media (max-width: 767px) {
160+
.library-item-list {
161+
display: flex;
162+
flex-direction: column;
163+
}
164+
165+
/* Container — stacked cards with shared borders */
166+
.library-item--list {
167+
display: grid;
168+
grid-template-columns: 1fr auto;
169+
row-gap: var(--space-medium);
170+
border-bottom: 1px solid var(--color-stroke-weak);
171+
border-radius: 0;
172+
background: var(--color-surface-weak);
173+
padding: var(--space-large);
174+
}
175+
176+
.library-item--list:first-child {
177+
border-radius: var(--border-radius-xl) var(--border-radius-xl) 0 0;
178+
}
179+
180+
.library-item--list:last-child {
181+
border-radius: 0 0 var(--border-radius-xl) var(--border-radius-xl);
182+
border-bottom: none;
183+
}
184+
185+
.library-item--list:only-child {
186+
border-radius: var(--border-radius-xl);
187+
}
188+
189+
/* Header — name + description, left column */
190+
.library-item--list .library-item__header {
191+
grid-column: 1;
192+
grid-row: 1;
193+
display: flex;
194+
flex-direction: column;
195+
gap: var(--space-card);
196+
}
197+
198+
/* Actions — doc icon only, top-right */
199+
.library-item--list .library-item__actions {
200+
grid-column: 2;
201+
grid-row: 1;
202+
min-width: unset;
203+
}
204+
205+
/* Version tag swap — hide desktop, show mobile (inline with tags) */
206+
.library-item--list .library-item__cpp-version--desktop {
207+
display: none;
208+
}
209+
210+
.library-item--list .library-item__cpp-version {
211+
display: inline-flex;
212+
}
213+
214+
/* Tags — category tags + C++ version, full width */
215+
.library-item--list .library-item__tags {
216+
grid-column: 1 / -1;
217+
grid-row: 2;
218+
}
219+
220+
/* Contributor — full width, bottom */
221+
.library-item--list .library-item__contributor {
222+
grid-column: 1 / -1;
223+
grid-row: 3;
224+
margin-top: auto;
225+
}
226+
}
227+
228+
/* ==========================================================================
229+
Card variant
230+
========================================================================== */
231+
232+
/* ---------- Desktop ---------- */
233+
234+
.library-item--card {
235+
display: grid;
236+
grid-template-columns: 1fr auto;
237+
column-gap: var(--space-large);
238+
width: 100%;
239+
border-radius: var(--border-radius-xl);
240+
border: 1px solid var(--color-stroke-mid);
241+
background: var(--color-surface-weak);
242+
padding: var(--space-large);
243+
}
244+
245+
.library-item--card .library-item__header {
246+
grid-column: 1;
247+
grid-row: 1;
248+
display: flex;
249+
flex-direction: column;
250+
gap: var(--space-large);
251+
}
252+
253+
.library-item--card .library-item__actions {
254+
grid-column: 2;
255+
grid-row: 1;
256+
display: flex;
257+
align-items: start;
258+
justify-content: end;
259+
}
260+
261+
.library-item--card .library-item__cpp-version--desktop {
262+
display: none;
263+
}
264+
265+
.library-item--card .library-item__cpp-version {
266+
display: inline-flex;
267+
}
268+
269+
.library-item--card .library-item__tags {
270+
display: flex;
271+
flex-wrap: wrap;
272+
align-items: center;
273+
gap: var(--space-s);
274+
}
275+
276+
.library-item--card .library-item__contributor {
277+
grid-column: 1 / -1;
278+
grid-row: 2;
279+
display: flex;
280+
align-items: center;
281+
gap: var(--space-default);
282+
margin-top: auto;
283+
padding-top: var(--space-large);
284+
}
285+
286+
/* ---------- Mobile (≤ 767px) ---------- */
287+
288+
@media (max-width: 767px) {
289+
.library-item-card-grid {
290+
grid-template-columns: 1fr;
291+
gap: var(--space-medium);
292+
}
293+
294+
.library-item--card .library-item__header {
295+
gap: var(--space-card);
296+
}
297+
298+
.library-item--card .library-item__contributor {
299+
padding-top: var(--space-card);
300+
}
301+
}

0 commit comments

Comments
 (0)