Skip to content

Commit 5cecfdb

Browse files
committed
feat: support rating in skills
1 parent 4847458 commit 5cecfdb

File tree

3 files changed

+82
-10
lines changed

3 files changed

+82
-10
lines changed

src/components.typ

Lines changed: 80 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -201,22 +201,96 @@
201201
}
202202

203203
// Skills component with categories
204+
// mode:
205+
// - "text" : only text (default, backwards compatible)
206+
// - "bar" : quantified progress bars
207+
// - "rating" : quantified stars or dots (controlled by rating-style)
204208
#let cv-skills(
205209
skills: (:),
206210
theme: (:),
207211
compact: false,
212+
mode: "text",
213+
rating-style: "stars", // "stars" or "dots"
214+
max-level: 5,
208215
) = {
216+
// Helper: normalize each item to a (name, level) pair.
217+
// Supports:
218+
// - "React" -> (name: "React", level: max-level)
219+
// - (name: "React", level: 4) -> (name: "React", level: 4)
220+
// - ("React", 4) -> (name: "React", level: 4)
221+
let normalize-items = items => {
222+
items.map(item => {
223+
if type(item) == str {
224+
(name: item, level: max-level)
225+
} else if "name" in item and "level" in item {
226+
item
227+
} else if type(item) == array and item.len() == 2 {
228+
(name: item.at(0), level: item.at(1))
229+
} else {
230+
// Fallback: try to stringify
231+
(name: str(item), level: max-level)
232+
}
233+
})
234+
}
235+
236+
let accent = if "colors" in theme and "accent" in theme.colors {
237+
theme.colors.accent
238+
} else if "primary" in theme.colors {
239+
theme.colors.primary
240+
} else {
241+
rgb("#1e88e5")
242+
}
243+
244+
let rating-filled-col = accent
245+
let rating-empty-col = accent.lighten(70%)
246+
209247
block[
210248
#set par(justify: false)
211249
#for (category, items) in skills {
212-
if compact {
213-
text(weight: "semibold", size: 9pt, category + ":")
214-
linebreak()
215-
text(size: 9pt, items.join(", "))
250+
if mode == "text" {
251+
if compact {
252+
text(weight: "semibold", size: 9pt, category + ":")
253+
linebreak()
254+
text(size: 9pt, items.join(", "))
255+
} else {
256+
text(weight: "semibold", category + ": ")
257+
items.join(", ")
258+
}
216259
} else {
217-
text(weight: "semibold", category + ": ")
218-
items.join(", ")
260+
let normalized = normalize-items(items)
261+
262+
if compact {
263+
text(weight: "semibold", size: 9pt, category)
264+
} else {
265+
text(weight: "semibold", category)
266+
}
267+
linebreak()
268+
269+
for skill in normalized {
270+
grid(
271+
columns: (auto, 1fr),
272+
column-gutter: 0.4em,
273+
[
274+
- #text(size: 9pt, skill.name)
275+
],
276+
align(right)[
277+
#if mode == "bar" {
278+
skill-bar(level: skill.level, max: max-level, width: 100%, color: accent)
279+
} else if mode == "rating" {
280+
rating(
281+
level: skill.level,
282+
max: max-level,
283+
style: rating-style,
284+
filled-color: rating-filled-col,
285+
empty-color: rating-empty-col,
286+
)
287+
}
288+
],
289+
)
290+
v(0.1em)
291+
}
219292
}
293+
220294
v(0.3em)
221295
}
222296
]

src/lib.typ

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,6 @@
1616
)
1717

1818
#let icon = icon
19-
#let skill-bar = skill-bar
20-
#let rating = rating
2119
#let tag = tag
2220
#let timeline-entry = timeline-entry
2321
#let section-divider = section-divider

src/utils.typ

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@
5757
// Rating display (stars or dots)
5858
#let rating(level: 3, max: 5, style: "stars", filled-color: rgb("#1e88e5"), empty-color: rgb("#e0e0e0")) = {
5959
let filled-icon = if style == "stars" { fa-star } else { fa-circle }
60-
let empty-icon = if style == "stars" { fa-star-o } else { fa-circle-o }
60+
let empty-icon = if style == "stars" { fa-star } else { fa-circle }
6161

6262
let items = ()
6363
for i in range(max) {
@@ -68,7 +68,7 @@
6868
}
6969
}
7070

71-
items.join(h(0.2em))
71+
items.join(h(0.1em))
7272
}
7373

7474
// Tag/badge component

0 commit comments

Comments
 (0)