Narrow component prop types, remove unused props and dead code (#2838)

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
This commit is contained in:
Paweł Kuna
2026-08-09 23:14:33 +02:00
committed by GitHub
co-authored by StarDev
parent 0085eeba6e
commit 4f7beecd2b
90 changed files with 265 additions and 656 deletions
+1 -1
View File
@@ -644,7 +644,7 @@ function greetUser(name) {
<div class="modal-dialog modal-sm modal-dialog-centered" role="document"> <div class="modal-dialog modal-sm modal-dialog-centered" role="document">
<div class="modal-content"> <div class="modal-content">
<div class="modal-body text-center py-4"> <div class="modal-body text-center py-4">
<Icon name="circle-check" color="success" size="xl" class="mb-2" /> <Icon name="circle-check" color="success" size="lg" class="mb-2" />
<h3>Success!</h3> <h3>Success!</h3>
<div class="text-secondary">Your action was completed successfully.</div> <div class="text-secondary">Your action was completed successfully.</div>
</div> </div>
+3 -3
View File
@@ -17,9 +17,9 @@ const colors = site.themeColors
const people8 = people.slice(0, 8) const people8 = people.slice(0, 8)
const people5 = people.slice(0, 5) const people5 = people.slice(0, 5)
const sizes = ['xxs', 'xs', 'sm', 'md', 'lg', 'xl'] const sizes = ['xxs', 'xs', 'sm', 'md', 'lg', 'xl'] as const
const listSizes = ['xxs', 'xs', 'sm', 'md', 'lg'] const listSizes = ['xxs', 'xs', 'sm', 'md', 'lg'] as const
const uploadSizes = ['xxs', 'xs', 'sm', 'md', 'lg', 'xl', '2xl'] const uploadSizes = ['xxs', 'xs', 'sm', 'md', 'lg', 'xl', '2xl'] as const
const statusColors = ['red', 'green', 'blue', 'yellow', 'secondary'] const statusColors = ['red', 'green', 'blue', 'yellow', 'secondary']
const brands = ['netflix', 'amazon', 'messenger', 'figma', 'twitch'] const brands = ['netflix', 'amazon', 'messenger', 'figma', 'twitch']
--- ---
+1 -1
View File
@@ -17,7 +17,7 @@ const colors = Object.entries(site.colors) as ColorEntry[]
const socialColors = Object.entries(site.socialColors) as ColorEntry[] const socialColors = Object.entries(site.socialColors) as ColorEntry[]
const actions = ['edit', 'copy', 'settings', 'clipboard', 'x'] const actions = ['edit', 'copy', 'settings', 'clipboard', 'x']
const sizes = ['sm', 'md', 'lg', 'xl'] const sizes = ['sm', 'md', 'lg', 'xl'] as const
--- ---
<DefaultLayout title="Buttons" pageHeader="Buttons" pageMenu="base.buttons"> <DefaultLayout title="Buttons" pageHeader="Buttons" pageMenu="base.buttons">
+1 -1
View File
@@ -7,7 +7,7 @@ import Offcanvas from '@ui/Offcanvas.astro'
import OffcanvasHeader from '@ui/OffcanvasHeader.astro' import OffcanvasHeader from '@ui/OffcanvasHeader.astro'
import OffcanvasBody from '@ui/OffcanvasBody.astro' import OffcanvasBody from '@ui/OffcanvasBody.astro'
const directions = ['start', 'end', 'top', 'bottom'] const directions = ['start', 'end', 'top', 'bottom'] as const
--- ---
<DefaultLayout title="Offcanvas" pageHeader="Offcanvas" pageMenu="base.offcanvas"> <DefaultLayout title="Offcanvas" pageHeader="Offcanvas" pageMenu="base.offcanvas">
+3 -3
View File
@@ -100,7 +100,7 @@ import Icon from '@ui/Icon.astro'
<Card id="tour-card-4"> <Card id="tour-card-4">
<CardBody class="text-center"> <CardBody class="text-center">
<div class="mb-3"> <div class="mb-3">
<Icon name="settings" size="48" /> <Icon name="settings" size="lg" />
</div> </div>
<CardTitle>Settings</CardTitle> <CardTitle>Settings</CardTitle>
<p class="text-secondary">Configure your application settings here.</p> <p class="text-secondary">Configure your application settings here.</p>
@@ -112,7 +112,7 @@ import Icon from '@ui/Icon.astro'
<Card id="tour-card-5"> <Card id="tour-card-5">
<CardBody class="text-center"> <CardBody class="text-center">
<div class="mb-3"> <div class="mb-3">
<Icon name="users" size="48" /> <Icon name="users" size="lg" />
</div> </div>
<CardTitle>Users</CardTitle> <CardTitle>Users</CardTitle>
<p class="text-secondary">Manage your team members and permissions.</p> <p class="text-secondary">Manage your team members and permissions.</p>
@@ -124,7 +124,7 @@ import Icon from '@ui/Icon.astro'
<Card id="tour-card-6"> <Card id="tour-card-6">
<CardBody class="text-center"> <CardBody class="text-center">
<div class="mb-3"> <div class="mb-3">
<Icon name="chart-bar" size="48" /> <Icon name="chart-bar" size="lg" />
</div> </div>
<CardTitle>Analytics</CardTitle> <CardTitle>Analytics</CardTitle>
<p class="text-secondary">View your application statistics and reports.</p> <p class="text-secondary">View your application statistics and reports.</p>
@@ -1,16 +1,15 @@
--- ---
interface Props { interface Props {
width?: number
height?: number height?: number
ratio?: string
class?: string class?: string
} }
const { width = 400, height = 200, ratio, class: className } = Astro.props const width = 400
const { height = 200, class: className } = Astro.props
--- ---
<div class={`card-body${className ? ` ${className}` : ''}`}> <div class={`card-body${className ? ` ${className}` : ''}`}>
<svg xmlns="http://www.w3.org/2000/svg" class="w-100 border rounded" preserveAspectRatio={ratio ? 'xMidYMid meet' : 'none'} width={width} height={height} viewBox={`0 0 ${width} ${height}`} fill="transparent" stroke="var(--tblr-border-color, #b8cef1)"> <svg xmlns="http://www.w3.org/2000/svg" class="w-100 border rounded" preserveAspectRatio="none" width={width} height={height} viewBox={`0 0 ${width} ${height}`} fill="transparent" stroke="var(--tblr-border-color, #b8cef1)">
<line x1="0" y1="0" x2={width} y2={height}></line> <line x1="0" y1="0" x2={width} y2={height}></line>
<line x1="0" y1={height} x2={width} y2="0"></line> <line x1="0" y1={height} x2={width} y2="0"></line>
</svg> </svg>
+10 -54
View File
@@ -1,5 +1,4 @@
--- ---
import CardSubtitle from '@ui/CardSubtitle.astro';
import Icon from '@ui/Icon.astro'; import Icon from '@ui/Icon.astro';
import AvatarList from '@ui/AvatarList.astro'; import AvatarList from '@ui/AvatarList.astro';
import Empty from '@ui/Empty.astro'; import Empty from '@ui/Empty.astro';
@@ -8,9 +7,6 @@ import photos from '@data/photos.json';
import { requireIndex } from '@shared/lib/array'; import { requireIndex } from '@shared/lib/array';
interface Props { interface Props {
link?: boolean;
active?: boolean;
inactive?: boolean;
class?: string; class?: string;
empty?: boolean; empty?: boolean;
imgTop?: boolean; imgTop?: boolean;
@@ -18,17 +14,9 @@ interface Props {
statusTop?: string; statusTop?: string;
statusBottom?: string; statusBottom?: string;
statusStart?: string; statusStart?: string;
header?: boolean;
headerTitle?: string;
headerTabs?: boolean; headerTabs?: boolean;
headerPills?: boolean; headerPills?: boolean;
alert?: string;
alertType?: string;
title?: string; title?: string;
subtitle?: boolean;
body?: string;
button?: boolean;
footer?: boolean;
footerButton?: boolean; footerButton?: boolean;
footerButtons?: boolean; footerButtons?: boolean;
/** a right-angle-bracket prefix pushes the element (and every following one) to the right — `right` is never reset */ /** a right-angle-bracket prefix pushes the element (and every following one) to the right — `right` is never reset */
@@ -37,9 +25,6 @@ interface Props {
} }
const { const {
link = false,
active,
inactive,
class: className, class: className,
empty, empty,
imgTop, imgTop,
@@ -47,31 +32,16 @@ const {
statusTop, statusTop,
statusBottom, statusBottom,
statusStart, statusStart,
header,
headerTitle,
headerTabs, headerTabs,
headerPills, headerPills,
alert,
alertType = 'success',
title, title,
subtitle,
body,
button,
footer,
footerButton, footerButton,
footerButtons, footerButtons,
footerElements, footerElements,
progress, progress,
}: Props = Astro.props; }: Props = Astro.props;
const Element = link ? 'a' : 'div'; const classes = ['card', className]
const classes = [
'card',
active && 'card-active',
inactive && 'card-inactive',
className,
]
// footer-elements parsing — `right` is never reset once set across iterations. // footer-elements parsing — `right` is never reset once set across iterations.
let right = false; let right = false;
@@ -84,14 +54,14 @@ const footerEls = (footerElements ?? []).map((element: string) => {
return { el, right }; return { el, right };
}); });
const hasHeader = header || headerTitle || headerTabs || headerPills; const hasHeader = headerTabs || headerPills;
const hasFooter = footer || footerButton || footerButtons || footerElements; const hasFooter = footerButton || footerButtons || footerElements;
const imgTopPhoto = requireIndex(photos as { file: string; title: string }[], 7); const imgTopPhoto = requireIndex(photos as { file: string; title: string }[], 7);
const imgBottomPhoto = requireIndex(photos as { file: string; title: string }[], 11); const imgBottomPhoto = requireIndex(photos as { file: string; title: string }[], 11);
--- ---
<Element href={link ? '#' : undefined} class:list={classes}> <div class:list={classes}>
{ {
empty ? ( empty ? (
<Empty illustration="not-found" height={160} /> <Empty illustration="not-found" height={160} />
@@ -142,32 +112,18 @@ const imgBottomPhoto = requireIndex(photos as { file: string; title: string }[],
</li> </li>
</ul> </ul>
) : ( ) : (
<h3 class="card-title" set:html={headerTitle ?? 'Header title'} /> <h3 class="card-title">Header title</h3>
)} )}
</div> </div>
)} )}
{alert && <div class={`card-alert alert alert-${alertType} mb-0`} set:html={alert} />}
<div class="card-body"> <div class="card-body">
{title && <h3 class="card-title" set:html={title} />} {title && <h3 class="card-title" set:html={title} />}
{subtitle && <CardSubtitle as="div">Card subtitle</CardSubtitle>}
{body ? ( <p class="text-secondary">
<p class="text-secondary" set:html={body} /> Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aperiam deleniti fugit incidunt, iste, itaque
) : ( minima neque pariatur perferendis sed suscipit velit vitae voluptatem.
<p class="text-secondary"> </p>
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aperiam deleniti fugit incidunt, iste, itaque
minima neque pariatur perferendis sed suscipit velit vitae voluptatem.
</p>
)}
{button && (
<div class="card-text">
<a href="#" class="btn btn-primary">
Go somewhere
</a>
</div>
)}
</div> </div>
{hasFooter && ( {hasFooter && (
@@ -228,4 +184,4 @@ const imgBottomPhoto = requireIndex(photos as { file: string; title: string }[],
</Fragment> </Fragment>
) )
} }
</Element> </div>
+2 -5
View File
@@ -1,14 +1,11 @@
--- ---
// Tabs/content blocks are HTML strings (different order when `bottom`). // Tabs/content blocks are HTML strings (different order when `bottom`).
interface Props { interface Props {
/** tabs-count — present but unused */
count?: number
id?: string id?: string
bottom?: boolean bottom?: boolean
borderless?: boolean
} }
const { id = 'top', bottom, borderless } = Astro.props const { id = 'top', bottom } = Astro.props
const tabs = ['Activity', 'Profile', 'Settings'] const tabs = ['Activity', 'Profile', 'Settings']
@@ -37,7 +34,7 @@ ${tabs
--- ---
<!-- Cards with tabs component --> <!-- Cards with tabs component -->
<div class={`card-tabs${borderless ? ' border-0' : ''}`}> <div class="card-tabs">
{ {
bottom ? ( bottom ? (
<Fragment> <Fragment>
+2 -2
View File
@@ -14,12 +14,12 @@ interface Props {
controls?: boolean controls?: boolean
captions?: boolean captions?: boolean
offset?: number offset?: number
limit?: number
fade?: boolean 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, fade } = Astro.props
const limit = 5
const carouselId = idProp ?? 'carousel' const carouselId = idProp ?? 'carousel'
const filteredPhotos = (photos as { file: string; title?: string; horizontal?: boolean }[]).filter((p) => p.horizontal) const filteredPhotos = (photos as { file: string; title?: string; horizontal?: boolean }[]).filter((p) => p.horizontal)
const slides = filteredPhotos.slice(offset, offset + limit) const slides = filteredPhotos.slice(offset, offset + limit)
+2 -2
View File
@@ -3,10 +3,10 @@ import MapVector from '@ui/MapVector.astro'
interface Props { interface Props {
title?: string title?: string
map?: string
} }
const { title, map = 'world' } = Astro.props const { title } = Astro.props
const map = 'world'
--- ---
<div class="card"> <div class="card">
+2 -2
View File
@@ -12,10 +12,10 @@ interface Person {
interface Props { interface Props {
person: Person person: Person
color?: string
} }
const { person, color = 'yellow' } = Astro.props const { person } = Astro.props
const color = 'yellow'
--- ---
<div class={`card card-gradient card-gradient-${color}`}> <div class={`card card-gradient card-gradient-${color}`}>
+2 -3
View File
@@ -9,17 +9,16 @@ interface Props {
badge?: string badge?: string
offset?: number offset?: number
limit?: number limit?: number
percentage?: number | string
percentageColor?: string percentageColor?: string
due?: string due?: string
value?: number | string value?: number | string
} }
const { title = 'Task Title', badge, offset = 40, limit = 7, percentage, percentageColor = 'green', due = '2 days', value = 20 } = Astro.props const { title = 'Task Title', badge, offset = 40, limit = 7, percentageColor = 'green', due = '2 days', value = 20 } = Astro.props
--- ---
<div class="card"> <div class="card">
<Progress value={percentage ?? value} class="card-progress" color={percentageColor} /> <Progress value={value} class="card-progress" color={percentageColor} />
<div class="card-body"> <div class="card-body">
<CardTitle> <CardTitle>
<a href="#">{title}</a> <a href="#">{title}</a>
+7 -12
View File
@@ -4,31 +4,26 @@ import AvatarList from '@ui/AvatarList.astro'
import Progress from '@ui/Progress.astro' import Progress from '@ui/Progress.astro'
interface Props { interface Props {
title?: string
date?: string
stageColor?: string stageColor?: string
stage?: string
projectColor?: string
percentage?: number | string
percentageColor?: string
avatarOffset?: number
avatarLimit?: number
} }
const { title = 'New website', date = '28 Aug 2019', stageColor = 'green', stage = 'Waiting', projectColor, percentage, percentageColor, avatarOffset, avatarLimit } = Astro.props const { stageColor = 'green' } = Astro.props
const title = 'New website'
const date = '28 Aug 2019'
const stage = 'Waiting'
--- ---
<div class="card"> <div class="card">
<div class="card-body p-4 py-5 text-center"> <div class="card-body p-4 py-5 text-center">
<Avatar size="xl" placeholder="W" class="mb-4" color={projectColor} /> <Avatar size="xl" placeholder="W" class="mb-4" />
<h3 class="mb-0">{title}</h3> <h3 class="mb-0">{title}</h3>
<p class="text-secondary">Due to: {date}</p> <p class="text-secondary">Due to: {date}</p>
<p class="mb-3"> <p class="mb-3">
<span class={`badge bg-${stageColor}-lt`}>{stage}</span> <span class={`badge bg-${stageColor}-lt`}>{stage}</span>
</p> </p>
<div> <div>
<AvatarList stacked offset={avatarOffset} limit={avatarLimit} /> <AvatarList stacked />
</div> </div>
</div> </div>
<Progress class="card-progress" value={percentage} color={percentageColor} /> <Progress class="card-progress" />
</div> </div>
+1 -23
View File
@@ -4,8 +4,6 @@
import Icon from '@ui/Icon.astro' import Icon from '@ui/Icon.astro'
import Avatar from '@ui/Avatar.astro' import Avatar from '@ui/Avatar.astro'
import ChartSparkline from '@ui/ChartSparkline.astro' import ChartSparkline from '@ui/ChartSparkline.astro'
import Trending from '@ui/Trending.astro'
import Button from '@ui/Button.astro'
interface Props { interface Props {
icon?: string icon?: string
@@ -19,16 +17,12 @@ interface Props {
chartType?: string chartType?: string
chartPosition?: string chartPosition?: string
chartData?: string | number chartData?: string | number
chartLabel?: string
chartLabelIcon?: string
smallIcon?: string smallIcon?: string
descriptionValue?: string descriptionValue?: string
descriptionValueColor?: string descriptionValueColor?: string
trending?: number
button?: string
} }
const { icon, color, lt, title, description, class: className, id, personId, chartType = 'line', chartPosition = 'right', chartData, smallIcon, descriptionValue, descriptionValueColor, trending, button } = Astro.props const { icon, color, lt, title, description, class: className, id, personId, chartType = 'line', chartPosition = 'right', chartData, smallIcon, descriptionValue, descriptionValueColor } = Astro.props
const chartColor = color ?? 'primary' const chartColor = color ?? 'primary'
const avatarClass = [color ? `bg-${color}${lt ? '-lt' : ' text-white'}` : '', 'avatar', 'avatar-square'] const avatarClass = [color ? `bg-${color}${lt ? '-lt' : ' text-white'}` : '', 'avatar', 'avatar-square']
@@ -73,22 +67,6 @@ const avatarClass = [color ? `bg-${color}${lt ? '-lt' : ' text-white'}` : '', 'a
</div> </div>
) )
} }
{
trending && (
<div class="col-auto">
<Trending value={trending} />
</div>
)
}
{
button && (
<div class="col-auto">
<Button text={button} size="sm" />
</div>
)
}
</div> </div>
</div> </div>
</div> </div>
+7 -10
View File
@@ -5,8 +5,6 @@ import DropdownMenu from '@ui/DropdownMenu.astro'
interface Props { interface Props {
id: string id: string
icons?: boolean icons?: boolean
/** hide-text param; not used by current call sites */
hideText?: boolean
reverse?: boolean reverse?: boolean
justified?: boolean justified?: boolean
activity?: boolean activity?: boolean
@@ -16,9 +14,8 @@ interface Props {
animation?: boolean animation?: boolean
} }
const { id, icons, hideText, reverse = false, justified, activity, disabled, dropdown, settings, animation } = Astro.props const { id, icons, 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 navClass = `nav nav-tabs card-header-tabs${reverse ? ' flex-row-reverse' : ''}${justified ? ' nav-fill' : ''}`
const paneClass = `tab-pane${animation ? ' fade' : ''}` const paneClass = `tab-pane${animation ? ' fade' : ''}`
--- ---
@@ -27,18 +24,18 @@ const paneClass = `tab-pane${animation ? ' fade' : ''}`
<div class="card-header"> <div class="card-header">
<ul class={navClass} role="tablist"> <ul class={navClass} role="tablist">
<li class="nav-item" role="presentation"> <li class="nav-item" role="presentation">
<a href={`#tabs-home-${id}`} id={`tabs-home-${id}-tab`} class="nav-link active" data-bs-toggle="tab" data-bs-target={`#tabs-home-${id}`} role="tab" aria-controls={`tabs-home-${id}`} aria-selected="true">{icons && <Icon name="home" class={iconClass} />}{!hideText && 'Home'}</a> <a href={`#tabs-home-${id}`} id={`tabs-home-${id}-tab`} class="nav-link active" data-bs-toggle="tab" data-bs-target={`#tabs-home-${id}`} role="tab" aria-controls={`tabs-home-${id}`} aria-selected="true">{icons && <Icon name="home" class="me-2" />}Home</a>
</li> </li>
<li class="nav-item" role="presentation"> <li class="nav-item" role="presentation">
<a href={`#tabs-profile-${id}`} id={`tabs-profile-${id}-tab`} class="nav-link" data-bs-toggle="tab" data-bs-target={`#tabs-profile-${id}`} role="tab" aria-controls={`tabs-profile-${id}`} aria-selected="false">{icons && <Icon name="user" class={iconClass} />}{!hideText && 'Profile'}</a> <a href={`#tabs-profile-${id}`} id={`tabs-profile-${id}-tab`} class="nav-link" data-bs-toggle="tab" data-bs-target={`#tabs-profile-${id}`} role="tab" aria-controls={`tabs-profile-${id}`} aria-selected="false">{icons && <Icon name="user" class="me-2" />}Profile</a>
</li> </li>
{ {
activity && ( activity && (
<li class="nav-item" role="presentation"> <li class="nav-item" role="presentation">
<a href={`#tabs-activity-${id}`} id={`tabs-activity-${id}-tab`} class="nav-link" data-bs-toggle="tab" data-bs-target={`#tabs-activity-${id}`} role="tab" aria-controls={`tabs-activity-${id}`} aria-selected="false"> <a href={`#tabs-activity-${id}`} id={`tabs-activity-${id}-tab`} class="nav-link" data-bs-toggle="tab" data-bs-target={`#tabs-activity-${id}`} role="tab" aria-controls={`tabs-activity-${id}`} aria-selected="false">
{icons && <Icon name="activity" class={iconClass} />} {icons && <Icon name="activity" class="me-2" />}
{!hideText && 'Activity'} Activity
</a> </a>
</li> </li>
) )
@@ -48,8 +45,8 @@ const paneClass = `tab-pane${animation ? ' fade' : ''}`
disabled && ( disabled && (
<li class="nav-item" role="presentation"> <li class="nav-item" role="presentation">
<a href="#" class="nav-link disabled" data-bs-toggle="tab" role="tab" aria-selected="false" aria-disabled="true" tabindex="-1"> <a href="#" class="nav-link disabled" data-bs-toggle="tab" role="tab" aria-selected="false" aria-disabled="true" tabindex="-1">
{icons && <Icon name="x" class={iconClass} />} {icons && <Icon name="x" class="me-2" />}
{!hideText && 'Disabled'} Disabled
</a> </a>
</li> </li>
) )
+2 -8
View File
@@ -16,18 +16,12 @@ interface Person {
[key: string]: unknown [key: string]: unknown
} }
interface Props { const person = requireIndex(people as Person[], 24)
title?: string
personId?: number
}
const { title = 'Basic info', personId = 25 } = Astro.props
const person = requireIndex(people as Person[], personId - 1)
--- ---
<div class="card"> <div class="card">
<div class="card-body"> <div class="card-body">
<h3 class="card-title">{title}</h3> <h3 class="card-title">Basic info</h3>
<div class="mb-2"> <div class="mb-2">
<Icon name="book" class="me-2 text-secondary" /> <Icon name="book" class="me-2 text-secondary" />
Went to: <strong>{person.university}</strong> Went to: <strong>{person.university}</strong>
+3 -2
View File
@@ -16,7 +16,6 @@ interface Person {
} }
interface Props { interface Props {
limit?: number
offset?: number offset?: number
hoverable?: boolean hoverable?: boolean
checkbox?: boolean checkbox?: boolean
@@ -32,7 +31,9 @@ interface Props {
hover?: boolean hover?: boolean
} }
const { limit = 8, offset = 0, hoverable = false, checkbox, checkedIds, title = 'Last commits', class: className } = Astro.props const { offset = 0, hoverable = false, checkbox, checkedIds, title = 'Last commits', class: className } = Astro.props
const limit = 8
const colors = ['green', 'red', 'yellow', 'x', 'x'] const colors = ['green', 'red', 'yellow', 'x', 'x']
const commitList = commits as { description: string }[] const commitList = commits as { description: string }[]
+5 -4
View File
@@ -12,13 +12,14 @@ interface Person {
} }
interface Props { interface Props {
limit?: number
offset?: number
title?: string
class?: string class?: string
} }
const { limit = 10, offset = 0, title = 'Top users', class: className } = Astro.props const { class: className } = Astro.props
const limit = 10
const offset = 0
const title = 'Top users'
const colors = ['green', 'red', 'yellow', 'x', 'x'] as const const colors = ['green', 'red', 'yellow', 'x', 'x'] as const
const statusLabels: Record<(typeof colors)[number], string> = { green: 'Online', red: 'Busy', yellow: 'Away', x: 'Offline' } const statusLabels: Record<(typeof colors)[number], string> = { green: 'Online', red: 'Busy', yellow: 'Away', x: 'Offline' }
@@ -15,11 +15,7 @@ interface Person {
[key: string]: unknown [key: string]: unknown
} }
interface Props { const title = 'People'
title?: string
}
const { title = 'People' } = Astro.props
const commitList = commits as { description: string }[] const commitList = commits as { description: string }[]
+1 -5
View File
@@ -2,7 +2,6 @@
// Layout front-matter flags are plain props here. // Layout front-matter flags are plain props here.
// TODO: layout-navbar-overlap && layout-navbar-dark → extra text-white class // TODO: layout-navbar-overlap && layout-navbar-dark → extra text-white class
// on .page-header — both unset on the homepage. // on .page-header — both unset on the homepage.
import Icon from '@ui/Icon.astro';
import HeaderActionsButtons from './HeaderActionsButtons.astro'; import HeaderActionsButtons from './HeaderActionsButtons.astro';
import HeaderActionsPrint from './HeaderActionsPrint.astro'; import HeaderActionsPrint from './HeaderActionsPrint.astro';
import HeaderActionsPhotos from './HeaderActionsPhotos.astro'; import HeaderActionsPhotos from './HeaderActionsPhotos.astro';
@@ -24,15 +23,13 @@ interface Props {
actions?: string | undefined; actions?: string | undefined;
/** page-header-class */ /** page-header-class */
class?: string; class?: string;
/** page-header-icon */
icon?: string;
/** page-header-file — name of an include from layout/headers/, e.g. "profile" */ /** page-header-file — name of an include from layout/headers/, e.g. "profile" */
file?: string | undefined; file?: string | undefined;
/** layout-navbar-overlap && layout-navbar-dark → text-white on .page-header */ /** layout-navbar-overlap && layout-navbar-dark → text-white on .page-header */
textWhite?: boolean | undefined; textWhite?: boolean | undefined;
} }
const { title, pretitle, description, actions, class: className, icon, file, textWhite } = Astro.props; const { title, pretitle, description, actions, class: className, file, textWhite } = Astro.props;
--- ---
{file === 'profile' && <HeaderProfile />} {file === 'profile' && <HeaderProfile />}
@@ -50,7 +47,6 @@ const { title, pretitle, description, actions, class: className, icon, file, tex
</Fragment> </Fragment>
)} )}
<h1 class="page-title"> <h1 class="page-title">
{icon && <Icon name={icon} />}
{title} {title}
</h1> </h1>
+1 -1
View File
@@ -28,7 +28,7 @@ const typedId = 'typed'
</div> </div>
<div class="col-md-6"> <div class="col-md-6">
<div class="hero-img-side"> <div class="hero-img-side">
<Carousel id="carousel-controls" slideCount={3} interval={4000}> <Carousel id="carousel-controls" interval={4000}>
<div class="carousel-item active"> <div class="carousel-item active">
<Illustration image="boy-with-key.svg" class="d-block mx-auto" height="350" /> <Illustration image="boy-with-key.svg" class="d-block mx-auto" height="350" />
</div> </div>
@@ -3,13 +3,12 @@ import Button from '@ui/Button.astro'
import ButtonList from '@ui/ButtonList.astro' import ButtonList from '@ui/ButtonList.astro'
interface Props { interface Props {
background?: string
class?: string class?: string
} }
const { background, class: className } = Astro.props const { class: className } = Astro.props
const sectionClass = ['section', background && `section-${background}`, className] const sectionClass = ['section', className]
--- ---
<section class:list={sectionClass}> <section class:list={sectionClass}>
@@ -1,20 +1,16 @@
--- ---
import SectionDivider from '../SectionDivider.astro'
import Icon from '@ui/Icon.astro' import Icon from '@ui/Icon.astro'
interface Props { interface Props {
background?: string
class?: string class?: string
divider?: string
} }
const { background, class: className, divider } = Astro.props const { class: className } = Astro.props
const sectionClass = ['section', background && `section-${background}`, className] const sectionClass = ['section', className]
--- ---
<section class:list={sectionClass}> <section class:list={sectionClass}>
<SectionDivider divider={divider} />
<div class="container"> <div class="container">
<div class="row items-center text-center g-lg-10"> <div class="row items-center text-center g-lg-10">
<div class="col-md-6 col-lg"> <div class="col-md-6 col-lg">
@@ -1,20 +1,17 @@
--- ---
import SectionDivider from '../SectionDivider.astro'
import Icon from '@ui/Icon.astro' import Icon from '@ui/Icon.astro'
interface Props { interface Props {
background?: string background?: string
class?: string class?: string
divider?: string
} }
const { background, class: className, divider } = Astro.props const { background, class: className } = Astro.props
const sectionClass = ['section', background && `section-${background}`, className] const sectionClass = ['section', background && `section-${background}`, className]
--- ---
<section class:list={sectionClass}> <section class:list={sectionClass}>
<SectionDivider divider={divider} />
<div class="container"> <div class="container">
<div class="section-header"> <div class="section-header">
<h2 class="section-title">Everything you need to deploy your app</h2> <h2 class="section-title">Everything you need to deploy your app</h2>
@@ -1,20 +1,17 @@
--- ---
import SectionDivider from '../SectionDivider.astro'
import Icon from '@ui/Icon.astro' import Icon from '@ui/Icon.astro'
interface Props { interface Props {
background?: string background?: string
class?: string class?: string
divider?: string
} }
const { background, class: className, divider } = Astro.props const { background, class: className } = Astro.props
const sectionClass = ['section', background && `section-${background}`, className] const sectionClass = ['section', background && `section-${background}`, className]
--- ---
<section class:list={sectionClass}> <section class:list={sectionClass}>
<SectionDivider divider={divider} />
<div class="container"> <div class="container">
<div class="section-header"> <div class="section-header">
<h2 class="section-title">Everything you need to deploy your app</h2> <h2 class="section-title">Everything you need to deploy your app</h2>
@@ -2,13 +2,12 @@
import Icon from '@ui/Icon.astro' import Icon from '@ui/Icon.astro'
interface Props { interface Props {
background?: string
class?: string class?: string
} }
const { background, class: className } = Astro.props const { class: className } = Astro.props
const sectionClass = ['section', background && `section-${background}`, className] const sectionClass = ['section', className]
--- ---
<section class:list={sectionClass}> <section class:list={sectionClass}>
@@ -1,13 +1,7 @@
--- ---
import Icon from '@ui/Icon.astro' import Icon from '@ui/Icon.astro'
interface Props { const sectionClass = ['section']
background?: string
}
const { background } = Astro.props
const sectionClass = ['section', background && `section-${background}`]
--- ---
<section class:list={sectionClass}> <section class:list={sectionClass}>
@@ -2,13 +2,12 @@
import Icon from '@ui/Icon.astro' import Icon from '@ui/Icon.astro'
interface Props { interface Props {
background?: string
class?: string class?: string
} }
const { background, class: className } = Astro.props const { class: className } = Astro.props
const sectionClass = ['section', background && `section-${background}`, className] const sectionClass = ['section', className]
--- ---
<section class:list={sectionClass}> <section class:list={sectionClass}>
@@ -13,15 +13,14 @@ interface Person {
} }
interface Props { interface Props {
background?: string
class?: string class?: string
limit?: number limit?: number
hideHeader?: boolean hideHeader?: boolean
} }
const { background, class: className, limit = 99, hideHeader } = Astro.props const { class: className, limit = 99, hideHeader } = Astro.props
const sectionClass = ['section', background && `section-${background}`, className] const sectionClass = ['section', className]
// Slice testimonials and split into 3 columns. // Slice testimonials and split into 3 columns.
const list = (testimonials as string[]).slice(0, limit) const list = (testimonials as string[]).slice(0, limit)
+7 -10
View File
@@ -3,24 +3,21 @@
// end of <body> (see PageModals). For inline gallery demos use ModalInline. // end of <body> (see PageModals). For inline gallery demos use ModalInline.
interface Props { interface Props {
modalId?: string modalId?: string
size?: string /** core/scss/bootstrap/_modal.scss + ui/_modals.scss `.modal-{size}` modifier classes */
size?: 'sm' | 'lg' | 'xl' | 'fullscreen' | 'full-width' | undefined
top?: boolean top?: boolean
scrollable?: boolean
class?: string class?: string
show?: boolean
style?: string
/** id of the title element the root references via aria-labelledby; defaults to `modal-${modalId}-title` */
titleId?: string
} }
const { modalId = 'simple', size, top, scrollable, class: className, show, style, titleId = `modal-${modalId}-title` } = Astro.props const { modalId = 'simple', size, top, class: className } = Astro.props
const titleId = `modal-${modalId}-title`
const modalClass = ['modal modal-blur fade', className, show && 'show'].filter(Boolean).join(' ') const modalClass = ['modal modal-blur fade', className].filter(Boolean).join(' ')
const dialogClass = ['modal-dialog', size && `modal-${size}`, !top && 'modal-dialog-centered', scrollable && 'modal-dialog-scrollable'].filter(Boolean).join(' ') const dialogClass = ['modal-dialog', size && `modal-${size}`, !top && 'modal-dialog-centered'].filter(Boolean).join(' ')
--- ---
<!-- BEGIN MODAL --> <!-- BEGIN MODAL -->
<div class={modalClass} style={style} id={`modal-${modalId}`} tabindex="-1" role="dialog" aria-modal="true" aria-labelledby={titleId} aria-hidden={show ? undefined : 'true'}> <div class={modalClass} id={`modal-${modalId}`} tabindex="-1" role="dialog" aria-modal="true" aria-labelledby={titleId} aria-hidden="true">
<div class={dialogClass}> <div class={dialogClass}>
<div class="modal-content"> <div class="modal-content">
<slot /> <slot />
+5 -6
View File
@@ -3,20 +3,19 @@
// modals gallery page where each modal is shown inline with `inline show`. // modals gallery page where each modal is shown inline with `inline show`.
interface Props { interface Props {
modalId?: string modalId?: string
size?: string /** core/scss/bootstrap/_modal.scss + ui/_modals.scss `.modal-{size}` modifier classes */
top?: boolean size?: 'sm' | 'lg' | 'xl' | 'fullscreen' | 'full-width' | undefined
scrollable?: boolean scrollable?: boolean
class?: string class?: string
show?: boolean show?: boolean
style?: string style?: string
/** id of the title element the root references via aria-labelledby; defaults to `modal-${modalId}-title` */
titleId?: string
} }
const { modalId = 'simple', size, top, scrollable, class: className, show, style, titleId = `modal-${modalId}-title` } = Astro.props const { modalId = 'simple', size, scrollable, class: className, show, style } = Astro.props
const titleId = `modal-${modalId}-title`
const modalClass = ['modal modal-blur fade', className, show && 'show'].filter(Boolean).join(' ') const modalClass = ['modal modal-blur fade', className, show && 'show'].filter(Boolean).join(' ')
const dialogClass = ['modal-dialog', size && `modal-${size}`, !top && 'modal-dialog-centered', scrollable && 'modal-dialog-scrollable'].filter(Boolean).join(' ') const dialogClass = ['modal-dialog', size && `modal-${size}`, 'modal-dialog-centered', scrollable && 'modal-dialog-scrollable'].filter(Boolean).join(' ')
--- ---
<!-- BEGIN MODAL --> <!-- BEGIN MODAL -->
+2 -2
View File
@@ -17,7 +17,6 @@ interface Props {
hideSearch?: boolean; hideSearch?: boolean;
/** equivalent of the page-menu front matter (active menu entry), e.g. "dashboards.default" */ /** equivalent of the page-menu front matter (active menu entry), e.g. "dashboards.default" */
pageMenu?: string | undefined; pageMenu?: string | undefined;
breakpoint?: string;
condensed?: boolean | undefined; condensed?: boolean | undefined;
dark?: boolean | undefined; dark?: boolean | undefined;
transparent?: boolean; transparent?: boolean;
@@ -48,7 +47,6 @@ interface Props {
const { const {
hideSearch = false, hideSearch = false,
pageMenu, pageMenu,
breakpoint = 'md',
condensed = false, condensed = false,
dark = false, dark = false,
transparent = false, transparent = false,
@@ -67,6 +65,8 @@ const {
class: className, class: className,
} = Astro.props; } = Astro.props;
const breakpoint = 'md';
const headerClass = [ const headerClass = [
`navbar navbar-expand-${breakpoint}`, `navbar navbar-expand-${breakpoint}`,
transparent ? 'navbar-transparent' : background && `bg-${background}`, transparent ? 'navbar-transparent' : background && `bg-${background}`,
+2 -3
View File
@@ -5,16 +5,15 @@ import InputIcon from '@ui/form/InputIcon.astro'
interface Props { interface Props {
class?: string class?: string
rounded?: boolean
} }
const { class: className, rounded = false } = Astro.props const { class: className } = Astro.props
--- ---
<!-- BEGIN NAVBAR SEARCH --> <!-- BEGIN NAVBAR SEARCH -->
<div class={className}> <div class={className}>
<form action="./" method="get" autocomplete="off" novalidate role="search"> <form action="./" method="get" autocomplete="off" novalidate role="search">
<InputIcon prepend rounded={rounded} ariaLabel="Search in website" /> <InputIcon prepend ariaLabel="Search in website" />
</form> </form>
</div> </div>
<!-- END NAVBAR SEARCH --> <!-- END NAVBAR SEARCH -->
+4 -1
View File
@@ -7,9 +7,12 @@ import NavbarSideApps from './NavbarSideApps.astro'
import NavbarSideLanguage from './NavbarSideLanguage.astro' import NavbarSideLanguage from './NavbarSideLanguage.astro'
import NavbarSideUser from './NavbarSideUser.astro' import NavbarSideUser from './NavbarSideUser.astro'
/** Bootstrap responsive breakpoint used for `d-{breakpoint}-flex` — $grid-breakpoints minus xs */
export type Breakpoint = 'sm' | 'md' | 'lg' | 'xl' | 'xxl'
interface Props { interface Props {
class?: string class?: string
breakpoint?: string breakpoint?: Breakpoint | undefined
condensed?: boolean condensed?: boolean
personId?: number | undefined personId?: number | undefined
hideUsername?: boolean hideUsername?: boolean
+2 -1
View File
@@ -3,10 +3,11 @@ import NavbarToggler from './NavbarToggler.astro'
import Logo from './Logo.astro' import Logo from './Logo.astro'
import NavbarSide from './NavbarSide.astro' import NavbarSide from './NavbarSide.astro'
import NavbarMenu from './NavbarMenu.astro' import NavbarMenu from './NavbarMenu.astro'
import type { Breakpoint } from './NavbarSide.astro'
interface Props { interface Props {
dark?: boolean | undefined dark?: boolean | undefined
breakpoint?: string breakpoint?: Breakpoint | undefined
/** layout-sidebar-end — navbar-end class */ /** layout-sidebar-end — navbar-end class */
end?: boolean | undefined end?: boolean | undefined
/** layout-navbar-transparent — navbar-transparent class */ /** layout-navbar-transparent — navbar-transparent class */
+2 -2
View File
@@ -12,11 +12,11 @@ interface Column {
} }
interface Props { interface Props {
data?: { columns: Column[] }
class?: string class?: string
} }
const { data = tasksData as unknown as { columns: Column[] }, class: className } = Astro.props const { class: className } = Astro.props
const data = tasksData as unknown as { columns: Column[] }
--- ---
<div class={`row${className ? ` ${className}` : ''}`}> <div class={`row${className ? ` ${className}` : ''}`}>
@@ -2,13 +2,6 @@
import CardTitle from '@ui/CardTitle.astro' import CardTitle from '@ui/CardTitle.astro'
import Chart from '@ui/Chart.astro' import Chart from '@ui/Chart.astro'
import DropdownDays from '@ui/DropdownDays.astro' import DropdownDays from '@ui/DropdownDays.astro'
interface Props {
/** hide-breakpoint — when set, the stats column drops the -md breakpoint */
hideBreakpoint?: boolean
}
const { hideBreakpoint } = Astro.props
--- ---
<div class="card"> <div class="card">
@@ -24,7 +17,7 @@ const { hideBreakpoint } = Astro.props
<div class="col"> <div class="col">
<Chart chartId="active-users-2" label="Active users" /> <Chart chartId="active-users-2" label="Active users" />
</div> </div>
<div class={`col${hideBreakpoint ? '' : '-md'}-auto`}> <div class="col-md-auto">
<div class="divide-y divide-y-fill"> <div class="divide-y divide-y-fill">
<div class="px-3"> <div class="px-3">
<div class="text-secondary"> <div class="text-secondary">
-12
View File
@@ -43,18 +43,6 @@ export function extractMarkedSnippet(source: string, marker: string, name: strin
.trim() .trim()
} }
/** Escape text for use in HTML attributes (e.g. data-clipboard-text). */
export function escapeAttribute(text: string): string {
return text
.replace(/&/g, '&amp;')
.replace(/'/g, '&apos;')
.replace(/"/g, '&quot;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/\r\n/g, '&#13;')
.replace(/[\r\n]/g, '&#13;')
}
/** Replace href="#" with javascript:void(0) in example markup. */ /** Replace href="#" with javascript:void(0) in example markup. */
export function removeHref(content: string): string { export function removeHref(content: string): string {
return content.replace(/href="#"/g, 'href="javascript:void(0)"') return content.replace(/href="#"/g, 'href="javascript:void(0)"')
-18
View File
@@ -1,18 +0,0 @@
import { describe, expect, it } from 'vitest'
import { includeArgCount } from './include-args'
describe('includeArgCount', () => {
const keys = ['a', 'b', 'c'] as const
it('counts only the keys present with a defined value', () => {
expect(includeArgCount({ a: 1, b: undefined, c: 'x' }, keys)).toBe(2)
})
it('returns 0 when no keys are present', () => {
expect(includeArgCount({}, keys)).toBe(0)
})
it('counts falsy-but-defined values (0, "", false) as present', () => {
expect(includeArgCount({ a: 0, b: '', c: false }, keys)).toBe(3)
})
})
-8
View File
@@ -1,8 +0,0 @@
/**
* Returns how many of the given prop keys are explicitly set (not undefined).
* Some components use this count as a class suffix when `size` (or similar) is
* omitted, e.g. `spinner-border-2` or `nav-2`.
*/
export function includeArgCount(props: Record<string, unknown>, keys: readonly string[]): number {
return keys.filter((k) => props[k] !== undefined).length
}
+1 -11
View File
@@ -1,5 +1,5 @@
import { describe, expect, it } from 'vitest' import { describe, expect, it } from 'vitest'
import { capitalize, firstLetters, formatNumber, millisecondsToMinutes, parseCurrency, parsePercentage, pathSlug, roundTo, slugifyWord, sortBy, splitDropTrailingEmpty, ucFirst } from './string-format' import { capitalize, firstLetters, formatNumber, millisecondsToMinutes, parseCurrency, parsePercentage, pathSlug, roundTo, slugifyWord, sortBy, ucFirst } from './string-format'
import { requireIndex } from './array' import { requireIndex } from './array'
describe('capitalize', () => { describe('capitalize', () => {
@@ -34,16 +34,6 @@ describe('formatNumber', () => {
}) })
}) })
describe('splitDropTrailingEmpty', () => {
it('drops trailing empty strings', () => {
expect(splitDropTrailingEmpty('Tabler,Pages,', ',')).toEqual(['Tabler', 'Pages'])
})
it('keeps internal empty segments', () => {
expect(splitDropTrailingEmpty('a,,b', ',')).toEqual(['a', '', 'b'])
})
})
describe('slugifyWord', () => { describe('slugifyWord', () => {
it('lowercases the input', () => { it('lowercases the input', () => {
expect(slugifyWord('Settings')).toBe('settings') expect(slugifyWord('Settings')).toBe('settings')
-7
View File
@@ -23,13 +23,6 @@ export function formatNumber(value: number): string {
return value.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',') return value.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',')
} }
/** Splits a string on `sep`, dropping trailing empty segments (e.g. "a,b," -> ["a", "b"]). */
export function splitDropTrailingEmpty(str: string, sep: string): string[] {
const items = str.split(sep)
while (items.length > 0 && items[items.length - 1] === '') items.pop()
return items
}
/** Lowercases a single-word string for use as an anchor slug. */ /** Lowercases a single-word string for use as an anchor slug. */
export function slugifyWord(text: string): string { export function slugifyWord(text: string): string {
return text.toLowerCase() return text.toLowerCase()
+1 -5
View File
@@ -4,11 +4,7 @@ import activity from '@data/activity.json'
import people from '@data/people.json' import people from '@data/people.json'
import { timeagoLabel } from '@shared/lib/pseudo-random' import { timeagoLabel } from '@shared/lib/pseudo-random'
interface Props { const limit = 40
limit?: number
}
const { limit = 40 } = Astro.props
interface Person { interface Person {
full_name?: string full_name?: string
+8 -21
View File
@@ -1,34 +1,33 @@
--- ---
import Icon from './Icon.astro' import Icon from './Icon.astro'
import ButtonList from './ButtonList.astro'
/** alert-{type} / btn-{type} — matches defaultIcons below */
type AlertType = 'success' | 'warning' | 'danger' | 'info'
interface Props { interface Props {
type?: string type?: AlertType
icon?: string
important?: boolean important?: boolean
minor?: boolean minor?: boolean
showClose?: boolean showClose?: boolean
avatar?: boolean
class?: string class?: string
title?: string title?: string
description?: string description?: string
list?: string[] list?: string[]
action?: string action?: string
link?: string link?: string
buttons?: boolean
} }
const { type = 'success', icon: iconProp, important, minor, showClose, avatar, class: className, title = 'This is a custom alert box!', description, list, action, link, buttons } = Astro.props const { type = 'success', important, minor, showClose, class: className, title = 'This is a custom alert box!', description, list, action, link } = Astro.props
const defaultIcons: Record<string, string> = { const defaultIcons: Record<AlertType, string> = {
success: 'check', success: 'check',
warning: 'alert-triangle', warning: 'alert-triangle',
danger: 'alert-circle', danger: 'alert-circle',
info: 'info-circle', info: 'info-circle',
} }
const icon = iconProp ?? defaultIcons[type] const icon = defaultIcons[type]
const classes = ['alert', important ? 'alert-important' : minor ? 'alert-minor' : undefined, `alert-${type}`, showClose && 'alert-dismissible', avatar && 'alert-avatar', className] const classes = ['alert', important ? 'alert-important' : minor ? 'alert-minor' : undefined, `alert-${type}`, showClose && 'alert-dismissible', className]
--- ---
<div class:list={classes} role="alert"> <div class:list={classes} role="alert">
@@ -65,17 +64,5 @@ const classes = ['alert', important ? 'alert-important' : minor ? 'alert-minor'
</Fragment> </Fragment>
) )
} }
{
buttons && (
<ButtonList>
<a href="#" class={`btn btn-${type}`}>
Okay
</a>
<a href="#" class="btn">
Cancel
</a>
</ButtonList>
)
}
{showClose && <a class="btn-close" data-bs-dismiss="alert" aria-label="close" />} {showClose && <a class="btn-close" data-bs-dismiss="alert" aria-label="close" />}
</div> </div>
+10 -11
View File
@@ -9,22 +9,22 @@ interface Person {
[key: string]: unknown [key: string]: unknown
} }
/** core/scss/_variables.scss `$avatar-sizes` map keys */
export type AvatarSize = 'xxs' | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl'
interface Props { interface Props {
src?: string src?: string
placeholder?: string | undefined placeholder?: string | undefined
personId?: number personId?: number
person?: Person | undefined person?: Person | undefined
link?: boolean link?: boolean
dropdown?: boolean size?: AvatarSize | undefined
size?: string | undefined
thumb?: boolean thumb?: boolean
class?: string class?: string
shape?: string shape?: string
color?: string | undefined color?: string | undefined
square?: boolean square?: boolean
status?: string status?: string
statusText?: string
statusIcon?: string
statusLabel?: string statusLabel?: string
brand?: string brand?: string
icon?: string | undefined icon?: string | undefined
@@ -34,7 +34,7 @@ interface Props {
[key: string]: unknown [key: string]: unknown
} }
const { src: srcProp, placeholder: placeholderProp, personId, person: personProp, link: linkProp = false, dropdown, size, thumb, class: className, shape, color, square, status, statusText, statusIcon, statusLabel, brand, icon, base = '.', ...rest } = Astro.props const { src: srcProp, placeholder: placeholderProp, personId, person: personProp, link: linkProp = false, size, thumb, class: className, shape, color, square, status, statusLabel, brand, icon, base = '.', ...rest } = Astro.props
let src = srcProp let src = srcProp
let placeholder = placeholderProp let placeholder = placeholderProp
@@ -47,14 +47,13 @@ if (person) {
} }
} }
const link = linkProp || Boolean(dropdown) const El = linkProp ? 'a' : 'span'
const El = link ? 'a' : 'span'
const classes = ['avatar', size && `avatar-${size}`, thumb && 'avatar-thumb', className, shape && `avatar-${shape}`, color && `bg-${color}-lt`, square && 'avatar-square'] const classes = ['avatar', size && `avatar-${size}`, thumb && 'avatar-thumb', className, shape && `avatar-${shape}`, color && `bg-${color}-lt`, square && 'avatar-square']
--- ---
<El class:list={classes} style={src ? `background-image: url(${base}/${src})` : undefined} data-bs-toggle={dropdown ? 'dropdown' : undefined} {...rest} <El class:list={classes} style={src ? `background-image: url(${base}/${src})` : undefined} {...rest}
>{status && <span class={`badge bg-${status}`}>{statusText ? statusText : statusIcon ? <Icon name={statusIcon} class="avatar-status-icon" /> : <span class="visually-hidden">{statusLabel ?? status}</span>}</span>}{ >{status && <span class={`badge bg-${status}`} aria-label={statusLabel ?? status} />}{brand && <span class="avatar-brand" style={`background-image: url(${base}/static/brands/${brand}.svg);`} />}{
brand && <span class="avatar-brand" style={`background-image: url(${base}/static/brands/${brand}.svg);`} /> src ? null : placeholder ? placeholder : icon ? <Icon name={icon} class="avatar-icon" /> : <Icon name="user" class="avatar-icon" />
}{src ? null : placeholder ? placeholder : icon ? <Icon name={icon} class="avatar-icon" /> : <Icon name="user" class="avatar-icon" />}</El }</El
> >
+2 -1
View File
@@ -3,6 +3,7 @@
// Pass children to override and render a custom list. // Pass children to override and render a custom list.
// NOTE: when `size` is omitted we pass NO size to Avatar — callers that omit size should not get avatar-N suffix classes. // NOTE: when `size` is omitted we pass NO size to Avatar — callers that omit size should not get avatar-N suffix classes.
import Avatar from './Avatar.astro' import Avatar from './Avatar.astro'
import type { AvatarSize } from './Avatar.astro'
import people from '@data/people.json' import people from '@data/people.json'
interface Person { interface Person {
@@ -16,7 +17,7 @@ interface Props {
offset?: number | undefined offset?: number | undefined
stacked?: boolean stacked?: boolean
/** avatar-list-{size} + forwarded to default Avatars */ /** avatar-list-{size} + forwarded to default Avatars */
size?: string size?: AvatarSize | undefined
class?: string class?: string
/** placeholder avatar appended after the list */ /** placeholder avatar appended after the list */
text?: string text?: string
+2 -1
View File
@@ -1,9 +1,10 @@
--- ---
import Icon from './Icon.astro' import Icon from './Icon.astro'
import type { AvatarSize } from './Avatar.astro'
interface Props { interface Props {
class?: string class?: string
size?: string size?: AvatarSize | undefined
} }
const { class: className, size } = Astro.props const { class: className, size } = Astro.props
+3 -5
View File
@@ -7,16 +7,14 @@ interface Props {
/** theme color the pattern ramps up to, e.g. 'primary' */ /** theme color the pattern ramps up to, e.g. 'primary' */
color?: string color?: string
size?: 'sm' | 'md' | 'lg' | 'xl' size?: 'sm' | 'md' | 'lg' | 'xl'
/** element to render instead of div */
as?: string
class?: string class?: string
/** remaining attributes (style, data-*, …) are forwarded to the element */ /** remaining attributes (style, data-*, …) are forwarded to the element */
[key: string]: unknown [key: string]: unknown
} }
const { pattern, color, size, as: Element = 'div', class: className, ...rest }: Props = Astro.props const { pattern, color, size, class: className, ...rest }: Props = Astro.props
--- ---
<Element class:list={[`bg-pattern-${pattern}`, color && `bg-pattern-${color}`, size && `bg-pattern-${size}`, className]} {...rest}> <div class:list={[`bg-pattern-${pattern}`, color && `bg-pattern-${color}`, size && `bg-pattern-${size}`, className]} {...rest}>
<slot /> <slot />
</Element> </div>
+2 -17
View File
@@ -1,13 +1,5 @@
--- ---
import Icon from './Icon.astro' import Icon from './Icon.astro'
import Avatar from './Avatar.astro'
import people from '@data/people.json'
interface Person {
full_name?: string
photo?: string
[key: string]: unknown
}
interface Props { interface Props {
text?: string text?: string
@@ -18,18 +10,11 @@ interface Props {
light?: boolean light?: boolean
class?: string class?: string
icon?: string icon?: string
/** person-id — renders an inline avatar badge for people[personId - 1] */
personId?: number
/** addon text in a <span class="badge-addon"> */
addon?: string
addonColor?: string
} }
const { text, color, scale, light, class: className, icon, personId, addon, addonColor } = Astro.props const { text, color, scale, light, class: className, icon } = Astro.props
const person = personId ? (people as Person[])[personId - 1] : undefined
const classes = ['badge', scale && `badge-${scale}`, color && `bg-${color}${light ? '-lt' : ''} text-${color}${light ? '-lt' : ''}-fg`, className] const classes = ['badge', scale && `badge-${scale}`, color && `bg-${color}${light ? '-lt' : ''} text-${color}${light ? '-lt' : ''}-fg`, className]
--- ---
<span class:list={classes}>{icon && <Icon name={icon} />}{personId && <Avatar personId={personId} />}{text ? text : person?.full_name}{addon && <span class={`badge-addon ${addonColor ? `bg-${addonColor}` : ''}`}>{addon}</span>}</span> <span class:list={classes}>{icon && <Icon name={icon} />}{text}</span>
+6 -30
View File
@@ -4,6 +4,9 @@
// TODO: color=false special case — pass no color prop instead. // TODO: color=false special case — pass no color prop instead.
import Icon from './Icon.astro' import Icon from './Icon.astro'
/** core/scss/ui/_buttons.scss `.btn-{size}` modifier classes — 'md' is the default (no class emitted) */
type ButtonSize = 'sm' | 'md' | 'lg' | 'xl'
interface Props { interface Props {
text?: string text?: string
href?: string href?: string
@@ -15,24 +18,13 @@ interface Props {
color?: string | undefined color?: string | undefined
outline?: boolean outline?: boolean
ghost?: boolean ghost?: boolean
/** btn-{height} right after btn */ size?: ButtonSize | undefined
height?: string
size?: string
block?: boolean block?: boolean
disabled?: boolean
square?: boolean
loading?: boolean
action?: boolean
pill?: boolean
/** btn-link */
link?: boolean
class?: string class?: string
icon?: string icon?: string
iconColor?: string iconColor?: string
iconEnd?: string iconEnd?: string
iconOnly?: boolean iconOnly?: boolean
/** animated dots after the text */
dots?: boolean
/** data-bs-toggle="modal" data-bs-target="#modal-{modalId}" (modal-id param) */ /** data-bs-toggle="modal" data-bs-target="#modal-{modalId}" (modal-id param) */
modalId?: string modalId?: string
/** data-bs-toggle="toast" data-bs-target="#toast-{toastId}" (toast-id param) */ /** data-bs-toggle="toast" data-bs-target="#toast-{toastId}" (toast-id param) */
@@ -43,33 +35,18 @@ interface Props {
dropdown?: boolean dropdown?: boolean
} }
const { text = 'Button', href, external, element, type, id, color, outline, ghost, height, size, block, disabled, square, loading, action, pill, link, class: className, icon, iconColor, iconEnd, iconOnly, dots, modalId, toastId, dismiss, dropdown } = Astro.props const { text = 'Button', href, external, element, type, id, color, outline, ghost, size, block, class: className, icon, iconColor, iconEnd, iconOnly, modalId, toastId, dismiss, dropdown } = Astro.props
// Without a real destination the control is an action, so render <button>; internal hrefs are prefixed with '/'. // Without a real destination the control is an action, so render <button>; internal hrefs are prefixed with '/'.
const Element = element ?? (href ? 'a' : 'button') const Element = element ?? (href ? 'a' : 'button')
const resolvedHref = href ? (external ? href : `/${href}`) : '#' const resolvedHref = href ? (external ? href : `/${href}`) : '#'
const classes = [ const classes = ['btn', color && `btn-${outline ? 'outline-' : ghost ? 'ghost-' : ''}${color}`, size && size !== 'md' && `btn-${size}`, className, block && 'w-100', iconOnly && 'btn-icon']
'btn',
height && `btn-${height}`,
color && `btn-${outline ? 'outline-' : ghost ? 'ghost-' : ''}${color}`,
disabled && 'disabled',
square && 'btn-square',
loading && 'btn-loading',
action && 'btn-action',
pill && 'btn-pill',
size && `btn-${size}`,
className,
block && 'w-100',
link && 'btn-link',
iconOnly && 'btn-icon',
]
--- ---
<Element <Element
href={Element === 'a' ? resolvedHref : undefined} href={Element === 'a' ? resolvedHref : undefined}
type={Element === 'button' ? (type ?? 'button') : undefined} type={Element === 'button' ? (type ?? 'button') : undefined}
disabled={Element === 'button' && disabled ? true : undefined}
id={id} id={id}
class:list={classes} class:list={classes}
target={external ? '_blank' : undefined} target={external ? '_blank' : undefined}
@@ -81,6 +58,5 @@ const classes = [
> >
{icon && <Icon name={icon} color={iconColor} />} {icon && <Icon name={icon} color={iconColor} />}
{!iconOnly && text} {!iconOnly && text}
{!iconOnly && dots && <span class="animated-dots" />}
{!iconOnly && iconEnd && <Icon name={iconEnd} class="icon-end" />} {!iconOnly && iconEnd && <Icon name={iconEnd} class="icon-end" />}
</Element> </Element>
+2 -5
View File
@@ -4,17 +4,14 @@
interface Props { interface Props {
/** use .btn-group-vertical instead of .btn-group */ /** use .btn-group-vertical instead of .btn-group */
vertical?: boolean vertical?: boolean
/** btn-group-{size}: sm | lg | xl */
size?: string
class?: string class?: string
role?: astroHTML.JSX.AriaRole
/** remaining attributes (aria-label, …) are forwarded */ /** remaining attributes (aria-label, …) are forwarded */
[key: string]: unknown [key: string]: unknown
} }
const { vertical, size, class: className, role = 'group', ...rest } = Astro.props const { vertical, class: className, ...rest } = Astro.props
--- ---
<div class:list={[vertical ? 'btn-group-vertical' : 'btn-group', size && `btn-group-${size}`, className]} role={role} {...rest}> <div class:list={[vertical ? 'btn-group-vertical' : 'btn-group', className]} role="group" {...rest}>
<slot /> <slot />
</div> </div>
+4 -2
View File
@@ -7,9 +7,11 @@ import type { HTMLTag } from 'astro/types'
// literally named `as` and silently falls back to untyped props. // literally named `as` and silently falls back to untyped props.
type PolymorphicProps = { as?: HTMLTag } type PolymorphicProps = { as?: HTMLTag }
/** core/scss/ui/_cards.scss `.card-{size}` body padding modifier classes */
type CardSize = 'sm' | 'md' | 'lg'
interface Props extends PolymorphicProps { interface Props extends PolymorphicProps {
/** card-{size} body padding: sm | md | lg */ size?: CardSize | undefined
size?: string
class?: string class?: string
/** remaining attributes (id, action, data-*, …) are forwarded to the element */ /** remaining attributes (id, action, data-*, …) are forwarded to the element */
[key: string]: unknown [key: string]: unknown
+2 -2
View File
@@ -2,11 +2,11 @@
import Icon from './Icon.astro' import Icon from './Icon.astro'
interface Props { interface Props {
icon?: string
class?: string class?: string
} }
const { icon = 'dots-vertical', class: className } = Astro.props const { class: className } = Astro.props
const icon = 'dots-vertical'
--- ---
<div class:list={['dropdown', className]}> <div class:list={['dropdown', className]}>
+2 -4
View File
@@ -2,14 +2,12 @@
// Card footer (.card-footer). // Card footer (.card-footer).
interface Props { interface Props {
/** card-footer-transparent */
transparent?: boolean
class?: string class?: string
} }
const { transparent, class: className } = Astro.props const { class: className } = Astro.props
--- ---
<div class:list={['card-footer', transparent && 'card-footer-transparent', className]}> <div class:list={['card-footer', className]}>
<slot /> <slot />
</div> </div>
+2 -4
View File
@@ -9,15 +9,13 @@ type PolymorphicProps = { as?: HTMLTag }
interface Props extends PolymorphicProps { interface Props extends PolymorphicProps {
title?: string title?: string
/** card-header-light */
light?: boolean
class?: string class?: string
} }
const { title, as: TitleTag = 'h3', light, class: className }: Props = Astro.props const { title, as: TitleTag = 'h3', class: className }: Props = Astro.props
--- ---
<div class:list={['card-header', light && 'card-header-light', className]}> <div class:list={['card-header', className]}>
{title && <TitleTag class:list={['card-title']}>{title}</TitleTag>} {title && <TitleTag class:list={['card-title']}>{title}</TitleTag>}
<slot /> <slot />
</div> </div>
+1 -1
View File
@@ -5,7 +5,7 @@ import Icon from './Icon.astro'
interface Props { interface Props {
icon?: string icon?: string
/** card-stamp-{size}; only 'lg' exists in SCSS (13rem instead of 7rem) */ /** card-stamp-{size}; only 'lg' exists in SCSS (13rem instead of 7rem) */
size?: string size?: 'lg' | undefined
/** bg-{color} on the icon wrapper; SCSS default is secondary */ /** bg-{color} on the icon wrapper; SCSS default is secondary */
color?: string color?: string
/** text-{textColor} on the icon wrapper (e.g. color="white" textColor="primary") */ /** text-{textColor} on the icon wrapper (e.g. color="white" textColor="primary") */
+4 -45
View File
@@ -1,67 +1,26 @@
--- ---
// Self-contained carousel (.carousel). Default slot holds the .carousel-item elements // Self-contained carousel (.carousel). Default slot holds the .carousel-item elements
// (the caller marks the first one "active" — see CarouselCard.astro / hero/Side.astro); // (the caller marks the first one "active" — see CarouselCard.astro / hero/Side.astro);
// indicators/controls are generated from props. A "controls" slot overrides the default // a "controls" slot renders custom prev/next markup (e.g. an <Icon>-based pair).
// icon-span controls with custom markup (e.g. an <Icon>-based pair).
interface Props { interface Props {
id?: string id?: string
/** slide count, used to generate indicator buttons */
slideCount: number
fade?: boolean
/** data-bs-interval in ms, or false to disable autoplay */ /** data-bs-interval in ms, or false to disable autoplay */
interval?: number | false interval?: number | false
indicators?: boolean
indicatorsVariant?: 'dot' | 'thumb'
indicatorsVertical?: boolean
/** ratio ratio-4x3 on each indicator button, typically paired with indicatorsVariant="thumb" */
indicatorsThumbRatio?: boolean
/** background-image per slide, used when indicatorsVariant="thumb" */
indicatorsImages?: string[]
controls?: boolean
class?: string class?: string
/** remaining attributes forwarded to the element */ /** remaining attributes forwarded to the element */
[key: string]: unknown [key: string]: unknown
} }
const { id, slideCount, fade, interval, indicators, indicatorsVariant, indicatorsVertical, indicatorsThumbRatio, indicatorsImages, controls, class: className, ...rest } = Astro.props const { id, interval, class: className, ...rest } = Astro.props
const target = id ? `#${id}` : undefined
const slideIndexes = Array.from({ length: slideCount }, (_, i) => i)
const hasCustomControls = Astro.slots.has('controls') const hasCustomControls = Astro.slots.has('controls')
--- ---
<div id={id} class:list={['carousel', 'slide', fade && 'carousel-fade', className]} data-bs-ride="carousel" data-bs-interval={interval === false ? 'false' : interval} {...rest}> <div id={id} class:list={['carousel', 'slide', className]} data-bs-ride="carousel" data-bs-interval={interval === false ? 'false' : interval} {...rest}>
{
indicators && (
<div class:list={['carousel-indicators', indicatorsVertical && 'carousel-indicators-vertical', indicatorsVariant === 'dot' && 'carousel-indicators-dot', indicatorsVariant === 'thumb' && 'carousel-indicators-thumb']}>
{slideIndexes.map((i) => (
<button type="button" data-bs-target={target} data-bs-slide-to={i} class:list={[indicatorsThumbRatio && 'ratio ratio-4x3', i === 0 && 'active']} style={indicatorsVariant === 'thumb' && indicatorsImages?.[i] ? `background-image: url(${indicatorsImages[i]})` : undefined} />
))}
</div>
)
}
<div class="carousel-inner"> <div class="carousel-inner">
<slot /> <slot />
</div> </div>
{ {hasCustomControls && <slot name="controls" />}
hasCustomControls ? (
<slot name="controls" />
) : (
controls && (
<Fragment>
<a class="carousel-control-prev" href={target} 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={target} 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>
+2 -22
View File
@@ -5,36 +5,21 @@ interface Props {
id?: string | undefined; id?: string | undefined;
type?: string; type?: string;
color?: string | undefined; color?: string | undefined;
height?: number;
small?: boolean; small?: boolean;
wide?: boolean;
data?: string | number; data?: string | number;
percentage?: string | number; percentage?: string | number;
/**
* Sparklines are typically decorative, sitting next to a visible stat
* number that already conveys the value — so they're hidden from
* assistive tech by default. Set to `false` (with `label`) when the
* sparkline is the only representation of the data.
*/
decorative?: boolean;
/** Accessible name, used only when `decorative` is explicitly `false`. */
label?: string;
} }
const { const {
id, id,
type = 'bar', type = 'bar',
color = 'primary', color = 'primary',
height: heightProp,
small, small,
wide,
data: dataProp, data: dataProp,
percentage, percentage,
decorative = true,
label,
} = Astro.props; } = Astro.props;
const height = heightProp ?? (small ? 1.5 : 2.5); const height = small ? 1.5 : 2.5;
const heightPx = Math.round(height * 16); const heightPx = Math.round(height * 16);
const data = percentage ?? dataProp; const data = percentage ?? dataProp;
@@ -45,17 +30,12 @@ const chartType = type === 'donut' ? 'radialBar' : type;
const classes = [ const classes = [
'chart-sparkline', 'chart-sparkline',
isSquare && 'chart-sparkline-square', isSquare && 'chart-sparkline-square',
wide && 'chart-sparkline-wide',
small && 'chart-sparkline-sm', small && 'chart-sparkline-sm',
] ]
const seriesData = String(data ?? '').split(',').map(Number); const seriesData = String(data ?? '').split(',').map(Number);
const chartKey = `sparkline-${id}`; const chartKey = `sparkline-${id}`;
const ariaHidden = decorative ? true : undefined;
const ariaRole = decorative ? undefined : 'img';
const ariaLabel = decorative ? undefined : (label ?? 'Chart');
const chartOptions = id const chartOptions = id
? { ? {
chart: { chart: {
@@ -87,7 +67,7 @@ const chartOptions = id
: undefined; : undefined;
--- ---
{id && <div class:list={classes} id={chartKey} role={ariaRole} aria-label={ariaLabel} aria-hidden={ariaHidden} />} {id && <div class:list={classes} id={chartKey} aria-hidden="true" />}
{ {
id && ( id && (
<CaptureScript> <CaptureScript>
+3 -5
View File
@@ -5,15 +5,14 @@ import CaptureScript from '../components/CaptureScript.astro'
interface Props { interface Props {
value?: string value?: string
id?: string | number id?: string | number
alpha?: boolean
format?: string | false format?: string | false
swatchesOnly?: boolean
class?: string class?: string
/** Accessible name for the input; falls back to a generic label when the caller doesn't wrap this in a labelled FormGroup */ /** Accessible name for the input; falls back to a generic label when the caller doesn't wrap this in a labelled FormGroup */
ariaLabel?: string ariaLabel?: string
} }
const { value, id = 'default', alpha = false, format = false, swatchesOnly, class: className, ariaLabel } = Astro.props const { value, id = 'default', format = false, class: className, ariaLabel } = Astro.props
const alpha = false
const colors = site.colors as Record<string, { prop: string }> const colors = site.colors as Record<string, { prop: string }>
const swatchProps = Object.values(colors).map((color) => color.prop) const swatchProps = Object.values(colors).map((color) => color.prop)
@@ -25,7 +24,7 @@ const colorpickerSelector = `#colorpicker-${id}`
<input type="text" class={`form-control d-block${className ? ` ${className}` : ''}`} id={colorpickerId} value={value} aria-label={ariaLabel ?? 'Color value'} /> <input type="text" class={`form-control d-block${className ? ` ${className}` : ''}`} id={colorpickerId} value={value} aria-label={ariaLabel ?? 'Color value'} />
<CaptureScript> <CaptureScript>
<!-- BEGIN COLORPICKER --> <!-- BEGIN COLORPICKER -->
<script is:inline define:vars={{ colorpickerId, colorpickerSelector, alpha, format, swatchesOnly, swatchProps }}> <script is:inline define:vars={{ colorpickerId, colorpickerSelector, alpha, format, swatchProps }}>
function initColorpicker() { function initColorpicker() {
window.tabler_colorpicker ??= {} window.tabler_colorpicker ??= {}
@@ -40,7 +39,6 @@ const colorpickerSelector = `#colorpicker-${id}`
selectInput: false, selectInput: false,
alpha, alpha,
...(format ? { format } : {}), ...(format ? { format } : {}),
...(swatchesOnly ? { swatchesOnly: true } : {}),
swatches, swatches,
}) })
window.tabler_colorpicker[colorpickerId] = colorpickerSelector window.tabler_colorpicker[colorpickerId] = colorpickerSelector
+2 -2
View File
@@ -5,7 +5,6 @@ import CaptureScript from '../components/CaptureScript.astro';
interface Props { interface Props {
id?: string; id?: string;
value?: string; value?: string;
placeholder?: string;
class?: string; class?: string;
/** 'icon' | 'icon-prepend' | undefined (plain) */ /** 'icon' | 'icon-prepend' | undefined (plain) */
layout?: string; layout?: string;
@@ -15,12 +14,13 @@ interface Props {
const { const {
id, id,
value = '2020-06-20', value = '2020-06-20',
placeholder = 'Select a date',
class: className, class: className,
layout, layout,
inline, inline,
} = Astro.props; } = Astro.props;
const placeholder = 'Select a date';
const chevronLeft = const chevronLeft =
'<!-- Download SVG icon from http://tabler.io/icons/icon/chevron-left -->\n\t<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false" class="icon"><path d="M15 6l-6 6l6 6" /></svg>'; '<!-- Download SVG icon from http://tabler.io/icons/icon/chevron-left -->\n\t<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false" class="icon"><path d="M15 6l-6 6l6 6" /></svg>';
const chevronRight = const chevronRight =
+2 -2
View File
@@ -2,10 +2,10 @@
interface Props { interface Props {
id?: string id?: string
label?: string label?: string
value?: string
} }
const { id, label = 'Select time range', value = 'Last 7 days' } = Astro.props const { id, label = 'Select time range' } = Astro.props
const value = 'Last 7 days'
--- ---
<div class="dropdown"> <div class="dropdown">
+3 -26
View File
@@ -11,12 +11,10 @@ interface Props {
arrow?: boolean arrow?: boolean
dark?: boolean dark?: boolean
class?: string class?: string
menu?: string[]
check?: boolean check?: boolean
radio?: boolean radio?: boolean
people?: boolean people?: boolean
flag?: boolean flag?: boolean
type?: string
header?: boolean header?: boolean
icons?: boolean icons?: boolean
badge?: boolean badge?: boolean
@@ -25,38 +23,17 @@ interface Props {
separated?: boolean separated?: boolean
} }
const { right, show, arrow, dark, class: className, menu, check, radio, people: showPeople, flag: showFlags, type, header, icons, badge, active, disabled, separated } = Astro.props const { right, show, arrow, dark, class: className, check, radio, people: showPeople, flag: showFlags, header, icons, badge, active, disabled, separated } = Astro.props
const classes = ['dropdown-menu', right && 'dropdown-menu-end', show && 'dropdown-menu-demo', arrow && 'dropdown-menu-arrow', className] const classes = ['dropdown-menu', right && 'dropdown-menu-end', show && 'dropdown-menu-demo', arrow && 'dropdown-menu-arrow', className]
const menuItems = menu
const peopleItems = (people as { id: string; full_name: string }[]).slice(0, 5) const peopleItems = (people as { id: string; full_name: string }[]).slice(0, 5)
const countryItems = (countries as { name: string; flag: string }[]).slice(0, 5) const countryItems = (countries as { name: string; flag: string }[]).slice(0, 5)
--- ---
<div class:list={classes} data-bs-theme={dark ? 'dark' : undefined}> <div class:list={classes} data-bs-theme={dark ? 'dark' : undefined}>
{ {
menuItems ? ( check || radio ? (
menuItems.map((item) =>
item === '|' ? (
<div class="dropdown-divider" />
) : item.includes('h:') ? (
<h3 class="dropdown-header">{item.split('h:').join('')}</h3>
) : item.includes('a:') ? (
<button type="button" class="dropdown-item active">
{item.split('a:').join('')}
</button>
) : item.includes('d:') ? (
<button type="button" class="dropdown-item text-danger">
{item.split('d:').join('')}
</button>
) : (
<button type="button" class="dropdown-item">
{item}
</button>
),
)
) : check || radio ? (
[1, 2, 3].map((i) => ( [1, 2, 3].map((i) => (
<label class="dropdown-item"> <label class="dropdown-item">
<input class="form-check-input m-0 me-2" type={radio ? 'radio' : 'checkbox'} name={radio ? 'dropdown-menu-radio' : undefined} /> Option {i} <input class="form-check-input m-0 me-2" type={radio ? 'radio' : 'checkbox'} name={radio ? 'dropdown-menu-radio' : undefined} /> Option {i}
@@ -74,7 +51,7 @@ const countryItems = (countries as { name: string; flag: string }[]).slice(0, 5)
<Flag flag={country.flag} size="xs" /> {country.name} <Flag flag={country.flag} size="xs" /> {country.name}
</button> </button>
)) ))
) : type === 'text' ? null : ( ) : (
<> <>
{header && <span class="dropdown-header">Dropdown header</span>} {header && <span class="dropdown-header">Dropdown header</span>}
<button type="button" class="dropdown-item"> <button type="button" class="dropdown-item">
+2 -2
View File
@@ -6,10 +6,10 @@ interface Props {
custom?: boolean custom?: boolean
text?: string text?: string
description?: string description?: string
label?: string
} }
const { id = 'default', multiple, custom, text = 'Text', description = 'Description', label = 'Upload file' } = Astro.props const { id = 'default', multiple, custom, text = 'Text', description = 'Description' } = Astro.props
const label = 'Upload file'
const dropzoneId = `dropzone-${id}` const dropzoneId = `dropzone-${id}`
const dropzoneSelector = `#dropzone-${id}` const dropzoneSelector = `#dropzone-${id}`
+2 -3
View File
@@ -6,7 +6,6 @@ import Illustration from './Illustration.astro'
interface Props { interface Props {
icon?: string icon?: string
height?: number | string height?: number | string
bordered?: boolean
class?: string class?: string
/** illustration file name, e.g. "computer-fix.svg" */ /** illustration file name, e.g. "computer-fix.svg" */
illustration?: string | undefined illustration?: string | undefined
@@ -18,9 +17,9 @@ interface Props {
buttonIcon?: string buttonIcon?: string
} }
const { icon = 'mood-sad', height = 256, bordered, class: className, illustration, iconText, title = 'No results found', subtitle = "Try adjusting your search or filter to find what you're looking for.", buttonText = 'Search again', buttonIcon = 'search' } = Astro.props const { icon = 'mood-sad', height = 256, class: className, illustration, iconText, title = 'No results found', subtitle = "Try adjusting your search or filter to find what you're looking for.", buttonText = 'Search again', buttonIcon = 'search' } = Astro.props
const classes = ['empty', bordered && 'empty-bordered', className] const classes = ['empty', className]
--- ---
<div class:list={classes}> <div class:list={classes}>
+4 -1
View File
@@ -1,7 +1,10 @@
--- ---
import type { AvatarSize } from './Avatar.astro'
interface Props { interface Props {
flag?: string | undefined flag?: string | undefined
size?: string /** core/scss/_variables.scss `$flag-sizes` (aliased to `$avatar-sizes`) */
size?: AvatarSize | undefined
class?: string class?: string
label?: string | undefined label?: string | undefined
} }
+2 -3
View File
@@ -2,13 +2,12 @@
interface Props { interface Props {
text?: string text?: string
position?: string position?: string
color?: string
class?: string class?: string
} }
const { text = 'Label', position, color, class: className } = Astro.props const { text = 'Label', position, class: className } = Astro.props
const classes = ['hr-text', position && `hr-text-${position.toLowerCase()}`, color && `text-${color}`, className] const classes = ['hr-text', position && `hr-text-${position.toLowerCase()}`, className]
--- ---
<div class:list={classes}>{text}</div> <div class:list={classes}>{text}</div>
+4 -1
View File
@@ -1,12 +1,15 @@
--- ---
import icons from '@data/icons.json' import icons from '@data/icons.json'
/** core/scss/ui/_icons.scss `.icon-{size}` modifier classes */
export type IconSize = 'sm' | 'md' | 'lg'
interface Props { interface Props {
name: string name: string
type?: 'outline' | 'filled' type?: 'outline' | 'filled'
class?: string | undefined class?: string | undefined
color?: string | undefined color?: string | undefined
size?: string size?: IconSize | undefined
inline?: boolean inline?: boolean
} }
+2 -3
View File
@@ -4,13 +4,12 @@ import Icon from './Icon.astro'
interface Props { interface Props {
pills?: boolean pills?: boolean
tabs?: boolean tabs?: boolean
header?: boolean
class?: string class?: string
} }
const { pills, tabs, header, class: className } = Astro.props const { pills, tabs, class: className } = Astro.props
const classes = ['nav', pills ? 'nav-pills' : tabs ? 'nav-tabs' : undefined, pills && header ? 'card-header-pills' : tabs && header ? 'card-header-tabs' : undefined, className] const classes = ['nav', pills ? 'nav-pills' : tabs ? 'nav-tabs' : undefined, className]
--- ---
<ul class:list={classes}> <ul class:list={classes}>
+5 -11
View File
@@ -1,39 +1,33 @@
--- ---
import Icon from './Icon.astro' import Icon from './Icon.astro'
import { includeArgCount } from '@shared/lib/include-args'
interface Props { interface Props {
items?: string[] items?: string[]
icons?: string[] icons?: string[]
disabled?: number[] disabled?: number[]
hover?: string
default?: number default?: number
vertical?: boolean vertical?: boolean
size?: string /** core/scss/ui/_segmented.scss `.nav-{size}` modifier classes */
size?: 'sm' | 'lg' | undefined
fullWidth?: boolean fullWidth?: boolean
name?: string name?: string
class?: string class?: string
} }
const { items, icons, disabled, hover = '', default: defaultIndex = 1, vertical, size, fullWidth, name, class: className } = Astro.props const { items, icons, disabled, default: defaultIndex = 1, vertical, size, fullWidth, name, class: className } = Astro.props
// When `size` is omitted, append `nav-{argCount}` (e.g. nav-1, nav-2). An explicit size uses nav-sm/nav-lg.
const includeArgKeys = ['items', 'icons', 'disabled', 'hover', 'default', 'vertical', 'size', 'fullWidth', 'class', 'name'] as const
const argCount = includeArgCount(Astro.props as Record<string, unknown>, includeArgKeys)
const sizeToken = size ? `nav-${size}` : `nav-${argCount}`
const itemsArr = items ?? [] const itemsArr = items ?? []
const iconsArr = icons ?? [] const iconsArr = icons ?? []
const count = Math.max(itemsArr.length, iconsArr.length) const count = Math.max(itemsArr.length, iconsArr.length)
const navClasses = ['nav', 'nav-segmented', vertical && 'nav-segmented-vertical', sizeToken, fullWidth && 'w-100', className] const navClasses = ['nav', 'nav-segmented', vertical && 'nav-segmented-vertical', size && `nav-${size}`, fullWidth && 'w-100', className]
const rows = Array.from({ length: count }, (_, i) => { const rows = Array.from({ length: count }, (_, i) => {
const forloopIndex = i + 1 const forloopIndex = i + 1
const index = String(forloopIndex) const index = String(forloopIndex)
const isDisabled = disabled?.includes(forloopIndex) ?? false const isDisabled = disabled?.includes(forloopIndex) ?? false
const isDefault = forloopIndex === defaultIndex const isDefault = forloopIndex === defaultIndex
const linkClasses = ['nav-link', isDisabled && 'disabled', isDefault && !name && 'active', hover === index && 'hover'] const linkClasses = ['nav-link', isDisabled && 'disabled', isDefault && !name && 'active']
return { forloopIndex, index, isDisabled, isDefault, linkClasses, icon: iconsArr[i], item: itemsArr[i] } return { forloopIndex, index, isDisabled, isDefault, linkClasses, icon: iconsArr[i], item: itemsArr[i] }
}) })
--- ---
+6 -8
View File
@@ -2,12 +2,10 @@
// Offcanvas container (.offcanvas). Renders inline at the call site (unlike Modal, which drains to the end of body). // Offcanvas container (.offcanvas). Renders inline at the call site (unlike Modal, which drains to the end of body).
interface Props { interface Props {
/** offcanvas-{direction}: start | end | top | bottom */ /** core/scss/bootstrap/_offcanvas.scss `.offcanvas-{direction}` modifier classes */
direction?: string direction?: 'start' | 'end' | 'top' | 'bottom' | undefined
/** responsive variant offcanvas-{size} (lg | xl | xxl) — replaces the base .offcanvas class */ /** responsive variant offcanvas-{size} ($grid-breakpoints minus xs) — replaces the base .offcanvas class */
size?: string size?: 'sm' | 'md' | 'lg' | 'xl' | 'xxl' | undefined
/** offcanvas-narrow (20rem width) */
narrow?: boolean
show?: boolean show?: boolean
class?: string class?: string
/** defaults to "-1" when not provided */ /** defaults to "-1" when not provided */
@@ -18,11 +16,11 @@ interface Props {
[key: string]: unknown [key: string]: unknown
} }
const { direction, size, narrow, show, class: className, tabindex, role, ...rest } = Astro.props const { direction, size, show, class: className, tabindex, role, ...rest } = Astro.props
--- ---
<!-- BEGIN OFFCANVAS --> <!-- BEGIN OFFCANVAS -->
<div class:list={[size ? `offcanvas-${size}` : 'offcanvas', direction && `offcanvas-${direction}`, narrow && 'offcanvas-narrow', show && 'show', className]} tabindex={tabindex ?? '-1'} role={role ?? 'dialog'} {...rest}> <div class:list={[size ? `offcanvas-${size}` : 'offcanvas', direction && `offcanvas-${direction}`, show && 'show', className]} tabindex={tabindex ?? '-1'} role={role ?? 'dialog'} {...rest}>
<slot /> <slot />
</div> </div>
<!-- END OFFCANVAS --> <!-- END OFFCANVAS -->
-13
View File
@@ -1,13 +0,0 @@
---
// Offcanvas footer (.offcanvas-footer).
interface Props {
class?: string
}
const { class: className } = Astro.props
---
<div class:list={['offcanvas-footer', className]}>
<slot />
</div>
+4 -1
View File
@@ -1,8 +1,11 @@
--- ---
import type { AvatarSize } from './Avatar.astro'
interface Props { interface Props {
payment?: string payment?: string
dark?: boolean dark?: boolean
size?: string /** core/scss/_variables.scss `$payment-sizes` (aliased to `$avatar-sizes`) */
size?: AvatarSize | undefined
class?: string class?: string
label?: string | undefined label?: string | undefined
} }
+4 -1
View File
@@ -5,12 +5,15 @@ interface Photo {
title?: string title?: string
} }
/** core/scss/_variables.scss `$aspect-ratios` map keys */
type PhotoRatio = '1x1' | '2x1' | '1x2' | '3x1' | '1x3' | '4x1' | '1x4' | '4x3' | '3x4' | '16x9' | '9x16' | '21x9' | '9x21'
interface Props { interface Props {
photo: Photo photo: Photo
/** background param — plain <div> with only the background-image (no ratio class) */ /** background param — plain <div> with only the background-image (no ratio class) */
background?: boolean background?: boolean
/** ratio param — img-responsive img-responsive-{ratio} wrapper */ /** ratio param — img-responsive img-responsive-{ratio} wrapper */
ratio?: string ratio?: PhotoRatio | undefined
class?: string class?: string
} }
+6 -4
View File
@@ -1,10 +1,12 @@
--- ---
/** core/scss/ui/_progress.scss `.progress-{size}` modifier classes */
export type ProgressSize = 'sm' | 'lg' | 'xl'
interface Props { interface Props {
value?: number | string | undefined value?: number | string | undefined
values?: (string | number)[] values?: (string | number)[]
color?: string | undefined color?: string | undefined
colors?: string[] size?: ProgressSize | undefined
size?: string | undefined
class?: string class?: string
width?: string width?: string
id?: string id?: string
@@ -14,11 +16,11 @@ interface Props {
showValue?: boolean showValue?: boolean
} }
const { value, values, color, colors: colorsProp, size, class: className, width, id, indeterminate, striped, animated, showValue } = Astro.props const { value, values, color, size, class: className, width, id, indeterminate, striped, animated, showValue } = Astro.props
const percentage = value !== undefined && value !== null && `${value}` !== '' ? `${value}`.split('%').join('') : '38' const percentage = value !== undefined && value !== null && `${value}` !== '' ? `${value}`.split('%').join('') : '38'
const colors = colorsProp ?? (color ? [color] : ['blue', 'red', 'green', 'yellow', 'dark']) const colors = color ? [color] : ['blue', 'red', 'green', 'yellow', 'dark']
const valueList = values const valueList = values
const classes = ['progress', size && `progress-${size}`, className] const classes = ['progress', size && `progress-${size}`, className]
+2 -11
View File
@@ -1,6 +1,5 @@
--- ---
import Progress from './Progress.astro' import Progress from './Progress.astro'
import Flag from './Flag.astro'
import { parsePercentage } from '@shared/lib/string-format' import { parsePercentage } from '@shared/lib/string-format'
interface Props { interface Props {
@@ -8,24 +7,16 @@ interface Props {
color?: string color?: string
class?: string class?: string
text?: string text?: string
flag?: string
showValue?: boolean showValue?: boolean
} }
const { value, color = 'primary-lt', class: className, text, flag, showValue } = Astro.props const { value, color = 'primary-lt', class: className, text, showValue } = Astro.props
const percentage = parsePercentage(value) const percentage = parsePercentage(value)
--- ---
<div class={`progressbg${className ? ` ${className}` : ''}`}> <div class={`progressbg${className ? ` ${className}` : ''}`}>
<Progress value={percentage} class="progressbg-progress" color={color} /> <Progress value={percentage} class="progressbg-progress" color={color} />
{ {text && <div class="progressbg-text">{text}</div>}
text && (
<div class="progressbg-text">
{flag && <Flag flag={flag} class="me-2" size="xs" />}
{text}
</div>
)
}
{showValue && <div class="progressbg-value">{percentage}%</div>} {showValue && <div class="progressbg-value">{percentage}%</div>}
</div> </div>
+2 -1
View File
@@ -1,13 +1,14 @@
--- ---
// Forward `size` only when explicitly provided. // Forward `size` only when explicitly provided.
import Progress from './Progress.astro' import Progress from './Progress.astro'
import type { ProgressSize } from './Progress.astro'
interface Props { interface Props {
label?: string label?: string
description?: string description?: string
value?: number | string value?: number | string
color?: string color?: string
size?: string size?: ProgressSize | undefined
class?: string class?: string
} }
+2 -2
View File
@@ -4,14 +4,14 @@ import CaptureScript from '../components/CaptureScript.astro';
interface Props { interface Props {
min?: number; min?: number;
max?: number; max?: number;
step?: number;
value?: string | number; value?: string | number;
id?: string; id?: string;
connect?: boolean; connect?: boolean;
class?: string; class?: string;
} }
const { min = 0, max = 100, step = 10, value = 50, id, connect = false, class: className } = Astro.props; const { min = 0, max = 100, value = 50, id, connect = false, class: className } = Astro.props;
const step = 10;
const values = String(value).split(','); const values = String(value).split(',');
const size = values.length; const size = values.length;
+2 -1
View File
@@ -1,12 +1,13 @@
--- ---
import icons from '@data/icons.json' import icons from '@data/icons.json'
import CaptureScript from '../components/CaptureScript.astro' import CaptureScript from '../components/CaptureScript.astro'
import type { IconSize } from './Icon.astro'
interface Props { interface Props {
id?: string id?: string
icon?: string icon?: string
color?: string color?: string
size?: string size?: IconSize | undefined
value?: number value?: number
} }
+3 -7
View File
@@ -4,18 +4,14 @@ import Icon from './Icon.astro'
interface Props { interface Props {
text?: string text?: string
top?: boolean top?: boolean
left?: boolean
bottom?: boolean
bookmark?: boolean
color?: string color?: string
filled?: boolean
} }
const { text, top, left, bottom, bookmark, color, filled } = Astro.props const { text, top, color } = Astro.props
const classes = ['ribbon', top && 'ribbon-top', left && 'ribbon-start', bottom && 'ribbon-bottom', bookmark && 'ribbon-bookmark', color && `bg-${color}`] const classes = ['ribbon', top && 'ribbon-top', color && `bg-${color}`]
--- ---
<div class:list={classes}> <div class:list={classes}>
{text ? text : <Icon name="star" type={filled ? 'filled' : 'outline'} />} {text ? text : <Icon name="star" />}
</div> </div>
+5 -7
View File
@@ -1,16 +1,14 @@
--- ---
// "Less -> More" gradient legend, e.g. for a choropleth map's color scale. // "Less -> More" gradient legend, e.g. for a choropleth map's color scale.
interface Props { interface Props {
/** number of swatches */
steps?: number
/** theme color the swatches ramp up to, e.g. 'primary' */
color?: string
minLabel?: string
maxLabel?: string
class?: string class?: string
} }
const { steps = 10, color = 'primary', minLabel = 'Less', maxLabel = 'More', class: className } = Astro.props const { class: className } = Astro.props
const steps = 10
const color = 'primary'
const minLabel = 'Less'
const maxLabel = 'More'
const items = Array.from({ length: steps }, (_, i) => i + 1) const items = Array.from({ length: steps }, (_, i) => i + 1)
--- ---
+3 -9
View File
@@ -10,29 +10,24 @@ import CaptureScript from '../components/CaptureScript.astro';
interface Props { interface Props {
id?: string; id?: string;
key?: string; key?: string;
value?: string;
class?: string; class?: string;
state?: string; state?: string;
placeholder?: string; placeholder?: string;
indicator?: string; indicator?: string;
multiple?: boolean;
values?: string[]; values?: string[];
showSearch?: boolean;
} }
const { const {
id: idProp, id: idProp,
key: keyProp, key: keyProp,
value = '',
class: className, class: className,
state, state,
placeholder, placeholder,
indicator, indicator,
multiple,
values, values,
showSearch,
} = Astro.props; } = Astro.props;
const value = '';
const id = idProp ?? keyProp; const id = idProp ?? keyProp;
const key = keyProp ?? 'people'; const key = keyProp ?? 'people';
const data = (selects as Record<string, any>)[key] ?? {}; const data = (selects as Record<string, any>)[key] ?? {};
@@ -43,7 +38,7 @@ const selectClass = [
state && `is-${state}`, state && `is-${state}`,
] ]
const isMultiple = Boolean(multiple || data.multiple); const isMultiple = Boolean(data.multiple);
interface Person { interface Person {
id: string; id: string;
@@ -125,7 +120,7 @@ const selectId = id ? `select-${id}` : undefined;
id && ( id && (
<CaptureScript> <CaptureScript>
<!-- BEGIN SELECT --> <!-- BEGIN SELECT -->
<script is:inline define:vars={{ selectId, showSearch }}> <script is:inline define:vars={{ selectId }}>
function initSelect() { function initSelect() {
window.tabler_select ??= {}; window.tabler_select ??= {};
@@ -140,7 +135,6 @@ const selectId = id ? `select-${id}` : undefined;
(window.tabler_select[selectId] = new TomSelect(document.getElementById(selectId), { (window.tabler_select[selectId] = new TomSelect(document.getElementById(selectId), {
copyClassesToDropdown: false, copyClassesToDropdown: false,
dropdownParent: 'body', dropdownParent: 'body',
...(showSearch === false ? { controlInput: null } : {}),
render: { render: {
item: renderOption, item: renderOption,
option: renderOption, option: renderOption,
+6 -14
View File
@@ -1,23 +1,15 @@
--- ---
// With explicit `size`: `spinner-{type}-{size}`. Without `size`: `spinner-{type}-{argCount}` (e.g. spinner-border-2).
import { includeArgCount } from '@shared/lib/include-args'
interface Props { interface Props {
element?: 'div' | 'span' type?: 'border' | 'grow'
type?: string
color?: string color?: string
size?: string /** core/scss/bootstrap/_spinners.scss `.spinner-{type}-{size}` — only 'sm' exists */
size?: 'sm' | undefined
class?: string class?: string
} }
const { element: Element = 'div', type = 'border', color, size, class: className } = Astro.props const { type = 'border', color, size, class: className } = Astro.props
const includeArgKeys = ['element', 'type', 'color', 'size', 'class'] as const const classes = [`spinner-${type}`, color && `text-${color}`, size && `spinner-${type}-${size}`, className]
const argCount = includeArgCount(Astro.props as Record<string, unknown>, includeArgKeys)
// include['size'] → explicit value, else the arg count meta-property.
const sizeValue = size !== undefined ? size : argCount || undefined
const classes = [`spinner-${type}`, color && `text-${color}`, sizeValue && `spinner-${type}-${sizeValue}`, className]
--- ---
<Element class:list={classes} role="status" /> <div class:list={classes} role="status"></div>
+4 -8
View File
@@ -4,15 +4,11 @@ interface Props {
count?: number | string count?: number | string
active?: number | string active?: number | string
numbers?: boolean numbers?: boolean
color?: string
size?: string
showTooltip?: boolean
showTitle?: boolean
} }
const { count = 4, active = 3, numbers, color, size, showTooltip, showTitle } = Astro.props const { count = 4, active = 3, numbers } = Astro.props
const classes = ['steps', numbers && 'steps-counter', color && `steps-${color}`, size && `steps-${size}`] const classes = ['steps', numbers && 'steps-counter']
const activeNum = Number(active) const activeNum = Number(active)
const total = Number(count) const total = Number(count)
@@ -30,8 +26,8 @@ const steps = Array.from({ length: total }, (_, i) => i + 1)
const fallbackLabel = `Step ${i}${isCurrent ? ' (current)' : isDone ? ' (completed)' : ''}` const fallbackLabel = `Step ${i}${isCurrent ? ' (current)' : isDone ? ' (completed)' : ''}`
return ( return (
<li class:list={itemClass}> <li class:list={itemClass}>
<Element href={isFuture ? undefined : '#'} aria-current={isCurrent ? 'step' : undefined} data-bs-toggle={showTooltip ? 'tooltip' : undefined} title={showTooltip ? `Step ${i} description` : undefined}> <Element href={isFuture ? undefined : '#'} aria-current={isCurrent ? 'step' : undefined}>
{showTitle ? `Step ${i}` : <span class="visually-hidden">{fallbackLabel}</span>} <span class="visually-hidden">{fallbackLabel}</span>
</Element> </Element>
</li> </li>
) )
+7 -13
View File
@@ -3,27 +3,21 @@ import Icon from './Icon.astro'
interface Props { interface Props {
icon?: string icon?: string
iconB?: string
iconAColor?: string
iconBColor?: string iconBColor?: string
iconBClass?: string
variant?: string variant?: string
active?: boolean
/** accessible name announced alongside the pressed state, e.g. "Favorite" */
label?: string
} }
const { icon = 'heart', iconB, iconAColor = 'muted', iconBColor = 'red', iconBClass, variant, active, label = 'Favorite' } = Astro.props const { icon = 'heart', iconBColor = 'red', variant } = Astro.props
const resolvedIconB = iconB ?? icon ?? 'heart' const resolvedIconB = icon ?? 'heart'
// star/heart force the filled variant; otherwise use the passed class. // star/heart force the filled variant; otherwise no extra class.
const resolvedIconBClass = icon === 'star' || icon === 'heart' ? 'icon-filled' : iconBClass const resolvedIconBClass = icon === 'star' || icon === 'heart' ? 'icon-filled' : undefined
const buttonClass = ['switch-icon', variant && `switch-icon-${variant}`, active && 'active'] const buttonClass = ['switch-icon', variant && `switch-icon-${variant}`]
--- ---
<button type="button" class:list={buttonClass} data-bs-toggle="switch-icon" aria-pressed={active ? 'true' : 'false'} aria-label={label}> <button type="button" class:list={buttonClass} data-bs-toggle="switch-icon" aria-pressed="false" aria-label="Favorite">
<span class={`switch-icon-a text-${iconAColor}`}> <span class="switch-icon-a text-muted">
<Icon name={icon} /> <Icon name={icon} />
</span> </span>
<span class={`switch-icon-b text-${iconBColor}`}> <span class={`switch-icon-b text-${iconBColor}`}>
+2 -7
View File
@@ -14,17 +14,16 @@ interface Props {
avatars?: boolean avatars?: boolean
mobile?: boolean mobile?: boolean
buttons?: boolean buttons?: boolean
nowrap?: boolean
} }
const { card, limit = 5, stripped, offset, avatars, mobile, buttons, nowrap } = Astro.props const { card, limit = 5, stripped, offset, avatars, mobile, buttons } = Astro.props
const roles = ['User', 'Admin', 'Owner'] const roles = ['User', 'Admin', 'Owner']
const start = offset ?? 0 const start = offset ?? 0
const rows = (people as Record<string, any>[]).slice(start, start + limit) const rows = (people as Record<string, any>[]).slice(start, start + limit)
const tableClasses = ['table', 'table-vcenter', mobile && 'table-mobile-md', card && 'card-table', stripped && 'table-striped', nowrap && 'table-nowrap'] const tableClasses = ['table', 'table-vcenter', mobile && 'table-mobile-md', card && 'card-table', stripped && 'table-striped']
--- ---
<div class="table-responsive"> <div class="table-responsive">
@@ -38,8 +37,6 @@ const tableClasses = ['table', 'table-vcenter', mobile && 'table-mobile-md', car
<th>Role</th> <th>Role</th>
{nowrap && <th />}
<th class="w-1"></th> <th class="w-1"></th>
</tr> </tr>
</thead> </thead>
@@ -90,8 +87,6 @@ const tableClasses = ['table', 'table-vcenter', mobile && 'table-mobile-md', car
{role} {role}
</td> </td>
{nowrap && <td>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Animi, commodi cupiditate debitis deserunt expedita hic incidunt iste modi molestiae nesciunt non nostrum perferendis perspiciatis placeat praesentium quaerat quo repellendus, voluptates.</td>}
<td> <td>
{buttons ? ( {buttons ? (
<ButtonList class="flex-nowrap"> <ButtonList class="flex-nowrap">
+2 -9
View File
@@ -2,7 +2,6 @@
import Icon from './Icon.astro' import Icon from './Icon.astro'
import Flag from './Flag.astro' import Flag from './Flag.astro'
import Avatar from './Avatar.astro' import Avatar from './Avatar.astro'
import Payment from './Payment.astro'
import Badge from './Badge.astro' import Badge from './Badge.astro'
interface Person { interface Person {
@@ -16,8 +15,6 @@ interface Props {
flag?: string | undefined flag?: string | undefined
icon?: string icon?: string
person?: Person person?: Person
personId?: number
payment?: string
status?: string status?: string
legend?: string legend?: string
checkbox?: boolean checkbox?: boolean
@@ -25,7 +22,7 @@ interface Props {
badge?: string | number badge?: string | number
} }
const { text, flag, icon, person, personId, payment, status, legend, checkbox, checked, badge } = Astro.props const { text, flag, icon, person, status, legend, checkbox, checked, badge } = Astro.props
--- ---
<span class="tag"> <span class="tag">
@@ -33,13 +30,9 @@ const { text, flag, icon, person, personId, payment, status, legend, checkbox, c
flag ? ( flag ? (
<Flag flag={flag} size="xxs" class="tag-flag" /> <Flag flag={flag} size="xxs" class="tag-flag" />
) : icon ? ( ) : icon ? (
<Icon name={icon} size="xxs" class="tag-icon" /> <Icon name={icon} size="sm" class="tag-icon" />
) : person ? ( ) : person ? (
<Avatar person={person} size="xxs" class="tag-avatar" /> <Avatar person={person} size="xxs" class="tag-avatar" />
) : personId ? (
<Avatar personId={personId} size="xxs" class="tag-avatar" />
) : payment ? (
<Payment payment={payment} size="xxs" class="tag-payment" />
) : status ? ( ) : status ? (
<Badge color={status} class="tag-status badge-dot" text="" /> <Badge color={status} class="tag-status badge-dot" text="" />
) : legend ? ( ) : legend ? (
+10 -16
View File
@@ -5,16 +5,12 @@ import { requireIndex } from '@shared/lib/array'
interface Props { interface Props {
toastId?: string toastId?: string
/** people index, 0-based (default 2 → Mallory Hulme) */
personId?: number
show?: boolean show?: boolean
hideHeader?: boolean
cookies?: boolean cookies?: boolean
date?: string
text?: string text?: string
} }
const { toastId = 'simple', personId = 2, show, hideHeader, cookies, date = '11 mins ago', text = 'Hello, world! This is a toast message.' } = Astro.props const { toastId = 'simple', show, cookies, text = 'Hello, world! This is a toast message.' } = Astro.props
interface Person { interface Person {
full_name?: string full_name?: string
@@ -22,20 +18,18 @@ interface Person {
[key: string]: unknown [key: string]: unknown
} }
const person = requireIndex(people as Person[], personId) const date = '11 mins ago'
/** people index, 0-based (default 2 → Mallory Hulme) */
const person = requireIndex(people as Person[], 2)
--- ---
<div class:list={['toast', show && 'show']} id={`toast-${toastId}`} role="alert" aria-live="assertive" aria-atomic="true" data-bs-autohide="false"> <div class:list={['toast', show && 'show']} id={`toast-${toastId}`} role="alert" aria-live="assertive" aria-atomic="true" data-bs-autohide="false">
{ <div class="toast-header">
!hideHeader && ( <Avatar person={person} class="me-2" size="xs" />
<div class="toast-header"> <strong class="me-auto">{person.full_name}</strong>
<Avatar person={person} class="me-2" size="xs" /> <small>{date}</small>
<strong class="me-auto">{person.full_name}</strong> <button type="button" class="ms-2 btn-close" data-bs-dismiss="toast" aria-label="Close"></button>
<small>{date}</small> </div>
<button type="button" class="ms-2 btn-close" data-bs-dismiss="toast" aria-label="Close" />
</div>
)
}
<div class="toast-body"> <div class="toast-body">
{ {
cookies ? ( cookies ? (
+7 -8
View File
@@ -4,20 +4,19 @@ import { capitalize } from '@shared/lib/string-format'
interface Props { interface Props {
type?: 'checkbox' | 'radio' type?: 'checkbox' | 'radio'
checked?: boolean checked?: boolean
disabled?: boolean
/** Switch mode (prop name isSwitch; `switch` is reserved). */ /** Switch mode (prop name isSwitch; `switch` is reserved). */
switch?: boolean switch?: boolean
title?: string | false title?: string | false
name?: string name?: string
inline?: boolean /** core/scss/ui/forms/_form-check.scss `.form-switch-{size}` — only 'lg' exists */
size?: string size?: 'lg' | undefined
empty?: boolean
titleOn?: string titleOn?: string
titleOff?: string titleOff?: string
class?: string class?: string
} }
const { type = 'checkbox', checked = false, disabled = false, switch: isSwitch = false, title: titleProp = false, name, inline, size, empty: isEmpty = false, titleOn, titleOff, class: className } = Astro.props const { type = 'checkbox', checked = false, switch: isSwitch = false, title: titleProp = false, name, size, titleOn, titleOff, class: className } = Astro.props
const disabled = false
let title = titleProp let title = titleProp
if (!title) { if (!title) {
@@ -32,9 +31,9 @@ if (!title) {
title = t title = t
} }
const labelClasses = ['form-check', inline && 'form-check-inline', isSwitch && 'form-switch', isSwitch && size && `form-switch-${size}`, className] const labelClasses = ['form-check', isSwitch && 'form-switch', isSwitch && size && `form-switch-${size}`, className]
const inputClasses = ['form-check-input', isEmpty && 'position-static'] const inputClasses = ['form-check-input']
--- ---
<label class:list={labelClasses}> <label class:list={labelClasses}>
@@ -46,7 +45,7 @@ const inputClasses = ['form-check-input', isEmpty && 'position-static']
<span class="form-check-label form-check-label-off">{titleOff}</span> <span class="form-check-label form-check-label-off">{titleOff}</span>
</Fragment> </Fragment>
) : ( ) : (
!isEmpty && <span class="form-check-label">{title}</span> <span class="form-check-label">{title}</span>
) )
} }
</label> </label>
+3 -6
View File
@@ -3,25 +3,22 @@ import Icon from '../Icon.astro'
interface Props { interface Props {
icon?: string icon?: string
iconClass?: string
class?: string class?: string
prepend?: boolean prepend?: boolean
type?: string type?: string
value?: string value?: string
light?: boolean
rounded?: boolean rounded?: boolean
inputClass?: string
ariaLabel?: string ariaLabel?: string
readonly?: boolean readonly?: boolean
placeholder?: string placeholder?: string
loader?: boolean loader?: boolean
} }
const { icon = 'search', iconClass, class: className, prepend, type = 'text', value = '', light, rounded, inputClass, ariaLabel, readonly, placeholder = 'Search…', loader } = Astro.props const { icon = 'search', class: className, prepend, type = 'text', value = '', rounded, ariaLabel, readonly, placeholder = 'Search…', loader } = Astro.props
const wrapperClass = ['input-icon', className] const wrapperClass = ['input-icon', className]
const addonClass = ['input-icon-addon', iconClass] const addonClass = ['input-icon-addon']
const controlClass = ['form-control', light && 'form-control-light', rounded && 'form-control-rounded', inputClass] const controlClass = ['form-control', rounded && 'form-control-rounded']
--- ---
<div class:list={wrapperClass}> <div class:list={wrapperClass}>
+2 -2
View File
@@ -1,14 +1,14 @@
--- ---
// IMask initializes from the data-mask attributes (tabler.js). // IMask initializes from the data-mask attributes (tabler.js).
interface Props { interface Props {
name?: string
mask?: string mask?: string
visible?: boolean visible?: boolean
placeholder?: string placeholder?: string
reverse?: boolean reverse?: boolean
} }
const { name = 'mask', mask = '00/00/0000', visible, placeholder, reverse } = Astro.props const { mask = '00/00/0000', visible, placeholder, reverse } = Astro.props
const name = 'mask'
--- ---
<input type="text" name={`input-${name}`} class="form-control" data-mask={mask} data-mask-visible={visible ? 'true' : undefined} placeholder={placeholder ?? mask} data-mask-reverse={reverse ? 'true' : undefined} autocomplete="off" /> <input type="text" name={`input-${name}`} class="form-control" data-mask={mask} data-mask-visible={visible ? 'true' : undefined} placeholder={placeholder ?? mask} data-mask-reverse={reverse ? 'true' : undefined} autocomplete="off" />