@thomasfosterau/components 0.0.0 → 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 +2 -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 +25 -25
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { HTMLImgAttributes } from 'svelte/elements';
|
|
2
|
+
interface Props extends Omit<HTMLImgAttributes, 'src' | 'alt' | 'width' | 'height' | 'loading' | 'class'> {
|
|
3
|
+
/** Image URL. */
|
|
4
|
+
src: string;
|
|
5
|
+
/** Alternative text; `''` only where the image is decoration. */
|
|
6
|
+
alt: string;
|
|
7
|
+
/** Rendered as the `<figcaption>`. */
|
|
8
|
+
caption?: string | undefined;
|
|
9
|
+
/** Intrinsic width in pixels; reserves the box. */
|
|
10
|
+
width?: number | undefined;
|
|
11
|
+
/** Intrinsic height in pixels; reserves the box. */
|
|
12
|
+
height?: number | undefined;
|
|
13
|
+
/** Eager-loads at high `fetchpriority` — for the page's LCP candidate. */
|
|
14
|
+
eager?: boolean | undefined;
|
|
15
|
+
/** Extra classes, merged last. */
|
|
16
|
+
class?: string | undefined;
|
|
17
|
+
}
|
|
18
|
+
/** The picture at the top of an `Infobox`, with its caption. */
|
|
19
|
+
declare const InfoboxImage: import("svelte").Component<Props, {}, "">;
|
|
20
|
+
type InfoboxImage = ReturnType<typeof InfoboxImage>;
|
|
21
|
+
export default InfoboxImage;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
<!-- @component
|
|
2
|
+
One label/value pair inside an `InfoboxSection`.
|
|
3
|
+
-->
|
|
4
|
+
<!--
|
|
5
|
+
InfoboxRow — one label/value pair inside an `InfoboxSection`.
|
|
6
|
+
|
|
7
|
+
Absent by default: a row with neither a `value` nor `children` renders no
|
|
8
|
+
DOM at all, so a page can build a run of rows straight off sparse resource
|
|
9
|
+
attributes without guarding each one — the same "has something to show"
|
|
10
|
+
contract `infoboxRowIsPresent` gives the typed `rows` convenience on
|
|
11
|
+
`Infobox`. `showEmpty` opts back: an em
|
|
12
|
+
dash captioned by `emptyLabel`, which says "asked, and there is no answer"
|
|
13
|
+
rather than "nobody asked" — for a section that always lists the same
|
|
14
|
+
facts, where a gap is itself informative.
|
|
15
|
+
|
|
16
|
+
`href` wraps the row's content in the shared `Link`, so an in-app path
|
|
17
|
+
resolves through the components context exactly like every other link in
|
|
18
|
+
the library; `external` is `Link`'s own external-link contract, passed
|
|
19
|
+
straight through.
|
|
20
|
+
-->
|
|
21
|
+
<script lang="ts">import Link from "../ui/Link.svelte";
|
|
22
|
+
let { label, value, children, href, external, emptyLabel = "unrecorded", showEmpty = false, class: className, ...rest } = $props();
|
|
23
|
+
const hasValue = $derived(value !== undefined && value !== "");
|
|
24
|
+
const present = $derived(children !== undefined || hasValue);
|
|
25
|
+
</script>
|
|
26
|
+
|
|
27
|
+
{#if present || showEmpty}
|
|
28
|
+
<div
|
|
29
|
+
{...rest}
|
|
30
|
+
class={[
|
|
31
|
+
'hover:bg-base-100 grid grid-cols-3 gap-2 px-4 py-2 transition-colors',
|
|
32
|
+
className,
|
|
33
|
+
]}
|
|
34
|
+
>
|
|
35
|
+
<dt class="text-base-content/70 col-span-1 text-sm font-semibold select-none">{label}</dt>
|
|
36
|
+
<dd class="col-span-2 text-sm">
|
|
37
|
+
{#if present && href !== undefined}
|
|
38
|
+
<Link {href} external={external ?? false}>
|
|
39
|
+
{#if children}{@render children()}{:else}{value}{/if}
|
|
40
|
+
</Link>
|
|
41
|
+
{:else if present && children}
|
|
42
|
+
{@render children()}
|
|
43
|
+
{:else if present}
|
|
44
|
+
{value}
|
|
45
|
+
{:else}
|
|
46
|
+
<span class="text-base-content/40" aria-label={emptyLabel}>—</span>
|
|
47
|
+
{/if}
|
|
48
|
+
</dd>
|
|
49
|
+
</div>
|
|
50
|
+
{/if}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
3
|
+
interface Props extends Omit<HTMLAttributes<HTMLDivElement>, 'children'> {
|
|
4
|
+
/** The term, rendered as the `<dt>`. */
|
|
5
|
+
label: string;
|
|
6
|
+
/** Plain text or number; `children` wins where both are supplied. */
|
|
7
|
+
value?: string | number | undefined;
|
|
8
|
+
/** Richer markup — a formatted quantity, a badge, more than one link. */
|
|
9
|
+
children?: Snippet<[]> | undefined;
|
|
10
|
+
/** Wraps the row's content in a `Link` resolved through the components context. */
|
|
11
|
+
href?: string | undefined;
|
|
12
|
+
/** Passed through to `Link` — an off-site destination. */
|
|
13
|
+
external?: boolean | undefined;
|
|
14
|
+
/**
|
|
15
|
+
* Stands in for the value, captioned by `aria-label`, when `showEmpty` and the row has nothing.
|
|
16
|
+
* @defaultValue `'unrecorded'`
|
|
17
|
+
*/
|
|
18
|
+
emptyLabel?: string | undefined;
|
|
19
|
+
/** Render the row even when empty, showing `emptyLabel` instead of nothing. */
|
|
20
|
+
showEmpty?: boolean | undefined;
|
|
21
|
+
}
|
|
22
|
+
/** One label/value pair inside an `InfoboxSection`. */
|
|
23
|
+
declare const InfoboxRow: import("svelte").Component<Props, {}, "">;
|
|
24
|
+
type InfoboxRow = ReturnType<typeof InfoboxRow>;
|
|
25
|
+
export default InfoboxRow;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
<!-- @component
|
|
2
|
+
A titled group of `InfoboxRow`s, with dividing rules between them.
|
|
3
|
+
-->
|
|
4
|
+
<!--
|
|
5
|
+
InfoboxSection — a titled group of `InfoboxRow`s, with the dividing rules
|
|
6
|
+
between them.
|
|
7
|
+
|
|
8
|
+
The `<dl>` lives here rather than on each row: a definition list whose
|
|
9
|
+
terms and definitions are split across a dozen sibling lists is not one
|
|
10
|
+
list, and a screen reader reading "list of 1 item" twelve times is the
|
|
11
|
+
audible version of that mistake. Rows contribute the `<dt>`/`<dd>` pairs.
|
|
12
|
+
-->
|
|
13
|
+
<script lang="ts">let { title, children, class: className, ...rest } = $props();
|
|
14
|
+
export {};
|
|
15
|
+
</script>
|
|
16
|
+
|
|
17
|
+
{#if title !== undefined && title !== ''}
|
|
18
|
+
<div class="border-base-300 bg-base-300 border-b px-4 py-2">
|
|
19
|
+
<h4 class="text-sm font-bold select-none">{title}</h4>
|
|
20
|
+
</div>
|
|
21
|
+
{/if}
|
|
22
|
+
<dl {...rest} class={['divide-base-300 divide-y', className]}>
|
|
23
|
+
{@render children()}
|
|
24
|
+
</dl>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
3
|
+
interface Props extends HTMLAttributes<HTMLDListElement> {
|
|
4
|
+
/** Names the group; omitted for an untitled run of rows. */
|
|
5
|
+
title?: string | undefined;
|
|
6
|
+
/** The rows — `InfoboxRow`s, or anything else that belongs in a `<dl>`. */
|
|
7
|
+
children: Snippet<[]>;
|
|
8
|
+
}
|
|
9
|
+
/** A titled group of `InfoboxRow`s, with dividing rules between them. */
|
|
10
|
+
declare const InfoboxSection: import("svelte").Component<Props, {}, "">;
|
|
11
|
+
type InfoboxSection = ReturnType<typeof InfoboxSection>;
|
|
12
|
+
export default InfoboxSection;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* infobox — the family that fills an entity page's summary rail, two ways
|
|
3
|
+
* at once.
|
|
4
|
+
*
|
|
5
|
+
* Composed primitives (`InfoboxHeader`, `InfoboxImage`, `InfoboxSection`,
|
|
6
|
+
* `InfoboxRow`) let a page build bespoke markup. A
|
|
7
|
+
* typed `rows` convenience on `Infobox` itself — the `InfoboxRowSpec` union
|
|
8
|
+
* and `infoboxRowIsPresent` — lets a page instead hand over a
|
|
9
|
+
* `$derived<InfoboxRowSpec[]>` built from sparse entity attributes; `rows` renders through the very
|
|
10
|
+
* same primitives, so a fix to row layout lands in one place regardless of
|
|
11
|
+
* which API a caller used to reach it.
|
|
12
|
+
*
|
|
13
|
+
* @module
|
|
14
|
+
*/
|
|
15
|
+
export { default as Infobox } from './Infobox.svelte';
|
|
16
|
+
export { default as InfoboxHeader } from './InfoboxHeader.svelte';
|
|
17
|
+
export { default as InfoboxImage } from './InfoboxImage.svelte';
|
|
18
|
+
export { default as InfoboxRow } from './InfoboxRow.svelte';
|
|
19
|
+
export { default as InfoboxSection } from './InfoboxSection.svelte';
|
|
20
|
+
export { infoboxRowIsPresent } from './types.ts';
|
|
21
|
+
export type { InfoboxCustomRow, InfoboxFactRow, InfoboxImageRow, InfoboxImageSpec, InfoboxLinkEntry, InfoboxLinkRow, InfoboxLinksRow, InfoboxRowSpec, } from './types.ts';
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* infobox — the family that fills an entity page's summary rail, two ways
|
|
3
|
+
* at once.
|
|
4
|
+
*
|
|
5
|
+
* Composed primitives (`InfoboxHeader`, `InfoboxImage`, `InfoboxSection`,
|
|
6
|
+
* `InfoboxRow`) let a page build bespoke markup. A
|
|
7
|
+
* typed `rows` convenience on `Infobox` itself — the `InfoboxRowSpec` union
|
|
8
|
+
* and `infoboxRowIsPresent` — lets a page instead hand over a
|
|
9
|
+
* `$derived<InfoboxRowSpec[]>` built from sparse entity attributes; `rows` renders through the very
|
|
10
|
+
* same primitives, so a fix to row layout lands in one place regardless of
|
|
11
|
+
* which API a caller used to reach it.
|
|
12
|
+
*
|
|
13
|
+
* @module
|
|
14
|
+
*/
|
|
15
|
+
export { default as Infobox } from './Infobox.svelte';
|
|
16
|
+
export { default as InfoboxHeader } from './InfoboxHeader.svelte';
|
|
17
|
+
export { default as InfoboxImage } from './InfoboxImage.svelte';
|
|
18
|
+
export { default as InfoboxRow } from './InfoboxRow.svelte';
|
|
19
|
+
export { default as InfoboxSection } from './InfoboxSection.svelte';
|
|
20
|
+
export { infoboxRowIsPresent } from "./types.js";
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The shapes the `infobox` family passes around: the image spec both
|
|
3
|
+
* `InfoboxImage` and an `image`-kind row accept, the row union `Infobox`'s
|
|
4
|
+
* `rows` convenience renders, and the single "has something to show" rule
|
|
5
|
+
* the component and its tests share.
|
|
6
|
+
*
|
|
7
|
+
* @module
|
|
8
|
+
*/
|
|
9
|
+
import type { Snippet } from 'svelte';
|
|
10
|
+
/** The picture at the top of an `Infobox`. */
|
|
11
|
+
export interface InfoboxImageSpec {
|
|
12
|
+
readonly src: string;
|
|
13
|
+
/** Empty for a purely decorative image; a description otherwise. */
|
|
14
|
+
readonly alt: string;
|
|
15
|
+
/** Shown beneath the picture — a photographer, a provenance, a scale. */
|
|
16
|
+
readonly caption?: string | undefined;
|
|
17
|
+
/** Intrinsic dimensions; supplying both is what stops the layout shifting. */
|
|
18
|
+
readonly width?: number | undefined;
|
|
19
|
+
readonly height?: number | undefined;
|
|
20
|
+
/**
|
|
21
|
+
* Eager-loads at high `fetchpriority`. Set when this image is the page's
|
|
22
|
+
* LCP candidate — typically an infobox's own identifying picture.
|
|
23
|
+
*/
|
|
24
|
+
readonly eager?: boolean | undefined;
|
|
25
|
+
}
|
|
26
|
+
/** A single identifying fact (e.g. publication year, ISBN, page count). */
|
|
27
|
+
export interface InfoboxFactRow {
|
|
28
|
+
readonly kind: 'fact';
|
|
29
|
+
readonly label: string;
|
|
30
|
+
/** `undefined` or an empty string skips the row. */
|
|
31
|
+
readonly value: string | number | undefined;
|
|
32
|
+
}
|
|
33
|
+
/** A single named link, rendered as one row with its own visible text. */
|
|
34
|
+
export interface InfoboxLinkRow {
|
|
35
|
+
readonly kind: 'link';
|
|
36
|
+
readonly label: string;
|
|
37
|
+
readonly text: string;
|
|
38
|
+
/** `undefined` or an empty string skips the row. */
|
|
39
|
+
readonly href: string | undefined;
|
|
40
|
+
/** Passed through to the underlying `Link` — an off-site destination. */
|
|
41
|
+
readonly external?: boolean | undefined;
|
|
42
|
+
}
|
|
43
|
+
/** One entry of an `InfoboxLinksRow`. */
|
|
44
|
+
export interface InfoboxLinkEntry {
|
|
45
|
+
readonly label: string;
|
|
46
|
+
readonly href: string;
|
|
47
|
+
}
|
|
48
|
+
/** A named outbound relationship rendered as one or more links. */
|
|
49
|
+
export interface InfoboxLinksRow {
|
|
50
|
+
readonly kind: 'links';
|
|
51
|
+
readonly label: string;
|
|
52
|
+
/** `undefined` or an empty list skips the row. */
|
|
53
|
+
readonly links: readonly InfoboxLinkEntry[] | undefined;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* A hero/identifying image row. Renders above the `<dl>` as an
|
|
57
|
+
* `InfoboxImage`, not as a `dt`/`dd` pair — the same shape `Infobox`'s own
|
|
58
|
+
* `image` prop takes, offered as a row for the callers who build the whole
|
|
59
|
+
* infobox from one `rows` array.
|
|
60
|
+
*/
|
|
61
|
+
export interface InfoboxImageRow extends InfoboxImageSpec {
|
|
62
|
+
readonly kind: 'image';
|
|
63
|
+
}
|
|
64
|
+
/** A row whose value is arbitrary markup (a badge, a formatted quantity). */
|
|
65
|
+
export interface InfoboxCustomRow {
|
|
66
|
+
readonly kind: 'custom';
|
|
67
|
+
readonly label: string;
|
|
68
|
+
readonly content: Snippet<[]>;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Discriminated union of every row kind `Infobox`'s `rows` prop accepts.
|
|
72
|
+
*
|
|
73
|
+
* Named `InfoboxRowSpec` rather than `InfoboxRow` (design.md's own name for
|
|
74
|
+
* it) because the family already has an `InfoboxRow` COMPONENT: Svelte's
|
|
75
|
+
* compiled default export carries a type meaning as well as a value one, so
|
|
76
|
+
* a value export and a type export cannot share that identifier in the same
|
|
77
|
+
* module without a "Duplicate identifier" error. `*Spec` matches
|
|
78
|
+
* `InfoboxImageSpec`, the sibling type this union's `image` member reuses.
|
|
79
|
+
*/
|
|
80
|
+
export type InfoboxRowSpec = InfoboxFactRow | InfoboxLinkRow | InfoboxLinksRow | InfoboxImageRow | InfoboxCustomRow;
|
|
81
|
+
/**
|
|
82
|
+
* Whether a row carries renderable content — the single definition of
|
|
83
|
+
* "empty" `Infobox` and its unit tests share. A `custom` row is always
|
|
84
|
+
* present: its snippet is the caller's decision, not this function's.
|
|
85
|
+
*/
|
|
86
|
+
export declare function infoboxRowIsPresent(row: InfoboxRowSpec): boolean;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The shapes the `infobox` family passes around: the image spec both
|
|
3
|
+
* `InfoboxImage` and an `image`-kind row accept, the row union `Infobox`'s
|
|
4
|
+
* `rows` convenience renders, and the single "has something to show" rule
|
|
5
|
+
* the component and its tests share.
|
|
6
|
+
*
|
|
7
|
+
* @module
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Whether a row carries renderable content — the single definition of
|
|
11
|
+
* "empty" `Infobox` and its unit tests share. A `custom` row is always
|
|
12
|
+
* present: its snippet is the caller's decision, not this function's.
|
|
13
|
+
*/
|
|
14
|
+
export function infoboxRowIsPresent(row) {
|
|
15
|
+
switch (row.kind) {
|
|
16
|
+
case 'fact':
|
|
17
|
+
return row.value !== undefined && row.value !== '';
|
|
18
|
+
case 'link':
|
|
19
|
+
return row.href !== undefined && row.href !== '';
|
|
20
|
+
case 'links':
|
|
21
|
+
return row.links !== undefined && row.links.length > 0;
|
|
22
|
+
case 'image':
|
|
23
|
+
return row.src !== '';
|
|
24
|
+
case 'custom':
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
<!-- @component
|
|
2
|
+
The ancestor trail above a page title, as daisyUI `breadcrumbs` inside a labelled navigation landmark.
|
|
3
|
+
-->
|
|
4
|
+
<!--
|
|
5
|
+
Breadcrumbs — the ancestor trail above a page title, as daisyUI's
|
|
6
|
+
`breadcrumbs` inside a labelled navigation landmark.
|
|
7
|
+
|
|
8
|
+
ARIA contract: a `<nav aria-label>` wrapping an `<ol>`, because a trail is
|
|
9
|
+
ordered and the order is the meaning — "Home, then Elections, then this".
|
|
10
|
+
Ancestors are links; the TRAILING crumb is rendered as text and carries
|
|
11
|
+
`aria-current="page"`, even when the caller gave it an `href`. A link to the
|
|
12
|
+
page you are already on is a link that does nothing, and `aria-current` is
|
|
13
|
+
what says "this is where you are" to someone who cannot see the trail.
|
|
14
|
+
|
|
15
|
+
`item` lets a caller render a crumb's contents themselves — an icon beside
|
|
16
|
+
the label, a `<Badge>`, a truncated middle. The `<li>` and the
|
|
17
|
+
`aria-current` stay this component's either way: when `item` is supplied the
|
|
18
|
+
attribute moves to the `<li>`, so the contract holds whatever the snippet
|
|
19
|
+
renders inside it.
|
|
20
|
+
|
|
21
|
+
Hrefs resolve through the components context, so a base path or a locale
|
|
22
|
+
prefix is applied once by the app rather than at every call site.
|
|
23
|
+
-->
|
|
24
|
+
<script lang="ts">import { getComponentsContext } from "../context.svelte.js";
|
|
25
|
+
let { items, label = "Breadcrumb", item, class: className, ...rest } = $props();
|
|
26
|
+
const context = getComponentsContext();
|
|
27
|
+
</script>
|
|
28
|
+
|
|
29
|
+
<nav
|
|
30
|
+
{...rest}
|
|
31
|
+
aria-label={label}
|
|
32
|
+
class={['breadcrumbs text-base-content/70 text-sm', className]}
|
|
33
|
+
>
|
|
34
|
+
<ol>
|
|
35
|
+
{#each items as crumb, index (index)}
|
|
36
|
+
{@const current = index === items.length - 1}
|
|
37
|
+
{#if item}
|
|
38
|
+
<li aria-current={current ? 'page' : undefined}>{@render item(crumb, index)}</li>
|
|
39
|
+
{:else}
|
|
40
|
+
<li>
|
|
41
|
+
{#if crumb.href !== undefined && !current}
|
|
42
|
+
<a href={context.href(crumb.href)} class="link link-hover">{crumb.label}</a>
|
|
43
|
+
{:else}
|
|
44
|
+
<span class="text-base-content" aria-current={current ? 'page' : undefined}>
|
|
45
|
+
{crumb.label}
|
|
46
|
+
</span>
|
|
47
|
+
{/if}
|
|
48
|
+
</li>
|
|
49
|
+
{/if}
|
|
50
|
+
{/each}
|
|
51
|
+
</ol>
|
|
52
|
+
</nav>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
3
|
+
import type { Crumb } from './types.ts';
|
|
4
|
+
interface Props extends HTMLAttributes<HTMLElement> {
|
|
5
|
+
/** Ordered ancestors, ending with the current page. */
|
|
6
|
+
items: readonly Crumb[];
|
|
7
|
+
/**
|
|
8
|
+
* Accessible name of the navigation landmark.
|
|
9
|
+
* @defaultValue `'Breadcrumb'`
|
|
10
|
+
*/
|
|
11
|
+
label?: string | undefined;
|
|
12
|
+
/** Render one crumb's contents. The `<li>` wrapper is still this component's. */
|
|
13
|
+
item?: Snippet<[Crumb, number]> | undefined;
|
|
14
|
+
}
|
|
15
|
+
/** The ancestor trail above a page title, as daisyUI `breadcrumbs` inside a labelled navigation landmark. */
|
|
16
|
+
declare const Breadcrumbs: import("svelte").Component<Props, {}, "">;
|
|
17
|
+
type Breadcrumbs = ReturnType<typeof Breadcrumbs>;
|
|
18
|
+
export default Breadcrumbs;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
<!-- @component
|
|
2
|
+
The two-column detail-page shell: a header across the top, a body column and a summary aside.
|
|
3
|
+
-->
|
|
4
|
+
<!--
|
|
5
|
+
DetailLayout — the two-column detail-page shell: a header across the top, a
|
|
6
|
+
body column, and a summary aside beside it.
|
|
7
|
+
|
|
8
|
+
The reflow is the whole point. On a wide viewport the aside sits to the
|
|
9
|
+
RIGHT of the body (a third of the width, the Wikipedia infobox position); on
|
|
10
|
+
a phone there is no beside, and the useful order is title → aside → body,
|
|
11
|
+
because the aside is the facts-at-a-glance block and a reader who has to
|
|
12
|
+
scroll past the whole article to reach it will never reach it. That is done
|
|
13
|
+
by putting the aside BEFORE the body in the column flow (`order-first`) and
|
|
14
|
+
overriding to explicit grid placement from `lg` — not by rendering it twice,
|
|
15
|
+
which would duplicate every id and every heading in the accessibility tree.
|
|
16
|
+
|
|
17
|
+
ARIA contract: the aside is an `<aside aria-label>` — a `complementary`
|
|
18
|
+
landmark, which is exactly what it is: content related to the page's main
|
|
19
|
+
content and meaningful on its own. It is named because a landmark a reader
|
|
20
|
+
cannot tell apart from another landmark is a landmark they will not use.
|
|
21
|
+
`header` is a snippet rather than a set of title/subtitle props: it takes a
|
|
22
|
+
`PageHeader`, which already owns the heading contract.
|
|
23
|
+
|
|
24
|
+
This renders no `<main>`. It is placed INSIDE `Main` by the page, because a
|
|
25
|
+
page that is two columns of a wider shell must not open a second landmark.
|
|
26
|
+
`data-testid="detail-body"` / `"detail-infobox"` are carried over.
|
|
27
|
+
-->
|
|
28
|
+
<script lang="ts">let { header, body, aside, asideLabel = "Summary", class: className, ...rest } = $props();
|
|
29
|
+
export {};
|
|
30
|
+
</script>
|
|
31
|
+
|
|
32
|
+
<div {...rest} class={['space-y-8', className]}>
|
|
33
|
+
{@render header()}
|
|
34
|
+
|
|
35
|
+
<div class="flex flex-col gap-8 lg:grid lg:grid-cols-3 lg:items-start">
|
|
36
|
+
<div data-testid="detail-body" class="min-w-0 space-y-8 lg:col-span-2 lg:col-start-1 lg:row-start-1">
|
|
37
|
+
{@render body()}
|
|
38
|
+
</div>
|
|
39
|
+
|
|
40
|
+
{#if aside}
|
|
41
|
+
<aside
|
|
42
|
+
data-testid="detail-infobox"
|
|
43
|
+
aria-label={asideLabel}
|
|
44
|
+
class="order-first min-w-0 lg:order-none lg:col-start-3 lg:row-start-1"
|
|
45
|
+
>
|
|
46
|
+
{@render aside()}
|
|
47
|
+
</aside>
|
|
48
|
+
{/if}
|
|
49
|
+
</div>
|
|
50
|
+
</div>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
3
|
+
interface Props extends HTMLAttributes<HTMLDivElement> {
|
|
4
|
+
/** The page header block — normally a `PageHeader`. */
|
|
5
|
+
header: Snippet<[]>;
|
|
6
|
+
/** The main body column. */
|
|
7
|
+
body: Snippet<[]>;
|
|
8
|
+
/** The summary column — an infobox, a fact list, a map. */
|
|
9
|
+
aside?: Snippet<[]> | undefined;
|
|
10
|
+
/**
|
|
11
|
+
* Accessible name of the `<aside>` landmark.
|
|
12
|
+
* @defaultValue `'Summary'`
|
|
13
|
+
*/
|
|
14
|
+
asideLabel?: string | undefined;
|
|
15
|
+
}
|
|
16
|
+
/** The two-column detail-page shell: a header across the top, a body column and a summary aside. */
|
|
17
|
+
declare const DetailLayout: import("svelte").Component<Props, {}, "">;
|
|
18
|
+
type DetailLayout = ReturnType<typeof DetailLayout>;
|
|
19
|
+
export default DetailLayout;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
<!-- @component
|
|
2
|
+
The row of filter options above a list, as links rather than a form.
|
|
3
|
+
-->
|
|
4
|
+
<!--
|
|
5
|
+
FilterBar — the row of filter options above a list, as links rather than a
|
|
6
|
+
form.
|
|
7
|
+
|
|
8
|
+
Links, deliberately: a filtered view is then a URL an operator can bookmark,
|
|
9
|
+
paste into a message, or reload — which a `<select>` that mutates client
|
|
10
|
+
state is not. `href` is supplied per option by the page, because a cursor,
|
|
11
|
+
a sort order and a search term all have to travel with the filter and only
|
|
12
|
+
the page knows what the rest of its query string is.
|
|
13
|
+
|
|
14
|
+
`active` is the caller's to decide for the same reason: only the page knows
|
|
15
|
+
which axis this row filters on.
|
|
16
|
+
|
|
17
|
+
ARIA contract: a `<nav aria-label>` — each surface filters on a different
|
|
18
|
+
axis ("Filter by status", "Filter by licence") and an unnamed second
|
|
19
|
+
navigation landmark on a page is one a reader cannot tell from the first.
|
|
20
|
+
The active option carries `aria-current="page"` as well as daisyUI's
|
|
21
|
+
`btn-active`, because the highlight is a colour and the attribute is what
|
|
22
|
+
says so out loud. The options are anchors, not buttons, so the `btn` class
|
|
23
|
+
here is a costume, not a role.
|
|
24
|
+
-->
|
|
25
|
+
<script lang="ts">import { getComponentsContext } from "../context.svelte.js";
|
|
26
|
+
let { options, label, class: className, ...rest } = $props();
|
|
27
|
+
const context = getComponentsContext();
|
|
28
|
+
</script>
|
|
29
|
+
|
|
30
|
+
<nav {...rest} aria-label={label} class={['flex flex-wrap items-center gap-2', className]}>
|
|
31
|
+
{#each options as option (option.key)}
|
|
32
|
+
<a
|
|
33
|
+
href={context.href(option.href)}
|
|
34
|
+
class={['btn btn-sm', option.active && 'btn-active']}
|
|
35
|
+
aria-current={option.active ? 'page' : undefined}
|
|
36
|
+
>
|
|
37
|
+
{option.label}
|
|
38
|
+
</a>
|
|
39
|
+
{/each}
|
|
40
|
+
</nav>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
2
|
+
import type { FilterOption } from './types.ts';
|
|
3
|
+
interface Props extends HTMLAttributes<HTMLElement> {
|
|
4
|
+
/** The filters; the current one gets `aria-current`. */
|
|
5
|
+
options: readonly FilterOption[];
|
|
6
|
+
/** Accessible name for the row — say which axis it filters on. */
|
|
7
|
+
label: string;
|
|
8
|
+
}
|
|
9
|
+
/** The row of filter options above a list, as links rather than a form. */
|
|
10
|
+
declare const FilterBar: import("svelte").Component<Props, {}, "">;
|
|
11
|
+
type FilterBar = ReturnType<typeof FilterBar>;
|
|
12
|
+
export default FilterBar;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
<!-- @component
|
|
2
|
+
The site footer: a `contentinfo` landmark with columns of links and an optional aside for the brand and attribution notices.
|
|
3
|
+
-->
|
|
4
|
+
<!--
|
|
5
|
+
Footer — the site footer: a `contentinfo` landmark holding the columns of
|
|
6
|
+
links an app puts at the bottom of every page, and an optional `aside` for
|
|
7
|
+
the brand block and the attribution notices (a data licence, a copyright
|
|
8
|
+
line) that belong beside them rather than in them.
|
|
9
|
+
|
|
10
|
+
ARIA contract: `<footer>` at the top of the document is the `contentinfo`
|
|
11
|
+
landmark — the one a screen-reader user jumps to for "the small print".
|
|
12
|
+
There is one per page, so a footer INSIDE an `<article>` or a `<section>` is
|
|
13
|
+
not this component. `aside` renders first, which is where the attribution
|
|
14
|
+
block reads: the columns are navigation, and navigation should not be
|
|
15
|
+
preceded by nothing.
|
|
16
|
+
|
|
17
|
+
Structure only. The columns are the app's — typically `<nav>` blocks with a
|
|
18
|
+
`footer-title` heading each, which is what makes daisyUI's grid line up.
|
|
19
|
+
-->
|
|
20
|
+
<script lang="ts">let { children, aside, class: className, ...rest } = $props();
|
|
21
|
+
export {};
|
|
22
|
+
</script>
|
|
23
|
+
|
|
24
|
+
<footer
|
|
25
|
+
{...rest}
|
|
26
|
+
class={['footer sm:footer-horizontal bg-base-200 text-base-content mt-auto p-10', className]}
|
|
27
|
+
>
|
|
28
|
+
{#if aside}
|
|
29
|
+
<aside>{@render aside()}</aside>
|
|
30
|
+
{/if}
|
|
31
|
+
{@render children()}
|
|
32
|
+
</footer>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
3
|
+
interface Props extends HTMLAttributes<HTMLElement> {
|
|
4
|
+
/** The columns — `<nav>` blocks with `footer-title` headings. */
|
|
5
|
+
children: Snippet<[]>;
|
|
6
|
+
/** Brand and attribution block, rendered before the columns. */
|
|
7
|
+
aside?: Snippet<[]> | undefined;
|
|
8
|
+
}
|
|
9
|
+
/** The site footer: a `contentinfo` landmark with columns of links and an optional aside for the brand and attribution notices. */
|
|
10
|
+
declare const Footer: import("svelte").Component<Props, {}, "">;
|
|
11
|
+
type Footer = ReturnType<typeof Footer>;
|
|
12
|
+
export default Footer;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
<!-- @component
|
|
2
|
+
The page’s content column and the `<main>` landmark.
|
|
3
|
+
-->
|
|
4
|
+
<!--
|
|
5
|
+
Main — the page's content column, and the `<main>` landmark that names it.
|
|
6
|
+
|
|
7
|
+
Every app in the survey had a component called `Main` and none of them
|
|
8
|
+
rendered a `<main>`: each one was a `<section>`, because each was mounted
|
|
9
|
+
inside a shell that had already opened the real `<main>`. That is a correct
|
|
10
|
+
thing to want and a bad thing to leave implicit, so the element is a prop.
|
|
11
|
+
`as="main"` is the default because a page that needs a landmark and does not
|
|
12
|
+
get one is the failure that matters; `as="section"` is for the nested case.
|
|
13
|
+
|
|
14
|
+
ARIA contract: exactly one `<main>` per rendered page — the landmark a
|
|
15
|
+
screen-reader user jumps to with a single keystroke, and the target of a
|
|
16
|
+
skip link. There must never be two, which is what `as="section"` exists to
|
|
17
|
+
prevent. A `<section>` is a `region` landmark only when it has an accessible
|
|
18
|
+
name, so pass `aria-label`/`aria-labelledby` (spread through `rest`) when
|
|
19
|
+
the nested block is one worth jumping to, and nothing when it is not.
|
|
20
|
+
-->
|
|
21
|
+
<script lang="ts">let { as = "main", children, class: className, ...rest } = $props();
|
|
22
|
+
export {};
|
|
23
|
+
</script>
|
|
24
|
+
|
|
25
|
+
<!-- `min-w-0` is the one class worth imposing: a content column is nearly
|
|
26
|
+
always a flex or grid child, and without it a wide table or a long
|
|
27
|
+
unbroken URL inside pushes the whole layout sideways. -->
|
|
28
|
+
<svelte:element this={as} {...rest} class={['min-w-0', className]}>
|
|
29
|
+
{@render children()}
|
|
30
|
+
</svelte:element>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
3
|
+
interface Props extends HTMLAttributes<HTMLElement> {
|
|
4
|
+
/**
|
|
5
|
+
* `main`, or `section` when a `<main>` is already open.
|
|
6
|
+
* @defaultValue `'main'`
|
|
7
|
+
*/
|
|
8
|
+
as?: 'main' | 'section' | undefined;
|
|
9
|
+
/** The page content, inside the landmark and the content column. */
|
|
10
|
+
children: Snippet<[]>;
|
|
11
|
+
}
|
|
12
|
+
/** The page’s content column and the `<main>` landmark. */
|
|
13
|
+
declare const Main: import("svelte").Component<Props, {}, "">;
|
|
14
|
+
type Main = ReturnType<typeof Main>;
|
|
15
|
+
export default Main;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
<!-- @component
|
|
2
|
+
The site banner: brand, primary links and account controls, with a disclosure for what does not fit on a narrow viewport.
|
|
3
|
+
-->
|
|
4
|
+
<!--
|
|
5
|
+
Navbar — the site's banner: the brand, the primary links, the account
|
|
6
|
+
controls, and on a narrow viewport the disclosure that holds whatever did
|
|
7
|
+
not fit.
|
|
8
|
+
|
|
9
|
+
The three regions are snippets (`start` / `center` / `end`) because the
|
|
10
|
+
content is the app's — its brand mark, its search form, its `Menu
|
|
11
|
+
mode="dropdown"` account menu. What this owns is the structure: the banner
|
|
12
|
+
landmark, the named navigation inside it, the responsive bar, and the
|
|
13
|
+
toggle.
|
|
14
|
+
|
|
15
|
+
The toggle is bits-ui's `Collapsible`, not a hand-rolled button and a
|
|
16
|
+
`hidden` div (which is what three of the four apps had) and not a
|
|
17
|
+
`<details>`. `<details>` would be tempting — it is the native disclosure —
|
|
18
|
+
but its ARIA is implicit: the browser maps the open state internally and
|
|
19
|
+
exposes no `aria-controls`, so the toggle cannot say WHICH region it opens,
|
|
20
|
+
and a `<summary>` cannot be styled as a `btn` without fighting the marker.
|
|
21
|
+
`Collapsible` gives the pairing this needs and nothing else: a real
|
|
22
|
+
`<button type="button">` carrying `aria-expanded` and `aria-controls`
|
|
23
|
+
pointing at the panel's id, and the panel kept in the DOM with `hidden` when
|
|
24
|
+
closed so the reference always resolves.
|
|
25
|
+
|
|
26
|
+
ARIA contract: `<header>` at the top of the document is the `banner`
|
|
27
|
+
landmark; the `<nav aria-label>` inside it is the named navigation. The
|
|
28
|
+
toggle is an icon button with a real accessible name (`menuLabel`) — its
|
|
29
|
+
glyph is `aria-hidden`, so the name never changes as the state does; the
|
|
30
|
+
state is `aria-expanded`'s job, not the label's. `menuOpen` is bindable, so
|
|
31
|
+
an app can close the panel after a route change.
|
|
32
|
+
-->
|
|
33
|
+
<script lang="ts">import { Collapsible } from "bits-ui";
|
|
34
|
+
import { Menu as MenuIcon } from "@lucide/svelte";
|
|
35
|
+
import VisuallyHidden from "../ui/VisuallyHidden.svelte";
|
|
36
|
+
let { label = "Primary", menuLabel = "Menu", menuOpen = $bindable(false), start, center, end, menu, class: className, ...rest } = $props();
|
|
37
|
+
</script>
|
|
38
|
+
|
|
39
|
+
<Collapsible.Root bind:open={menuOpen}>
|
|
40
|
+
{#snippet child({ props })}
|
|
41
|
+
<header {...rest} {...props} class={['bg-base-200 border-base-300 border-b', className]}>
|
|
42
|
+
<nav aria-label={label} class="navbar mx-auto max-w-7xl px-4 sm:px-6">
|
|
43
|
+
<div class="navbar-start min-w-0">{@render start()}</div>
|
|
44
|
+
{#if center}
|
|
45
|
+
<div class="navbar-center hidden lg:flex">{@render center()}</div>
|
|
46
|
+
{/if}
|
|
47
|
+
<div class="navbar-end gap-2">
|
|
48
|
+
{#if end}{@render end()}{/if}
|
|
49
|
+
{#if menu}
|
|
50
|
+
<Collapsible.Trigger>
|
|
51
|
+
{#snippet child({ props: triggerProps })}
|
|
52
|
+
<button {...triggerProps} class="btn btn-ghost btn-square lg:hidden">
|
|
53
|
+
<MenuIcon class="size-5" aria-hidden="true" />
|
|
54
|
+
<VisuallyHidden>{menuLabel}</VisuallyHidden>
|
|
55
|
+
</button>
|
|
56
|
+
{/snippet}
|
|
57
|
+
</Collapsible.Trigger>
|
|
58
|
+
{/if}
|
|
59
|
+
</div>
|
|
60
|
+
</nav>
|
|
61
|
+
{#if menu}
|
|
62
|
+
<Collapsible.Content class="border-base-300 border-t px-4 py-3 lg:hidden">
|
|
63
|
+
{@render menu()}
|
|
64
|
+
</Collapsible.Content>
|
|
65
|
+
{/if}
|
|
66
|
+
</header>
|
|
67
|
+
{/snippet}
|
|
68
|
+
</Collapsible.Root>
|