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,16 +1,15 @@
---
interface Props {
width?: number
height?: number
ratio?: 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}` : ''}`}>
<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={height} x2={width} y2="0"></line>
</svg>
+10 -54
View File
@@ -1,5 +1,4 @@
---
import CardSubtitle from '@ui/CardSubtitle.astro';
import Icon from '@ui/Icon.astro';
import AvatarList from '@ui/AvatarList.astro';
import Empty from '@ui/Empty.astro';
@@ -8,9 +7,6 @@ import photos from '@data/photos.json';
import { requireIndex } from '@shared/lib/array';
interface Props {
link?: boolean;
active?: boolean;
inactive?: boolean;
class?: string;
empty?: boolean;
imgTop?: boolean;
@@ -18,17 +14,9 @@ interface Props {
statusTop?: string;
statusBottom?: string;
statusStart?: string;
header?: boolean;
headerTitle?: string;
headerTabs?: boolean;
headerPills?: boolean;
alert?: string;
alertType?: string;
title?: string;
subtitle?: boolean;
body?: string;
button?: boolean;
footer?: boolean;
footerButton?: boolean;
footerButtons?: boolean;
/** 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 {
link = false,
active,
inactive,
class: className,
empty,
imgTop,
@@ -47,31 +32,16 @@ const {
statusTop,
statusBottom,
statusStart,
header,
headerTitle,
headerTabs,
headerPills,
alert,
alertType = 'success',
title,
subtitle,
body,
button,
footer,
footerButton,
footerButtons,
footerElements,
progress,
}: Props = Astro.props;
const Element = link ? 'a' : 'div';
const classes = [
'card',
active && 'card-active',
inactive && 'card-inactive',
className,
]
const classes = ['card', className]
// footer-elements parsing — `right` is never reset once set across iterations.
let right = false;
@@ -84,14 +54,14 @@ const footerEls = (footerElements ?? []).map((element: string) => {
return { el, right };
});
const hasHeader = header || headerTitle || headerTabs || headerPills;
const hasFooter = footer || footerButton || footerButtons || footerElements;
const hasHeader = headerTabs || headerPills;
const hasFooter = footerButton || footerButtons || footerElements;
const imgTopPhoto = requireIndex(photos as { file: string; title: string }[], 7);
const imgBottomPhoto = requireIndex(photos as { file: string; title: string }[], 11);
---
<Element href={link ? '#' : undefined} class:list={classes}>
<div class:list={classes}>
{
empty ? (
<Empty illustration="not-found" height={160} />
@@ -142,32 +112,18 @@ const imgBottomPhoto = requireIndex(photos as { file: string; title: string }[],
</li>
</ul>
) : (
<h3 class="card-title" set:html={headerTitle ?? 'Header title'} />
<h3 class="card-title">Header title</h3>
)}
</div>
)}
{alert && <div class={`card-alert alert alert-${alertType} mb-0`} set:html={alert} />}
<div class="card-body">
{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">
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aperiam deleniti fugit incidunt, iste, itaque
minima neque pariatur perferendis sed suscipit velit vitae voluptatem.
</p>
)}
{button && (
<div class="card-text">
<a href="#" class="btn btn-primary">
Go somewhere
</a>
</div>
)}
<p class="text-secondary">
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aperiam deleniti fugit incidunt, iste, itaque
minima neque pariatur perferendis sed suscipit velit vitae voluptatem.
</p>
</div>
{hasFooter && (
@@ -228,4 +184,4 @@ const imgBottomPhoto = requireIndex(photos as { file: string; title: string }[],
</Fragment>
)
}
</Element>
</div>
+2 -5
View File
@@ -1,14 +1,11 @@
---
// Tabs/content blocks are HTML strings (different order when `bottom`).
interface Props {
/** tabs-count — present but unused */
count?: number
id?: string
bottom?: boolean
borderless?: boolean
}
const { id = 'top', bottom, borderless } = Astro.props
const { id = 'top', bottom } = Astro.props
const tabs = ['Activity', 'Profile', 'Settings']
@@ -37,7 +34,7 @@ ${tabs
---
<!-- Cards with tabs component -->
<div class={`card-tabs${borderless ? ' border-0' : ''}`}>
<div class="card-tabs">
{
bottom ? (
<Fragment>
+2 -2
View File
@@ -14,12 +14,12 @@ interface Props {
controls?: boolean
captions?: boolean
offset?: number
limit?: number
fade?: boolean
}
const { id: idProp, title = 'Carousel', indicators, indicatorsThumb, indicatorsThumbRatio, indicatorsDot, indicatorsVertical, controls, captions, offset = 0, limit = 5, fade } = Astro.props
const { id: idProp, title = 'Carousel', indicators, indicatorsThumb, indicatorsThumbRatio, indicatorsDot, indicatorsVertical, controls, captions, offset = 0, fade } = Astro.props
const limit = 5
const carouselId = idProp ?? 'carousel'
const filteredPhotos = (photos as { file: string; title?: string; horizontal?: boolean }[]).filter((p) => p.horizontal)
const slides = filteredPhotos.slice(offset, offset + limit)
+2 -2
View File
@@ -3,10 +3,10 @@ import MapVector from '@ui/MapVector.astro'
interface Props {
title?: string
map?: string
}
const { title, map = 'world' } = Astro.props
const { title } = Astro.props
const map = 'world'
---
<div class="card">
+2 -2
View File
@@ -12,10 +12,10 @@ interface Person {
interface Props {
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}`}>
+2 -3
View File
@@ -9,17 +9,16 @@ interface Props {
badge?: string
offset?: number
limit?: number
percentage?: number | string
percentageColor?: string
due?: 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">
<Progress value={percentage ?? value} class="card-progress" color={percentageColor} />
<Progress value={value} class="card-progress" color={percentageColor} />
<div class="card-body">
<CardTitle>
<a href="#">{title}</a>
+7 -12
View File
@@ -4,31 +4,26 @@ import AvatarList from '@ui/AvatarList.astro'
import Progress from '@ui/Progress.astro'
interface Props {
title?: string
date?: 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-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>
<p class="text-secondary">Due to: {date}</p>
<p class="mb-3">
<span class={`badge bg-${stageColor}-lt`}>{stage}</span>
</p>
<div>
<AvatarList stacked offset={avatarOffset} limit={avatarLimit} />
<AvatarList stacked />
</div>
</div>
<Progress class="card-progress" value={percentage} color={percentageColor} />
<Progress class="card-progress" />
</div>
+1 -23
View File
@@ -4,8 +4,6 @@
import Icon from '@ui/Icon.astro'
import Avatar from '@ui/Avatar.astro'
import ChartSparkline from '@ui/ChartSparkline.astro'
import Trending from '@ui/Trending.astro'
import Button from '@ui/Button.astro'
interface Props {
icon?: string
@@ -19,16 +17,12 @@ interface Props {
chartType?: string
chartPosition?: string
chartData?: string | number
chartLabel?: string
chartLabelIcon?: string
smallIcon?: string
descriptionValue?: 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 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>
)
}
{
trending && (
<div class="col-auto">
<Trending value={trending} />
</div>
)
}
{
button && (
<div class="col-auto">
<Button text={button} size="sm" />
</div>
)
}
</div>
</div>
</div>
+7 -10
View File
@@ -5,8 +5,6 @@ import DropdownMenu from '@ui/DropdownMenu.astro'
interface Props {
id: string
icons?: boolean
/** hide-text param; not used by current call sites */
hideText?: boolean
reverse?: boolean
justified?: boolean
activity?: boolean
@@ -16,9 +14,8 @@ interface Props {
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 paneClass = `tab-pane${animation ? ' fade' : ''}`
---
@@ -27,18 +24,18 @@ const paneClass = `tab-pane${animation ? ' fade' : ''}`
<div class="card-header">
<ul class={navClass} role="tablist">
<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 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>
{
activity && (
<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">
{icons && <Icon name="activity" class={iconClass} />}
{!hideText && 'Activity'}
{icons && <Icon name="activity" class="me-2" />}
Activity
</a>
</li>
)
@@ -48,8 +45,8 @@ const paneClass = `tab-pane${animation ? ' fade' : ''}`
disabled && (
<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">
{icons && <Icon name="x" class={iconClass} />}
{!hideText && 'Disabled'}
{icons && <Icon name="x" class="me-2" />}
Disabled
</a>
</li>
)
+2 -8
View File
@@ -16,18 +16,12 @@ interface Person {
[key: string]: unknown
}
interface Props {
title?: string
personId?: number
}
const { title = 'Basic info', personId = 25 } = Astro.props
const person = requireIndex(people as Person[], personId - 1)
const person = requireIndex(people as Person[], 24)
---
<div class="card">
<div class="card-body">
<h3 class="card-title">{title}</h3>
<h3 class="card-title">Basic info</h3>
<div class="mb-2">
<Icon name="book" class="me-2 text-secondary" />
Went to: <strong>{person.university}</strong>
+3 -2
View File
@@ -16,7 +16,6 @@ interface Person {
}
interface Props {
limit?: number
offset?: number
hoverable?: boolean
checkbox?: boolean
@@ -32,7 +31,9 @@ interface Props {
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 commitList = commits as { description: string }[]
+5 -4
View File
@@ -12,13 +12,14 @@ interface Person {
}
interface Props {
limit?: number
offset?: number
title?: 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 statusLabels: Record<(typeof colors)[number], string> = { green: 'Online', red: 'Busy', yellow: 'Away', x: 'Offline' }
@@ -15,11 +15,7 @@ interface Person {
[key: string]: unknown
}
interface Props {
title?: string
}
const { title = 'People' } = Astro.props
const title = 'People'
const commitList = commits as { description: string }[]
+1 -5
View File
@@ -2,7 +2,6 @@
// Layout front-matter flags are plain props here.
// TODO: layout-navbar-overlap && layout-navbar-dark → extra text-white class
// on .page-header — both unset on the homepage.
import Icon from '@ui/Icon.astro';
import HeaderActionsButtons from './HeaderActionsButtons.astro';
import HeaderActionsPrint from './HeaderActionsPrint.astro';
import HeaderActionsPhotos from './HeaderActionsPhotos.astro';
@@ -24,15 +23,13 @@ interface Props {
actions?: string | undefined;
/** page-header-class */
class?: string;
/** page-header-icon */
icon?: string;
/** page-header-file — name of an include from layout/headers/, e.g. "profile" */
file?: string | undefined;
/** layout-navbar-overlap && layout-navbar-dark → text-white on .page-header */
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 />}
@@ -50,7 +47,6 @@ const { title, pretitle, description, actions, class: className, icon, file, tex
</Fragment>
)}
<h1 class="page-title">
{icon && <Icon name={icon} />}
{title}
</h1>
+1 -1
View File
@@ -28,7 +28,7 @@ const typedId = 'typed'
</div>
<div class="col-md-6">
<div class="hero-img-side">
<Carousel id="carousel-controls" slideCount={3} interval={4000}>
<Carousel id="carousel-controls" interval={4000}>
<div class="carousel-item active">
<Illustration image="boy-with-key.svg" class="d-block mx-auto" height="350" />
</div>
@@ -3,13 +3,12 @@ import Button from '@ui/Button.astro'
import ButtonList from '@ui/ButtonList.astro'
interface Props {
background?: 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}>
@@ -1,20 +1,16 @@
---
import SectionDivider from '../SectionDivider.astro'
import Icon from '@ui/Icon.astro'
interface Props {
background?: 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}>
<SectionDivider divider={divider} />
<div class="container">
<div class="row items-center text-center g-lg-10">
<div class="col-md-6 col-lg">
@@ -1,20 +1,17 @@
---
import SectionDivider from '../SectionDivider.astro'
import Icon from '@ui/Icon.astro'
interface Props {
background?: 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]
---
<section class:list={sectionClass}>
<SectionDivider divider={divider} />
<div class="container">
<div class="section-header">
<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'
interface Props {
background?: 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]
---
<section class:list={sectionClass}>
<SectionDivider divider={divider} />
<div class="container">
<div class="section-header">
<h2 class="section-title">Everything you need to deploy your app</h2>
@@ -2,13 +2,12 @@
import Icon from '@ui/Icon.astro'
interface Props {
background?: 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}>
@@ -1,13 +1,7 @@
---
import Icon from '@ui/Icon.astro'
interface Props {
background?: string
}
const { background } = Astro.props
const sectionClass = ['section', background && `section-${background}`]
const sectionClass = ['section']
---
<section class:list={sectionClass}>
@@ -2,13 +2,12 @@
import Icon from '@ui/Icon.astro'
interface Props {
background?: 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}>
@@ -13,15 +13,14 @@ interface Person {
}
interface Props {
background?: string
class?: string
limit?: number
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.
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.
interface Props {
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
scrollable?: boolean
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 dialogClass = ['modal-dialog', size && `modal-${size}`, !top && 'modal-dialog-centered', scrollable && 'modal-dialog-scrollable'].filter(Boolean).join(' ')
const modalClass = ['modal modal-blur fade', className].filter(Boolean).join(' ')
const dialogClass = ['modal-dialog', size && `modal-${size}`, !top && 'modal-dialog-centered'].filter(Boolean).join(' ')
---
<!-- 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="modal-content">
<slot />
+5 -6
View File
@@ -3,20 +3,19 @@
// modals gallery page where each modal is shown inline with `inline show`.
interface Props {
modalId?: string
size?: string
top?: boolean
/** core/scss/bootstrap/_modal.scss + ui/_modals.scss `.modal-{size}` modifier classes */
size?: 'sm' | 'lg' | 'xl' | 'fullscreen' | 'full-width' | undefined
scrollable?: boolean
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, 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 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 -->
+2 -2
View File
@@ -17,7 +17,6 @@ interface Props {
hideSearch?: boolean;
/** equivalent of the page-menu front matter (active menu entry), e.g. "dashboards.default" */
pageMenu?: string | undefined;
breakpoint?: string;
condensed?: boolean | undefined;
dark?: boolean | undefined;
transparent?: boolean;
@@ -48,7 +47,6 @@ interface Props {
const {
hideSearch = false,
pageMenu,
breakpoint = 'md',
condensed = false,
dark = false,
transparent = false,
@@ -67,6 +65,8 @@ const {
class: className,
} = Astro.props;
const breakpoint = 'md';
const headerClass = [
`navbar navbar-expand-${breakpoint}`,
transparent ? 'navbar-transparent' : background && `bg-${background}`,
+2 -3
View File
@@ -5,16 +5,15 @@ import InputIcon from '@ui/form/InputIcon.astro'
interface Props {
class?: string
rounded?: boolean
}
const { class: className, rounded = false } = Astro.props
const { class: className } = Astro.props
---
<!-- BEGIN NAVBAR SEARCH -->
<div class={className}>
<form action="./" method="get" autocomplete="off" novalidate role="search">
<InputIcon prepend rounded={rounded} ariaLabel="Search in website" />
<InputIcon prepend ariaLabel="Search in website" />
</form>
</div>
<!-- END NAVBAR SEARCH -->
+4 -1
View File
@@ -7,9 +7,12 @@ import NavbarSideApps from './NavbarSideApps.astro'
import NavbarSideLanguage from './NavbarSideLanguage.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 {
class?: string
breakpoint?: string
breakpoint?: Breakpoint | undefined
condensed?: boolean
personId?: number | undefined
hideUsername?: boolean
+2 -1
View File
@@ -3,10 +3,11 @@ import NavbarToggler from './NavbarToggler.astro'
import Logo from './Logo.astro'
import NavbarSide from './NavbarSide.astro'
import NavbarMenu from './NavbarMenu.astro'
import type { Breakpoint } from './NavbarSide.astro'
interface Props {
dark?: boolean | undefined
breakpoint?: string
breakpoint?: Breakpoint | undefined
/** layout-sidebar-end — navbar-end class */
end?: boolean | undefined
/** layout-navbar-transparent — navbar-transparent class */
+2 -2
View File
@@ -12,11 +12,11 @@ interface Column {
}
interface Props {
data?: { columns: Column[] }
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}` : ''}`}>
@@ -2,13 +2,6 @@
import CardTitle from '@ui/CardTitle.astro'
import Chart from '@ui/Chart.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">
@@ -24,7 +17,7 @@ const { hideBreakpoint } = Astro.props
<div class="col">
<Chart chartId="active-users-2" label="Active users" />
</div>
<div class={`col${hideBreakpoint ? '' : '-md'}-auto`}>
<div class="col-md-auto">
<div class="divide-y divide-y-fill">
<div class="px-3">
<div class="text-secondary">
-12
View File
@@ -43,18 +43,6 @@ export function extractMarkedSnippet(source: string, marker: string, name: strin
.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. */
export function removeHref(content: string): string {
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 { 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'
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', () => {
it('lowercases the input', () => {
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, ',')
}
/** 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. */
export function slugifyWord(text: string): string {
return text.toLowerCase()
+1 -5
View File
@@ -4,11 +4,7 @@ import activity from '@data/activity.json'
import people from '@data/people.json'
import { timeagoLabel } from '@shared/lib/pseudo-random'
interface Props {
limit?: number
}
const { limit = 40 } = Astro.props
const limit = 40
interface Person {
full_name?: string
+8 -21
View File
@@ -1,34 +1,33 @@
---
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 {
type?: string
icon?: string
type?: AlertType
important?: boolean
minor?: boolean
showClose?: boolean
avatar?: boolean
class?: string
title?: string
description?: string
list?: string[]
action?: 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',
warning: 'alert-triangle',
danger: 'alert-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">
@@ -65,17 +64,5 @@ const classes = ['alert', important ? 'alert-important' : minor ? 'alert-minor'
</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" />}
</div>
+10 -11
View File
@@ -9,22 +9,22 @@ interface Person {
[key: string]: unknown
}
/** core/scss/_variables.scss `$avatar-sizes` map keys */
export type AvatarSize = 'xxs' | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl'
interface Props {
src?: string
placeholder?: string | undefined
personId?: number
person?: Person | undefined
link?: boolean
dropdown?: boolean
size?: string | undefined
size?: AvatarSize | undefined
thumb?: boolean
class?: string
shape?: string
color?: string | undefined
square?: boolean
status?: string
statusText?: string
statusIcon?: string
statusLabel?: string
brand?: string
icon?: string | undefined
@@ -34,7 +34,7 @@ interface Props {
[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 placeholder = placeholderProp
@@ -47,14 +47,13 @@ if (person) {
}
}
const link = linkProp || Boolean(dropdown)
const El = link ? 'a' : 'span'
const El = linkProp ? 'a' : 'span'
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}
>{status && <span class={`badge bg-${status}`}>{statusText ? statusText : statusIcon ? <Icon name={statusIcon} class="avatar-status-icon" /> : <span class="visually-hidden">{statusLabel ?? status}</span>}</span>}{
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" />}</El
<El class:list={classes} style={src ? `background-image: url(${base}/${src})` : undefined} {...rest}
>{status && <span class={`badge bg-${status}`} aria-label={statusLabel ?? status} />}{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" />
}</El
>
+2 -1
View File
@@ -3,6 +3,7 @@
// 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.
import Avatar from './Avatar.astro'
import type { AvatarSize } from './Avatar.astro'
import people from '@data/people.json'
interface Person {
@@ -16,7 +17,7 @@ interface Props {
offset?: number | undefined
stacked?: boolean
/** avatar-list-{size} + forwarded to default Avatars */
size?: string
size?: AvatarSize | undefined
class?: string
/** placeholder avatar appended after the list */
text?: string
+2 -1
View File
@@ -1,9 +1,10 @@
---
import Icon from './Icon.astro'
import type { AvatarSize } from './Avatar.astro'
interface Props {
class?: string
size?: string
size?: AvatarSize | undefined
}
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' */
color?: string
size?: 'sm' | 'md' | 'lg' | 'xl'
/** element to render instead of div */
as?: string
class?: string
/** remaining attributes (style, data-*, …) are forwarded to the element */
[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 />
</Element>
</div>
+2 -17
View File
@@ -1,13 +1,5 @@
---
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 {
text?: string
@@ -18,18 +10,11 @@ interface Props {
light?: boolean
class?: 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 person = personId ? (people as Person[])[personId - 1] : undefined
const { text, color, scale, light, class: className, icon } = Astro.props
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.
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 {
text?: string
href?: string
@@ -15,24 +18,13 @@ interface Props {
color?: string | undefined
outline?: boolean
ghost?: boolean
/** btn-{height} right after btn */
height?: string
size?: string
size?: ButtonSize | undefined
block?: boolean
disabled?: boolean
square?: boolean
loading?: boolean
action?: boolean
pill?: boolean
/** btn-link */
link?: boolean
class?: string
icon?: string
iconColor?: string
iconEnd?: string
iconOnly?: boolean
/** animated dots after the text */
dots?: boolean
/** data-bs-toggle="modal" data-bs-target="#modal-{modalId}" (modal-id param) */
modalId?: string
/** data-bs-toggle="toast" data-bs-target="#toast-{toastId}" (toast-id param) */
@@ -43,33 +35,18 @@ interface Props {
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 '/'.
const Element = element ?? (href ? 'a' : 'button')
const resolvedHref = href ? (external ? href : `/${href}`) : '#'
const classes = [
'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',
]
const classes = ['btn', color && `btn-${outline ? 'outline-' : ghost ? 'ghost-' : ''}${color}`, size && size !== 'md' && `btn-${size}`, className, block && 'w-100', iconOnly && 'btn-icon']
---
<Element
href={Element === 'a' ? resolvedHref : undefined}
type={Element === 'button' ? (type ?? 'button') : undefined}
disabled={Element === 'button' && disabled ? true : undefined}
id={id}
class:list={classes}
target={external ? '_blank' : undefined}
@@ -81,6 +58,5 @@ const classes = [
>
{icon && <Icon name={icon} color={iconColor} />}
{!iconOnly && text}
{!iconOnly && dots && <span class="animated-dots" />}
{!iconOnly && iconEnd && <Icon name={iconEnd} class="icon-end" />}
</Element>
+2 -5
View File
@@ -4,17 +4,14 @@
interface Props {
/** use .btn-group-vertical instead of .btn-group */
vertical?: boolean
/** btn-group-{size}: sm | lg | xl */
size?: string
class?: string
role?: astroHTML.JSX.AriaRole
/** remaining attributes (aria-label, …) are forwarded */
[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 />
</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.
type PolymorphicProps = { as?: HTMLTag }
/** core/scss/ui/_cards.scss `.card-{size}` body padding modifier classes */
type CardSize = 'sm' | 'md' | 'lg'
interface Props extends PolymorphicProps {
/** card-{size} body padding: sm | md | lg */
size?: string
size?: CardSize | undefined
class?: string
/** remaining attributes (id, action, data-*, …) are forwarded to the element */
[key: string]: unknown
+2 -2
View File
@@ -2,11 +2,11 @@
import Icon from './Icon.astro'
interface Props {
icon?: 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]}>
+2 -4
View File
@@ -2,14 +2,12 @@
// Card footer (.card-footer).
interface Props {
/** card-footer-transparent */
transparent?: boolean
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 />
</div>
+2 -4
View File
@@ -9,15 +9,13 @@ type PolymorphicProps = { as?: HTMLTag }
interface Props extends PolymorphicProps {
title?: string
/** card-header-light */
light?: boolean
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>}
<slot />
</div>
+1 -1
View File
@@ -5,7 +5,7 @@ import Icon from './Icon.astro'
interface Props {
icon?: string
/** 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 */
color?: string
/** 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
// (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
// icon-span controls with custom markup (e.g. an <Icon>-based pair).
// a "controls" slot renders custom prev/next markup (e.g. an <Icon>-based pair).
interface Props {
id?: string
/** slide count, used to generate indicator buttons */
slideCount: number
fade?: boolean
/** data-bs-interval in ms, or false to disable autoplay */
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
/** remaining attributes forwarded to the element */
[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')
---
<div id={id} class:list={['carousel', 'slide', fade && 'carousel-fade', 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 id={id} class:list={['carousel', 'slide', className]} data-bs-ride="carousel" data-bs-interval={interval === false ? 'false' : interval} {...rest}>
<div class="carousel-inner">
<slot />
</div>
{
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>
)
)
}
{hasCustomControls && <slot name="controls" />}
</div>
+2 -22
View File
@@ -5,36 +5,21 @@ interface Props {
id?: string | undefined;
type?: string;
color?: string | undefined;
height?: number;
small?: boolean;
wide?: boolean;
data?: 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 {
id,
type = 'bar',
color = 'primary',
height: heightProp,
small,
wide,
data: dataProp,
percentage,
decorative = true,
label,
} = Astro.props;
const height = heightProp ?? (small ? 1.5 : 2.5);
const height = small ? 1.5 : 2.5;
const heightPx = Math.round(height * 16);
const data = percentage ?? dataProp;
@@ -45,17 +30,12 @@ const chartType = type === 'donut' ? 'radialBar' : type;
const classes = [
'chart-sparkline',
isSquare && 'chart-sparkline-square',
wide && 'chart-sparkline-wide',
small && 'chart-sparkline-sm',
]
const seriesData = String(data ?? '').split(',').map(Number);
const chartKey = `sparkline-${id}`;
const ariaHidden = decorative ? true : undefined;
const ariaRole = decorative ? undefined : 'img';
const ariaLabel = decorative ? undefined : (label ?? 'Chart');
const chartOptions = id
? {
chart: {
@@ -87,7 +67,7 @@ const chartOptions = id
: 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 && (
<CaptureScript>
+3 -5
View File
@@ -5,15 +5,14 @@ import CaptureScript from '../components/CaptureScript.astro'
interface Props {
value?: string
id?: string | number
alpha?: boolean
format?: string | false
swatchesOnly?: boolean
class?: string
/** Accessible name for the input; falls back to a generic label when the caller doesn't wrap this in a labelled FormGroup */
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 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'} />
<CaptureScript>
<!-- 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() {
window.tabler_colorpicker ??= {}
@@ -40,7 +39,6 @@ const colorpickerSelector = `#colorpicker-${id}`
selectInput: false,
alpha,
...(format ? { format } : {}),
...(swatchesOnly ? { swatchesOnly: true } : {}),
swatches,
})
window.tabler_colorpicker[colorpickerId] = colorpickerSelector
+2 -2
View File
@@ -5,7 +5,6 @@ import CaptureScript from '../components/CaptureScript.astro';
interface Props {
id?: string;
value?: string;
placeholder?: string;
class?: string;
/** 'icon' | 'icon-prepend' | undefined (plain) */
layout?: string;
@@ -15,12 +14,13 @@ interface Props {
const {
id,
value = '2020-06-20',
placeholder = 'Select a date',
class: className,
layout,
inline,
} = Astro.props;
const placeholder = 'Select a date';
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>';
const chevronRight =
+2 -2
View File
@@ -2,10 +2,10 @@
interface Props {
id?: 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">
+3 -26
View File
@@ -11,12 +11,10 @@ interface Props {
arrow?: boolean
dark?: boolean
class?: string
menu?: string[]
check?: boolean
radio?: boolean
people?: boolean
flag?: boolean
type?: string
header?: boolean
icons?: boolean
badge?: boolean
@@ -25,38 +23,17 @@ interface Props {
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 menuItems = menu
const peopleItems = (people as { id: string; full_name: 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}>
{
menuItems ? (
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 ? (
check || radio ? (
[1, 2, 3].map((i) => (
<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}
@@ -74,7 +51,7 @@ const countryItems = (countries as { name: string; flag: string }[]).slice(0, 5)
<Flag flag={country.flag} size="xs" /> {country.name}
</button>
))
) : type === 'text' ? null : (
) : (
<>
{header && <span class="dropdown-header">Dropdown header</span>}
<button type="button" class="dropdown-item">
+2 -2
View File
@@ -6,10 +6,10 @@ interface Props {
custom?: boolean
text?: 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 dropzoneSelector = `#dropzone-${id}`
+2 -3
View File
@@ -6,7 +6,6 @@ import Illustration from './Illustration.astro'
interface Props {
icon?: string
height?: number | string
bordered?: boolean
class?: string
/** illustration file name, e.g. "computer-fix.svg" */
illustration?: string | undefined
@@ -18,9 +17,9 @@ interface Props {
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}>
+4 -1
View File
@@ -1,7 +1,10 @@
---
import type { AvatarSize } from './Avatar.astro'
interface Props {
flag?: string | undefined
size?: string
/** core/scss/_variables.scss `$flag-sizes` (aliased to `$avatar-sizes`) */
size?: AvatarSize | undefined
class?: string
label?: string | undefined
}
+2 -3
View File
@@ -2,13 +2,12 @@
interface Props {
text?: string
position?: string
color?: 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>
+4 -1
View File
@@ -1,12 +1,15 @@
---
import icons from '@data/icons.json'
/** core/scss/ui/_icons.scss `.icon-{size}` modifier classes */
export type IconSize = 'sm' | 'md' | 'lg'
interface Props {
name: string
type?: 'outline' | 'filled'
class?: string | undefined
color?: string | undefined
size?: string
size?: IconSize | undefined
inline?: boolean
}
+2 -3
View File
@@ -4,13 +4,12 @@ import Icon from './Icon.astro'
interface Props {
pills?: boolean
tabs?: boolean
header?: boolean
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}>
+5 -11
View File
@@ -1,39 +1,33 @@
---
import Icon from './Icon.astro'
import { includeArgCount } from '@shared/lib/include-args'
interface Props {
items?: string[]
icons?: string[]
disabled?: number[]
hover?: string
default?: number
vertical?: boolean
size?: string
/** core/scss/ui/_segmented.scss `.nav-{size}` modifier classes */
size?: 'sm' | 'lg' | undefined
fullWidth?: boolean
name?: string
class?: string
}
const { items, icons, disabled, hover = '', 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 { items, icons, disabled, default: defaultIndex = 1, vertical, size, fullWidth, name, class: className } = Astro.props
const itemsArr = items ?? []
const iconsArr = icons ?? []
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 forloopIndex = i + 1
const index = String(forloopIndex)
const isDisabled = disabled?.includes(forloopIndex) ?? false
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] }
})
---
+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).
interface Props {
/** offcanvas-{direction}: start | end | top | bottom */
direction?: string
/** responsive variant offcanvas-{size} (lg | xl | xxl) — replaces the base .offcanvas class */
size?: string
/** offcanvas-narrow (20rem width) */
narrow?: boolean
/** core/scss/bootstrap/_offcanvas.scss `.offcanvas-{direction}` modifier classes */
direction?: 'start' | 'end' | 'top' | 'bottom' | undefined
/** responsive variant offcanvas-{size} ($grid-breakpoints minus xs) — replaces the base .offcanvas class */
size?: 'sm' | 'md' | 'lg' | 'xl' | 'xxl' | undefined
show?: boolean
class?: string
/** defaults to "-1" when not provided */
@@ -18,11 +16,11 @@ interface Props {
[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 -->
<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 />
</div>
<!-- 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 {
payment?: string
dark?: boolean
size?: string
/** core/scss/_variables.scss `$payment-sizes` (aliased to `$avatar-sizes`) */
size?: AvatarSize | undefined
class?: string
label?: string | undefined
}
+4 -1
View File
@@ -5,12 +5,15 @@ interface Photo {
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 {
photo: Photo
/** background param — plain <div> with only the background-image (no ratio class) */
background?: boolean
/** ratio param — img-responsive img-responsive-{ratio} wrapper */
ratio?: string
ratio?: PhotoRatio | undefined
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 {
value?: number | string | undefined
values?: (string | number)[]
color?: string | undefined
colors?: string[]
size?: string | undefined
size?: ProgressSize | undefined
class?: string
width?: string
id?: string
@@ -14,11 +16,11 @@ interface Props {
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 colors = colorsProp ?? (color ? [color] : ['blue', 'red', 'green', 'yellow', 'dark'])
const colors = color ? [color] : ['blue', 'red', 'green', 'yellow', 'dark']
const valueList = values
const classes = ['progress', size && `progress-${size}`, className]
+2 -11
View File
@@ -1,6 +1,5 @@
---
import Progress from './Progress.astro'
import Flag from './Flag.astro'
import { parsePercentage } from '@shared/lib/string-format'
interface Props {
@@ -8,24 +7,16 @@ interface Props {
color?: string
class?: string
text?: string
flag?: string
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)
---
<div class={`progressbg${className ? ` ${className}` : ''}`}>
<Progress value={percentage} class="progressbg-progress" color={color} />
{
text && (
<div class="progressbg-text">
{flag && <Flag flag={flag} class="me-2" size="xs" />}
{text}
</div>
)
}
{text && <div class="progressbg-text">{text}</div>}
{showValue && <div class="progressbg-value">{percentage}%</div>}
</div>
+2 -1
View File
@@ -1,13 +1,14 @@
---
// Forward `size` only when explicitly provided.
import Progress from './Progress.astro'
import type { ProgressSize } from './Progress.astro'
interface Props {
label?: string
description?: string
value?: number | string
color?: string
size?: string
size?: ProgressSize | undefined
class?: string
}
+2 -2
View File
@@ -4,14 +4,14 @@ import CaptureScript from '../components/CaptureScript.astro';
interface Props {
min?: number;
max?: number;
step?: number;
value?: string | number;
id?: string;
connect?: boolean;
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 size = values.length;
+2 -1
View File
@@ -1,12 +1,13 @@
---
import icons from '@data/icons.json'
import CaptureScript from '../components/CaptureScript.astro'
import type { IconSize } from './Icon.astro'
interface Props {
id?: string
icon?: string
color?: string
size?: string
size?: IconSize | undefined
value?: number
}
+3 -7
View File
@@ -4,18 +4,14 @@ import Icon from './Icon.astro'
interface Props {
text?: string
top?: boolean
left?: boolean
bottom?: boolean
bookmark?: boolean
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}>
{text ? text : <Icon name="star" type={filled ? 'filled' : 'outline'} />}
{text ? text : <Icon name="star" />}
</div>
+5 -7
View File
@@ -1,16 +1,14 @@
---
// "Less -> More" gradient legend, e.g. for a choropleth map's color scale.
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
}
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)
---
+3 -9
View File
@@ -10,29 +10,24 @@ import CaptureScript from '../components/CaptureScript.astro';
interface Props {
id?: string;
key?: string;
value?: string;
class?: string;
state?: string;
placeholder?: string;
indicator?: string;
multiple?: boolean;
values?: string[];
showSearch?: boolean;
}
const {
id: idProp,
key: keyProp,
value = '',
class: className,
state,
placeholder,
indicator,
multiple,
values,
showSearch,
} = Astro.props;
const value = '';
const id = idProp ?? keyProp;
const key = keyProp ?? 'people';
const data = (selects as Record<string, any>)[key] ?? {};
@@ -43,7 +38,7 @@ const selectClass = [
state && `is-${state}`,
]
const isMultiple = Boolean(multiple || data.multiple);
const isMultiple = Boolean(data.multiple);
interface Person {
id: string;
@@ -125,7 +120,7 @@ const selectId = id ? `select-${id}` : undefined;
id && (
<CaptureScript>
<!-- BEGIN SELECT -->
<script is:inline define:vars={{ selectId, showSearch }}>
<script is:inline define:vars={{ selectId }}>
function initSelect() {
window.tabler_select ??= {};
@@ -140,7 +135,6 @@ const selectId = id ? `select-${id}` : undefined;
(window.tabler_select[selectId] = new TomSelect(document.getElementById(selectId), {
copyClassesToDropdown: false,
dropdownParent: 'body',
...(showSearch === false ? { controlInput: null } : {}),
render: {
item: 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 {
element?: 'div' | 'span'
type?: string
type?: 'border' | 'grow'
color?: string
size?: string
/** core/scss/bootstrap/_spinners.scss `.spinner-{type}-{size}` — only 'sm' exists */
size?: 'sm' | undefined
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 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]
const classes = [`spinner-${type}`, color && `text-${color}`, size && `spinner-${type}-${size}`, 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
active?: number | string
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 total = Number(count)
@@ -30,8 +26,8 @@ const steps = Array.from({ length: total }, (_, i) => i + 1)
const fallbackLabel = `Step ${i}${isCurrent ? ' (current)' : isDone ? ' (completed)' : ''}`
return (
<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}>
{showTitle ? `Step ${i}` : <span class="visually-hidden">{fallbackLabel}</span>}
<Element href={isFuture ? undefined : '#'} aria-current={isCurrent ? 'step' : undefined}>
<span class="visually-hidden">{fallbackLabel}</span>
</Element>
</li>
)
+7 -13
View File
@@ -3,27 +3,21 @@ import Icon from './Icon.astro'
interface Props {
icon?: string
iconB?: string
iconAColor?: string
iconBColor?: string
iconBClass?: 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'
// star/heart force the filled variant; otherwise use the passed class.
const resolvedIconBClass = icon === 'star' || icon === 'heart' ? 'icon-filled' : iconBClass
const resolvedIconB = icon ?? 'heart'
// star/heart force the filled variant; otherwise no extra class.
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}>
<span class={`switch-icon-a text-${iconAColor}`}>
<button type="button" class:list={buttonClass} data-bs-toggle="switch-icon" aria-pressed="false" aria-label="Favorite">
<span class="switch-icon-a text-muted">
<Icon name={icon} />
</span>
<span class={`switch-icon-b text-${iconBColor}`}>
+2 -7
View File
@@ -14,17 +14,16 @@ interface Props {
avatars?: boolean
mobile?: 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 start = offset ?? 0
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">
@@ -38,8 +37,6 @@ const tableClasses = ['table', 'table-vcenter', mobile && 'table-mobile-md', car
<th>Role</th>
{nowrap && <th />}
<th class="w-1"></th>
</tr>
</thead>
@@ -90,8 +87,6 @@ const tableClasses = ['table', 'table-vcenter', mobile && 'table-mobile-md', car
{role}
</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>
{buttons ? (
<ButtonList class="flex-nowrap">
+2 -9
View File
@@ -2,7 +2,6 @@
import Icon from './Icon.astro'
import Flag from './Flag.astro'
import Avatar from './Avatar.astro'
import Payment from './Payment.astro'
import Badge from './Badge.astro'
interface Person {
@@ -16,8 +15,6 @@ interface Props {
flag?: string | undefined
icon?: string
person?: Person
personId?: number
payment?: string
status?: string
legend?: string
checkbox?: boolean
@@ -25,7 +22,7 @@ interface Props {
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">
@@ -33,13 +30,9 @@ const { text, flag, icon, person, personId, payment, status, legend, checkbox, c
flag ? (
<Flag flag={flag} size="xxs" class="tag-flag" />
) : icon ? (
<Icon name={icon} size="xxs" class="tag-icon" />
<Icon name={icon} size="sm" class="tag-icon" />
) : person ? (
<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 ? (
<Badge color={status} class="tag-status badge-dot" text="" />
) : legend ? (
+10 -16
View File
@@ -5,16 +5,12 @@ import { requireIndex } from '@shared/lib/array'
interface Props {
toastId?: string
/** people index, 0-based (default 2 → Mallory Hulme) */
personId?: number
show?: boolean
hideHeader?: boolean
cookies?: boolean
date?: 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 {
full_name?: string
@@ -22,20 +18,18 @@ interface Person {
[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">
{
!hideHeader && (
<div class="toast-header">
<Avatar person={person} class="me-2" size="xs" />
<strong class="me-auto">{person.full_name}</strong>
<small>{date}</small>
<button type="button" class="ms-2 btn-close" data-bs-dismiss="toast" aria-label="Close" />
</div>
)
}
<div class="toast-header">
<Avatar person={person} class="me-2" size="xs" />
<strong class="me-auto">{person.full_name}</strong>
<small>{date}</small>
<button type="button" class="ms-2 btn-close" data-bs-dismiss="toast" aria-label="Close"></button>
</div>
<div class="toast-body">
{
cookies ? (
+7 -8
View File
@@ -4,20 +4,19 @@ import { capitalize } from '@shared/lib/string-format'
interface Props {
type?: 'checkbox' | 'radio'
checked?: boolean
disabled?: boolean
/** Switch mode (prop name isSwitch; `switch` is reserved). */
switch?: boolean
title?: string | false
name?: string
inline?: boolean
size?: string
empty?: boolean
/** core/scss/ui/forms/_form-check.scss `.form-switch-{size}` — only 'lg' exists */
size?: 'lg' | undefined
titleOn?: string
titleOff?: 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
if (!title) {
@@ -32,9 +31,9 @@ if (!title) {
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}>
@@ -46,7 +45,7 @@ const inputClasses = ['form-check-input', isEmpty && 'position-static']
<span class="form-check-label form-check-label-off">{titleOff}</span>
</Fragment>
) : (
!isEmpty && <span class="form-check-label">{title}</span>
<span class="form-check-label">{title}</span>
)
}
</label>
+3 -6
View File
@@ -3,25 +3,22 @@ import Icon from '../Icon.astro'
interface Props {
icon?: string
iconClass?: string
class?: string
prepend?: boolean
type?: string
value?: string
light?: boolean
rounded?: boolean
inputClass?: string
ariaLabel?: string
readonly?: boolean
placeholder?: string
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 addonClass = ['input-icon-addon', iconClass]
const controlClass = ['form-control', light && 'form-control-light', rounded && 'form-control-rounded', inputClass]
const addonClass = ['input-icon-addon']
const controlClass = ['form-control', rounded && 'form-control-rounded']
---
<div class:list={wrapperClass}>
+2 -2
View File
@@ -1,14 +1,14 @@
---
// IMask initializes from the data-mask attributes (tabler.js).
interface Props {
name?: string
mask?: string
visible?: boolean
placeholder?: string
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" />