--- import DefaultLayout from '@shared/layouts/DefaultLayout.astro' import Card from '@ui/Card.astro' import CardBody from '@ui/CardBody.astro' import CardHeader from '@ui/CardHeader.astro' import CardTitle from '@ui/CardTitle.astro' import CardSubtitle from '@ui/CardSubtitle.astro' import CardDropdown from '@ui/CardDropdown.astro' import UsersList from '@shared/components/cards/UsersList.astro' import UsersList2 from '@shared/components/cards/UsersList2.astro' import UsersListHeaders from '@shared/components/cards/UsersListHeaders.astro' import ListGroup from '@ui/ListGroup.astro' import ListGroupItem from '@ui/ListGroupItem.astro' import Avatar from '@ui/Avatar.astro' import Badge from '@ui/Badge.astro' import Flag from '@ui/Flag.astro' import Icon from '@ui/Icon.astro' import Progress from '@ui/Progress.astro' import StatusDot from '@ui/StatusDot.astro' import DocsLink from '@ui/DocsLink.astro' import Empty from '@ui/Empty.astro' import { people } from '@shared/lib/people' import type { FlagCountry } from '@shared/lib/tokens' import transactionsData from '@data/transactions.json' import notificationsData from '@data/notifications.json' const team = people.slice(0, 4) const folders = [ { icon: 'inbox', title: 'Inbox', description: 'Messages waiting for a reply', count: 12 }, { icon: 'send', title: 'Sent', description: 'Everything you have sent', count: 148 }, { icon: 'archive', title: 'Archive', description: 'Closed conversations', count: 2304 }, { icon: 'trash', title: 'Trash', description: 'Removed after 30 days', count: 7 }, ] const plans = [ { id: 'starter', title: 'Starter', description: 'Up to 3 projects', price: '$9' }, { id: 'team', title: 'Team', description: 'Unlimited projects, 10 members', price: '$29' }, { id: 'business', title: 'Business', description: 'Unlimited members and SSO', price: '$79' }, ] const channels = [ { id: 'email', icon: 'mail', title: 'Email', description: 'A summary every morning', checked: true }, { id: 'push', icon: 'device-mobile', title: 'Push', description: 'Mentions and direct messages', checked: true }, { id: 'slack', icon: 'brand-slack', title: 'Slack', description: 'Posts to the #updates channel', checked: false }, ] const comments = [ { text: 'The new list layout works well on the dashboard. Can we use the same slots for the search results?', time: '2 h' }, { text: 'Yes, the results only need a wrapped description and the avatar aligned to the top.', time: '1 h' }, { text: 'Done. I also added the nested items to the sidebar.', time: '12 min' }, ] const tree = [ { id: 'list-nested-projects', icon: 'folder', title: 'Projects', open: true, items: ['Website redesign', 'Mobile app', 'Design system'] }, { id: 'list-nested-teams', icon: 'users', title: 'Teams', open: false, items: ['Design', 'Engineering'] }, ] const sizes = [ { size: undefined, title: 'Default', subtitle: 'Without a size class the items use the default padding.' }, { size: 'sm', title: 'Small', subtitle: 'Add list-group-sm for a compact list.' }, { size: 'lg', title: 'Large', subtitle: 'Add list-group-lg for more room around each item.' }, ] as const const files = [ { icon: 'file-type-pdf', name: 'Annual report 2025.pdf', size: '2.4 MB' }, { icon: 'file-spreadsheet', name: 'Budget forecast.xlsx', size: '318 kB' }, { icon: 'photo', name: 'Office photos.zip', size: '48 MB' }, { icon: 'file-text', name: 'Meeting notes.md', size: '4 kB' }, ] const visits: { country: string; flag: FlagCountry; value: number }[] = [ { country: 'United States', flag: 'us', value: 64 }, { country: 'Germany', flag: 'de', value: 48 }, { country: 'Poland', flag: 'pl', value: 31 }, { country: 'Japan', flag: 'jp', value: 17 }, ] // Rows of transactions.json whose logo exists in static/. const transactionIds = ['3', '4', '9', '11'] const transactions = (transactionsData as { id: string; counterparty: string; counterparty_logo: string; description: string; amount: number }[]) .filter((item) => transactionIds.includes(item.id)) .map((item) => ({ ...item, amountLabel: `${item.amount < 0 ? '−' : '+'}${Math.abs(item.amount).toLocaleString('en-US', { style: 'currency', currency: 'USD' })}`, })) const notifications = (notificationsData as { title: string; description: string; status: string }[]).slice(0, 4).map((item, index) => ({ ...item, unread: item.status === 'unread', time: `${(index + 1) * 2} h`, })) ---
Text only A plain list-group-item holds one line of text. {folders.map((item) => {item.title})}
Title and description Put list-group-item-title and list-group-item-description in list-group-item-content. {folders.map((item) => )}
With icon The list-group-item-media slot holds an icon before the content. { folders.map((item) => ( )) }
With avatar The media slot takes an avatar the same way. { team.map((person) => ( )) }
With meta Use list-group-item-meta for a time, a count or a badge at the end. { folders.map((item) => ( {item.count} )) }
All slots Media, content, meta and list-group-item-actions in one item. { team.map((person, index) => ( {index === 0 ? : `${index * 3} d`} )) }
Links Add list-group-item-action to a link item. It supports active and disabled. { folders.map((item, index) => ( )) }
Actions on hover In a list-group-hoverable list the actions show on hover. Add show to keep them visible. { team.map((person, index) => ( )) }
Buttons The actions slot holds any control, for example a button. { team.map((person, index) => ( )) }
Checkboxes In a list-group-selectable list a checked list-group-selectable-check marks its item. { team.map((person, index) => ( )) }
Radios Radio inputs with one name make a single choice list. Use a label as the item. { plans.map((plan, index) => ( {plan.price} )) }
Switches Put a switch in the actions slot to build a settings list. { channels.map((channel) => ( )) }
Wrapped description Add align-items-start to the item and text-wrap to the description for longer text. { comments.map((comment, index) => (
{comment.text}
{comment.time}
)) }
Nested items Put list-group-nested on a collapse after the parent item. list-group-item-toggle turns the chevron. { tree.map((group) => ( <>
{group.items.map((item) => ( {item} ))}
)) }
Drag handle Add data-sortable to the list and put the handle in the media slot. { team.map((person) => ( )) }
{ sizes.map((item) => (
{item.title} {team.map((person) => ( ))}
)) }
{ notifications.map((item) => ( {item.time} )) }
{ transactions.map((item) => ( {item.amountLabel} )) }
{ files.map((file) => ( )) }
{ visits.map((item) => ( {item.value}% )) }
Inside a card body Without list-group-flush the list keeps its own border and rounded corners. { files.slice(0, 3).map((file) => ( {file.size} )) }