mirror of
https://github.com/tiennm99/HugoBlox-kit.git
synced 2026-10-13 02:14:21 +00:00
feat(team-showcase): polish sorting, layout, and docs for labs/startups
- default to last-name sort with numeric-friendly keys for weight/graduation_year - support per-group sorting, optional empty-state, and role fallback to affiliation - add grid/interest caps, alignment controls, and avatar initial placeholders - expose graduation_year/weight/name_family_pref in profiles; update schema/README
This commit is contained in:
1 parent
4a758fb82e
commit
8ca8e40a8f
4 files changed
+276
-65
No files matched your search
@@ -8,7 +8,7 @@ Display your team members with a beautiful, responsive grid layout featuring ric
|
||||
- **User Groups**: Organize team members by role (e.g., Principal Investigators, PhD Students)
|
||||
- **Rich Profiles**: Display avatar, name, role, bio, interests, and social links
|
||||
- **Customizable Display**: Toggle visibility of role, organization, interests, and social links
|
||||
- **Sorting**: Sort team members by last name or custom field
|
||||
- **Sorting**: Sort by last name, graduation year, weight, or any author field (no `Params.` prefix needed); per-group sorting supported
|
||||
- **Hover Effects**: Smooth image zoom and shadow effects on hover
|
||||
- **Call to Action**: Optional button to recruitment or team page
|
||||
|
||||
@@ -25,53 +25,55 @@ sections:
|
||||
- Principal Investigators
|
||||
- Postdoctoral Researchers
|
||||
- PhD Students
|
||||
sort_by: 'Params.last_name'
|
||||
sort_ascending: true
|
||||
- name: Alumni # optional per-group sort override
|
||||
sort_by: graduation_year
|
||||
sort_ascending: false
|
||||
sort_by: 'graduation_year' # legacy 'Params.' prefix optional
|
||||
sort_ascending: false
|
||||
cta:
|
||||
text: Join Our Team
|
||||
url: /opportunities
|
||||
icon: user-plus
|
||||
design:
|
||||
show_role: true
|
||||
show_organizations: false
|
||||
show_organizations: true
|
||||
show_interests: true
|
||||
max_interests: 3 # set 0 to hide interests even if provided
|
||||
align: center # or "left" to align header + CTA left
|
||||
max_columns: 4 # 2, 3, or 4
|
||||
show_social: true
|
||||
show_empty_groups: false # show a placeholder when a group has no members
|
||||
# Section background color (CSS class)
|
||||
css_class: "bg-gray-50 dark:bg-gray-900"
|
||||
```
|
||||
|
||||
## Content Structure
|
||||
|
||||
Team members should be created in the `content/authors/` directory. Each team member needs:
|
||||
Team members are defined in `data/authors/<slug>.yaml`. All fields in the data file can be sorted on directly—no `Params.` prefix is required.
|
||||
|
||||
```yaml
|
||||
# content/authors/jane-doe/_index.md
|
||||
---
|
||||
title: Dr. Jane Doe
|
||||
# data/authors/jane-doe.yaml
|
||||
name:
|
||||
display: Dr. Jane Doe
|
||||
role: Postdoctoral Researcher
|
||||
bio: Research interests include machine learning and computational biology.
|
||||
interests:
|
||||
- Machine Learning
|
||||
- Computational Biology
|
||||
- Data Science
|
||||
organizations:
|
||||
affiliations:
|
||||
- name: University of Excellence
|
||||
url: https://example.edu
|
||||
social:
|
||||
links:
|
||||
- icon: envelope
|
||||
icon_pack: fas
|
||||
link: 'mailto:jane@example.edu'
|
||||
url: 'mailto:jane@example.edu'
|
||||
- icon: twitter
|
||||
icon_pack: fab
|
||||
link: https://x.com/janedoe
|
||||
url: https://x.com/janedoe
|
||||
- icon: github
|
||||
icon_pack: fab
|
||||
link: https://github.com/janedoe
|
||||
url: https://github.com/janedoe
|
||||
user_groups:
|
||||
- Postdoctoral Researchers
|
||||
---
|
||||
|
||||
Full biography content here...
|
||||
graduation_year: 2024
|
||||
```
|
||||
|
||||
## Options
|
||||
@@ -81,17 +83,39 @@ Full biography content here...
|
||||
- **title**: Main heading for the team section
|
||||
- **subtitle**: Optional subtitle
|
||||
- **text**: Optional description text (supports Markdown)
|
||||
- **user_groups**: Array of group names to display
|
||||
- **sort_by**: Field to sort by (default: "Params.last_name")
|
||||
- **user_groups**: Array of group names to display, or objects `{ name, sort_by?, sort_ascending? }` for per-group sorting
|
||||
- **sort_by**: Field to sort by (default: "name_family", falling back to the last word of `title`). Use plain field names from `data/authors`—the legacy `Params.` prefix is optional for backwards compatibility. Recommended: `name_family` (last name), `graduation_year` (alumni), `weight` (manual order).
|
||||
- **sort_ascending**: Sort direction (default: true)
|
||||
- **cta**: Optional call-to-action button with text, url, and icon
|
||||
|
||||
### Design Options
|
||||
|
||||
- **show_role**: Display team member roles (default: true)
|
||||
- **show_organizations**: Display affiliated organizations (default: false)
|
||||
- **show_organizations**: Display affiliated organizations (default: true)
|
||||
- **show_interests**: Display research interests (default: false)
|
||||
- **max_interests**: Maximum number of interests to show (default: 3; 0 hides interests)
|
||||
- **show_social**: Display social media links (default: true)
|
||||
- **align**: Align header and CTA (`center` or `left`, default `center`)
|
||||
- **max_columns**: Limit grid columns on wide screens (2, 3, or 4; default 4)
|
||||
- **show_empty_groups**: If true, render an empty-state message for groups with no members
|
||||
|
||||
## Recipes
|
||||
|
||||
- **Lab alumni list**: `user_groups: ['Alumni']`, `sort_by: graduation_year`, `sort_ascending: false`, set `graduation_year` in each author file.
|
||||
- **Startup leadership-first**: set `weight` on leadership (e.g., 1–10), `sort_by: weight`, `sort_ascending: true`, leave others empty or higher.
|
||||
- **Classic last-name sort**: ensure `name.family` is set; leave defaults or set `sort_by: name_family`.
|
||||
- **Hide interests but keep data**: `show_interests: true`, `max_interests: 0`.
|
||||
- **Left-aligned hero style**: `design.align: left` (header + CTA).
|
||||
- **Per-group sorting**: mix strings and objects in `user_groups`, e.g. `- name: Alumni; sort_by: graduation_year; sort_ascending: false`.
|
||||
|
||||
## Best Practices
|
||||
|
||||
- Keep `data/authors/<slug>.yaml` tidy: set `name.display`, `name.family`, `role`, short `bio`, `affiliations` (first shown), `links` with icons (`github`, `linkedin`, `orcid`, `google-scholar`), and `user_groups`.
|
||||
- Use square WebP avatars in `assets/media/authors/<slug>.*`, ~400×400px, optimized (<200 KB). Names center-cropped.
|
||||
- Prefer high-signal interests (3 or fewer); longer lists belong on the profile page.
|
||||
- For credibility, show organizations (default on) and keep roles concise.
|
||||
- Review people data each semester/quarter to keep roles and photos fresh.
|
||||
- Use valid icon names for `links.icon` (e.g., `github`, `linkedin`, `orcid`, `google-scholar`, `twitter`) to avoid broken social icons.
|
||||
|
||||
## Styling
|
||||
|
||||
|
||||
@@ -8,16 +8,38 @@
|
||||
{{ $show_social := $block.design.show_social | default true }}
|
||||
{{ $show_interests := $block.design.show_interests | default false }}
|
||||
{{ $show_role := $block.design.show_role | default true }}
|
||||
{{ $show_organizations := $block.design.show_organizations | default false }}
|
||||
{{ $show_organizations := $block.design.show_organizations | default true }}
|
||||
{{ $max_interests := $block.design.max_interests | default 3 }}
|
||||
{{ $max_interests_int := int $max_interests }}
|
||||
{{ $align := $block.design.align | default "center" }}
|
||||
{{ $max_columns := $block.design.max_columns | default 4 }}
|
||||
{{ $show_empty_groups := $block.design.show_empty_groups | default false }}
|
||||
{{ $sort_field := $block.content.sort_by | default "name_family" }}
|
||||
{{ $sort_asc := $block.content.sort_ascending | default true }}
|
||||
{{ $sort_field_lc := lower $sort_field }}
|
||||
{{ if hasPrefix $sort_field_lc "params." }}
|
||||
{{/* Backwards compatibility with Params.* used when authors were pages */}}
|
||||
{{ $sort_field = substr $sort_field 7 }}
|
||||
{{ $sort_field_lc = lower $sort_field }}
|
||||
{{ end }}
|
||||
|
||||
{{ $title := $block.content.title | emojify | $page.RenderString }}
|
||||
{{ $subtitle := $block.content.subtitle | emojify | $page.RenderString }}
|
||||
{{ $text := $block.content.text | emojify | $page.RenderString }}
|
||||
{{ $alignClass := cond (eq $align "left") "items-start text-left" "items-center text-center" }}
|
||||
{{ $ctaAlignClass := cond (eq $align "left") "text-left" "text-center" }}
|
||||
{{ $columnsClasses := dict
|
||||
2 "grid grid-cols-1 gap-8 sm:grid-cols-2"
|
||||
3 "grid grid-cols-1 gap-8 sm:grid-cols-2 lg:grid-cols-3"
|
||||
4 "grid grid-cols-1 gap-8 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4"
|
||||
}}
|
||||
{{ $max_columns_int := int $max_columns }}
|
||||
{{ $gridClass := index $columnsClasses $max_columns_int | default "grid grid-cols-1 gap-8 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4" }}
|
||||
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
|
||||
{{/* Section Header */}}
|
||||
<div class="flex flex-col items-center text-center mb-12">
|
||||
<div class="flex flex-col {{ $alignClass }} mb-12">
|
||||
{{ with $title }}
|
||||
<h2 class="text-3xl sm:text-4xl lg:text-5xl font-bold text-gray-900 dark:text-white tracking-tight">
|
||||
{{ . }}
|
||||
@@ -40,61 +62,126 @@
|
||||
{{/* Build profiles from data/authors */}}
|
||||
{{ $allProfiles := slice }}
|
||||
{{ range $slug, $_ := site.Data.authors }}
|
||||
{{ $allProfiles = $allProfiles | append (partial "functions/get_author_profile" (dict "slug" $slug)) }}
|
||||
{{ $profile := partial "functions/get_author_profile" (dict "slug" $slug) }}
|
||||
{{ $profileData := index site.Data.authors $slug }}
|
||||
{{ $allProfiles = $allProfiles | append (dict "profile" $profile "profileData" $profileData) }}
|
||||
{{ end }}
|
||||
|
||||
{{ $groups := $block.content.user_groups }}
|
||||
|
||||
{{ if $groups }}
|
||||
{{ range $group := $groups }}
|
||||
{{ range $groupItem := $groups }}
|
||||
{{ $groupName := "" }}
|
||||
{{ $groupSortField := $sort_field }}
|
||||
{{ $groupSortAsc := $sort_asc }}
|
||||
{{ $groupSortFieldLc := "" }}
|
||||
{{ $itemIsMap := reflect.IsMap $groupItem }}
|
||||
{{ if $itemIsMap }}
|
||||
{{ $groupName = index $groupItem "name" | default (index $groupItem "title") | default "" }}
|
||||
{{ $groupSortField = index $groupItem "sort_by" | default $groupSortField }}
|
||||
{{ $groupSortAsc = index $groupItem "sort_ascending" | default $groupSortAsc }}
|
||||
{{ else }}
|
||||
{{ $groupName = $groupItem }}
|
||||
{{ end }}
|
||||
{{ $groupSortFieldLc = lower $groupSortField }}
|
||||
{{ if hasPrefix $groupSortFieldLc "params." }}
|
||||
{{ $groupSortField = substr $groupSortField 7 }}
|
||||
{{ $groupSortFieldLc = lower $groupSortField }}
|
||||
{{ end }}
|
||||
{{ $groupIsNumeric := or (eq $groupSortFieldLc "weight") (eq $groupSortFieldLc "graduation_year") }}
|
||||
{{ $groupOrder := cond (and (not $groupIsNumeric) (not $groupSortAsc)) "desc" "asc" }}
|
||||
|
||||
{{ $groupProfiles := slice }}
|
||||
{{ range $allProfiles }}
|
||||
{{ $u := .user_groups | default (slice) }}
|
||||
{{ if gt (len (intersect $u (slice $group))) 0 }}
|
||||
{{ $groupProfiles = $groupProfiles | append . }}
|
||||
{{ $profile := .profile }}
|
||||
{{ $u := $profile.user_groups | default (slice) }}
|
||||
{{ if gt (len (intersect $u (slice $groupName))) 0 }}
|
||||
{{ $profileData := .profileData }}
|
||||
{{ $family := $profile.name_family_pref | default $profile.name_family | default (delimit (last 1 (split $profile.title " ")) "") | default $profile.title }}
|
||||
{{ $familyLower := lower $family }}
|
||||
{{ $raw := index $profile $groupSortField | default (index $profileData $groupSortField) }}
|
||||
{{ $isNumeric := $groupIsNumeric }}
|
||||
{{ $primary := "" }}
|
||||
{{ if or (eq $groupSortFieldLc "name.family") (eq $groupSortFieldLc "family") (eq $groupSortFieldLc "last_name") (eq $groupSortFieldLc "name_family") (eq $groupSortFieldLc "family_name") }}
|
||||
{{ $primary = $familyLower }}
|
||||
{{ else if $isNumeric }}
|
||||
{{ $primary = or (and (ne $raw nil) (int $raw)) 999999 }}
|
||||
{{ else if ne $raw nil }}
|
||||
{{ $primary = lower (printf "%v" $raw) }}
|
||||
{{ else }}
|
||||
{{ $primary = lower $profile.title }}
|
||||
{{ end }}
|
||||
{{ $sortKey := "" }}
|
||||
{{ if $isNumeric }}
|
||||
{{ $num := $primary | default 999999 }}
|
||||
{{ if not $groupSortAsc }}{{ $num = sub 999999999 $num }}{{ end }}
|
||||
{{ $sortKey = printf "%010d|%s" $num $familyLower }}
|
||||
{{ else }}
|
||||
{{ $sortKey = printf "%s|%s" $primary $familyLower }}
|
||||
{{ end }}
|
||||
{{ $groupProfiles = $groupProfiles | append (dict "profile" $profile "sort_val" $sortKey) }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
{{ $groupProfiles = sort $groupProfiles "title" }}
|
||||
{{ $groupProfiles = sort $groupProfiles "sort_val" $groupOrder }}
|
||||
|
||||
{{ if and $groupProfiles (gt (len $groups) 1) }}
|
||||
{{ if and (or (gt (len $groupProfiles) 0) $show_empty_groups) (gt (len $groups) 1) }}
|
||||
<div class="col-span-full mb-4">
|
||||
<h3 class="text-2xl font-bold text-gray-900 dark:text-white mb-6 pb-2 border-b border-gray-200 dark:border-gray-700">
|
||||
{{ $group | markdownify }}
|
||||
{{ $groupName | markdownify }}
|
||||
</h3>
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
<div class="grid grid-cols-1 gap-8 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 w-full">
|
||||
{{ range $profile := $groupProfiles }}
|
||||
{{ if or (gt (len $groupProfiles) 0) $show_empty_groups }}
|
||||
<div class="{{ $gridClass }} w-full">
|
||||
{{ if eq (len $groupProfiles) 0 }}
|
||||
{{ if $show_empty_groups }}
|
||||
<div class="text-gray-600 dark:text-gray-400">{{ i18n "no_entries" | default "No entries yet." }}</div>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
{{ range $groupProfile := $groupProfiles }}
|
||||
{{ $profile := $groupProfile.profile }}
|
||||
{{ $avatar := $profile.avatar }}
|
||||
{{ $link := printf "/authors/%s/" $profile.slug }}
|
||||
{{ $initial := upper (substr ($profile.name_given | default $profile.title) 0 1) }}
|
||||
|
||||
<div class="group relative">
|
||||
<div class="relative overflow-hidden rounded-2xl bg-white dark:bg-gray-800 shadow-lg hover:shadow-xl transition-all duration-300 transform hover:-translate-y-1">
|
||||
|
||||
{{ if $avatar }}
|
||||
<a href="{{ $link }}" class="block relative h-64 overflow-hidden">
|
||||
{{ $image := $avatar.Fill "400x400 Center webp" }}
|
||||
<img src="{{ $image.RelPermalink }}"
|
||||
alt="{{ $profile.title }}"
|
||||
width="{{ $image.Width }}"
|
||||
height="{{ $image.Height }}"
|
||||
loading="lazy"
|
||||
class="w-full h-full object-cover transition-transform duration-300 group-hover:scale-110">
|
||||
<div class="absolute inset-0 bg-gradient-to-t from-black/60 via-transparent to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-300"></div>
|
||||
<a href="{{ $link }}" class="block relative h-64 overflow-hidden" aria-label="{{ $profile.title }}">
|
||||
{{ if $avatar }}
|
||||
{{ $image := $avatar.Fill "400x400 Center webp" }}
|
||||
<img src="{{ $image.RelPermalink }}"
|
||||
alt="{{ $profile.title }}"
|
||||
width="{{ $image.Width }}"
|
||||
height="{{ $image.Height }}"
|
||||
loading="lazy"
|
||||
class="w-full h-full object-cover transition-transform duration-300 group-hover:scale-110">
|
||||
<div class="absolute inset-0 bg-gradient-to-t from-black/60 via-transparent to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-300"></div>
|
||||
{{ else }}
|
||||
<div class="absolute inset-0 bg-gray-100 dark:bg-gray-700 flex items-center justify-center text-4xl font-semibold text-gray-600 dark:text-gray-200">
|
||||
{{ $initial }}
|
||||
</div>
|
||||
{{ end }}
|
||||
</a>
|
||||
{{ end }}
|
||||
|
||||
<div class="p-6">
|
||||
<h3 class="text-xl font-bold text-gray-900 dark:text-white mb-1">
|
||||
<a href="{{ $link }}" class="hover:text-primary-600 dark:hover:text-primary-400 transition-colors">
|
||||
<a href="{{ $link }}" class="hover:text-primary-600 dark:hover:text-primary-400 transition-colors" aria-label="{{ $profile.title }}">
|
||||
{{ $profile.title }}
|
||||
</a>
|
||||
</h3>
|
||||
|
||||
{{ if $show_role }}
|
||||
<div class="text-gray-600 dark:text-gray-400 mb-3">
|
||||
{{ with $profile.role }}<p class="font-medium">{{ . }}</p>{{ end }}
|
||||
{{ $roleText := "" }}
|
||||
{{ with $profile.role }}{{ $roleText = . }}{{ end }}
|
||||
{{ if and (not $roleText) $profile.affiliations }}
|
||||
{{ range first 1 $profile.affiliations }}
|
||||
{{ $roleText = .name }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
{{ with $roleText }}<p class="font-medium">{{ . }}</p>{{ end }}
|
||||
{{ if and $show_organizations $profile.affiliations }}
|
||||
{{ range first 1 $profile.affiliations }}
|
||||
<p class="text-sm mt-1">{{ .name }}</p>
|
||||
@@ -112,10 +199,12 @@
|
||||
{{ if and $show_interests $profile.interests }}
|
||||
<div class="mb-4">
|
||||
<div class="flex flex-wrap gap-1">
|
||||
{{ range first 3 $profile.interests }}
|
||||
<span class="inline-block bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300 text-xs px-2 py-1 rounded">
|
||||
{{ . }}
|
||||
</span>
|
||||
{{ if gt $max_interests_int 0 }}
|
||||
{{ range first $max_interests_int $profile.interests }}
|
||||
<span class="inline-block bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300 text-xs px-2 py-1 rounded">
|
||||
{{ . }}
|
||||
</span>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
</div>
|
||||
</div>
|
||||
@@ -149,17 +238,49 @@
|
||||
</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
{{ else }}
|
||||
{{ $allProfiles = sort $allProfiles "title" }}
|
||||
<div class="grid grid-cols-1 gap-8 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
|
||||
{{ range $profile := $allProfiles }}
|
||||
{{ $isNumeric := or (eq $sort_field_lc "weight") (eq $sort_field_lc "graduation_year") }}
|
||||
{{ $order := cond (and (not $isNumeric) (not $sort_asc)) "desc" "asc" }}
|
||||
{{ $sorted := slice }}
|
||||
{{ range $allProfiles }}
|
||||
{{ $profile := .profile }}
|
||||
{{ $profileData := .profileData }}
|
||||
{{ $family := $profile.name_family_pref | default $profile.name_family | default (delimit (last 1 (split $profile.title " ")) "") | default $profile.title }}
|
||||
{{ $familyLower := lower $family }}
|
||||
{{ $raw := index $profile $sort_field | default (index $profileData $sort_field) }}
|
||||
{{ $primary := "" }}
|
||||
{{ if or (eq $sort_field_lc "name.family") (eq $sort_field_lc "family") (eq $sort_field_lc "last_name") (eq $sort_field_lc "name_family") (eq $sort_field_lc "family_name") }}
|
||||
{{ $primary = $familyLower }}
|
||||
{{ else if $isNumeric }}
|
||||
{{ $primary = or (and (ne $raw nil) (int $raw)) 999999 }}
|
||||
{{ else if ne $raw nil }}
|
||||
{{ $primary = lower (printf "%v" $raw) }}
|
||||
{{ else }}
|
||||
{{ $primary = lower $profile.title }}
|
||||
{{ end }}
|
||||
{{ $sortKey := "" }}
|
||||
{{ if $isNumeric }}
|
||||
{{ $num := $primary | default 999999 }}
|
||||
{{ if not $sort_asc }}{{ $num = sub 999999999 $num }}{{ end }}
|
||||
{{ $sortKey = printf "%010d|%s" $num $familyLower }}
|
||||
{{ else }}
|
||||
{{ $sortKey = printf "%s|%s" $primary $familyLower }}
|
||||
{{ end }}
|
||||
{{ $sorted = $sorted | append (dict "profile" $profile "sort_val" $sortKey) }}
|
||||
{{ end }}
|
||||
{{ $sorted = sort $sorted "sort_val" $order }}
|
||||
<div class="{{ $gridClass }}">
|
||||
{{ range $sortedProfile := $sorted }}
|
||||
{{ $profile := $sortedProfile.profile }}
|
||||
{{ $avatar := $profile.avatar }}
|
||||
{{ $link := printf "/authors/%s/" $profile.slug }}
|
||||
{{ $initial := upper (substr ($profile.name_given | default $profile.title) 0 1) }}
|
||||
<div class="group relative">
|
||||
<div class="relative overflow-hidden rounded-2xl bg-white dark:bg-gray-800 shadow-lg hover:shadow-xl transition-all duration-300 transform hover:-translate-y-1">
|
||||
{{ if $avatar }}
|
||||
<a href="{{ $link }}" class="block relative h-64 overflow-hidden">
|
||||
<a href="{{ $link }}" class="block relative h-64 overflow-hidden" aria-label="{{ $profile.title }}">
|
||||
{{ if $avatar }}
|
||||
{{ $image := $avatar.Fill "400x400 Center webp" }}
|
||||
<img src="{{ $image.RelPermalink }}"
|
||||
alt="{{ $profile.title }}"
|
||||
@@ -168,11 +289,15 @@
|
||||
loading="lazy"
|
||||
class="w-full h-full object-cover transition-transform duration-300 group-hover:scale-110">
|
||||
<div class="absolute inset-0 bg-gradient-to-t from-black/60 via-transparent to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-300"></div>
|
||||
{{ else }}
|
||||
<div class="absolute inset-0 bg-gray-100 dark:bg-gray-700 flex items-center justify-center text-4xl font-semibold text-gray-600 dark:text-gray-200">
|
||||
{{ $initial }}
|
||||
</div>
|
||||
{{ end }}
|
||||
</a>
|
||||
{{ end }}
|
||||
<div class="p-6">
|
||||
<h3 class="text-xl font-bold text-gray-900 dark:text-white mb-1">
|
||||
<a href="{{ $link }}" class="hover:text-primary-600 dark:hover:text-primary-400 transition-colors">
|
||||
<a href="{{ $link }}" class="hover:text-primary-600 dark:hover:text-primary-400 transition-colors" aria-label="{{ $profile.title }}">
|
||||
{{ $profile.title }}
|
||||
</a>
|
||||
</h3>
|
||||
@@ -192,8 +317,10 @@
|
||||
{{ if and $show_interests $profile.interests }}
|
||||
<div class="mb-4">
|
||||
<div class="flex flex-wrap gap-1">
|
||||
{{ range first 3 $profile.interests }}
|
||||
<span class="inline-block bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300 text-xs px-2 py-1 rounded">{{ . }}</span>
|
||||
{{ if gt $max_interests_int 0 }}
|
||||
{{ range first $max_interests_int $profile.interests }}
|
||||
<span class="inline-block bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300 text-xs px-2 py-1 rounded">{{ . }}</span>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
</div>
|
||||
</div>
|
||||
@@ -225,7 +352,7 @@
|
||||
|
||||
{{/* Call to Action */}}
|
||||
{{ with $block.content.cta }}
|
||||
<div class="mt-12 text-center">
|
||||
<div class="mt-12 {{ $ctaAlignClass }}">
|
||||
<a href="{{ .url }}" class="inline-flex items-center px-6 py-3 bg-primary-600 hover:bg-primary-700 text-white font-medium rounded-lg shadow-lg hover:shadow-xl transition-all duration-300">
|
||||
{{ .text }}
|
||||
{{ with .icon }}
|
||||
|
||||
@@ -32,11 +32,42 @@
|
||||
},
|
||||
"user_groups": {
|
||||
"type": "array",
|
||||
"description": "Filter to authors that belong to these user groups (defaults to all authors)",
|
||||
"description": "Filter to authors that belong to these user groups (defaults to all authors). Accepts strings or objects { name, sort_by?, sort_ascending? } for per-group sorting.",
|
||||
"items": {
|
||||
"type": "string"
|
||||
"oneOf": [
|
||||
{ "type": "string" },
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": { "type": "string" },
|
||||
"sort_by": {
|
||||
"type": "string",
|
||||
"description": "Per-group override of sort field"
|
||||
},
|
||||
"sort_ascending": {
|
||||
"type": "boolean",
|
||||
"description": "Per-group override of sort direction"
|
||||
}
|
||||
},
|
||||
"required": ["name"],
|
||||
"additionalProperties": false
|
||||
}
|
||||
]
|
||||
},
|
||||
"examples": [["Principal Investigators", "PhD Students"]]
|
||||
"examples": [
|
||||
["Principal Investigators", "PhD Students"],
|
||||
[{ "name": "Alumni", "sort_by": "graduation_year", "sort_ascending": false }, "Research Engineers"]
|
||||
]
|
||||
},
|
||||
"sort_by": {
|
||||
"type": "string",
|
||||
"description": "Field to sort authors by (e.g. 'name_family', 'graduation_year', 'weight'); the legacy 'Params.' prefix is optional for compatibility",
|
||||
"default": "name_family"
|
||||
},
|
||||
"sort_ascending": {
|
||||
"type": "boolean",
|
||||
"description": "Sort direction (true for ascending, false for descending)",
|
||||
"default": true
|
||||
},
|
||||
"cta": {
|
||||
"type": "object",
|
||||
@@ -73,7 +104,7 @@
|
||||
"show_organizations": {
|
||||
"type": "boolean",
|
||||
"description": "Display the first organization/affiliation per profile",
|
||||
"default": false
|
||||
"default": true
|
||||
},
|
||||
"show_interests": {
|
||||
"type": "boolean",
|
||||
@@ -84,6 +115,29 @@
|
||||
"type": "boolean",
|
||||
"description": "Show social media icons pulled from the author profile links",
|
||||
"default": true
|
||||
},
|
||||
"max_interests": {
|
||||
"type": "integer",
|
||||
"description": "Maximum number of interests to display per profile (0 hides interests; default 3)",
|
||||
"default": 3,
|
||||
"minimum": 0
|
||||
},
|
||||
"align": {
|
||||
"type": "string",
|
||||
"description": "Align the section header and CTA",
|
||||
"enum": ["center", "left"],
|
||||
"default": "center"
|
||||
},
|
||||
"max_columns": {
|
||||
"type": "integer",
|
||||
"description": "Maximum number of columns on wide screens (2-4)",
|
||||
"enum": [2, 3, 4],
|
||||
"default": 4
|
||||
},
|
||||
"show_empty_groups": {
|
||||
"type": "boolean",
|
||||
"description": "If true, show an empty-state message for groups with no matching authors",
|
||||
"default": false
|
||||
}
|
||||
},
|
||||
"additionalProperties": true
|
||||
|
||||
@@ -85,6 +85,9 @@ Output (dict):
|
||||
{{- $skills := $data.skills -}}
|
||||
{{- $languages := $data.languages -}}
|
||||
{{- $awards := $data.awards -}}
|
||||
{{- $graduationYear := $data.graduation_year -}}
|
||||
{{- $weight := $data.weight -}}
|
||||
{{- $nameFamily := $data.name.family | default $family -}}
|
||||
{{- $postnominals := slice -}}
|
||||
{{- if $data.postnominals -}}
|
||||
{{- if (reflect.IsSlice $data.postnominals) -}}
|
||||
@@ -103,6 +106,7 @@ Output (dict):
|
||||
"name_display" $display
|
||||
"name_given" $given
|
||||
"name_family" $family
|
||||
"name_family_pref" $nameFamily
|
||||
"name_middle" $middle
|
||||
"name_alternate" $alternate
|
||||
"name_pronunciation" $namePronunciation
|
||||
@@ -120,6 +124,8 @@ Output (dict):
|
||||
"skills" $skills
|
||||
"languages" $languages
|
||||
"awards" $awards
|
||||
"graduation_year" $graduationYear
|
||||
"weight" $weight
|
||||
"postnominals" $postnominals
|
||||
"is_owner" $isOwner
|
||||
"has_data" (not (eq $hasData nil))
|
||||
|
||||
Reference in new issue
Block a user