Add item slots, sizes, selectable and nested items to list group (#3272)

This commit is contained in:
Paweł Kuna authored and GitHub committed 2026-10-08 14:20:02 +02:00
1 parent 5aad08b7c1
commit 69c1114f7a
10 files changed
+898 -112

No files matched your search

+18 -33
View File
@@ -18,7 +18,7 @@ interface Props {
class?: string
}
const { offset = 0, hoverable = false, checkbox, checkedIds, title = 'Last commits', class: className } = Astro.props
const { offset = 0, hoverable = false, checkbox = false, checkedIds, title = 'Last commits', class: className } = Astro.props
const limit = 8
@@ -44,40 +44,25 @@ const rows = people.slice(offset, offset + limit).map((person, idx) => {
<div class="card-header">
<CardTitle>{title}</CardTitle>
</div>
<ListGroup flush hoverable={hoverable}>
<ListGroup flush hoverable={hoverable} selectable={checkbox}>
{
rows.map((row) => (
<ListGroupItem active={row.checked}>
<div class="row align-items-center">
{checkbox ? (
<div class="col-auto">
<input type="checkbox" class="form-check-input" checked={row.checked} />
</div>
) : (
<div class="col-auto">
<span class={`badge${row.color !== 'x' ? ` bg-${row.color}` : ''}`} />
</div>
)}
<div class="col-auto">
<a href="#" tabindex="-1" aria-hidden="true">
<Avatar person={row.person} />
</a>
</div>
<div class="col text-truncate">
<a href="#" class="text-reset d-block">
{row.person.full_name}
</a>
<div class="d-block text-secondary text-truncate mt-n1">{row.description}</div>
</div>
{hoverable && (
<div class="col-auto">
<a href="#" class={`list-group-item-actions${row.checked ? ' show' : ''}`} aria-label={row.checked ? 'Remove from favorites' : 'Add to favorites'}>
<Icon name="star" class={row.starColor} />
</a>
</div>
)}
</div>
<ListGroupItem active={!checkbox && row.checked} showActions={row.checked}>
<Fragment slot="media">
{checkbox ? <input type="checkbox" class="form-check-input list-group-selectable-check m-0" checked={row.checked} aria-label={`Select ${row.person.full_name}`} /> : <span class={`badge${row.color !== 'x' ? ` bg-${row.color}` : ''}`} />}
<a href="#" class="ms-3" tabindex="-1" aria-hidden="true">
<Avatar person={row.person} />
</a>
</Fragment>
<a href="#" class="list-group-item-title">
{row.person.full_name}
</a>
<div class="list-group-item-description">{row.description}</div>
{hoverable && (
<a href="#" slot="actions" aria-label={row.checked ? 'Remove from favorites' : 'Add to favorites'}>
<Icon name="star" class={row.starColor} />
</a>
)}
</ListGroupItem>
))
}
+7 -13
View File
@@ -39,19 +39,13 @@ const rows = sorted.map((person, idx) => {
<>
{row.header && <ListGroupHeader class="sticky-top">{row.header}</ListGroupHeader>}
<ListGroupItem>
<div class="row">
<div class="col-auto">
<a href="#" tabindex="-1" aria-hidden="true">
<Avatar person={row.person} />
</a>
</div>
<div class="col text-truncate">
<a href="#" class="text-body d-block">
{row.person.full_name}
</a>
<div class="text-secondary text-truncate mt-n1">{row.description}</div>
</div>
</div>
<a href="#" slot="media" tabindex="-1" aria-hidden="true">
<Avatar person={row.person} />
</a>
<a href="#" class="list-group-item-title">
{row.person.full_name}
</a>
<div class="list-group-item-description">{row.description}</div>
</ListGroupItem>
</>
))
+8 -2
View File
@@ -5,18 +5,24 @@ import type { HTMLTag } from 'astro/types'
type PolymorphicProps = { as?: HTMLTag }
/** core/scss/ui/_lists.scss `.list-group-{size}` density modifiers */
type ListGroupSize = 'sm' | 'lg'
interface Props extends PolymorphicProps {
size?: ListGroupSize | undefined
flush?: boolean
hoverable?: boolean
/** list-group-selectable: an item with a checked .list-group-selectable-check input gets the active look */
selectable?: boolean
transparent?: boolean
class?: string
/** remaining attributes (data-sortable, style, …) are forwarded to the element */
[key: string]: unknown
}
const { as: Element = 'div', flush, hoverable, transparent, class: className, ...rest }: Props = Astro.props
const { as: Element = 'div', size, flush, hoverable, selectable, transparent, class: className, ...rest }: Props = Astro.props
---
<Element class:list={['list-group', flush && 'list-group-flush', hoverable && 'list-group-hoverable', transparent && 'list-group-transparent', className]} {...rest}>
<Element class:list={['list-group', size && `list-group-${size}`, flush && 'list-group-flush', hoverable && 'list-group-hoverable', selectable && 'list-group-selectable', transparent && 'list-group-transparent', className]} {...rest}>
<slot />
</Element>
+42 -3
View File
@@ -1,7 +1,8 @@
---
import type { HTMLTag } from 'astro/types'
// List group item (.list-group-item).
// List group item (.list-group-item). With a title, a description or a named
// slot it renders the slot layout: media, content, meta and actions in a row.
type PolymorphicProps = { as?: HTMLTag }
@@ -10,14 +11,52 @@ interface Props extends PolymorphicProps {
action?: boolean
active?: boolean
disabled?: boolean
/** first line of the content slot (.list-group-item-title) */
title?: string | undefined
/** second line of the content slot (.list-group-item-description) */
description?: string | undefined
/** keeps the actions slot visible in a hoverable list (.show) */
showActions?: boolean
class?: string
/** remaining attributes (href, data-*, …) are forwarded to the element */
[key: string]: unknown
}
const { as: Element = 'div', action, active, disabled, class: className, ...rest }: Props = Astro.props
const { as: Element = 'div', action, active, disabled, title, description, showActions, class: className, ...rest }: Props = Astro.props
const hasMedia = Astro.slots.has('media')
const hasMeta = Astro.slots.has('meta')
const hasActions = Astro.slots.has('actions')
const hasSlots = Boolean(title || description || hasMedia || hasMeta || hasActions)
---
<Element class:list={['list-group-item', action && 'list-group-item-action', active && 'active', disabled && 'disabled', className]} aria-disabled={disabled ? 'true' : undefined} tabindex={Element === 'a' && disabled ? -1 : undefined} {...rest}>
<slot />
{
hasSlots ? (
<>
{hasMedia && (
<div class="list-group-item-media">
<slot name="media" />
</div>
)}
<div class="list-group-item-content">
{title && <div class="list-group-item-title">{title}</div>}
{description && <div class="list-group-item-description">{description}</div>}
<slot />
</div>
{hasMeta && (
<div class="list-group-item-meta">
<slot name="meta" />
</div>
)}
{hasActions && (
<div class:list={['list-group-item-actions', showActions && 'show']}>
<slot name="actions" />
</div>
)}
</>
) : (
<slot />
)
}
</Element>
+1 -1
View File
@@ -1,6 +1,6 @@
---
interface Props {
color?: string
color?: string | undefined
animated?: boolean
/** accessible name (visually-hidden text inside the dot) */
ariaLabel?: string