@thomasfosterau/components 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +817 -2
- package/dist/canvas/VectorCanvas.svelte +239 -0
- package/dist/canvas/VectorCanvas.svelte.d.ts +87 -0
- package/dist/canvas/history.d.ts +105 -0
- package/dist/canvas/history.js +147 -0
- package/dist/canvas/index.d.ts +15 -0
- package/dist/canvas/index.js +14 -0
- package/dist/canvas/types.d.ts +91 -0
- package/dist/canvas/types.js +13 -0
- package/dist/canvas/utils.d.ts +66 -0
- package/dist/canvas/utils.js +114 -0
- package/dist/charts/Chart.svelte +300 -0
- package/dist/charts/Chart.svelte.d.ts +50 -0
- package/dist/charts/index.d.ts +11 -0
- package/dist/charts/index.js +10 -0
- package/dist/charts/types.d.ts +28 -0
- package/dist/charts/types.js +6 -0
- package/dist/citations/BookCitation.svelte +53 -0
- package/dist/citations/BookCitation.svelte.d.ts +41 -0
- package/dist/citations/ChapterCitation.svelte +38 -0
- package/dist/citations/ChapterCitation.svelte.d.ts +50 -0
- package/dist/citations/Citation.svelte +47 -0
- package/dist/citations/Citation.svelte.d.ts +24 -0
- package/dist/citations/CreditList.svelte +69 -0
- package/dist/citations/CreditList.svelte.d.ts +47 -0
- package/dist/citations/DOILink.svelte +28 -0
- package/dist/citations/DOILink.svelte.d.ts +19 -0
- package/dist/citations/Footnote.svelte +39 -0
- package/dist/citations/Footnote.svelte.d.ts +17 -0
- package/dist/citations/FootnoteList.svelte +36 -0
- package/dist/citations/FootnoteList.svelte.d.ts +21 -0
- package/dist/citations/ISBNLink.svelte +29 -0
- package/dist/citations/ISBNLink.svelte.d.ts +19 -0
- package/dist/citations/ISSNLink.svelte +29 -0
- package/dist/citations/ISSNLink.svelte.d.ts +19 -0
- package/dist/citations/Publisher.svelte +34 -0
- package/dist/citations/Publisher.svelte.d.ts +14 -0
- package/dist/citations/ReferenceList.svelte +53 -0
- package/dist/citations/ReferenceList.svelte.d.ts +21 -0
- package/dist/citations/ReportCitation.svelte +41 -0
- package/dist/citations/ReportCitation.svelte.d.ts +36 -0
- package/dist/citations/ScholarlyArticleCitation.svelte +64 -0
- package/dist/citations/ScholarlyArticleCitation.svelte.d.ts +41 -0
- package/dist/citations/ThesisCitation.svelte +35 -0
- package/dist/citations/ThesisCitation.svelte.d.ts +29 -0
- package/dist/citations/bibliography.svelte.d.ts +55 -0
- package/dist/citations/bibliography.svelte.js +78 -0
- package/dist/citations/citation.d.ts +71 -0
- package/dist/citations/citation.js +121 -0
- package/dist/citations/csl.d.ts +102 -0
- package/dist/citations/csl.js +20 -0
- package/dist/citations/index.d.ts +31 -0
- package/dist/citations/index.js +36 -0
- package/dist/citations/list-format.d.ts +34 -0
- package/dist/citations/list-format.js +38 -0
- package/dist/context.svelte.d.ts +46 -0
- package/dist/context.svelte.js +51 -0
- package/dist/data/DataGrid.svelte +150 -0
- package/dist/data/DataGrid.svelte.d.ts +118 -0
- package/dist/data/DescriptionList.svelte +47 -0
- package/dist/data/DescriptionList.svelte.d.ts +26 -0
- package/dist/data/DescriptionRow.svelte +55 -0
- package/dist/data/DescriptionRow.svelte.d.ts +21 -0
- package/dist/data/DiffText.svelte +29 -0
- package/dist/data/DiffText.svelte.d.ts +19 -0
- package/dist/data/ItemList.svelte +80 -0
- package/dist/data/ItemList.svelte.d.ts +52 -0
- package/dist/data/LinkListItem.svelte +46 -0
- package/dist/data/LinkListItem.svelte.d.ts +21 -0
- package/dist/data/Pagination.svelte +136 -0
- package/dist/data/Pagination.svelte.d.ts +71 -0
- package/dist/data/PaginationFooter.svelte +41 -0
- package/dist/data/PaginationFooter.svelte.d.ts +59 -0
- package/dist/data/ResultCount.svelte +27 -0
- package/dist/data/ResultCount.svelte.d.ts +18 -0
- package/dist/data/Table.svelte +145 -0
- package/dist/data/Table.svelte.d.ts +75 -0
- package/dist/data/TreeList.svelte +41 -0
- package/dist/data/TreeList.svelte.d.ts +17 -0
- package/dist/data/TreeListItem.svelte +29 -0
- package/dist/data/TreeListItem.svelte.d.ts +10 -0
- package/dist/data/description-context.d.ts +33 -0
- package/dist/data/description-context.js +35 -0
- package/dist/data/diff.d.ts +14 -0
- package/dist/data/diff.js +10 -0
- package/dist/data/index.d.ts +35 -0
- package/dist/data/index.js +35 -0
- package/dist/data/list-item.d.ts +30 -0
- package/dist/data/list-item.js +19 -0
- package/dist/data/pagination.d.ts +81 -0
- package/dist/data/pagination.js +79 -0
- package/dist/data/table-types.d.ts +64 -0
- package/dist/data/table-types.js +35 -0
- package/dist/feedback/ConfirmDialog.svelte +79 -0
- package/dist/feedback/ConfirmDialog.svelte.d.ts +82 -0
- package/dist/feedback/Dialog.svelte +77 -0
- package/dist/feedback/Dialog.svelte.d.ts +37 -0
- package/dist/feedback/LoadingBoundary.svelte +61 -0
- package/dist/feedback/LoadingBoundary.svelte.d.ts +34 -0
- package/dist/feedback/ProgressBar.svelte +46 -0
- package/dist/feedback/ProgressBar.svelte.d.ts +28 -0
- package/dist/feedback/SegmentBar.svelte +83 -0
- package/dist/feedback/SegmentBar.svelte.d.ts +36 -0
- package/dist/feedback/StatTile.svelte +77 -0
- package/dist/feedback/StatTile.svelte.d.ts +49 -0
- package/dist/feedback/Stats.svelte +18 -0
- package/dist/feedback/Stats.svelte.d.ts +16 -0
- package/dist/feedback/Timeline.svelte +86 -0
- package/dist/feedback/Timeline.svelte.d.ts +52 -0
- package/dist/feedback/Toaster.svelte +78 -0
- package/dist/feedback/Toaster.svelte.d.ts +32 -0
- package/dist/feedback/index.d.ts +31 -0
- package/dist/feedback/index.js +27 -0
- package/dist/feedback/sizes.d.ts +27 -0
- package/dist/feedback/sizes.js +41 -0
- package/dist/feedback/toaster.d.ts +48 -0
- package/dist/feedback/toaster.js +29 -0
- package/dist/fields/Checkbox.svelte +33 -0
- package/dist/fields/Checkbox.svelte.d.ts +24 -0
- package/dist/fields/CheckboxGroup.svelte +41 -0
- package/dist/fields/CheckboxGroup.svelte.d.ts +40 -0
- package/dist/fields/Combobox.svelte +222 -0
- package/dist/fields/Combobox.svelte.d.ts +98 -0
- package/dist/fields/DateInput.svelte +31 -0
- package/dist/fields/DateInput.svelte.d.ts +29 -0
- package/dist/fields/FieldErrors.svelte +30 -0
- package/dist/fields/FieldErrors.svelte.d.ts +11 -0
- package/dist/fields/Fieldset.svelte +48 -0
- package/dist/fields/Fieldset.svelte.d.ts +26 -0
- package/dist/fields/FileInput.svelte +67 -0
- package/dist/fields/FileInput.svelte.d.ts +33 -0
- package/dist/fields/FormField.svelte +54 -0
- package/dist/fields/FormField.svelte.d.ts +29 -0
- package/dist/fields/ImageInput.svelte +87 -0
- package/dist/fields/ImageInput.svelte.d.ts +39 -0
- package/dist/fields/PushToggle.svelte +93 -0
- package/dist/fields/PushToggle.svelte.d.ts +55 -0
- package/dist/fields/RadioGroup.svelte +85 -0
- package/dist/fields/RadioGroup.svelte.d.ts +41 -0
- package/dist/fields/SearchInput.svelte +57 -0
- package/dist/fields/SearchInput.svelte.d.ts +33 -0
- package/dist/fields/Select.svelte +36 -0
- package/dist/fields/Select.svelte.d.ts +29 -0
- package/dist/fields/TextInput.svelte +26 -0
- package/dist/fields/TextInput.svelte.d.ts +30 -0
- package/dist/fields/Textarea.svelte +22 -0
- package/dist/fields/Textarea.svelte.d.ts +25 -0
- package/dist/fields/Toggle.svelte +72 -0
- package/dist/fields/Toggle.svelte.d.ts +32 -0
- package/dist/fields/VideoInput.svelte +35 -0
- package/dist/fields/VideoInput.svelte.d.ts +40 -0
- package/dist/fields/field-types.d.ts +51 -0
- package/dist/fields/field-types.js +60 -0
- package/dist/fields/index.d.ts +34 -0
- package/dist/fields/index.js +34 -0
- package/dist/formatting/DateTime.svelte +35 -0
- package/dist/formatting/DateTime.svelte.d.ts +26 -0
- package/dist/formatting/FormatList.svelte +31 -0
- package/dist/formatting/FormatList.svelte.d.ts +46 -0
- package/dist/formatting/Highlight.svelte +47 -0
- package/dist/formatting/Highlight.svelte.d.ts +15 -0
- package/dist/formatting/Number.svelte +35 -0
- package/dist/formatting/Number.svelte.d.ts +43 -0
- package/dist/formatting/RelativeTime.svelte +69 -0
- package/dist/formatting/RelativeTime.svelte.d.ts +26 -0
- package/dist/formatting/Year.svelte +39 -0
- package/dist/formatting/Year.svelte.d.ts +21 -0
- package/dist/formatting/index.d.ts +12 -0
- package/dist/formatting/index.js +14 -0
- package/dist/forms/EntityForm.svelte +177 -0
- package/dist/forms/EntityForm.svelte.d.ts +57 -0
- package/dist/forms/FormActions.svelte +29 -0
- package/dist/forms/FormActions.svelte.d.ts +37 -0
- package/dist/forms/FormErrors.svelte +21 -0
- package/dist/forms/FormErrors.svelte.d.ts +16 -0
- package/dist/forms/HiddenField.svelte +22 -0
- package/dist/forms/HiddenField.svelte.d.ts +11 -0
- package/dist/forms/SearchForm.svelte +43 -0
- package/dist/forms/SearchForm.svelte.d.ts +36 -0
- package/dist/forms/entity-form.d.ts +12 -0
- package/dist/forms/entity-form.js +12 -0
- package/dist/forms/index.d.ts +14 -0
- package/dist/forms/index.js +14 -0
- package/dist/hotkeys/HotkeyBadge.svelte +39 -0
- package/dist/hotkeys/HotkeyBadge.svelte.d.ts +25 -0
- package/dist/hotkeys/HotkeyHelpDialog.svelte +68 -0
- package/dist/hotkeys/HotkeyHelpDialog.svelte.d.ts +35 -0
- package/dist/hotkeys/HotkeyProvider.svelte +92 -0
- package/dist/hotkeys/HotkeyProvider.svelte.d.ts +4 -0
- package/dist/hotkeys/context.svelte.d.ts +47 -0
- package/dist/hotkeys/context.svelte.js +72 -0
- package/dist/hotkeys/index.d.ts +23 -0
- package/dist/hotkeys/index.js +22 -0
- package/dist/hotkeys/match.d.ts +65 -0
- package/dist/hotkeys/match.js +133 -0
- package/dist/hotkeys/registry.svelte.d.ts +55 -0
- package/dist/hotkeys/registry.svelte.js +110 -0
- package/dist/hotkeys/types.d.ts +72 -0
- package/dist/hotkeys/types.js +18 -0
- package/dist/index.d.ts +36 -0
- package/dist/index.js +36 -0
- package/dist/infobox/Infobox.svelte +79 -0
- package/dist/infobox/Infobox.svelte.d.ts +26 -0
- package/dist/infobox/InfoboxHeader.svelte +27 -0
- package/dist/infobox/InfoboxHeader.svelte.d.ts +11 -0
- package/dist/infobox/InfoboxImage.svelte +38 -0
- package/dist/infobox/InfoboxImage.svelte.d.ts +21 -0
- package/dist/infobox/InfoboxRow.svelte +50 -0
- package/dist/infobox/InfoboxRow.svelte.d.ts +25 -0
- package/dist/infobox/InfoboxSection.svelte +24 -0
- package/dist/infobox/InfoboxSection.svelte.d.ts +12 -0
- package/dist/infobox/index.d.ts +21 -0
- package/dist/infobox/index.js +20 -0
- package/dist/infobox/types.d.ts +86 -0
- package/dist/infobox/types.js +27 -0
- package/dist/layout/Breadcrumbs.svelte +52 -0
- package/dist/layout/Breadcrumbs.svelte.d.ts +18 -0
- package/dist/layout/DetailLayout.svelte +50 -0
- package/dist/layout/DetailLayout.svelte.d.ts +19 -0
- package/dist/layout/FilterBar.svelte +40 -0
- package/dist/layout/FilterBar.svelte.d.ts +12 -0
- package/dist/layout/Footer.svelte +32 -0
- package/dist/layout/Footer.svelte.d.ts +12 -0
- package/dist/layout/Main.svelte +30 -0
- package/dist/layout/Main.svelte.d.ts +15 -0
- package/dist/layout/Navbar.svelte +68 -0
- package/dist/layout/Navbar.svelte.d.ts +31 -0
- package/dist/layout/PageHeader.svelte +77 -0
- package/dist/layout/PageHeader.svelte.d.ts +33 -0
- package/dist/layout/Section.svelte +72 -0
- package/dist/layout/Section.svelte.d.ts +40 -0
- package/dist/layout/Sidebar.svelte +54 -0
- package/dist/layout/Sidebar.svelte.d.ts +27 -0
- package/dist/layout/Tabs.svelte +85 -0
- package/dist/layout/Tabs.svelte.d.ts +42 -0
- package/dist/layout/index.d.ts +29 -0
- package/dist/layout/index.js +29 -0
- package/dist/layout/types.d.ts +52 -0
- package/dist/layout/types.js +27 -0
- package/dist/links/ContextLinks.svelte +28 -0
- package/dist/links/ContextLinks.svelte.d.ts +20 -0
- package/dist/links/EmailLink.svelte +26 -0
- package/dist/links/EmailLink.svelte.d.ts +20 -0
- package/dist/links/index.d.ts +7 -0
- package/dist/links/index.js +7 -0
- package/dist/media/Figure.svelte +26 -0
- package/dist/media/Figure.svelte.d.ts +12 -0
- package/dist/media/Image.svelte +76 -0
- package/dist/media/Image.svelte.d.ts +41 -0
- package/dist/media/Video.svelte +65 -0
- package/dist/media/Video.svelte.d.ts +29 -0
- package/dist/media/image-dimensions.d.ts +28 -0
- package/dist/media/image-dimensions.js +34 -0
- package/dist/media/index.d.ts +10 -0
- package/dist/media/index.js +10 -0
- package/dist/search/CommandPalette.svelte +257 -0
- package/dist/search/CommandPalette.svelte.d.ts +59 -0
- package/dist/search/index.d.ts +13 -0
- package/dist/search/index.js +13 -0
- package/dist/search/types.d.ts +37 -0
- package/dist/search/types.js +20 -0
- package/dist/seo/Head.svelte +190 -0
- package/dist/seo/Head.svelte.d.ts +86 -0
- package/dist/seo/JsonLd.svelte +30 -0
- package/dist/seo/JsonLd.svelte.d.ts +20 -0
- package/dist/seo/index.d.ts +18 -0
- package/dist/seo/index.js +17 -0
- package/dist/seo/json-ld.d.ts +33 -0
- package/dist/seo/json-ld.js +29 -0
- package/dist/seo/og-image.d.ts +71 -0
- package/dist/seo/og-image.js +81 -0
- package/dist/seo/types.d.ts +43 -0
- package/dist/seo/types.js +6 -0
- package/dist/theme.css +161 -0
- package/dist/tips/PopoverTip.svelte +80 -0
- package/dist/tips/PopoverTip.svelte.d.ts +33 -0
- package/dist/tips/Tip.svelte +91 -0
- package/dist/tips/Tip.svelte.d.ts +25 -0
- package/dist/tips/index.d.ts +15 -0
- package/dist/tips/index.js +14 -0
- package/dist/tips/tip-stack.d.ts +25 -0
- package/dist/tips/tip-stack.js +37 -0
- package/dist/tips/tip-store.svelte.d.ts +74 -0
- package/dist/tips/tip-store.svelte.js +158 -0
- package/dist/tips/types.d.ts +46 -0
- package/dist/tips/types.js +6 -0
- package/dist/ui/Alert.svelte +61 -0
- package/dist/ui/Alert.svelte.d.ts +37 -0
- package/dist/ui/Badge.svelte +39 -0
- package/dist/ui/Badge.svelte.d.ts +32 -0
- package/dist/ui/Button.svelte +43 -0
- package/dist/ui/Button.svelte.d.ts +29 -0
- package/dist/ui/Card.svelte +49 -0
- package/dist/ui/Card.svelte.d.ts +44 -0
- package/dist/ui/Chip.svelte +58 -0
- package/dist/ui/Chip.svelte.d.ts +21 -0
- package/dist/ui/ConfirmButton.svelte +75 -0
- package/dist/ui/ConfirmButton.svelte.d.ts +48 -0
- package/dist/ui/Disclosure.svelte +39 -0
- package/dist/ui/Disclosure.svelte.d.ts +22 -0
- package/dist/ui/EmptyState.svelte +37 -0
- package/dist/ui/EmptyState.svelte.d.ts +22 -0
- package/dist/ui/ExternalLink.svelte +38 -0
- package/dist/ui/ExternalLink.svelte.d.ts +34 -0
- package/dist/ui/Heading.svelte +58 -0
- package/dist/ui/Heading.svelte.d.ts +22 -0
- package/dist/ui/Link.svelte +34 -0
- package/dist/ui/Link.svelte.d.ts +20 -0
- package/dist/ui/LinkButton.svelte +51 -0
- package/dist/ui/LinkButton.svelte.d.ts +33 -0
- package/dist/ui/Menu.svelte +54 -0
- package/dist/ui/Menu.svelte.d.ts +34 -0
- package/dist/ui/MenuItem.svelte +75 -0
- package/dist/ui/MenuItem.svelte.d.ts +21 -0
- package/dist/ui/MenuSeparator.svelte +26 -0
- package/dist/ui/MenuSeparator.svelte.d.ts +8 -0
- package/dist/ui/Popover.svelte +63 -0
- package/dist/ui/Popover.svelte.d.ts +40 -0
- package/dist/ui/ShareButton.svelte +64 -0
- package/dist/ui/ShareButton.svelte.d.ts +39 -0
- package/dist/ui/Skeleton.svelte +43 -0
- package/dist/ui/Skeleton.svelte.d.ts +22 -0
- package/dist/ui/ThemeToggle.svelte +65 -0
- package/dist/ui/ThemeToggle.svelte.d.ts +24 -0
- package/dist/ui/Tooltip.svelte +54 -0
- package/dist/ui/Tooltip.svelte.d.ts +35 -0
- package/dist/ui/VisuallyHidden.svelte +23 -0
- package/dist/ui/VisuallyHidden.svelte.d.ts +10 -0
- package/dist/ui/colour-token.d.ts +41 -0
- package/dist/ui/colour-token.js +49 -0
- package/dist/ui/index.d.ts +44 -0
- package/dist/ui/index.js +44 -0
- package/dist/ui/menu-context.d.ts +22 -0
- package/dist/ui/menu-context.js +26 -0
- package/dist/ui/slug.d.ts +19 -0
- package/dist/ui/slug.js +25 -0
- package/dist/ui/tones.d.ts +68 -0
- package/dist/ui/tones.js +150 -0
- package/package.json +101 -3
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
<!-- @component
|
|
2
|
+
The loading and error chrome around one read.
|
|
3
|
+
-->
|
|
4
|
+
<!--
|
|
5
|
+
LoadingBoundary — the loading and error chrome around one read.
|
|
6
|
+
|
|
7
|
+
The four states exist because "not settled" is two different things, and
|
|
8
|
+
collapsing them is what made every mutation blank its list back to
|
|
9
|
+
"Loading…": `loading` is a FIRST read with nothing to show yet, while
|
|
10
|
+
`refreshing` is a re-fetch over content that is already on screen. Children
|
|
11
|
+
keep rendering through `refreshing` — only a first read or a failure shows
|
|
12
|
+
chrome instead of content.
|
|
13
|
+
|
|
14
|
+
The two `tone`s are the two treatments the app repos had, not a new pair:
|
|
15
|
+
`page` is a route's own read, where the message stands alone under the page
|
|
16
|
+
heading, and `section` is a detail page's secondary read, indented into the
|
|
17
|
+
flow and deliberately quiet so a failed sidebar section degrades on its own
|
|
18
|
+
instead of taking the page with it.
|
|
19
|
+
|
|
20
|
+
ARIA contract: the loading chrome is a polite `role="status"` named by what
|
|
21
|
+
it is loading, so a reader who arrives mid-fetch is told rather than
|
|
22
|
+
finding nothing. During `refreshing` the same announcement is made from a
|
|
23
|
+
visually hidden status, because the content is still there to read. A
|
|
24
|
+
failure is assertive: the reader is waiting for something that is not
|
|
25
|
+
coming.
|
|
26
|
+
-->
|
|
27
|
+
<script lang="ts">import Alert from "../ui/Alert.svelte";
|
|
28
|
+
import VisuallyHidden from "../ui/VisuallyHidden.svelte";
|
|
29
|
+
let { state, loadingLabel = "Loading…", errorLabel = "That didn’t load. Try again.", tone = "page", class: className, loading, failed, children } = $props();
|
|
30
|
+
</script>
|
|
31
|
+
|
|
32
|
+
{#if state === 'error'}
|
|
33
|
+
{#if failed}
|
|
34
|
+
{@render failed({ message: errorLabel })}
|
|
35
|
+
{:else if tone === 'section'}
|
|
36
|
+
<p role="alert" class={['text-base-content/60 mt-3 text-sm leading-6', className]}>
|
|
37
|
+
{errorLabel}
|
|
38
|
+
</p>
|
|
39
|
+
{:else}
|
|
40
|
+
<Alert tone="error" live="assertive" class={className}>{errorLabel}</Alert>
|
|
41
|
+
{/if}
|
|
42
|
+
{:else if state === 'loading'}
|
|
43
|
+
{#if loading}
|
|
44
|
+
{@render loading()}
|
|
45
|
+
{:else}
|
|
46
|
+
<p
|
|
47
|
+
role="status"
|
|
48
|
+
class={[
|
|
49
|
+
tone === 'section' ? 'text-base-content/50 mt-3 text-sm' : 'text-base-content/50',
|
|
50
|
+
className,
|
|
51
|
+
]}
|
|
52
|
+
>
|
|
53
|
+
{loadingLabel}
|
|
54
|
+
</p>
|
|
55
|
+
{/if}
|
|
56
|
+
{:else}
|
|
57
|
+
{#if state === 'refreshing'}
|
|
58
|
+
<VisuallyHidden role="status">{loadingLabel}</VisuallyHidden>
|
|
59
|
+
{/if}
|
|
60
|
+
{@render children()}
|
|
61
|
+
{/if}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
interface Props {
|
|
3
|
+
/** Where the read has got to. */
|
|
4
|
+
state: 'loading' | 'refreshing' | 'error' | 'ready';
|
|
5
|
+
/**
|
|
6
|
+
* Announced, and shown, while the first read is in flight.
|
|
7
|
+
* @defaultValue `'Loading…'`
|
|
8
|
+
*/
|
|
9
|
+
loadingLabel?: string | undefined;
|
|
10
|
+
/**
|
|
11
|
+
* The message for a failed read. Every call site has a specific one.
|
|
12
|
+
* @defaultValue `'That didn’t load. Try again.'`
|
|
13
|
+
*/
|
|
14
|
+
errorLabel?: string | undefined;
|
|
15
|
+
/**
|
|
16
|
+
* Which of the two established treatments to render.
|
|
17
|
+
* @defaultValue `'page'`
|
|
18
|
+
*/
|
|
19
|
+
tone?: 'page' | 'section' | undefined;
|
|
20
|
+
/** Extra classes, merged last. */
|
|
21
|
+
class?: string | undefined;
|
|
22
|
+
/** Replaces the default loading chrome. */
|
|
23
|
+
loading?: Snippet<[]> | undefined;
|
|
24
|
+
/** Replaces the default error chrome. */
|
|
25
|
+
failed?: Snippet<[{
|
|
26
|
+
message: string;
|
|
27
|
+
}]> | undefined;
|
|
28
|
+
/** The loaded content. Rendered from the first success onwards. */
|
|
29
|
+
children: Snippet<[]>;
|
|
30
|
+
}
|
|
31
|
+
/** The loading and error chrome around one read. */
|
|
32
|
+
declare const LoadingBoundary: import("svelte").Component<Props, {}, "">;
|
|
33
|
+
type LoadingBoundary = ReturnType<typeof LoadingBoundary>;
|
|
34
|
+
export default LoadingBoundary;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
<!-- @component
|
|
2
|
+
A native `<progress>` in a daisyUI tone.
|
|
3
|
+
-->
|
|
4
|
+
<!--
|
|
5
|
+
ProgressBar — a native `<progress>` in a daisyUI tone.
|
|
6
|
+
|
|
7
|
+
Native rather than a `div role="progressbar"`: the element already carries
|
|
8
|
+
the role, the value semantics and the platform's own indeterminate
|
|
9
|
+
rendering, and no hand-rolled version gets the indeterminate state right.
|
|
10
|
+
|
|
11
|
+
ARIA contract: `label` is the accessible name (a `<progress>` takes no name
|
|
12
|
+
from its contents), and `aria-valuetext` carries the percentage as words so
|
|
13
|
+
a reader hears "62 per cent" rather than "62 of 100". Omitting `value`
|
|
14
|
+
leaves the element indeterminate — no value attribute at all, which is what
|
|
15
|
+
makes the platform animate it — and drops `aria-valuetext` with it. The
|
|
16
|
+
printed percentage beside the bar is `aria-hidden`, because it restates the
|
|
17
|
+
name and value the element already exposes.
|
|
18
|
+
-->
|
|
19
|
+
<script lang="ts">import { getComponentsContext } from "../context.svelte.js";
|
|
20
|
+
import { progressTone } from "../ui/tones.js";
|
|
21
|
+
let { label, value, max = 100, tone = "primary", showValue = false, locales, class: className, ...rest } = $props();
|
|
22
|
+
const context = getComponentsContext();
|
|
23
|
+
const fraction = $derived(value === undefined || max <= 0 ? undefined : Math.min(1, Math.max(0, value / max)));
|
|
24
|
+
// The formatter is built in one `$derived` and applied in another: building
|
|
25
|
+
// an `Intl` formatter is the expensive half, and it must not be redone every
|
|
26
|
+
// time the value ticks.
|
|
27
|
+
const percent = $derived(new Intl.NumberFormat(locales ?? context.locale(), {
|
|
28
|
+
style: "percent",
|
|
29
|
+
maximumFractionDigits: 1
|
|
30
|
+
}));
|
|
31
|
+
const valueText = $derived(fraction === undefined ? undefined : percent.format(fraction));
|
|
32
|
+
</script>
|
|
33
|
+
|
|
34
|
+
<div class={['flex items-center gap-3', className]}>
|
|
35
|
+
<progress
|
|
36
|
+
{...rest}
|
|
37
|
+
class={['progress', progressTone[tone]]}
|
|
38
|
+
value={value === undefined ? undefined : Math.min(max, Math.max(0, value))}
|
|
39
|
+
{max}
|
|
40
|
+
aria-label={label}
|
|
41
|
+
aria-valuetext={valueText}
|
|
42
|
+
></progress>
|
|
43
|
+
{#if showValue && valueText !== undefined}
|
|
44
|
+
<span class="w-14 shrink-0 text-right text-sm tabular-nums" aria-hidden="true">{valueText}</span>
|
|
45
|
+
{/if}
|
|
46
|
+
</div>
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { HTMLProgressAttributes } from 'svelte/elements';
|
|
2
|
+
import { type Tone } from '../ui/tones.ts';
|
|
3
|
+
interface Props extends Omit<HTMLProgressAttributes, 'value' | 'max'> {
|
|
4
|
+
/** Accessible name, e.g. `Seeds germinated`. */
|
|
5
|
+
label: string;
|
|
6
|
+
/** Current value in `[0, max]`. Omit for an indeterminate bar. */
|
|
7
|
+
value?: number | undefined;
|
|
8
|
+
/**
|
|
9
|
+
* The value that means complete.
|
|
10
|
+
* @defaultValue `100`
|
|
11
|
+
*/
|
|
12
|
+
max?: number | undefined;
|
|
13
|
+
/**
|
|
14
|
+
* Semantic colour.
|
|
15
|
+
* @defaultValue `'primary'`
|
|
16
|
+
*/
|
|
17
|
+
tone?: Tone | undefined;
|
|
18
|
+
/** Print the percentage beside the bar. */
|
|
19
|
+
showValue?: boolean | undefined;
|
|
20
|
+
/** Locale(s) for the percentage. Defaults to the components context's. */
|
|
21
|
+
locales?: string | readonly string[] | undefined;
|
|
22
|
+
/** Extra classes on the wrapper. */
|
|
23
|
+
class?: string | undefined;
|
|
24
|
+
}
|
|
25
|
+
/** A native `<progress>` in a daisyUI tone. */
|
|
26
|
+
declare const ProgressBar: import("svelte").Component<Props, {}, "">;
|
|
27
|
+
type ProgressBar = ReturnType<typeof ProgressBar>;
|
|
28
|
+
export default ProgressBar;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
<!-- @component
|
|
2
|
+
One horizontal bar split into proportional, coloured segments: a vote share, a storage breakdown, a pass/fail split.
|
|
3
|
+
-->
|
|
4
|
+
<!--
|
|
5
|
+
SegmentBar — one horizontal bar split into proportional, coloured segments:
|
|
6
|
+
a vote share, a storage breakdown, a pass/fail split.
|
|
7
|
+
|
|
8
|
+
ARIA contract, and the whole reason this is a component: the bar is a
|
|
9
|
+
SINGLE `role="img"` whose `aria-label` spells out every segment ("Storage:
|
|
10
|
+
photos 62%, video 31%, other 7%"), so assistive technology gets the picture
|
|
11
|
+
in one read. The alternative — a run of coloured boxes, each with its own
|
|
12
|
+
label — is a dozen unlabelled or half-labelled announcements that never add
|
|
13
|
+
up to the comparison the bar is drawn to make. Every segment is therefore
|
|
14
|
+
`aria-hidden`, and the in-segment percentages are decorative.
|
|
15
|
+
|
|
16
|
+
Fractions are given, not derived from values: the caller already knows its
|
|
17
|
+
denominator (and whether the remainder should stay unfilled), and passing
|
|
18
|
+
shares keeps this component out of the business of summing anything. They
|
|
19
|
+
are clamped, and a total under 1 leaves the track showing.
|
|
20
|
+
|
|
21
|
+
A segment's colour is either a semantic `tone` or a `colourToken` — a CSS
|
|
22
|
+
custom-property NAME the app's theme declares in both its light and dark
|
|
23
|
+
themes. The value never appears here; see `ui/colour-token.ts`.
|
|
24
|
+
-->
|
|
25
|
+
<script lang="ts">import { getComponentsContext } from "../context.svelte.js";
|
|
26
|
+
import { backgroundTone } from "../ui/tones.js";
|
|
27
|
+
import { colourTokenStyle } from "../ui/colour-token.js";
|
|
28
|
+
import { barSize } from "./sizes.js";
|
|
29
|
+
/** The custom property a segment's `colourToken` is bound to. */
|
|
30
|
+
const SEGMENT_COLOUR_PROPERTY = "--segment-colour";
|
|
31
|
+
let { label, segments, showValues = false, marker, size = "md", locales, class: className, ...rest } = $props();
|
|
32
|
+
const context = getComponentsContext();
|
|
33
|
+
const clamp = (fraction) => Math.min(1, Math.max(0, fraction));
|
|
34
|
+
const percent = $derived(new Intl.NumberFormat(locales ?? context.locale(), {
|
|
35
|
+
style: "percent",
|
|
36
|
+
maximumFractionDigits: 1
|
|
37
|
+
}));
|
|
38
|
+
const shares = $derived(segments.map((segment) => ({
|
|
39
|
+
segment,
|
|
40
|
+
fraction: clamp(segment.fraction)
|
|
41
|
+
})));
|
|
42
|
+
const description = $derived(`${label}: ${shares.map(({ segment, fraction }) => `${segment.label} ${percent.format(fraction)}`).join(", ")}`);
|
|
43
|
+
</script>
|
|
44
|
+
|
|
45
|
+
<div
|
|
46
|
+
{...rest}
|
|
47
|
+
role="img"
|
|
48
|
+
aria-label={description}
|
|
49
|
+
class={[
|
|
50
|
+
'bg-base-300 relative flex w-full overflow-hidden rounded-(--radius-selector)',
|
|
51
|
+
barSize[size],
|
|
52
|
+
className,
|
|
53
|
+
]}
|
|
54
|
+
>
|
|
55
|
+
{#each shares as { segment, fraction }, index (index)}
|
|
56
|
+
<div
|
|
57
|
+
aria-hidden="true"
|
|
58
|
+
class={[
|
|
59
|
+
'flex h-full items-center justify-center overflow-hidden text-xs font-medium whitespace-nowrap',
|
|
60
|
+
segment.colourToken === undefined
|
|
61
|
+
? [backgroundTone[segment.tone ?? 'neutral'], 'text-neutral-content']
|
|
62
|
+
: 'text-base-100 bg-(--segment-colour)',
|
|
63
|
+
]}
|
|
64
|
+
style={[
|
|
65
|
+
`width: ${(fraction * 100).toFixed(2)}%`,
|
|
66
|
+
colourTokenStyle(SEGMENT_COLOUR_PROPERTY, segment.colourToken),
|
|
67
|
+
]
|
|
68
|
+
.filter((part) => part !== undefined)
|
|
69
|
+
.join('; ')}
|
|
70
|
+
>
|
|
71
|
+
{#if showValues && size !== 'sm' && fraction >= 0.08}
|
|
72
|
+
{percent.format(fraction)}
|
|
73
|
+
{/if}
|
|
74
|
+
</div>
|
|
75
|
+
{/each}
|
|
76
|
+
{#if marker !== undefined}
|
|
77
|
+
<div
|
|
78
|
+
aria-hidden="true"
|
|
79
|
+
class="bg-base-content absolute inset-y-0 w-0.5"
|
|
80
|
+
style={`left: ${(clamp(marker) * 100).toFixed(2)}%`}
|
|
81
|
+
></div>
|
|
82
|
+
{/if}
|
|
83
|
+
</div>
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
2
|
+
import { type Tone } from '../ui/tones.ts';
|
|
3
|
+
import { type BarSize } from './sizes.ts';
|
|
4
|
+
export interface BarSegment {
|
|
5
|
+
/** Read out for this segment in the bar's accessible label. */
|
|
6
|
+
readonly label: string;
|
|
7
|
+
/** Share of the whole bar, in `[0, 1]`. */
|
|
8
|
+
readonly fraction: number;
|
|
9
|
+
/** A CSS custom-property name the app's theme declares (`--party-alp`). */
|
|
10
|
+
readonly colourToken?: string | undefined;
|
|
11
|
+
/** Semantic colour, used when there is no `colourToken`. */
|
|
12
|
+
readonly tone?: Tone | undefined;
|
|
13
|
+
}
|
|
14
|
+
interface Props extends Omit<HTMLAttributes<HTMLDivElement>, 'role'> {
|
|
15
|
+
/** What the bar shows. Leads the accessible label. */
|
|
16
|
+
label: string;
|
|
17
|
+
/** The parts, in order; widths are proportional to their values. */
|
|
18
|
+
segments: readonly BarSegment[];
|
|
19
|
+
/** Print each segment's percentage inside it when it fits. */
|
|
20
|
+
showValues?: boolean | undefined;
|
|
21
|
+
/** Draw a marker at this fraction — a 50% majority line, a quota. */
|
|
22
|
+
marker?: number | undefined;
|
|
23
|
+
/**
|
|
24
|
+
* Bar height step.
|
|
25
|
+
* @defaultValue `'md'`
|
|
26
|
+
*/
|
|
27
|
+
size?: BarSize | undefined;
|
|
28
|
+
/** Locale(s) for the percentages. Defaults to the components context's. */
|
|
29
|
+
locales?: string | readonly string[] | undefined;
|
|
30
|
+
/** Extra classes, merged last. */
|
|
31
|
+
class?: string | undefined;
|
|
32
|
+
}
|
|
33
|
+
/** One horizontal bar split into proportional, coloured segments: a vote share, a storage breakdown, a pass/fail split. */
|
|
34
|
+
declare const SegmentBar: import("svelte").Component<Props, {}, "">;
|
|
35
|
+
type SegmentBar = ReturnType<typeof SegmentBar>;
|
|
36
|
+
export default SegmentBar;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
<!-- @component
|
|
2
|
+
One daisyUI `stat`: a headline figure with its label and optional change line and description; wrap a row in `Stats`.
|
|
3
|
+
-->
|
|
4
|
+
<!--
|
|
5
|
+
StatTile — one daisyUI `stat`: a headline figure with its label, an optional
|
|
6
|
+
change line and an optional description. Wrap a row of them in `Stats`.
|
|
7
|
+
|
|
8
|
+
ARIA contract: no roles, on purpose. A stat is text, and daisyUI's
|
|
9
|
+
`stat-title` / `stat-value` / `stat-desc` order puts it in the reading order
|
|
10
|
+
a reader expects — label, then figure, then the change. What the markup
|
|
11
|
+
cannot convey is the DIRECTION of the change: the arrow that tells a sighted
|
|
12
|
+
reader "up" is `aria-hidden`, so the direction is also spelled out in
|
|
13
|
+
visually hidden text beside it, and the tone colour is never the only
|
|
14
|
+
carrier.
|
|
15
|
+
|
|
16
|
+
Numbers are formatted through `Intl` for the context's locale; pass a
|
|
17
|
+
pre-formatted string for anything that is not a plain count.
|
|
18
|
+
-->
|
|
19
|
+
<script lang="ts">import { Minus, TrendingDown, TrendingUp } from "@lucide/svelte";
|
|
20
|
+
import { getComponentsContext } from "../context.svelte.js";
|
|
21
|
+
let { label, value, delta, description, upLabel = "Up", downLabel = "Down", flatLabel = "No change", locales, figure, class: className, ...rest } = $props();
|
|
22
|
+
const context = getComponentsContext();
|
|
23
|
+
const chosenLocales = $derived(locales ?? context.locale());
|
|
24
|
+
const countFormat = $derived(new Intl.NumberFormat(chosenLocales));
|
|
25
|
+
const signedCount = $derived(new Intl.NumberFormat(chosenLocales, {
|
|
26
|
+
signDisplay: "exceptZero",
|
|
27
|
+
maximumFractionDigits: 0
|
|
28
|
+
}));
|
|
29
|
+
const signedDecimal = $derived(new Intl.NumberFormat(chosenLocales, {
|
|
30
|
+
signDisplay: "exceptZero",
|
|
31
|
+
maximumFractionDigits: 1
|
|
32
|
+
}));
|
|
33
|
+
const signedPercent = $derived(new Intl.NumberFormat(chosenLocales, {
|
|
34
|
+
style: "percent",
|
|
35
|
+
signDisplay: "exceptZero",
|
|
36
|
+
maximumFractionDigits: 1
|
|
37
|
+
}));
|
|
38
|
+
const formattedValue = $derived(typeof value === "number" ? countFormat.format(value) : value);
|
|
39
|
+
const deltaText = $derived.by(() => {
|
|
40
|
+
if (delta === undefined) return "";
|
|
41
|
+
const unit = delta.unit ?? "count";
|
|
42
|
+
const number = unit === "count" ? signedCount.format(delta.value) : unit === "percent" ? signedPercent.format(delta.value / 100) : signedDecimal.format(delta.value);
|
|
43
|
+
return delta.label === undefined ? number : `${number} ${delta.label}`;
|
|
44
|
+
});
|
|
45
|
+
const direction = $derived(delta === undefined || delta.value === 0 ? "flat" : delta.value > 0 ? "up" : "down");
|
|
46
|
+
const deltaClass = {
|
|
47
|
+
up: "text-success",
|
|
48
|
+
down: "text-error",
|
|
49
|
+
flat: "text-base-content/70"
|
|
50
|
+
};
|
|
51
|
+
</script>
|
|
52
|
+
|
|
53
|
+
<div {...rest} class={['stat', className]}>
|
|
54
|
+
{#if figure}
|
|
55
|
+
<div class="stat-figure">{@render figure()}</div>
|
|
56
|
+
{/if}
|
|
57
|
+
<div class="stat-title">{label}</div>
|
|
58
|
+
<div class="stat-value tabular-nums">{formattedValue}</div>
|
|
59
|
+
{#if delta !== undefined}
|
|
60
|
+
<div class={['stat-desc flex items-center gap-1 tabular-nums', deltaClass[direction]]}>
|
|
61
|
+
{#if direction === 'up'}
|
|
62
|
+
<TrendingUp class="size-4" aria-hidden="true" />
|
|
63
|
+
<span class="sr-only">{upLabel}</span>
|
|
64
|
+
{:else if direction === 'down'}
|
|
65
|
+
<TrendingDown class="size-4" aria-hidden="true" />
|
|
66
|
+
<span class="sr-only">{downLabel}</span>
|
|
67
|
+
{:else}
|
|
68
|
+
<Minus class="size-4" aria-hidden="true" />
|
|
69
|
+
<span class="sr-only">{flatLabel}</span>
|
|
70
|
+
{/if}
|
|
71
|
+
<span>{deltaText}</span>
|
|
72
|
+
</div>
|
|
73
|
+
{/if}
|
|
74
|
+
{#if description !== undefined}
|
|
75
|
+
<div class="stat-desc">{description}</div>
|
|
76
|
+
{/if}
|
|
77
|
+
</div>
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
3
|
+
export interface StatDelta {
|
|
4
|
+
/**
|
|
5
|
+
* The change. Positive is up. With `unit: 'percent'` it is read as a
|
|
6
|
+
* percentage — `3.2` renders as `+3.2%`.
|
|
7
|
+
*/
|
|
8
|
+
readonly value: number;
|
|
9
|
+
/**
|
|
10
|
+
* How to render it: a whole count, a percentage, or a decimal whose unit
|
|
11
|
+
* the caller names in `label` (`'pts swing'`).
|
|
12
|
+
*/
|
|
13
|
+
readonly unit?: 'count' | 'percent' | 'points' | undefined;
|
|
14
|
+
/** Trailing words, e.g. `swing` or `since April`. */
|
|
15
|
+
readonly label?: string | undefined;
|
|
16
|
+
}
|
|
17
|
+
interface Props extends HTMLAttributes<HTMLDivElement> {
|
|
18
|
+
/** The `stat-title`. */
|
|
19
|
+
label: string;
|
|
20
|
+
/** The headline figure; a number is formatted as a count. */
|
|
21
|
+
value: string | number;
|
|
22
|
+
/** Change line under the value. */
|
|
23
|
+
delta?: StatDelta | undefined;
|
|
24
|
+
/** Plain description, rendered after the delta when both are present. */
|
|
25
|
+
description?: string | undefined;
|
|
26
|
+
/**
|
|
27
|
+
* Announced in place of the rising arrow.
|
|
28
|
+
* @defaultValue `'Up'`
|
|
29
|
+
*/
|
|
30
|
+
upLabel?: string | undefined;
|
|
31
|
+
/**
|
|
32
|
+
* Announced in place of the falling arrow.
|
|
33
|
+
* @defaultValue `'Down'`
|
|
34
|
+
*/
|
|
35
|
+
downLabel?: string | undefined;
|
|
36
|
+
/**
|
|
37
|
+
* Announced in place of the flat marker.
|
|
38
|
+
* @defaultValue `'No change'`
|
|
39
|
+
*/
|
|
40
|
+
flatLabel?: string | undefined;
|
|
41
|
+
/** Locale(s) for the figures. Defaults to the components context's. */
|
|
42
|
+
locales?: string | readonly string[] | undefined;
|
|
43
|
+
/** Icon or sparkline in the `stat-figure` slot. */
|
|
44
|
+
figure?: Snippet<[]> | undefined;
|
|
45
|
+
}
|
|
46
|
+
/** One daisyUI `stat`: a headline figure with its label and optional change line and description; wrap a row in `Stats`. */
|
|
47
|
+
declare const StatTile: import("svelte").Component<Props, {}, "">;
|
|
48
|
+
type StatTile = ReturnType<typeof StatTile>;
|
|
49
|
+
export default StatTile;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
<!-- @component
|
|
2
|
+
The daisyUI `stats` wrapper a row of `StatTile`s sits in, drawing the dividers between them.
|
|
3
|
+
-->
|
|
4
|
+
<!--
|
|
5
|
+
Stats — the daisyUI `stats` wrapper a row of `StatTile`s sits in. It is what
|
|
6
|
+
draws the dividers between tiles, so a lone tile still wants one.
|
|
7
|
+
|
|
8
|
+
No ARIA of its own: it is a grouping box around text. When a row of stats
|
|
9
|
+
needs a name (a dashboard with several), give it `aria-label` — the native
|
|
10
|
+
attributes spread — or put it inside a `Section` that already has a heading.
|
|
11
|
+
-->
|
|
12
|
+
<script lang="ts">import { statsOrientation } from "./sizes.js";
|
|
13
|
+
let { orientation = "responsive", children, class: className, ...rest } = $props();
|
|
14
|
+
</script>
|
|
15
|
+
|
|
16
|
+
<div {...rest} class={['stats', statsOrientation[orientation], className]}>
|
|
17
|
+
{@render children()}
|
|
18
|
+
</div>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
3
|
+
import { type StatsOrientation } from './sizes.ts';
|
|
4
|
+
interface Props extends HTMLAttributes<HTMLDivElement> {
|
|
5
|
+
/**
|
|
6
|
+
* Row, column, or stacked-then-row from the `sm` breakpoint up.
|
|
7
|
+
* @defaultValue `'responsive'`
|
|
8
|
+
*/
|
|
9
|
+
orientation?: StatsOrientation | undefined;
|
|
10
|
+
/** The `StatTile`s. */
|
|
11
|
+
children: Snippet<[]>;
|
|
12
|
+
}
|
|
13
|
+
/** The daisyUI `stats` wrapper a row of `StatTile`s sits in, drawing the dividers between them. */
|
|
14
|
+
declare const Stats: import("svelte").Component<Props, {}, "">;
|
|
15
|
+
type Stats = ReturnType<typeof Stats>;
|
|
16
|
+
export default Stats;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
<!-- @component
|
|
2
|
+
A vertical daisyUI `timeline` of dated entries.
|
|
3
|
+
-->
|
|
4
|
+
<!--
|
|
5
|
+
Timeline — a vertical daisyUI `timeline` of dated entries: offices held,
|
|
6
|
+
releases shipped, a plant's life in a bed.
|
|
7
|
+
|
|
8
|
+
An `<ol>` because the order is the content: read out of sequence the list
|
|
9
|
+
means something else. Each entry shows its span on the start side, a marker
|
|
10
|
+
in the middle (filled for an open-ended, still-current entry, tinted with
|
|
11
|
+
the caller's colour token when given) and a `timeline-box` with the entry
|
|
12
|
+
itself.
|
|
13
|
+
|
|
14
|
+
ARIA contract: native list semantics plus an `aria-label` on the list, so a
|
|
15
|
+
reader hears what the sequence is before its length. The dash between the
|
|
16
|
+
two dates is `aria-hidden` and `toLabel` is read in its place — "May 2019 –
|
|
17
|
+
May 2022" is announced as a range, not a subtraction — and an open-ended
|
|
18
|
+
entry is introduced by `currentLabel` rather than by the filled marker
|
|
19
|
+
alone. Both markers are `aria-hidden`.
|
|
20
|
+
|
|
21
|
+
`colourToken` is a CSS custom-property NAME the app's theme declares; the
|
|
22
|
+
colour value itself never appears here. See `ui/colour-token.ts`.
|
|
23
|
+
-->
|
|
24
|
+
<script lang="ts">import { Circle, CircleDot } from "@lucide/svelte";
|
|
25
|
+
import { getComponentsContext } from "../context.svelte.js";
|
|
26
|
+
import { colourTokenStyle } from "../ui/colour-token.js";
|
|
27
|
+
/** The custom property an entry's `colourToken` is bound to. */
|
|
28
|
+
const TIMELINE_COLOUR_PROPERTY = "--timeline-colour";
|
|
29
|
+
let { items, presentLabel = "present", toLabel = "to", currentLabel = "Current", label = "Timeline", item: itemBody, class: className, ...rest } = $props();
|
|
30
|
+
const context = getComponentsContext();
|
|
31
|
+
</script>
|
|
32
|
+
|
|
33
|
+
<ol
|
|
34
|
+
{...rest}
|
|
35
|
+
aria-label={label}
|
|
36
|
+
class={['timeline timeline-vertical timeline-compact timeline-snap-icon', className]}
|
|
37
|
+
>
|
|
38
|
+
{#each items as entry, index (entry.id)}
|
|
39
|
+
{@const current = entry.end === undefined}
|
|
40
|
+
<li style={colourTokenStyle(TIMELINE_COLOUR_PROPERTY, entry.colourToken)}>
|
|
41
|
+
{#if index > 0}<hr />{/if}
|
|
42
|
+
<div class="timeline-start text-base-content/70 mb-2 text-sm tabular-nums md:mb-0">
|
|
43
|
+
<time datetime={entry.start.dateTime}>{entry.start.label}</time>
|
|
44
|
+
<span aria-hidden="true">–</span>
|
|
45
|
+
<span class="sr-only">{toLabel}</span>
|
|
46
|
+
{#if entry.end === undefined}
|
|
47
|
+
<span>{presentLabel}</span>
|
|
48
|
+
{:else}
|
|
49
|
+
<time datetime={entry.end.dateTime}>{entry.end.label}</time>
|
|
50
|
+
{/if}
|
|
51
|
+
</div>
|
|
52
|
+
<div
|
|
53
|
+
class={[
|
|
54
|
+
'timeline-middle',
|
|
55
|
+
entry.colourToken === undefined
|
|
56
|
+
? 'text-base-content/60'
|
|
57
|
+
: 'text-(--timeline-colour)',
|
|
58
|
+
]}
|
|
59
|
+
>
|
|
60
|
+
{#if current}
|
|
61
|
+
<CircleDot class="size-5" aria-hidden="true" />
|
|
62
|
+
<span class="sr-only">{currentLabel}</span>
|
|
63
|
+
{:else}
|
|
64
|
+
<Circle class="size-5" aria-hidden="true" />
|
|
65
|
+
{/if}
|
|
66
|
+
</div>
|
|
67
|
+
<div class="timeline-end timeline-box">
|
|
68
|
+
{#if itemBody}
|
|
69
|
+
{@render itemBody(entry)}
|
|
70
|
+
{:else}
|
|
71
|
+
<p class="font-medium">
|
|
72
|
+
{#if entry.href !== undefined}
|
|
73
|
+
<a href={context.href(entry.href)} class="link link-hover">{entry.label}</a>
|
|
74
|
+
{:else}
|
|
75
|
+
{entry.label}
|
|
76
|
+
{/if}
|
|
77
|
+
</p>
|
|
78
|
+
{#if entry.subtitle !== undefined}
|
|
79
|
+
<p class="text-base-content/70 text-sm">{entry.subtitle}</p>
|
|
80
|
+
{/if}
|
|
81
|
+
{/if}
|
|
82
|
+
</div>
|
|
83
|
+
{#if index < items.length - 1}<hr />{/if}
|
|
84
|
+
</li>
|
|
85
|
+
{/each}
|
|
86
|
+
</ol>
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { HTMLOlAttributes } from 'svelte/elements';
|
|
3
|
+
export interface TimelineDate {
|
|
4
|
+
/** Display text, e.g. `March 1996`. */
|
|
5
|
+
readonly label: string;
|
|
6
|
+
/** Machine-readable `<time datetime>` value, e.g. `1996-03-02`. */
|
|
7
|
+
readonly dateTime?: string | undefined;
|
|
8
|
+
}
|
|
9
|
+
export interface TimelineEntry {
|
|
10
|
+
readonly id: string;
|
|
11
|
+
/** The entry itself — the line in the box. */
|
|
12
|
+
readonly label: string;
|
|
13
|
+
/** Second line under it. */
|
|
14
|
+
readonly subtitle?: string | undefined;
|
|
15
|
+
readonly start: TimelineDate;
|
|
16
|
+
/** Omit for an open-ended, still-current entry. */
|
|
17
|
+
readonly end?: TimelineDate | undefined;
|
|
18
|
+
/** Links the entry's label, through the components context. */
|
|
19
|
+
readonly href?: string | undefined;
|
|
20
|
+
/** A CSS custom-property name the app's theme declares (`--party-alp`). */
|
|
21
|
+
readonly colourToken?: string | undefined;
|
|
22
|
+
}
|
|
23
|
+
interface Props extends Omit<HTMLOlAttributes, 'children'> {
|
|
24
|
+
/** The entries, in display order. */
|
|
25
|
+
items: readonly TimelineEntry[];
|
|
26
|
+
/**
|
|
27
|
+
* Shown in place of a missing `end`.
|
|
28
|
+
* @defaultValue `'present'`
|
|
29
|
+
*/
|
|
30
|
+
presentLabel?: string | undefined;
|
|
31
|
+
/**
|
|
32
|
+
* Read between the two dates of a span.
|
|
33
|
+
* @defaultValue `'to'`
|
|
34
|
+
*/
|
|
35
|
+
toLabel?: string | undefined;
|
|
36
|
+
/**
|
|
37
|
+
* Introduces an open-ended entry.
|
|
38
|
+
* @defaultValue `'Current'`
|
|
39
|
+
*/
|
|
40
|
+
currentLabel?: string | undefined;
|
|
41
|
+
/**
|
|
42
|
+
* Accessible name of the list.
|
|
43
|
+
* @defaultValue `'Timeline'`
|
|
44
|
+
*/
|
|
45
|
+
label?: string | undefined;
|
|
46
|
+
/** Replaces the default box body. */
|
|
47
|
+
item?: Snippet<[TimelineEntry]> | undefined;
|
|
48
|
+
}
|
|
49
|
+
/** A vertical daisyUI `timeline` of dated entries. */
|
|
50
|
+
declare const Timeline: import("svelte").Component<Props, {}, "">;
|
|
51
|
+
type Timeline = ReturnType<typeof Timeline>;
|
|
52
|
+
export default Timeline;
|