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>
+6 -50
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" set:html={body} />
) : (
<p class="text-secondary"> <p class="text-secondary">
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aperiam deleniti fugit incidunt, iste, itaque Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aperiam deleniti fugit incidunt, iste, itaque
minima neque pariatur perferendis sed suscipit velit vitae voluptatem. minima neque pariatur perferendis sed suscipit velit vitae voluptatem.
</p> </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 ? (
+5 -11
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">
{
!hideHeader && (
<div class="toast-header"> <div class="toast-header">
<Avatar person={person} class="me-2" size="xs" /> <Avatar person={person} class="me-2" size="xs" />
<strong class="me-auto">{person.full_name}</strong> <strong class="me-auto">{person.full_name}</strong>
<small>{date}</small> <small>{date}</small>
<button type="button" class="ms-2 btn-close" data-bs-dismiss="toast" aria-label="Close" /> <button type="button" class="ms-2 btn-close" data-bs-dismiss="toast" aria-label="Close"></button>
</div> </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" />