mirror of
https://github.com/tabler/tabler.git
synced 2026-08-29 13:21:29 +04:00
Fix Prettier formatting regression and Astro type-check errors
`Render Modal as real markup` (#2742) reformatted ~140 files back to tabs/semicolons, breaking the Prettier config added by the Astro quality gates (#2749). Re-run prettier --write to restore it, and fix the type errors it had been masking: HTMLElement casts and `this` typing in ChangePasswordModalContent/ConfirmDeleteModalContent scripts, plain-JS syntax in the inline-injected progress.astro script, narrowed prop unions in Button/Check/Spinner, nullable icon svg casts in Icons/Rating, and a duplicate firstLetters/invalid `placeholder` attribute in Select.astro. Also fix shared/vitest.config.mts's stale `astro/lib/**/*.test.ts` include glob left over from the shared source restructure, which made `pnpm test` report zero test files.
This commit is contained in:
@@ -1,30 +1,30 @@
|
||||
---
|
||||
import Button from '@ui/Button.astro';
|
||||
import Avatar from '@ui/Avatar.astro';
|
||||
import CardTitle from '@ui/CardTitle.astro';
|
||||
import people from '@data/people.json';
|
||||
import Button from '@ui/Button.astro'
|
||||
import Avatar from '@ui/Avatar.astro'
|
||||
import CardTitle from '@ui/CardTitle.astro'
|
||||
import people from '@data/people.json'
|
||||
|
||||
const person = people[0];
|
||||
const person = people[0]
|
||||
---
|
||||
|
||||
<form class="card card-md" action="./" method="get" autocomplete="off" novalidate>
|
||||
<div class="card-body text-center">
|
||||
<div class="mb-4">
|
||||
<CardTitle as="h2">Account Locked</CardTitle>
|
||||
<p class="text-secondary">Please enter your password to unlock your account</p>
|
||||
</div>
|
||||
<div class="card-body text-center">
|
||||
<div class="mb-4">
|
||||
<CardTitle as="h2">Account Locked</CardTitle>
|
||||
<p class="text-secondary">Please enter your password to unlock your account</p>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<Avatar person={person} size="xl" class="mb-3" />
|
||||
<h3>{person.full_name}</h3>
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<Avatar person={person} size="xl" class="mb-3" />
|
||||
<h3>{person.full_name}</h3>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<input type="password" class="form-control" placeholder="Password…" />
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<input type="password" class="form-control" placeholder="Password…" />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Button color="primary" block icon="lock-open" text="Unlock" />
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<Button color="primary" block icon="lock-open" text="Unlock" />
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
---
|
||||
import CardStamp from '@ui/CardStamp.astro';
|
||||
import CardTitle from '@ui/CardTitle.astro';
|
||||
import CardStamp from '@ui/CardStamp.astro'
|
||||
import CardTitle from '@ui/CardTitle.astro'
|
||||
---
|
||||
|
||||
<div class="card bg-primary text-primary-fg">
|
||||
<CardStamp icon="star" color="white" textColor="primary" />
|
||||
<div class="card-body">
|
||||
<CardTitle>Card with background and icon</CardTitle>
|
||||
<p>
|
||||
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Architecto at consectetur culpa ducimus eum fuga fugiat,
|
||||
ipsa iusto, modi nostrum recusandae reiciendis saepe.
|
||||
</p>
|
||||
</div>
|
||||
<CardStamp icon="star" color="white" textColor="primary" />
|
||||
<div class="card-body">
|
||||
<CardTitle>Card with background and icon</CardTitle>
|
||||
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Architecto at consectetur culpa ducimus eum fuga fugiat, ipsa iusto, modi nostrum recusandae reiciendis saepe.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,32 +1,29 @@
|
||||
---
|
||||
import photos from '@data/photos.json';
|
||||
import photos from '@data/photos.json'
|
||||
|
||||
interface Props {
|
||||
title?: string;
|
||||
right?: boolean;
|
||||
imgId?: number;
|
||||
title?: string
|
||||
right?: boolean
|
||||
imgId?: number
|
||||
}
|
||||
|
||||
const { title, right, imgId = 1 } = Astro.props;
|
||||
const { title, right, imgId = 1 } = Astro.props
|
||||
|
||||
const photo = (photos as { file: string; title: string }[])[imgId];
|
||||
const imgClass = `w-100 h-100 object-cover ${right ? 'card-img-end' : 'card-img-start'}`;
|
||||
const photo = (photos as { file: string; title: string }[])[imgId]
|
||||
const imgClass = `w-100 h-100 object-cover ${right ? 'card-img-end' : 'card-img-start'}`
|
||||
---
|
||||
|
||||
<div class="card">
|
||||
<div class="row row-0">
|
||||
<div class={`col-3${right ? ' order-md-last' : ''}`}>
|
||||
<!-- Photo -->
|
||||
<img src={`./static/photos/${photo.file}`} class={imgClass} alt={photo.title} />
|
||||
</div>
|
||||
<div class="col">
|
||||
<div class="card-body">
|
||||
{title && <h3 class="card-title" set:html={title} />}
|
||||
<p class="text-secondary">
|
||||
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aperiam deleniti fugit incidunt, iste, itaque minima
|
||||
neque pariatur perferendis sed suscipit velit vitae voluptatem.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row row-0">
|
||||
<div class={`col-3${right ? ' order-md-last' : ''}`}>
|
||||
<!-- Photo -->
|
||||
<img src={`./static/photos/${photo.file}`} class={imgClass} alt={photo.title} />
|
||||
</div>
|
||||
<div class="col">
|
||||
<div class="card-body">
|
||||
{title && <h3 class="card-title" set:html={title} />}
|
||||
<p class="text-secondary">Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aperiam deleniti fugit incidunt, iste, itaque minima neque pariatur perferendis sed suscipit velit vitae voluptatem.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
---
|
||||
import CardTitle from '@ui/CardTitle.astro';
|
||||
import CardTitle from '@ui/CardTitle.astro'
|
||||
---
|
||||
|
||||
<div class="card">
|
||||
<div class="ribbon bg-red">NEW</div>
|
||||
<div class="card-body">
|
||||
<CardTitle>Card with text ribbon</CardTitle>
|
||||
<p class="text-secondary">
|
||||
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Architecto at consectetur culpa ducimus eum fuga fugiat,
|
||||
ipsa iusto, modi nostrum recusandae reiciendis saepe.
|
||||
</p>
|
||||
</div>
|
||||
<div class="ribbon bg-red">NEW</div>
|
||||
<div class="card-body">
|
||||
<CardTitle>Card with text ribbon</CardTitle>
|
||||
<p class="text-secondary">Lorem ipsum dolor sit amet, consectetur adipisicing elit. Architecto at consectetur culpa ducimus eum fuga fugiat, ipsa iusto, modi nostrum recusandae reiciendis saepe.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
---
|
||||
import Icon from '@ui/Icon.astro';
|
||||
import CardTitle from '@ui/CardTitle.astro';
|
||||
import Icon from '@ui/Icon.astro'
|
||||
import CardTitle from '@ui/CardTitle.astro'
|
||||
---
|
||||
|
||||
<div class="card">
|
||||
<div class="ribbon ribbon-top bg-yellow"><Icon name="star" /></div>
|
||||
<div class="card-body">
|
||||
<CardTitle>Card with top ribbon</CardTitle>
|
||||
<p class="text-secondary">
|
||||
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Architecto at consectetur culpa ducimus eum fuga fugiat,
|
||||
ipsa iusto, modi nostrum recusandae reiciendis saepe.
|
||||
</p>
|
||||
</div>
|
||||
<div class="ribbon ribbon-top bg-yellow"><Icon name="star" /></div>
|
||||
<div class="card-body">
|
||||
<CardTitle>Card with top ribbon</CardTitle>
|
||||
<p class="text-secondary">Lorem ipsum dolor sit amet, consectetur adipisicing elit. Architecto at consectetur culpa ducimus eum fuga fugiat, ipsa iusto, modi nostrum recusandae reiciendis saepe.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,33 +1,28 @@
|
||||
---
|
||||
// Tabs/content blocks are HTML strings (different order when `bottom`).
|
||||
interface Props {
|
||||
/** tabs-count — present but unused */
|
||||
count?: number;
|
||||
id?: string;
|
||||
bottom?: boolean;
|
||||
borderless?: boolean;
|
||||
/** tabs-count — present but unused */
|
||||
count?: number
|
||||
id?: string
|
||||
bottom?: boolean
|
||||
borderless?: boolean
|
||||
}
|
||||
|
||||
const { id = 'top', bottom, borderless } = Astro.props;
|
||||
const { id = 'top', bottom, borderless } = Astro.props
|
||||
|
||||
const tabs = ['Activity', 'Profile', 'Settings'];
|
||||
const tabs = ['Activity', 'Profile', 'Settings']
|
||||
|
||||
const tabsHtml = `
|
||||
<!-- Cards navigation -->
|
||||
<ul class="nav nav-tabs${bottom ? ' nav-tabs-bottom' : ''}">
|
||||
${tabs
|
||||
.map(
|
||||
(tab, i) =>
|
||||
` <li class="nav-item"><a href="#tab-${id}-${i + 1}" class="nav-link${i === 0 ? ' active' : ''}" data-bs-toggle="tab">${tab}</a></li>`,
|
||||
)
|
||||
.join('\n')}
|
||||
</ul>`;
|
||||
${tabs.map((tab, i) => ` <li class="nav-item"><a href="#tab-${id}-${i + 1}" class="nav-link${i === 0 ? ' active' : ''}" data-bs-toggle="tab">${tab}</a></li>`).join('\n')}
|
||||
</ul>`
|
||||
|
||||
const tabsContentHtml = `
|
||||
<div class="tab-content">
|
||||
${tabs
|
||||
.map(
|
||||
(tab, i) => ` <!-- Content of card #${tab} -->
|
||||
.map(
|
||||
(tab, i) => ` <!-- Content of card #${tab} -->
|
||||
<div id="tab-${id}-${i + 1}" class="card tab-pane${i === 0 ? ' active show' : ''}">
|
||||
<div class="card-body">
|
||||
<div class="card-title">${tab}</div>
|
||||
@@ -36,24 +31,24 @@ ${tabs
|
||||
</p>
|
||||
</div>
|
||||
</div>`,
|
||||
)
|
||||
.join('\n')}
|
||||
</div>`;
|
||||
)
|
||||
.join('\n')}
|
||||
</div>`
|
||||
---
|
||||
|
||||
<!-- Cards with tabs component -->
|
||||
<div class={`card-tabs${borderless ? ' border-0' : ''}`}>
|
||||
{
|
||||
bottom ? (
|
||||
<Fragment>
|
||||
<Fragment set:html={tabsContentHtml} />
|
||||
<Fragment set:html={tabsHtml} />
|
||||
</Fragment>
|
||||
) : (
|
||||
<Fragment>
|
||||
<Fragment set:html={tabsHtml} />
|
||||
<Fragment set:html={tabsContentHtml} />
|
||||
</Fragment>
|
||||
)
|
||||
}
|
||||
{
|
||||
bottom ? (
|
||||
<Fragment>
|
||||
<Fragment set:html={tabsContentHtml} />
|
||||
<Fragment set:html={tabsHtml} />
|
||||
</Fragment>
|
||||
) : (
|
||||
<Fragment>
|
||||
<Fragment set:html={tabsHtml} />
|
||||
<Fragment set:html={tabsContentHtml} />
|
||||
</Fragment>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
|
||||
@@ -1,103 +1,80 @@
|
||||
---
|
||||
// Photos filtered to horizontal=true, then sliced by offset/limit.
|
||||
import photos from '@data/photos.json';
|
||||
import CardTitle from '@ui/CardTitle.astro';
|
||||
import photos from '@data/photos.json'
|
||||
import CardTitle from '@ui/CardTitle.astro'
|
||||
|
||||
interface Props {
|
||||
id?: string;
|
||||
title?: string;
|
||||
indicators?: boolean;
|
||||
indicatorsThumb?: boolean;
|
||||
indicatorsThumbRatio?: boolean;
|
||||
indicatorsDot?: boolean;
|
||||
indicatorsVertical?: boolean;
|
||||
controls?: boolean;
|
||||
captions?: boolean;
|
||||
offset?: number;
|
||||
limit?: number;
|
||||
fade?: boolean;
|
||||
id?: string
|
||||
title?: string
|
||||
indicators?: boolean
|
||||
indicatorsThumb?: boolean
|
||||
indicatorsThumbRatio?: boolean
|
||||
indicatorsDot?: boolean
|
||||
indicatorsVertical?: boolean
|
||||
controls?: boolean
|
||||
captions?: boolean
|
||||
offset?: number
|
||||
limit?: number
|
||||
fade?: boolean
|
||||
}
|
||||
|
||||
const {
|
||||
id: idProp,
|
||||
title = 'Carousel',
|
||||
indicators,
|
||||
indicatorsThumb,
|
||||
indicatorsThumbRatio,
|
||||
indicatorsDot,
|
||||
indicatorsVertical,
|
||||
controls,
|
||||
captions,
|
||||
offset = 0,
|
||||
limit = 5,
|
||||
fade,
|
||||
} = Astro.props;
|
||||
const { id: idProp, title = 'Carousel', indicators, indicatorsThumb, indicatorsThumbRatio, indicatorsDot, indicatorsVertical, controls, captions, offset = 0, limit = 5, fade } = Astro.props
|
||||
|
||||
const carouselId = idProp ?? 'carousel';
|
||||
const filteredPhotos = (photos as { file: string; horizontal?: boolean }[]).filter((p) => p.horizontal);
|
||||
const slides = filteredPhotos.slice(offset, offset + limit);
|
||||
const carouselId = idProp ?? 'carousel'
|
||||
const filteredPhotos = (photos as { file: string; horizontal?: boolean }[]).filter((p) => p.horizontal)
|
||||
const slides = filteredPhotos.slice(offset, offset + limit)
|
||||
---
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<CardTitle>{title}</CardTitle>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div id={`carousel-${carouselId}`} class={`carousel slide${fade ? ' carousel-fade' : ''}`} data-bs-ride="carousel">
|
||||
{
|
||||
indicators && (
|
||||
<div
|
||||
class={`carousel-indicators${indicatorsVertical ? ' carousel-indicators-vertical' : ''}${
|
||||
indicatorsDot ? ' carousel-indicators-dot' : indicatorsThumb ? ' carousel-indicators-thumb' : ''
|
||||
}`}
|
||||
>
|
||||
{slides.map((photo, i) => (
|
||||
<button
|
||||
type="button"
|
||||
data-bs-target={`#carousel-${carouselId}`}
|
||||
data-bs-slide-to={`${i}`}
|
||||
class={`${indicatorsThumbRatio ? ' ratio ratio-4x3' : ''}${i === 0 ? ' active' : ''}`}
|
||||
style={indicatorsThumb ? `background-image: url(./static/photos/${photo.file})` : undefined}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
<div class="card-header">
|
||||
<CardTitle>{title}</CardTitle>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div id={`carousel-${carouselId}`} class={`carousel slide${fade ? ' carousel-fade' : ''}`} data-bs-ride="carousel">
|
||||
{
|
||||
indicators && (
|
||||
<div class={`carousel-indicators${indicatorsVertical ? ' carousel-indicators-vertical' : ''}${indicatorsDot ? ' carousel-indicators-dot' : indicatorsThumb ? ' carousel-indicators-thumb' : ''}`}>
|
||||
{slides.map((photo, i) => (
|
||||
<button type="button" data-bs-target={`#carousel-${carouselId}`} data-bs-slide-to={`${i}`} class={`${indicatorsThumbRatio ? ' ratio ratio-4x3' : ''}${i === 0 ? ' active' : ''}`} style={indicatorsThumb ? `background-image: url(./static/photos/${photo.file})` : undefined} />
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
<div class="carousel-inner">
|
||||
{
|
||||
slides.map((photo, i) => (
|
||||
<div class={`carousel-item${i === 0 ? ' active' : ''}`}>
|
||||
<img class="d-block w-100" alt="" src={`./static/photos/${photo.file}`} />
|
||||
<div class="carousel-inner">
|
||||
{
|
||||
slides.map((photo, i) => (
|
||||
<div class={`carousel-item${i === 0 ? ' active' : ''}`}>
|
||||
<img class="d-block w-100" alt="" src={`./static/photos/${photo.file}`} />
|
||||
|
||||
{captions && (
|
||||
<Fragment>
|
||||
<div class="carousel-caption-background d-none d-md-block" />
|
||||
<div class="carousel-caption d-none d-md-block">
|
||||
<h3>Slide label</h3>
|
||||
<p>Nulla vitae elit libero, a pharetra augue mollis interdum.</p>
|
||||
</div>
|
||||
</Fragment>
|
||||
)}
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
{captions && (
|
||||
<Fragment>
|
||||
<div class="carousel-caption-background d-none d-md-block" />
|
||||
<div class="carousel-caption d-none d-md-block">
|
||||
<h3>Slide label</h3>
|
||||
<p>Nulla vitae elit libero, a pharetra augue mollis interdum.</p>
|
||||
</div>
|
||||
</Fragment>
|
||||
)}
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
|
||||
{
|
||||
controls && (
|
||||
<Fragment>
|
||||
<a class="carousel-control-prev" href={`#carousel-${carouselId}`} role="button" data-bs-slide="prev">
|
||||
<span class="carousel-control-prev-icon" aria-hidden="true" />
|
||||
<span class="visually-hidden">Previous</span>
|
||||
</a>
|
||||
<a class="carousel-control-next" href={`#carousel-${carouselId}`} role="button" data-bs-slide="next">
|
||||
<span class="carousel-control-next-icon" aria-hidden="true" />
|
||||
<span class="visually-hidden">Next</span>
|
||||
</a>
|
||||
</Fragment>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
{
|
||||
controls && (
|
||||
<Fragment>
|
||||
<a class="carousel-control-prev" href={`#carousel-${carouselId}`} role="button" data-bs-slide="prev">
|
||||
<span class="carousel-control-prev-icon" aria-hidden="true" />
|
||||
<span class="visually-hidden">Previous</span>
|
||||
</a>
|
||||
<a class="carousel-control-next" href={`#carousel-${carouselId}`} role="button" data-bs-slide="next">
|
||||
<span class="carousel-control-next-icon" aria-hidden="true" />
|
||||
<span class="visually-hidden">Next</span>
|
||||
</a>
|
||||
</Fragment>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
// Raw CSS text inside .card-code (no highlight markup).
|
||||
import CardTitle from '@ui/CardTitle.astro';
|
||||
import CardTitle from '@ui/CardTitle.astro'
|
||||
|
||||
const code = `
|
||||
.card-footer {
|
||||
@@ -10,12 +10,12 @@ const code = `
|
||||
border-radius: 0 0 1 2;
|
||||
}
|
||||
}
|
||||
`;
|
||||
`
|
||||
---
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<CardTitle>Card with code</CardTitle>
|
||||
</div>
|
||||
<div class="card-code">{code}</div>
|
||||
<div class="card-header">
|
||||
<CardTitle>Card with code</CardTitle>
|
||||
</div>
|
||||
<div class="card-code">{code}</div>
|
||||
</div>
|
||||
|
||||
@@ -1,58 +1,50 @@
|
||||
---
|
||||
import Button from '@ui/Button.astro';
|
||||
import FormGroup from '@ui/FormGroup.astro';
|
||||
import Button from '@ui/Button.astro'
|
||||
import FormGroup from '@ui/FormGroup.astro'
|
||||
|
||||
const months = Array.from({ length: 12 }, (_, i) => i + 1);
|
||||
const years = Array.from({ length: 11 }, (_, i) => 2020 + i);
|
||||
const months = Array.from({ length: 12 }, (_, i) => i + 1)
|
||||
const years = Array.from({ length: 11 }, (_, i) => 2020 + i)
|
||||
---
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div class="mb-3">
|
||||
<div class="form-label">Card number</div>
|
||||
<input
|
||||
type="text"
|
||||
name="input-mask"
|
||||
class="form-control"
|
||||
data-mask="0000 0000 0000 0000"
|
||||
data-mask-visible="true"
|
||||
placeholder="0000 0000 0000 0000"
|
||||
autocomplete="off"
|
||||
/>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="mb-3">
|
||||
<div class="form-label">Card number</div>
|
||||
<input type="text" name="input-mask" class="form-control" data-mask="0000 0000 0000 0000" data-mask-visible="true" placeholder="0000 0000 0000 0000" autocomplete="off" />
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<div class="form-label">Card name</div>
|
||||
<input type="text" class="form-control" />
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<div class="form-label">Card name</div>
|
||||
<input type="text" class="form-control" />
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-8">
|
||||
<FormGroup label="Expiration date">
|
||||
<div class="row g-2">
|
||||
<div class="col">
|
||||
<select class="form-select">
|
||||
{months.map((month) => <option value={month}>{month}</option>)}
|
||||
</select>
|
||||
</div>
|
||||
<div class="col">
|
||||
<select class="form-select">
|
||||
{years.map((year) => <option value={year}>{year}</option>)}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</FormGroup>
|
||||
</div>
|
||||
<div class="col">
|
||||
<div class="mb-3">
|
||||
<div class="form-label">CVV</div>
|
||||
<input type="number" class="form-control" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-8">
|
||||
<FormGroup label="Expiration date">
|
||||
<div class="row g-2">
|
||||
<div class="col">
|
||||
<select class="form-select">
|
||||
{months.map((month) => <option value={month}>{month}</option>)}
|
||||
</select>
|
||||
</div>
|
||||
<div class="col">
|
||||
<select class="form-select">
|
||||
{years.map((year) => <option value={year}>{year}</option>)}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</FormGroup>
|
||||
</div>
|
||||
<div class="col">
|
||||
<div class="mb-3">
|
||||
<div class="form-label">CVV</div>
|
||||
<input type="number" class="form-control" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-2">
|
||||
<Button text="Pay now" color="primary" block />
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-2">
|
||||
<Button text="Pay now" color="primary" block />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,59 +1,52 @@
|
||||
---
|
||||
import Subheader from '@ui/Subheader.astro';
|
||||
import Icon from '@ui/Icon.astro';
|
||||
import Trending from '@ui/Trending.astro';
|
||||
import Subheader from '@ui/Subheader.astro'
|
||||
import Icon from '@ui/Icon.astro'
|
||||
import Trending from '@ui/Trending.astro'
|
||||
|
||||
interface Props {
|
||||
color?: string;
|
||||
icon?: string;
|
||||
/** lt — light avatar background (bg-{color}-lt) instead of bg-{color} text-white */
|
||||
lt?: boolean;
|
||||
title?: string;
|
||||
description?: string;
|
||||
/** change-value — passed to Trending (keeps its +/- sign) */
|
||||
changeValue?: string;
|
||||
/** change-value-unit — trending unit; defaults to '%' in Trending when unset */
|
||||
changeValueUnit?: string;
|
||||
class?: string;
|
||||
color?: string
|
||||
icon?: string
|
||||
/** lt — light avatar background (bg-{color}-lt) instead of bg-{color} text-white */
|
||||
lt?: boolean
|
||||
title?: string
|
||||
description?: string
|
||||
/** change-value — passed to Trending (keeps its +/- sign) */
|
||||
changeValue?: string
|
||||
/** change-value-unit — trending unit; defaults to '%' in Trending when unset */
|
||||
changeValueUnit?: string
|
||||
class?: string
|
||||
}
|
||||
|
||||
const {
|
||||
color,
|
||||
icon,
|
||||
lt,
|
||||
title = '1700',
|
||||
description = 'Users',
|
||||
changeValue,
|
||||
changeValueUnit,
|
||||
class: className,
|
||||
} = Astro.props;
|
||||
const { color, icon, lt, title = '1700', description = 'Users', changeValue, changeValueUnit, class: className } = Astro.props
|
||||
|
||||
const avatarClass = [color ? `bg-${color}${lt ? '-lt' : ' text-white'}` : '', 'avatar', 'avatar-square'];
|
||||
const avatarClass = [color ? `bg-${color}${lt ? '-lt' : ' text-white'}` : '', 'avatar', 'avatar-square']
|
||||
---
|
||||
|
||||
<div class:list={['card card-sm', className]}>
|
||||
<div class="card-body p-3">
|
||||
<div class="row align-items-center">
|
||||
{
|
||||
icon && (
|
||||
<div class="col-auto">
|
||||
<span class:list={avatarClass}><Icon name={icon} /></span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
<div class="card-body p-3">
|
||||
<div class="row align-items-center">
|
||||
{
|
||||
icon && (
|
||||
<div class="col-auto">
|
||||
<span class:list={avatarClass}>
|
||||
<Icon name={icon} />
|
||||
</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
<div class="col">
|
||||
<Subheader>
|
||||
{title}
|
||||
</Subheader>
|
||||
<div class="h3 m-0 p-0">
|
||||
{description}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col">
|
||||
<Subheader>
|
||||
{title}
|
||||
</Subheader>
|
||||
<div class="h3 m-0 p-0">
|
||||
{description}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-auto">
|
||||
<Trending value={changeValue as unknown as number} unit={changeValueUnit} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<Trending value={changeValue as unknown as number} unit={changeValueUnit} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
---
|
||||
import AvatarList from '@ui/AvatarList.astro';
|
||||
import Button from '@ui/Button.astro';
|
||||
import CardTitle from '@ui/CardTitle.astro';
|
||||
import AvatarList from '@ui/AvatarList.astro'
|
||||
import Button from '@ui/Button.astro'
|
||||
import CardTitle from '@ui/CardTitle.astro'
|
||||
---
|
||||
|
||||
<div class="card card-lg card-dashed card-transparent">
|
||||
<div class="card-body text-center">
|
||||
<div class="mb-3">
|
||||
<AvatarList stacked={true} />
|
||||
</div>
|
||||
<CardTitle>No Team Members</CardTitle>
|
||||
<p class="text-secondary">Invite your team to<br />collaborate on this project.</p>
|
||||
<div class="mt-4">
|
||||
<Button text="Invite Members" color="primary" icon="plus" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body text-center">
|
||||
<div class="mb-3">
|
||||
<AvatarList stacked={true} />
|
||||
</div>
|
||||
<CardTitle>No Team Members</CardTitle>
|
||||
<p class="text-secondary">Invite your team to<br />collaborate on this project.</p>
|
||||
<div class="mt-4">
|
||||
<Button text="Invite Members" color="primary" icon="plus" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
---
|
||||
|
||||
import FormFooter from '@ui/FormFooter.astro'
|
||||
import Button from '@ui/Button.astro'
|
||||
import FormGroup from '@ui/FormGroup.astro'
|
||||
|
||||
@@ -1,57 +1,57 @@
|
||||
---
|
||||
// `photo` and `index` are passed explicitly from the parent loop.
|
||||
import Avatar from '@ui/Avatar.astro';
|
||||
import Icon from '@ui/Icon.astro';
|
||||
import { randomNumber, timeagoLabel } from '@shared/lib/pseudo-random';
|
||||
import Avatar from '@ui/Avatar.astro'
|
||||
import Icon from '@ui/Icon.astro'
|
||||
import { randomNumber, timeagoLabel } from '@shared/lib/pseudo-random'
|
||||
|
||||
interface Photo {
|
||||
file: string;
|
||||
[key: string]: unknown;
|
||||
file: string
|
||||
[key: string]: unknown
|
||||
}
|
||||
|
||||
interface Person {
|
||||
full_name?: string;
|
||||
photo?: string;
|
||||
[key: string]: unknown;
|
||||
full_name?: string
|
||||
photo?: string
|
||||
[key: string]: unknown
|
||||
}
|
||||
|
||||
interface Props {
|
||||
photo: Photo;
|
||||
person: Person;
|
||||
/** 1-based loop index — seeds the deterministic pseudo-random values */
|
||||
index: number;
|
||||
hideLikes?: boolean;
|
||||
photo: Photo
|
||||
person: Person
|
||||
/** 1-based loop index — seeds the deterministic pseudo-random values */
|
||||
index: number
|
||||
hideLikes?: boolean
|
||||
}
|
||||
|
||||
const { photo, person, index, hideLikes } = Astro.props;
|
||||
const { photo, person, index, hideLikes } = Astro.props
|
||||
|
||||
// Heart filled when (index > 2 && index < 9) || index === 10.
|
||||
const heartClass = (index > 2 && index < 9) || index === 10 ? 'icon-filled text-red' : undefined;
|
||||
const heartClass = (index > 2 && index < 9) || index === 10 ? 'icon-filled text-red' : undefined
|
||||
---
|
||||
|
||||
<div class="card card-sm">
|
||||
<a href="#" class="d-block"><img src={`./static/photos/${photo.file}`} class="card-img-top" /></a>
|
||||
<div class="card-body">
|
||||
<div class="d-flex align-items-center">
|
||||
<Avatar person={person} class="me-3 rounded" />
|
||||
<div>
|
||||
<div>{person.full_name}</div>
|
||||
<div class="text-secondary">{timeagoLabel(index, 10)}</div>
|
||||
</div>
|
||||
{
|
||||
!hideLikes && (
|
||||
<div class="ms-auto">
|
||||
<a href="#" class="text-secondary">
|
||||
<Icon name="eye" />
|
||||
{randomNumber(index, 300, 600)}
|
||||
</a>
|
||||
<a href="#" class="ms-3 text-secondary">
|
||||
<Icon name="heart" class={heartClass} />
|
||||
{randomNumber(index, 20, 100)}
|
||||
</a>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<a href="#" class="d-block"><img src={`./static/photos/${photo.file}`} class="card-img-top" /></a>
|
||||
<div class="card-body">
|
||||
<div class="d-flex align-items-center">
|
||||
<Avatar person={person} class="me-3 rounded" />
|
||||
<div>
|
||||
<div>{person.full_name}</div>
|
||||
<div class="text-secondary">{timeagoLabel(index, 10)}</div>
|
||||
</div>
|
||||
{
|
||||
!hideLikes && (
|
||||
<div class="ms-auto">
|
||||
<a href="#" class="text-secondary">
|
||||
<Icon name="eye" />
|
||||
{randomNumber(index, 300, 600)}
|
||||
</a>
|
||||
<a href="#" class="ms-3 text-secondary">
|
||||
<Icon name="heart" class={heartClass} />
|
||||
{randomNumber(index, 20, 100)}
|
||||
</a>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,52 +1,41 @@
|
||||
---
|
||||
import Icon from '@ui/Icon.astro';
|
||||
import icons from '@data/icons.json';
|
||||
import Icon from '@ui/Icon.astro'
|
||||
import icons from '@data/icons.json'
|
||||
|
||||
interface Props {
|
||||
title?: string;
|
||||
type?: 'outline' | 'filled';
|
||||
title?: string
|
||||
type?: 'outline' | 'filled'
|
||||
}
|
||||
|
||||
const { title, type = 'outline' } = Astro.props;
|
||||
const { title, type = 'outline' } = Astro.props
|
||||
|
||||
// Dev preview limits icons to 20 (see BaseLayout).
|
||||
// The limit applies before the svg[type] filter — the "filled" card shows only filled icons among the first 20.
|
||||
const limit = 20;
|
||||
const limit = 20
|
||||
|
||||
const entries = Object.entries(icons as Record<string, { svg?: Record<string, string> }>).slice(
|
||||
0,
|
||||
limit,
|
||||
);
|
||||
const entries = Object.entries(icons as Record<string, { svg?: Record<string, string | null> }>).slice(0, limit)
|
||||
---
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<div class="card-title">{title}</div>
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
<div class="demo-icons-list-wrap">
|
||||
<div class="demo-icons-list">
|
||||
{
|
||||
entries.map(
|
||||
([iconName, icon]) =>
|
||||
icon.svg?.[type] && (
|
||||
<a
|
||||
href={`https://tabler.io/icons/icon/${iconName}`}
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
class="demo-icons-list-item"
|
||||
title={iconName}
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-placement="top"
|
||||
>
|
||||
<Icon name={iconName} type={type} />
|
||||
</a>
|
||||
),
|
||||
)
|
||||
}
|
||||
{/* 21 empty divs (flexbox filler) */}
|
||||
{Array.from({ length: 21 }).map(() => <div />)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-header">
|
||||
<div class="card-title">{title}</div>
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
<div class="demo-icons-list-wrap">
|
||||
<div class="demo-icons-list">
|
||||
{
|
||||
entries.map(
|
||||
([iconName, icon]) =>
|
||||
icon.svg?.[type] && (
|
||||
<a href={`https://tabler.io/icons/icon/${iconName}`} target="_blank" rel="noopener" class="demo-icons-list-item" title={iconName} data-bs-toggle="tooltip" data-bs-placement="top">
|
||||
<Icon name={iconName} type={type} />
|
||||
</a>
|
||||
),
|
||||
)
|
||||
}
|
||||
{/* 21 empty divs (flexbox filler) */}
|
||||
{Array.from({ length: 21 }).map(() => <div />)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
---
|
||||
|
||||
---
|
||||
|
||||
<div class="card card-lg">
|
||||
|
||||
@@ -1,54 +1,50 @@
|
||||
---
|
||||
// Feature names are a fixed list; the `features` string is split per-character into a 1/0 availability mask.
|
||||
import Icon from '@ui/Icon.astro';
|
||||
import Icon from '@ui/Icon.astro'
|
||||
|
||||
interface Props {
|
||||
price?: string;
|
||||
users?: string | number;
|
||||
category?: string;
|
||||
features?: string;
|
||||
/** featured-color — ribbon + button color (e.g. "green") */
|
||||
featuredColor?: string;
|
||||
price?: string
|
||||
users?: string | number
|
||||
category?: string
|
||||
features?: string
|
||||
/** featured-color — ribbon + button color (e.g. "green") */
|
||||
featuredColor?: string
|
||||
}
|
||||
|
||||
const { price = '79', users = 10, category = 'Enterprise', features = '1000', featuredColor } = Astro.props;
|
||||
const { price = '79', users = 10, category = 'Enterprise', features = '1000', featuredColor } = Astro.props
|
||||
|
||||
const featureNames = ['Sharing Tools', 'Design Tools', 'Private Messages', 'Twitter API'];
|
||||
const availableFeatures = features.split('');
|
||||
const featureNames = ['Sharing Tools', 'Design Tools', 'Private Messages', 'Twitter API']
|
||||
const availableFeatures = features.split('')
|
||||
---
|
||||
|
||||
<div class="card card-md">
|
||||
{
|
||||
featuredColor && (
|
||||
<div class={`ribbon ribbon-top ribbon-bookmark bg-${featuredColor}`}>
|
||||
<Icon name="star" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
{
|
||||
featuredColor && (
|
||||
<div class={`ribbon ribbon-top ribbon-bookmark bg-${featuredColor}`}>
|
||||
<Icon name="star" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
<div class="card-body text-center">
|
||||
<div class="text-uppercase text-secondary fw-medium">{category}</div>
|
||||
<div class="card-body text-center">
|
||||
<div class="text-uppercase text-secondary fw-medium">{category}</div>
|
||||
|
||||
<div class="display-5 fw-bold my-3">${price}</div>
|
||||
<div class="display-5 fw-bold my-3">${price}</div>
|
||||
|
||||
<ul class="list-unstyled lh-lg">
|
||||
<li><strong>{users}</strong> Users</li>
|
||||
{
|
||||
featureNames.map((feature, i) => (
|
||||
<li>
|
||||
{availableFeatures[i] === '1' ? (
|
||||
<Icon name="check" class="me-1 text-success" />
|
||||
) : (
|
||||
<Icon name="x" class="me-1 text-danger" />
|
||||
)}
|
||||
{feature}
|
||||
</li>
|
||||
))
|
||||
}
|
||||
</ul>
|
||||
<ul class="list-unstyled lh-lg">
|
||||
<li><strong>{users}</strong> Users</li>
|
||||
{
|
||||
featureNames.map((feature, i) => (
|
||||
<li>
|
||||
{availableFeatures[i] === '1' ? <Icon name="check" class="me-1 text-success" /> : <Icon name="x" class="me-1 text-danger" />}
|
||||
{feature}
|
||||
</li>
|
||||
))
|
||||
}
|
||||
</ul>
|
||||
|
||||
<div class="text-center mt-4">
|
||||
<a href="#" class={`btn${featuredColor ? ` btn-${featuredColor}` : ''} w-100`}>Choose plan</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-center mt-4">
|
||||
<a href="#" class={`btn${featuredColor ? ` btn-${featuredColor}` : ''} w-100`}>Choose plan</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,38 +1,38 @@
|
||||
---
|
||||
// TODO: fallback to people[1] when person omitted — unused on card-gradients, so `person` is required here.
|
||||
import Icon from '@ui/Icon.astro';
|
||||
import Avatar from '@ui/Avatar.astro';
|
||||
import ButtonList from '@ui/ButtonList.astro';
|
||||
import Icon from '@ui/Icon.astro'
|
||||
import Avatar from '@ui/Avatar.astro'
|
||||
import ButtonList from '@ui/ButtonList.astro'
|
||||
|
||||
interface Person {
|
||||
full_name?: string;
|
||||
job_title?: string;
|
||||
[key: string]: unknown;
|
||||
full_name?: string
|
||||
job_title?: string
|
||||
[key: string]: unknown
|
||||
}
|
||||
|
||||
interface Props {
|
||||
person: Person;
|
||||
color?: string;
|
||||
person: Person
|
||||
color?: string
|
||||
}
|
||||
|
||||
const { person, color = 'yellow' } = Astro.props;
|
||||
const { person, color = 'yellow' } = Astro.props
|
||||
---
|
||||
|
||||
<div class={`card card-gradient card-gradient-${color}`}>
|
||||
<div class="card-body text-center py-6">
|
||||
<div class="position-absolute top-0 end-0 p-1">
|
||||
<div class="btn btn-action"><Icon name="star" color="yellow" type="filled" /></div>
|
||||
</div>
|
||||
<div>
|
||||
<Avatar size="2xl" person={person} shape="rounded-circle" />
|
||||
</div>
|
||||
<div class="h1 mt-4 mb-1">{person.full_name}</div>
|
||||
<div class="text-secondary">{person.job_title}</div>
|
||||
<div class="card-body text-center py-6">
|
||||
<div class="position-absolute top-0 end-0 p-1">
|
||||
<div class="btn btn-action"><Icon name="star" color="yellow" type="filled" /></div>
|
||||
</div>
|
||||
<div>
|
||||
<Avatar size="2xl" person={person} shape="rounded-circle" />
|
||||
</div>
|
||||
<div class="h1 mt-4 mb-1">{person.full_name}</div>
|
||||
<div class="text-secondary">{person.job_title}</div>
|
||||
|
||||
<ButtonList class="justify-content-center mt-3">
|
||||
<a href="#" class="btn btn-icon btn-pill" title="Message" data-bs-toggle="tooltip" data-bs-placement="top"><Icon name="message" /></a>
|
||||
<a href="#" class="btn btn-icon btn-pill" title="Phone" data-bs-toggle="tooltip" data-bs-placement="top"><Icon name="phone" /></a>
|
||||
<a href="#" class="btn btn-icon btn-pill" title="Email" data-bs-toggle="tooltip" data-bs-placement="top"><Icon name="mail" /></a>
|
||||
</ButtonList>
|
||||
</div>
|
||||
<ButtonList class="justify-content-center mt-3">
|
||||
<a href="#" class="btn btn-icon btn-pill" title="Message" data-bs-toggle="tooltip" data-bs-placement="top"><Icon name="message" /></a>
|
||||
<a href="#" class="btn btn-icon btn-pill" title="Phone" data-bs-toggle="tooltip" data-bs-placement="top"><Icon name="phone" /></a>
|
||||
<a href="#" class="btn btn-icon btn-pill" title="Email" data-bs-toggle="tooltip" data-bs-placement="top"><Icon name="mail" /></a>
|
||||
</ButtonList>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,49 +1,48 @@
|
||||
---
|
||||
// NOTE: widgets page passes `value=` but only `percentage` (default 20) drives the bar — `value` is accepted but ignored.
|
||||
import Progress from '@ui/Progress.astro';
|
||||
import AvatarList from '@ui/AvatarList.astro';
|
||||
import Icon from '@ui/Icon.astro';
|
||||
import CardTitle from '@ui/CardTitle.astro';
|
||||
import Progress from '@ui/Progress.astro'
|
||||
import AvatarList from '@ui/AvatarList.astro'
|
||||
import Icon from '@ui/Icon.astro'
|
||||
import CardTitle from '@ui/CardTitle.astro'
|
||||
|
||||
interface Props {
|
||||
title?: string;
|
||||
badge?: string;
|
||||
offset?: number;
|
||||
limit?: number;
|
||||
percentage?: number | string;
|
||||
percentageColor?: string;
|
||||
due?: string;
|
||||
/** unused — see note above */
|
||||
value?: number | string;
|
||||
title?: string
|
||||
badge?: string
|
||||
offset?: number
|
||||
limit?: number
|
||||
percentage?: number | string
|
||||
percentageColor?: string
|
||||
due?: string
|
||||
/** unused — see note above */
|
||||
value?: number | string
|
||||
}
|
||||
|
||||
const {
|
||||
title = 'Task Title',
|
||||
badge,
|
||||
offset = 40,
|
||||
limit = 7,
|
||||
percentage = 20,
|
||||
percentageColor = 'green',
|
||||
due = '2 days',
|
||||
} = Astro.props;
|
||||
const { title = 'Task Title', badge, offset = 40, limit = 7, percentage = 20, percentageColor = 'green', due = '2 days' } = Astro.props
|
||||
---
|
||||
|
||||
<div class="card">
|
||||
<Progress value={percentage} class="card-progress" color={percentageColor} />
|
||||
<div class="card-body">
|
||||
<CardTitle>
|
||||
<a href="#">{title}</a>
|
||||
{badge && <Fragment> <span class="badge ms-2">{badge}</span></Fragment>}
|
||||
</CardTitle>
|
||||
<Progress value={percentage} class="card-progress" color={percentageColor} />
|
||||
<div class="card-body">
|
||||
<CardTitle>
|
||||
<a href="#">{title}</a>
|
||||
{
|
||||
badge && (
|
||||
<Fragment>
|
||||
{' '}
|
||||
<span class="badge ms-2">{badge}</span>
|
||||
</Fragment>
|
||||
)
|
||||
}
|
||||
</CardTitle>
|
||||
|
||||
<AvatarList offset={offset} limit={limit} stacked class="mb-3" />
|
||||
<AvatarList offset={offset} limit={limit} stacked class="mb-3" />
|
||||
|
||||
<div class="card-meta d-flex justify-content-between">
|
||||
<div class="d-flex align-items-center">
|
||||
<Icon name="check" class="me-2" />
|
||||
<span>5/10</span>
|
||||
</div>
|
||||
<span>Due {due}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-meta d-flex justify-content-between">
|
||||
<div class="d-flex align-items-center">
|
||||
<Icon name="check" class="me-2" />
|
||||
<span>5/10</span>
|
||||
</div>
|
||||
<span>Due {due}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,53 +1,51 @@
|
||||
---
|
||||
// NOTE: `days-ago` param accepted but unused ("Updated 2 hours ago" is hardcoded).
|
||||
import Progress from '@ui/Progress.astro';
|
||||
import CardDropdown from '@ui/CardDropdown.astro';
|
||||
import projects from '@data/projects.json';
|
||||
import Progress from '@ui/Progress.astro'
|
||||
import CardDropdown from '@ui/CardDropdown.astro'
|
||||
import projects from '@data/projects.json'
|
||||
|
||||
interface Project {
|
||||
title?: string;
|
||||
image?: string;
|
||||
[key: string]: unknown;
|
||||
title?: string
|
||||
image?: string
|
||||
[key: string]: unknown
|
||||
}
|
||||
|
||||
interface Props {
|
||||
projectId?: number;
|
||||
progress?: number;
|
||||
daysAgo?: number;
|
||||
projectId?: number
|
||||
progress?: number
|
||||
daysAgo?: number
|
||||
}
|
||||
|
||||
const { projectId = 0, progress = 25 } = Astro.props;
|
||||
const project = (projects as Project[])[projectId];
|
||||
const { projectId = 0, progress = 25 } = Astro.props
|
||||
const project = (projects as Project[])[projectId]
|
||||
---
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-3">
|
||||
<img src={project.image} alt={project.title} class="rounded" />
|
||||
</div>
|
||||
<div class="col">
|
||||
<h3 class="card-title mb-1">
|
||||
<a href="#" class="text-reset">{project.title}</a>
|
||||
</h3>
|
||||
<div class="text-secondary">
|
||||
Updated 2 hours ago
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-3">
|
||||
<img src={project.image} alt={project.title} class="rounded" />
|
||||
</div>
|
||||
<div class="col">
|
||||
<h3 class="card-title mb-1">
|
||||
<a href="#" class="text-reset">{project.title}</a>
|
||||
</h3>
|
||||
<div class="text-secondary">Updated 2 hours ago</div>
|
||||
|
||||
<div class="mt-3">
|
||||
<div class="row g-2 align-items-center">
|
||||
<div class="col-auto">
|
||||
{progress}%
|
||||
</div>
|
||||
<div class="col">
|
||||
<Progress value={progress} size="sm" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<CardDropdown />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-3">
|
||||
<div class="row g-2 align-items-center">
|
||||
<div class="col-auto">
|
||||
{progress}%
|
||||
</div>
|
||||
<div class="col">
|
||||
<Progress value={progress} size="sm" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<CardDropdown />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,39 +1,39 @@
|
||||
---
|
||||
// Icon class is a literal template string (color token is not interpolated).
|
||||
import CardActions from '@ui/CardActions.astro';
|
||||
import Icon from '@ui/Icon.astro';
|
||||
import CardTitle from '@ui/CardTitle.astro';
|
||||
import crmDashboard from '@data/crm-dashboard.json';
|
||||
import CardActions from '@ui/CardActions.astro'
|
||||
import Icon from '@ui/Icon.astro'
|
||||
import CardTitle from '@ui/CardTitle.astro'
|
||||
import crmDashboard from '@data/crm-dashboard.json'
|
||||
|
||||
const activity = crmDashboard.recent_activity;
|
||||
const activity = crmDashboard.recent_activity
|
||||
---
|
||||
|
||||
<div class="card" style="height: 28rem">
|
||||
<div class="card-header">
|
||||
<CardTitle>{activity.title}</CardTitle>
|
||||
<CardActions>
|
||||
<a href="#" class="small fw-medium text-primary">{activity.view_all_text}</a>
|
||||
</CardActions>
|
||||
</div>
|
||||
<div class="card-body card-body-scrollable card-body-scrollable-shadow">
|
||||
<div class="divide-y">
|
||||
{
|
||||
activity.items.map((item) => (
|
||||
<div class="py-3">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-auto">
|
||||
<span class={`avatar bg-${item.icon_color}-lt`}>
|
||||
<Icon name={item.icon} class={'icon text-{{ item.icon_color }}'} />
|
||||
</span>
|
||||
</div>
|
||||
<div class="col">
|
||||
<div class="fw-medium">{item.text}</div>
|
||||
<div class="small text-secondary">{item.time}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-header">
|
||||
<CardTitle>{activity.title}</CardTitle>
|
||||
<CardActions>
|
||||
<a href="#" class="small fw-medium text-primary">{activity.view_all_text}</a>
|
||||
</CardActions>
|
||||
</div>
|
||||
<div class="card-body card-body-scrollable card-body-scrollable-shadow">
|
||||
<div class="divide-y">
|
||||
{
|
||||
activity.items.map((item) => (
|
||||
<div class="py-3">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-auto">
|
||||
<span class={`avatar bg-${item.icon_color}-lt`}>
|
||||
<Icon name={item.icon} class={'icon text-{{ item.icon_color }}'} />
|
||||
</span>
|
||||
</div>
|
||||
<div class="col">
|
||||
<div class="fw-medium">{item.text}</div>
|
||||
<div class="small text-secondary">{item.time}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
---
|
||||
|
||||
import FormFooter from '@ui/FormFooter.astro'
|
||||
import InputGroup from '@ui/InputGroup.astro'
|
||||
import FormGroup from '@ui/FormGroup.astro'
|
||||
|
||||
@@ -1,39 +1,38 @@
|
||||
---
|
||||
import { formatNumber } from '@shared/lib/string-format';
|
||||
import CardTitle from '@ui/CardTitle.astro';
|
||||
import { formatNumber } from '@shared/lib/string-format'
|
||||
import CardTitle from '@ui/CardTitle.astro'
|
||||
|
||||
const services = 'Instagram:3550,Twitter:1798,Facebook:1245,TikTok:986,Pinterest:854,VK:650,Pinterest:420'
|
||||
.split(',')
|
||||
.map((service) => {
|
||||
const [name, visitors] = service.split(':');
|
||||
return { name, visitors: Number(visitors) };
|
||||
});
|
||||
const services = 'Instagram:3550,Twitter:1798,Facebook:1245,TikTok:986,Pinterest:854,VK:650,Pinterest:420'.split(',').map((service) => {
|
||||
const [name, visitors] = service.split(':')
|
||||
return { name, visitors: Number(visitors) }
|
||||
})
|
||||
---
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<CardTitle>Social Media Traffic</CardTitle>
|
||||
</div>
|
||||
<table class="table card-table table-vcenter">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Network</th>
|
||||
<th colspan="2">Visitors</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{services.map((service) => (
|
||||
<tr>
|
||||
<td>{service.name}</td>
|
||||
<td>{formatNumber(service.visitors)}</td>
|
||||
<td class="w-50">
|
||||
<div class="progress progress-xs">
|
||||
|
||||
<div class="progress-bar bg-primary" style={`width: ${(service.visitors / 5000.0) * 100}%`}></div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="card-header">
|
||||
<CardTitle>Social Media Traffic</CardTitle>
|
||||
</div>
|
||||
<table class="table card-table table-vcenter">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Network</th>
|
||||
<th colspan="2">Visitors</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{
|
||||
services.map((service) => (
|
||||
<tr>
|
||||
<td>{service.name}</td>
|
||||
<td>{formatNumber(service.visitors)}</td>
|
||||
<td class="w-50">
|
||||
<div class="progress progress-xs">
|
||||
<div class="progress-bar bg-primary" style={`width: ${(service.visitors / 5000.0) * 100}%`} />
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
))
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
@@ -1,96 +1,107 @@
|
||||
---
|
||||
import Icon from '@ui/Icon.astro';
|
||||
import DropdownMenu from '@ui/DropdownMenu.astro';
|
||||
import Icon from '@ui/Icon.astro'
|
||||
import DropdownMenu from '@ui/DropdownMenu.astro'
|
||||
|
||||
interface Props {
|
||||
id: string;
|
||||
icons?: boolean;
|
||||
/** hide-text param; not used by current call sites */
|
||||
hideText?: boolean;
|
||||
reverse?: boolean;
|
||||
justified?: boolean;
|
||||
activity?: boolean;
|
||||
disabled?: boolean;
|
||||
dropdown?: boolean;
|
||||
settings?: boolean;
|
||||
animation?: boolean;
|
||||
id: string
|
||||
icons?: boolean
|
||||
/** hide-text param; not used by current call sites */
|
||||
hideText?: boolean
|
||||
reverse?: boolean
|
||||
justified?: boolean
|
||||
activity?: boolean
|
||||
disabled?: boolean
|
||||
dropdown?: boolean
|
||||
settings?: boolean
|
||||
animation?: boolean
|
||||
}
|
||||
|
||||
const {
|
||||
id,
|
||||
icons,
|
||||
hideText,
|
||||
reverse = false,
|
||||
justified,
|
||||
activity,
|
||||
disabled,
|
||||
dropdown,
|
||||
settings,
|
||||
animation,
|
||||
} = Astro.props;
|
||||
const { id, icons, hideText, reverse = false, justified, activity, disabled, dropdown, settings, animation } = Astro.props
|
||||
|
||||
const iconClass = hideText ? undefined : 'me-2';
|
||||
const navClass = `nav nav-tabs card-header-tabs${reverse ? ' flex-row-reverse' : ''}${justified ? ' nav-fill' : ''}`;
|
||||
const paneClass = `tab-pane${animation ? ' fade' : ''}`;
|
||||
const iconClass = hideText ? undefined : 'me-2'
|
||||
const navClass = `nav nav-tabs card-header-tabs${reverse ? ' flex-row-reverse' : ''}${justified ? ' nav-fill' : ''}`
|
||||
const paneClass = `tab-pane${animation ? ' fade' : ''}`
|
||||
---
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<ul class={navClass} data-bs-toggle="tabs">
|
||||
<li class="nav-item">
|
||||
<a href={`#tabs-home-${id}`} class="nav-link active" data-bs-toggle="tab">{icons && <Icon name="home" class={iconClass} />}{!hideText && 'Home'}</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href={`#tabs-profile-${id}`} class="nav-link" data-bs-toggle="tab">{icons && <Icon name="user" class={iconClass} />}{!hideText && 'Profile'}</a>
|
||||
</li>
|
||||
<div class="card-header">
|
||||
<ul class={navClass} data-bs-toggle="tabs">
|
||||
<li class="nav-item">
|
||||
<a href={`#tabs-home-${id}`} class="nav-link active" data-bs-toggle="tab">{icons && <Icon name="home" class={iconClass} />}{!hideText && 'Home'}</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href={`#tabs-profile-${id}`} class="nav-link" data-bs-toggle="tab">{icons && <Icon name="user" class={iconClass} />}{!hideText && 'Profile'}</a>
|
||||
</li>
|
||||
|
||||
{activity && (
|
||||
<li class="nav-item">
|
||||
<a href={`#tabs-activity-${id}`} class="nav-link" data-bs-toggle="tab">{icons && <Icon name="activity" class={iconClass} />}{!hideText && 'Activity'}</a>
|
||||
</li>
|
||||
)}
|
||||
{
|
||||
activity && (
|
||||
<li class="nav-item">
|
||||
<a href={`#tabs-activity-${id}`} class="nav-link" data-bs-toggle="tab">
|
||||
{icons && <Icon name="activity" class={iconClass} />}
|
||||
{!hideText && 'Activity'}
|
||||
</a>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
|
||||
{disabled && (
|
||||
<li class="nav-item">
|
||||
<a href={`#tabs-activity-${id}`} class="nav-link disabled" data-bs-toggle="tab">{icons && <Icon name="x" class={iconClass} />}{!hideText && 'Disabled'}</a>
|
||||
</li>
|
||||
)}
|
||||
{
|
||||
disabled && (
|
||||
<li class="nav-item">
|
||||
<a href={`#tabs-activity-${id}`} class="nav-link disabled" data-bs-toggle="tab">
|
||||
{icons && <Icon name="x" class={iconClass} />}
|
||||
{!hideText && 'Disabled'}
|
||||
</a>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
|
||||
{dropdown && (
|
||||
<li class="nav-item dropdown">
|
||||
<a class="nav-link dropdown-toggle" data-bs-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">Dropdown</a>
|
||||
<DropdownMenu />
|
||||
</li>
|
||||
)}
|
||||
{settings && (
|
||||
<li class={`nav-item ${reverse ? 'me-auto' : 'ms-auto'}`}>
|
||||
<a href={`#tabs-settings-${id}`} class="nav-link" title="Settings" data-bs-toggle="tab"><Icon name="settings" /></a>
|
||||
</li>
|
||||
)}
|
||||
</ul>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="tab-content">
|
||||
<div class={`${paneClass} active show`} id={`tabs-home-${id}`}>
|
||||
<h4>Home tab</h4>
|
||||
<div>Cursus turpis vestibulum, dui in pharetra vulputate id sed non turpis ultricies fringilla at sed facilisis lacus pellentesque purus nibh</div>
|
||||
</div>
|
||||
<div class={paneClass} id={`tabs-profile-${id}`}>
|
||||
<h4>Profile tab</h4>
|
||||
<div>Fringilla egestas nunc quis tellus diam rhoncus ultricies tristique enim at diam, sem nunc amet, pellentesque id egestas velit sed</div>
|
||||
</div>
|
||||
{settings && (
|
||||
<div class={paneClass} id={`tabs-settings-${id}`}>
|
||||
<h4>Settings tab</h4>
|
||||
<div>Donec ac vitae diam amet vel leo egestas consequat rhoncus in luctus amet, facilisi sit mauris accumsan nibh habitant senectus</div>
|
||||
</div>
|
||||
)}
|
||||
{activity && (
|
||||
<div class={paneClass} id={`tabs-activity-${id}`}>
|
||||
<h4>Activity tab</h4>
|
||||
<div>Donec ac vitae diam amet vel leo egestas consequat rhoncus in luctus amet, facilisi sit mauris accumsan nibh habitant senectus</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{
|
||||
dropdown && (
|
||||
<li class="nav-item dropdown">
|
||||
<a class="nav-link dropdown-toggle" data-bs-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">
|
||||
Dropdown
|
||||
</a>
|
||||
<DropdownMenu />
|
||||
</li>
|
||||
)
|
||||
}
|
||||
{
|
||||
settings && (
|
||||
<li class={`nav-item ${reverse ? 'me-auto' : 'ms-auto'}`}>
|
||||
<a href={`#tabs-settings-${id}`} class="nav-link" title="Settings" data-bs-toggle="tab">
|
||||
<Icon name="settings" />
|
||||
</a>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="tab-content">
|
||||
<div class={`${paneClass} active show`} id={`tabs-home-${id}`}>
|
||||
<h4>Home tab</h4>
|
||||
<div>Cursus turpis vestibulum, dui in pharetra vulputate id sed non turpis ultricies fringilla at sed facilisis lacus pellentesque purus nibh</div>
|
||||
</div>
|
||||
<div class={paneClass} id={`tabs-profile-${id}`}>
|
||||
<h4>Profile tab</h4>
|
||||
<div>Fringilla egestas nunc quis tellus diam rhoncus ultricies tristique enim at diam, sem nunc amet, pellentesque id egestas velit sed</div>
|
||||
</div>
|
||||
{
|
||||
settings && (
|
||||
<div class={paneClass} id={`tabs-settings-${id}`}>
|
||||
<h4>Settings tab</h4>
|
||||
<div>Donec ac vitae diam amet vel leo egestas consequat rhoncus in luctus amet, facilisi sit mauris accumsan nibh habitant senectus</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
{
|
||||
activity && (
|
||||
<div class={paneClass} id={`tabs-activity-${id}`}>
|
||||
<h4>Activity tab</h4>
|
||||
<div>Donec ac vitae diam amet vel leo egestas consequat rhoncus in luctus amet, facilisi sit mauris accumsan nibh habitant senectus</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2,41 +2,38 @@
|
||||
// person = people[person-id]; the cover photo is photos[person-id].file.
|
||||
// Base card-cover already carries `card-cover-blurred`; `blurred` adds it a 2nd time
|
||||
// Base card-cover already carries `card-cover-blurred`; `blurred` adds it a second time.
|
||||
import Avatar from '@ui/Avatar.astro';
|
||||
import people from '@data/people.json';
|
||||
import photos from '@data/photos.json';
|
||||
import Avatar from '@ui/Avatar.astro'
|
||||
import people from '@data/people.json'
|
||||
import photos from '@data/photos.json'
|
||||
|
||||
interface Person {
|
||||
full_name?: string;
|
||||
job_title?: string;
|
||||
photo?: string;
|
||||
[key: string]: unknown;
|
||||
full_name?: string
|
||||
job_title?: string
|
||||
photo?: string
|
||||
[key: string]: unknown
|
||||
}
|
||||
|
||||
interface Photo {
|
||||
file?: string;
|
||||
[key: string]: unknown;
|
||||
file?: string
|
||||
[key: string]: unknown
|
||||
}
|
||||
|
||||
interface Props {
|
||||
personId?: number;
|
||||
blurred?: boolean;
|
||||
personId?: number
|
||||
blurred?: boolean
|
||||
}
|
||||
|
||||
const { personId = 0, blurred } = Astro.props;
|
||||
const person = (people as Person[])[personId];
|
||||
const photo = (photos as Photo[])[personId];
|
||||
const { personId = 0, blurred } = Astro.props
|
||||
const person = (people as Person[])[personId]
|
||||
const photo = (photos as Photo[])[personId]
|
||||
---
|
||||
|
||||
<a class="card card-link" href="#">
|
||||
<div
|
||||
class={`card-cover card-cover-blurred text-center${blurred ? ' card-cover-blurred' : ''}`}
|
||||
style={`background-image: url(./static/photos/${photo.file})`}
|
||||
>
|
||||
<Avatar size="xl" person={person} thumb />
|
||||
</div>
|
||||
<div class="card-body text-center">
|
||||
<div class="card-title mb-1">{person.full_name}</div>
|
||||
<div class="text-secondary">{person.job_title}</div>
|
||||
</div>
|
||||
<div class={`card-cover card-cover-blurred text-center${blurred ? ' card-cover-blurred' : ''}`} style={`background-image: url(./static/photos/${photo.file})`}>
|
||||
<Avatar size="xl" person={person} thumb />
|
||||
</div>
|
||||
<div class="card-body text-center">
|
||||
<div class="card-title mb-1">{person.full_name}</div>
|
||||
<div class="text-secondary">{person.job_title}</div>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
@@ -1,29 +1,29 @@
|
||||
---
|
||||
import Avatar from '@ui/Avatar.astro';
|
||||
import people from '@data/people.json';
|
||||
import Avatar from '@ui/Avatar.astro'
|
||||
import people from '@data/people.json'
|
||||
|
||||
interface Person {
|
||||
full_name?: string;
|
||||
job_title?: string;
|
||||
photo?: string;
|
||||
[key: string]: unknown;
|
||||
full_name?: string
|
||||
job_title?: string
|
||||
photo?: string
|
||||
[key: string]: unknown
|
||||
}
|
||||
|
||||
interface Props {
|
||||
personId?: number;
|
||||
personId?: number
|
||||
}
|
||||
|
||||
const { personId = 0 } = Astro.props;
|
||||
const person = (people as Person[])[personId];
|
||||
const { personId = 0 } = Astro.props
|
||||
const person = (people as Person[])[personId]
|
||||
---
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body text-center">
|
||||
<div class="mb-3">
|
||||
<Avatar size="xl" person={person} />
|
||||
</div>
|
||||
<div class="card-title mb-1">{person.full_name}</div>
|
||||
<div class="text-secondary">{person.job_title}</div>
|
||||
</div>
|
||||
<a href="#" class="card-btn">View full profile</a>
|
||||
<div class="card-body text-center">
|
||||
<div class="mb-3">
|
||||
<Avatar size="xl" person={person} />
|
||||
</div>
|
||||
<div class="card-title mb-1">{person.full_name}</div>
|
||||
<div class="text-secondary">{person.job_title}</div>
|
||||
</div>
|
||||
<a href="#" class="card-btn">View full profile</a>
|
||||
</div>
|
||||
|
||||
@@ -1,55 +1,55 @@
|
||||
---
|
||||
// person = people[person-id - 1] (default person-id = 25 → people[24]).
|
||||
import Icon from '@ui/Icon.astro';
|
||||
import Flag from '@ui/Flag.astro';
|
||||
import people from '@data/people.json';
|
||||
import Icon from '@ui/Icon.astro'
|
||||
import Flag from '@ui/Flag.astro'
|
||||
import people from '@data/people.json'
|
||||
|
||||
interface Person {
|
||||
university?: string;
|
||||
company?: string;
|
||||
city?: string;
|
||||
country?: string;
|
||||
country_code?: string;
|
||||
birth_date?: string;
|
||||
time_zone?: string;
|
||||
[key: string]: unknown;
|
||||
university?: string
|
||||
company?: string
|
||||
city?: string
|
||||
country?: string
|
||||
country_code?: string
|
||||
birth_date?: string
|
||||
time_zone?: string
|
||||
[key: string]: unknown
|
||||
}
|
||||
|
||||
interface Props {
|
||||
title?: string;
|
||||
personId?: number;
|
||||
title?: string
|
||||
personId?: number
|
||||
}
|
||||
|
||||
const { title = 'Basic info', personId = 25 } = Astro.props;
|
||||
const person = (people as Person[])[personId - 1];
|
||||
const { title = 'Basic info', personId = 25 } = Astro.props
|
||||
const person = (people as Person[])[personId - 1]
|
||||
---
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div class="card-title">{title}</div>
|
||||
<div class="mb-2">
|
||||
<Icon name="book" class="me-2 text-secondary" />
|
||||
Went to: <strong>{person.university}</strong>
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<Icon name="briefcase" class="me-2 text-secondary" />
|
||||
Worked at: <strong>{person.company}</strong>
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<Icon name="home" class="me-2 text-secondary" />
|
||||
Lives in: <strong>{person.city}, {person.country}</strong>
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<Icon name="map-pin" class="me-2 text-secondary" />
|
||||
From: <strong><Flag size="xs" flag={person.country_code} /> {person.country}</strong>
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<Icon name="calendar" class="me-2 text-secondary" />
|
||||
Birth date: <strong>{person.birth_date}</strong>
|
||||
</div>
|
||||
<div>
|
||||
<Icon name="clock" class="me-2 text-secondary" />
|
||||
Time zone: <strong>{person.time_zone}</strong>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="card-title">{title}</div>
|
||||
<div class="mb-2">
|
||||
<Icon name="book" class="me-2 text-secondary" />
|
||||
Went to: <strong>{person.university}</strong>
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<Icon name="briefcase" class="me-2 text-secondary" />
|
||||
Worked at: <strong>{person.company}</strong>
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<Icon name="home" class="me-2 text-secondary" />
|
||||
Lives in: <strong>{person.city}, {person.country}</strong>
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<Icon name="map-pin" class="me-2 text-secondary" />
|
||||
From: <strong><Flag size="xs" flag={person.country_code} /> {person.country}</strong>
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<Icon name="calendar" class="me-2 text-secondary" />
|
||||
Birth date: <strong>{person.birth_date}</strong>
|
||||
</div>
|
||||
<div>
|
||||
<Icon name="clock" class="me-2 text-secondary" />
|
||||
Time zone: <strong>{person.time_zone}</strong>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,103 +1,97 @@
|
||||
---
|
||||
import Avatar from '@ui/Avatar.astro';
|
||||
import Icon from '@ui/Icon.astro';
|
||||
import ListGroup from '@ui/ListGroup.astro';
|
||||
import ListGroupItem from '@ui/ListGroupItem.astro';
|
||||
import CardTitle from '@ui/CardTitle.astro';
|
||||
import people from '@data/people.json';
|
||||
import commits from '@data/commits.json';
|
||||
import { randomNumber } from '@shared/lib/pseudo-random';
|
||||
import Avatar from '@ui/Avatar.astro'
|
||||
import Icon from '@ui/Icon.astro'
|
||||
import ListGroup from '@ui/ListGroup.astro'
|
||||
import ListGroupItem from '@ui/ListGroupItem.astro'
|
||||
import CardTitle from '@ui/CardTitle.astro'
|
||||
import people from '@data/people.json'
|
||||
import commits from '@data/commits.json'
|
||||
import { randomNumber } from '@shared/lib/pseudo-random'
|
||||
|
||||
interface Person {
|
||||
full_name?: string;
|
||||
photo?: string;
|
||||
last_name?: string;
|
||||
[key: string]: unknown;
|
||||
full_name?: string
|
||||
photo?: string
|
||||
last_name?: string
|
||||
[key: string]: unknown
|
||||
}
|
||||
|
||||
interface Props {
|
||||
limit?: number;
|
||||
offset?: number;
|
||||
hoverable?: boolean;
|
||||
checkbox?: boolean;
|
||||
/** checked-ids — 1-based indices, e.g. [2, 5, 8] */
|
||||
checkedIds?: number[];
|
||||
title?: string;
|
||||
class?: string;
|
||||
/**
|
||||
* Inert. The lists.html Contacts card passes `hover=true`, but
|
||||
* users-list.html never reads it — only `hoverable` toggles the star column.
|
||||
* Declared for call-site compatibility.
|
||||
*/
|
||||
hover?: boolean;
|
||||
limit?: number
|
||||
offset?: number
|
||||
hoverable?: boolean
|
||||
checkbox?: boolean
|
||||
/** checked-ids — 1-based indices, e.g. [2, 5, 8] */
|
||||
checkedIds?: number[]
|
||||
title?: string
|
||||
class?: string
|
||||
/**
|
||||
* Inert. The lists.html Contacts card passes `hover=true`, but
|
||||
* users-list.html never reads it — only `hoverable` toggles the star column.
|
||||
* Declared for call-site compatibility.
|
||||
*/
|
||||
hover?: boolean
|
||||
}
|
||||
|
||||
const {
|
||||
limit = 8,
|
||||
offset = 0,
|
||||
hoverable = false,
|
||||
checkbox,
|
||||
checkedIds,
|
||||
title = 'Last commits',
|
||||
class: className,
|
||||
} = Astro.props;
|
||||
const { limit = 8, offset = 0, hoverable = false, checkbox, checkedIds, title = 'Last commits', class: className } = Astro.props
|
||||
|
||||
const colors = ['green', 'red', 'yellow', 'x', 'x'];
|
||||
const commitList = commits as { description: string }[];
|
||||
const colors = ['green', 'red', 'yellow', 'x', 'x']
|
||||
const commitList = commits as { description: string }[]
|
||||
|
||||
const rows = (people as Person[]).slice(offset, offset + limit).map((person, idx) => {
|
||||
const index = idx + 1; // forloop.index (1-based)
|
||||
const color = colors[randomNumber(index + 5, 0, colors.length - 1)];
|
||||
const checked = checkedIds?.includes(index) ?? false;
|
||||
const i = index + offset;
|
||||
return {
|
||||
person,
|
||||
color,
|
||||
checked,
|
||||
description: commitList[i]?.description,
|
||||
starColor: checked ? 'text-yellow' : 'text-secondary',
|
||||
};
|
||||
});
|
||||
const index = idx + 1 // forloop.index (1-based)
|
||||
const color = colors[randomNumber(index + 5, 0, colors.length - 1)]
|
||||
const checked = checkedIds?.includes(index) ?? false
|
||||
const i = index + offset
|
||||
return {
|
||||
person,
|
||||
color,
|
||||
checked,
|
||||
description: commitList[i]?.description,
|
||||
starColor: checked ? 'text-yellow' : 'text-secondary',
|
||||
}
|
||||
})
|
||||
---
|
||||
|
||||
<div class={`card${className ? ` ${className}` : ''}`}>
|
||||
<div class="card-header">
|
||||
<CardTitle>{title}</CardTitle>
|
||||
</div>
|
||||
<ListGroup flush hoverable={hoverable}>
|
||||
{
|
||||
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="#">
|
||||
<Avatar person={row.person} />
|
||||
</a>
|
||||
</div>
|
||||
<div class="card-header">
|
||||
<CardTitle>{title}</CardTitle>
|
||||
</div>
|
||||
<ListGroup flush hoverable={hoverable}>
|
||||
{
|
||||
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="#">
|
||||
<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' : ''}`}>
|
||||
<Icon name="star" class={row.starColor} />
|
||||
</a>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</ListGroupItem>
|
||||
))
|
||||
}
|
||||
</ListGroup>
|
||||
<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' : ''}`}>
|
||||
<Icon name="star" class={row.starColor} />
|
||||
</a>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</ListGroupItem>
|
||||
))
|
||||
}
|
||||
</ListGroup>
|
||||
</div>
|
||||
|
||||
@@ -1,57 +1,59 @@
|
||||
---
|
||||
import Avatar from '@ui/Avatar.astro';
|
||||
import CardTitle from '@ui/CardTitle.astro';
|
||||
import people from '@data/people.json';
|
||||
import { randomNumber, timeagoLabel } from '@shared/lib/pseudo-random';
|
||||
import Avatar from '@ui/Avatar.astro'
|
||||
import CardTitle from '@ui/CardTitle.astro'
|
||||
import people from '@data/people.json'
|
||||
import { randomNumber, timeagoLabel } from '@shared/lib/pseudo-random'
|
||||
|
||||
interface Person {
|
||||
full_name?: string;
|
||||
photo?: string;
|
||||
[key: string]: unknown;
|
||||
full_name?: string
|
||||
photo?: string
|
||||
[key: string]: unknown
|
||||
}
|
||||
|
||||
interface Props {
|
||||
limit?: number;
|
||||
offset?: number;
|
||||
title?: string;
|
||||
class?: string;
|
||||
limit?: number
|
||||
offset?: number
|
||||
title?: string
|
||||
class?: string
|
||||
}
|
||||
|
||||
const { limit = 10, offset = 0, title = 'Top users', class: className } = Astro.props;
|
||||
const { limit = 10, offset = 0, title = 'Top users', class: className } = Astro.props
|
||||
|
||||
const colors = 'green,red,yellow,x,x'.split(',');
|
||||
const colors = 'green,red,yellow,x,x'.split(',')
|
||||
|
||||
const rows = (people as Person[]).slice(offset, offset + limit).map((person, idx) => {
|
||||
const index = idx + 1; // forloop.index (1-based)
|
||||
return {
|
||||
person,
|
||||
status: colors[randomNumber(index + 5, 0, colors.length - 1)],
|
||||
timeago: timeagoLabel(index, 6),
|
||||
};
|
||||
});
|
||||
const index = idx + 1 // forloop.index (1-based)
|
||||
return {
|
||||
person,
|
||||
status: colors[randomNumber(index + 5, 0, colors.length - 1)],
|
||||
timeago: timeagoLabel(index, 6),
|
||||
}
|
||||
})
|
||||
---
|
||||
|
||||
<div class={`card${className ? ` ${className}` : ''}`}>
|
||||
<div class="card-header">
|
||||
<CardTitle>{title}</CardTitle>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row g-3">
|
||||
{
|
||||
rows.map((row) => (
|
||||
<div class="col-6">
|
||||
<div class="row g-3 align-items-center">
|
||||
<a href="#" class="col-auto">
|
||||
<Avatar person={row.person} status={row.status} />
|
||||
</a>
|
||||
<div class="col text-truncate">
|
||||
<a href="#" class="text-reset d-block text-truncate">{row.person.full_name}</a>
|
||||
<div class="text-secondary text-truncate mt-n1">{row.timeago}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-header">
|
||||
<CardTitle>{title}</CardTitle>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row g-3">
|
||||
{
|
||||
rows.map((row) => (
|
||||
<div class="col-6">
|
||||
<div class="row g-3 align-items-center">
|
||||
<a href="#" class="col-auto">
|
||||
<Avatar person={row.person} status={row.status} />
|
||||
</a>
|
||||
<div class="col text-truncate">
|
||||
<a href="#" class="text-reset d-block text-truncate">
|
||||
{row.person.full_name}
|
||||
</a>
|
||||
<div class="text-secondary text-truncate mt-n1">{row.timeago}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,70 +1,72 @@
|
||||
---
|
||||
import Avatar from '@ui/Avatar.astro';
|
||||
import ListGroup from '@ui/ListGroup.astro';
|
||||
import ListGroupItem from '@ui/ListGroupItem.astro';
|
||||
import ListGroupHeader from '@ui/ListGroupHeader.astro';
|
||||
import CardTitle from '@ui/CardTitle.astro';
|
||||
import people from '@data/people.json';
|
||||
import commits from '@data/commits.json';
|
||||
import { sortBy } from '@shared/lib/string-format';
|
||||
import Avatar from '@ui/Avatar.astro'
|
||||
import ListGroup from '@ui/ListGroup.astro'
|
||||
import ListGroupItem from '@ui/ListGroupItem.astro'
|
||||
import ListGroupHeader from '@ui/ListGroupHeader.astro'
|
||||
import CardTitle from '@ui/CardTitle.astro'
|
||||
import people from '@data/people.json'
|
||||
import commits from '@data/commits.json'
|
||||
import { sortBy } from '@shared/lib/string-format'
|
||||
|
||||
interface Person {
|
||||
full_name?: string;
|
||||
photo?: string;
|
||||
last_name?: string;
|
||||
[key: string]: unknown;
|
||||
full_name?: string
|
||||
photo?: string
|
||||
last_name?: string
|
||||
[key: string]: unknown
|
||||
}
|
||||
|
||||
interface Props {
|
||||
title?: string;
|
||||
title?: string
|
||||
}
|
||||
|
||||
const { title = 'People' } = Astro.props;
|
||||
const { title = 'People' } = Astro.props
|
||||
|
||||
const commitList = commits as { description: string }[];
|
||||
const commitList = commits as { description: string }[]
|
||||
|
||||
// Sort by last_name (case-sensitive).
|
||||
const sorted = sortBy(people as Person[], (p) => p.last_name ?? '');
|
||||
const sorted = sortBy(people as Person[], (p) => p.last_name ?? '')
|
||||
|
||||
// offset is unset in the include, so `forloop.index | plus: offset` == forloop.index.
|
||||
let prevLetter = '';
|
||||
let prevLetter = ''
|
||||
const rows = sorted.map((person, idx) => {
|
||||
const index = idx + 1; // forloop.index (1-based)
|
||||
const firstLetter = (person.last_name ?? '').slice(0, 1);
|
||||
let header: string | null = null;
|
||||
if (prevLetter !== firstLetter) {
|
||||
prevLetter = firstLetter;
|
||||
header = firstLetter;
|
||||
}
|
||||
return { person, header, description: commitList[index]?.description };
|
||||
});
|
||||
const index = idx + 1 // forloop.index (1-based)
|
||||
const firstLetter = (person.last_name ?? '').slice(0, 1)
|
||||
let header: string | null = null
|
||||
if (prevLetter !== firstLetter) {
|
||||
prevLetter = firstLetter
|
||||
header = firstLetter
|
||||
}
|
||||
return { person, header, description: commitList[index]?.description }
|
||||
})
|
||||
---
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<CardTitle>{title}</CardTitle>
|
||||
</div>
|
||||
<div class="card-header">
|
||||
<CardTitle>{title}</CardTitle>
|
||||
</div>
|
||||
|
||||
<ListGroup flush class="overflow-auto" style="max-height: 35rem">
|
||||
{
|
||||
rows.map((row) => (
|
||||
<>
|
||||
{row.header && <ListGroupHeader class="sticky-top">{row.header}</ListGroupHeader>}
|
||||
<ListGroupItem>
|
||||
<div class="row">
|
||||
<div class="col-auto">
|
||||
<a href="#">
|
||||
<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>
|
||||
</ListGroupItem>
|
||||
</>
|
||||
))
|
||||
}
|
||||
</ListGroup>
|
||||
<ListGroup flush class="overflow-auto" style="max-height: 35rem">
|
||||
{
|
||||
rows.map((row) => (
|
||||
<>
|
||||
{row.header && <ListGroupHeader class="sticky-top">{row.header}</ListGroupHeader>}
|
||||
<ListGroupItem>
|
||||
<div class="row">
|
||||
<div class="col-auto">
|
||||
<a href="#">
|
||||
<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>
|
||||
</ListGroupItem>
|
||||
</>
|
||||
))
|
||||
}
|
||||
</ListGroup>
|
||||
</div>
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
---
|
||||
// legend=true at call site is inert — Chart reads legend from chart data, not props.
|
||||
import CardTitle from '@ui/CardTitle.astro';
|
||||
import Chart from '@ui/Chart.astro';
|
||||
import DropdownDays from '@ui/DropdownDays.astro';
|
||||
import CardTitle from '@ui/CardTitle.astro'
|
||||
import Chart from '@ui/Chart.astro'
|
||||
import DropdownDays from '@ui/DropdownDays.astro'
|
||||
---
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div class="d-flex">
|
||||
<CardTitle>Social referrals</CardTitle>
|
||||
<div class="ms-auto">
|
||||
<DropdownDays id="social-referrals" label="Select time range for sales data" />
|
||||
</div>
|
||||
</div>
|
||||
<Chart chartId="social-referrals" />
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="d-flex">
|
||||
<CardTitle>Social referrals</CardTitle>
|
||||
<div class="ms-auto">
|
||||
<DropdownDays id="social-referrals" label="Select time range for sales data" />
|
||||
</div>
|
||||
</div>
|
||||
<Chart chartId="social-referrals" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,55 +1,53 @@
|
||||
---
|
||||
// Switch-icon heart variant inlined: icon-b="heart", icon-b-class="icon-filled", icon-a-color="muted", icon-b-color="red".
|
||||
import Icon from '@ui/Icon.astro';
|
||||
import DropdownMenu from '@ui/DropdownMenu.astro';
|
||||
import ListGroup from '@ui/ListGroup.astro';
|
||||
import ListGroupItem from '@ui/ListGroupItem.astro';
|
||||
import tracks from '@data/tracks.json';
|
||||
import { millisecondsToMinutes } from '@shared/lib/string-format';
|
||||
import Icon from '@ui/Icon.astro'
|
||||
import DropdownMenu from '@ui/DropdownMenu.astro'
|
||||
import ListGroup from '@ui/ListGroup.astro'
|
||||
import ListGroupItem from '@ui/ListGroupItem.astro'
|
||||
import tracks from '@data/tracks.json'
|
||||
import { millisecondsToMinutes } from '@shared/lib/string-format'
|
||||
|
||||
const items = tracks.slice(0, 12);
|
||||
const items = tracks.slice(0, 12)
|
||||
---
|
||||
|
||||
<div class="card">
|
||||
<ListGroup class="card-list-group">
|
||||
{items.map((track, index) => (
|
||||
<ListGroupItem>
|
||||
<div class="row g-2 align-items-center">
|
||||
<div class="col-auto fs-3">
|
||||
{index + 1}
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<img src={`./static/tracks/${track.album.images[1].path}`} class="rounded" alt={track.name} width="40" height="40" />
|
||||
</div>
|
||||
<div class="col">
|
||||
{track.name}
|
||||
<div class="text-secondary">
|
||||
{track.artists.map((artist) => artist.name).join(', ')}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-auto text-secondary">
|
||||
{millisecondsToMinutes(track.duration_ms)}
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<a href="#" class="link-secondary">
|
||||
<button class="switch-icon" data-bs-toggle="switch-icon">
|
||||
<span class="switch-icon-a text-muted">
|
||||
<Icon name="heart" />
|
||||
</span>
|
||||
<span class="switch-icon-b text-red">
|
||||
<Icon name="heart" class="icon-filled" />
|
||||
</span>
|
||||
</button>
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-auto lh-1">
|
||||
<div class="dropdown">
|
||||
<a href="#" class="link-secondary" data-bs-toggle="dropdown"><Icon name="dots" /></a>
|
||||
<DropdownMenu right />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ListGroupItem>
|
||||
))}
|
||||
</ListGroup>
|
||||
<ListGroup class="card-list-group">
|
||||
{
|
||||
items.map((track, index) => (
|
||||
<ListGroupItem>
|
||||
<div class="row g-2 align-items-center">
|
||||
<div class="col-auto fs-3">{index + 1}</div>
|
||||
<div class="col-auto">
|
||||
<img src={`./static/tracks/${track.album.images[1].path}`} class="rounded" alt={track.name} width="40" height="40" />
|
||||
</div>
|
||||
<div class="col">
|
||||
{track.name}
|
||||
<div class="text-secondary">{track.artists.map((artist) => artist.name).join(', ')}</div>
|
||||
</div>
|
||||
<div class="col-auto text-secondary">{millisecondsToMinutes(track.duration_ms)}</div>
|
||||
<div class="col-auto">
|
||||
<a href="#" class="link-secondary">
|
||||
<button class="switch-icon" data-bs-toggle="switch-icon">
|
||||
<span class="switch-icon-a text-muted">
|
||||
<Icon name="heart" />
|
||||
</span>
|
||||
<span class="switch-icon-b text-red">
|
||||
<Icon name="heart" class="icon-filled" />
|
||||
</span>
|
||||
</button>
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-auto lh-1">
|
||||
<div class="dropdown">
|
||||
<a href="#" class="link-secondary" data-bs-toggle="dropdown">
|
||||
<Icon name="dots" />
|
||||
</a>
|
||||
<DropdownMenu right />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ListGroupItem>
|
||||
))
|
||||
}
|
||||
</ListGroup>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
---
|
||||
|
||||
---
|
||||
|
||||
<div class="card placeholder-glow">
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
---
|
||||
|
||||
---
|
||||
|
||||
<div class="card">
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
---
|
||||
|
||||
---
|
||||
|
||||
<div class="card">
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
---
|
||||
|
||||
---
|
||||
|
||||
<div class="card">
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
---
|
||||
|
||||
---
|
||||
|
||||
<div class="card">
|
||||
|
||||
@@ -1,30 +1,30 @@
|
||||
---
|
||||
import ListGroup from '@ui/ListGroup.astro';
|
||||
import ListGroupItem from '@ui/ListGroupItem.astro';
|
||||
import ListGroup from '@ui/ListGroup.astro'
|
||||
import ListGroupItem from '@ui/ListGroupItem.astro'
|
||||
|
||||
const items = [1, 2, 3, 4];
|
||||
const items = [1, 2, 3, 4]
|
||||
---
|
||||
|
||||
<div class="card">
|
||||
<ListGroup as="ul" flush class="placeholder-glow">
|
||||
{
|
||||
items.map(() => (
|
||||
<ListGroupItem as="li">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-auto">
|
||||
<div class="avatar avatar-rounded placeholder" />
|
||||
</div>
|
||||
<div class="col-7">
|
||||
<div class="placeholder placeholder-xs col-9" />
|
||||
<div class="placeholder placeholder-xs col-7" />
|
||||
</div>
|
||||
<div class="col-2 ms-auto text-end">
|
||||
<div class="placeholder placeholder-xs col-8" />
|
||||
<div class="placeholder placeholder-xs col-10" />
|
||||
</div>
|
||||
</div>
|
||||
</ListGroupItem>
|
||||
))
|
||||
}
|
||||
</ListGroup>
|
||||
<ListGroup as="ul" flush class="placeholder-glow">
|
||||
{
|
||||
items.map(() => (
|
||||
<ListGroupItem as="li">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-auto">
|
||||
<div class="avatar avatar-rounded placeholder" />
|
||||
</div>
|
||||
<div class="col-7">
|
||||
<div class="placeholder placeholder-xs col-9" />
|
||||
<div class="placeholder placeholder-xs col-7" />
|
||||
</div>
|
||||
<div class="col-2 ms-auto text-end">
|
||||
<div class="placeholder placeholder-xs col-8" />
|
||||
<div class="placeholder placeholder-xs col-10" />
|
||||
</div>
|
||||
</div>
|
||||
</ListGroupItem>
|
||||
))
|
||||
}
|
||||
</ListGroup>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user