@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,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The tip store: which tips exist, which have been seen, and whether one may
|
|
3
|
+
* show right now.
|
|
4
|
+
*
|
|
5
|
+
* This is a plain object with `$state` inside it, so a tip re-renders when
|
|
6
|
+
* its state changes rather than when a promise resolves. Rules are
|
|
7
|
+
* synchronous for the same reason a network round-trip would show a tip a
|
|
8
|
+
* beat after the page did, while the reader is already doing something else:
|
|
9
|
+
* await first, then declare the tip.
|
|
10
|
+
*
|
|
11
|
+
* Storage is optional and every access is guarded: a private window can have
|
|
12
|
+
* `localStorage` present and throwing, and a tip system that breaks the page
|
|
13
|
+
* it is meant to explain is worse than one that forgets.
|
|
14
|
+
*
|
|
15
|
+
* @module
|
|
16
|
+
*/
|
|
17
|
+
import type { TipConfig, TipState } from './types.ts';
|
|
18
|
+
/**
|
|
19
|
+
* The subset of `Storage` this needs, so a test can pass a plain object.
|
|
20
|
+
*
|
|
21
|
+
* The signatures are the DOM `Storage` interface's own, `null` included: this
|
|
22
|
+
* type exists so `globalThis.localStorage` satisfies it structurally.
|
|
23
|
+
*/
|
|
24
|
+
export interface TipStorage {
|
|
25
|
+
getItem: (key: string) => string | null;
|
|
26
|
+
setItem: (key: string, value: string) => void;
|
|
27
|
+
removeItem: (key: string) => void;
|
|
28
|
+
}
|
|
29
|
+
/** What `createTipStore` returns: registers tips, decides whether each may show, and records what the reader did with it. */
|
|
30
|
+
export interface TipStore {
|
|
31
|
+
/** Declare a tip. Re-registering the same id replaces its configuration. */
|
|
32
|
+
register: (config: TipConfig) => void;
|
|
33
|
+
/** Whether this tip may show right now. */
|
|
34
|
+
shouldDisplay: (id: string) => boolean;
|
|
35
|
+
/** What is remembered about this tip. */
|
|
36
|
+
getState: (id: string) => TipState;
|
|
37
|
+
/** Record that it has just been shown. */
|
|
38
|
+
markShown: (id: string) => void;
|
|
39
|
+
/** The reader closed it: never show it again. */
|
|
40
|
+
dismiss: (id: string) => void;
|
|
41
|
+
/** The reader took its action: never show it again. */
|
|
42
|
+
complete: (id: string) => void;
|
|
43
|
+
/** Forget every tip's state — the "show me the tour again" button. */
|
|
44
|
+
resetAll: () => void;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Storage that forgets when the page does.
|
|
48
|
+
*
|
|
49
|
+
* The fallback for a browser that refuses persistence, and the right storage
|
|
50
|
+
* for a test or a Storybook story — a tip dismissed while reviewing one story
|
|
51
|
+
* must not be invisible in the next reviewer's session.
|
|
52
|
+
*/
|
|
53
|
+
export declare const memoryTipStorage: () => TipStorage;
|
|
54
|
+
/**
|
|
55
|
+
* `globalThis.localStorage` where it exists and is usable, and
|
|
56
|
+
* {@link memoryTipStorage} where it does not — the server, and a browser that
|
|
57
|
+
* refuses storage.
|
|
58
|
+
*/
|
|
59
|
+
export declare const browserTipStorage: () => TipStorage;
|
|
60
|
+
/** Options for {@link createTipStore}. */
|
|
61
|
+
export interface TipStoreOptions {
|
|
62
|
+
/** Defaults to {@link browserTipStorage}. */
|
|
63
|
+
readonly storage?: TipStorage | undefined;
|
|
64
|
+
/** The storage key prefix each tip's entry is written under. */
|
|
65
|
+
readonly prefix?: string | undefined;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Build a tip store over the given storage.
|
|
69
|
+
*
|
|
70
|
+
* Call once, high in the tree, and hand it to the tips (or put it in a context
|
|
71
|
+
* of the app's own). The in-memory state is what drives re-rendering; storage
|
|
72
|
+
* is where it survives a reload.
|
|
73
|
+
*/
|
|
74
|
+
export declare const createTipStore: (options?: TipStoreOptions) => TipStore;
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The tip store: which tips exist, which have been seen, and whether one may
|
|
3
|
+
* show right now.
|
|
4
|
+
*
|
|
5
|
+
* This is a plain object with `$state` inside it, so a tip re-renders when
|
|
6
|
+
* its state changes rather than when a promise resolves. Rules are
|
|
7
|
+
* synchronous for the same reason a network round-trip would show a tip a
|
|
8
|
+
* beat after the page did, while the reader is already doing something else:
|
|
9
|
+
* await first, then declare the tip.
|
|
10
|
+
*
|
|
11
|
+
* Storage is optional and every access is guarded: a private window can have
|
|
12
|
+
* `localStorage` present and throwing, and a tip system that breaks the page
|
|
13
|
+
* it is meant to explain is worse than one that forgets.
|
|
14
|
+
*
|
|
15
|
+
* @module
|
|
16
|
+
*/
|
|
17
|
+
const DEFAULT_PREFIX = 'tips:';
|
|
18
|
+
/** Milliseconds a frequency has to elapse before a tip may reappear. */
|
|
19
|
+
const INTERVALS = {
|
|
20
|
+
hourly: 60 * 60 * 1000,
|
|
21
|
+
daily: 24 * 60 * 60 * 1000,
|
|
22
|
+
weekly: 7 * 24 * 60 * 60 * 1000,
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Storage that forgets when the page does.
|
|
26
|
+
*
|
|
27
|
+
* The fallback for a browser that refuses persistence, and the right storage
|
|
28
|
+
* for a test or a Storybook story — a tip dismissed while reviewing one story
|
|
29
|
+
* must not be invisible in the next reviewer's session.
|
|
30
|
+
*/
|
|
31
|
+
export const memoryTipStorage = () => {
|
|
32
|
+
const memory = new Map();
|
|
33
|
+
return {
|
|
34
|
+
getItem: (key) => memory.get(key) ?? null,
|
|
35
|
+
setItem: (key, value) => {
|
|
36
|
+
memory.set(key, value);
|
|
37
|
+
},
|
|
38
|
+
removeItem: (key) => {
|
|
39
|
+
memory.delete(key);
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* `globalThis.localStorage` where it exists and is usable, and
|
|
45
|
+
* {@link memoryTipStorage} where it does not — the server, and a browser that
|
|
46
|
+
* refuses storage.
|
|
47
|
+
*/
|
|
48
|
+
export const browserTipStorage = () => {
|
|
49
|
+
try {
|
|
50
|
+
const storage = globalThis.localStorage;
|
|
51
|
+
// Presence is not permission: Safari's private mode throws on write.
|
|
52
|
+
storage.setItem('tips:probe', '1');
|
|
53
|
+
storage.removeItem('tips:probe');
|
|
54
|
+
return storage;
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
return memoryTipStorage();
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
/**
|
|
61
|
+
* Build a tip store over the given storage.
|
|
62
|
+
*
|
|
63
|
+
* Call once, high in the tree, and hand it to the tips (or put it in a context
|
|
64
|
+
* of the app's own). The in-memory state is what drives re-rendering; storage
|
|
65
|
+
* is where it survives a reload.
|
|
66
|
+
*/
|
|
67
|
+
export const createTipStore = (options = {}) => {
|
|
68
|
+
const storage = options.storage ?? browserTipStorage();
|
|
69
|
+
const prefix = options.prefix ?? DEFAULT_PREFIX;
|
|
70
|
+
const configs = new Map();
|
|
71
|
+
const states = $state(new Map());
|
|
72
|
+
const read = (id) => {
|
|
73
|
+
const live = states.get(id);
|
|
74
|
+
if (live !== undefined)
|
|
75
|
+
return live;
|
|
76
|
+
let stored = { id, status: 'pending', displayCount: 0 };
|
|
77
|
+
try {
|
|
78
|
+
const raw = storage.getItem(`${prefix}${id}`);
|
|
79
|
+
// Trusted only for its shape: it is this app's own write, and a corrupt
|
|
80
|
+
// entry falls back to "never seen" rather than throwing at the reader.
|
|
81
|
+
if (raw !== null)
|
|
82
|
+
stored = JSON.parse(raw);
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
/* unreadable or malformed — treat the tip as unseen */
|
|
86
|
+
}
|
|
87
|
+
states.set(id, stored);
|
|
88
|
+
return stored;
|
|
89
|
+
};
|
|
90
|
+
const write = (next) => {
|
|
91
|
+
states.set(next.id, next);
|
|
92
|
+
try {
|
|
93
|
+
storage.setItem(`${prefix}${next.id}`, JSON.stringify(next));
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
/* storage refused; the in-memory state still drives this session */
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
const frequencyAllows = (config, state) => {
|
|
100
|
+
if (state.lastShown === undefined)
|
|
101
|
+
return true;
|
|
102
|
+
const frequency = config.frequency ?? 'immediate';
|
|
103
|
+
if (frequency === 'immediate')
|
|
104
|
+
return true;
|
|
105
|
+
if (frequency === 'once')
|
|
106
|
+
return false;
|
|
107
|
+
const since = Date.now() - new Date(state.lastShown).getTime();
|
|
108
|
+
return since >= (INTERVALS[frequency] ?? 0);
|
|
109
|
+
};
|
|
110
|
+
return {
|
|
111
|
+
register: (config) => {
|
|
112
|
+
configs.set(config.id, config);
|
|
113
|
+
},
|
|
114
|
+
getState: read,
|
|
115
|
+
shouldDisplay: (id) => {
|
|
116
|
+
const config = configs.get(id);
|
|
117
|
+
// An unregistered tip shows nothing: a typo in an id should be an absent
|
|
118
|
+
// tip, not a tip with no rules.
|
|
119
|
+
if (config === undefined)
|
|
120
|
+
return false;
|
|
121
|
+
const state = read(id);
|
|
122
|
+
if (state.status === 'dismissed' || state.status === 'completed')
|
|
123
|
+
return false;
|
|
124
|
+
if (config.maxDisplayCount !== undefined && state.displayCount >= config.maxDisplayCount) {
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
if (!frequencyAllows(config, state))
|
|
128
|
+
return false;
|
|
129
|
+
return (config.rules ?? []).every((rule) => rule.evaluate());
|
|
130
|
+
},
|
|
131
|
+
markShown: (id) => {
|
|
132
|
+
const state = read(id);
|
|
133
|
+
write({
|
|
134
|
+
...state,
|
|
135
|
+
status: 'available',
|
|
136
|
+
displayCount: state.displayCount + 1,
|
|
137
|
+
lastShown: new Date().toISOString(),
|
|
138
|
+
});
|
|
139
|
+
},
|
|
140
|
+
dismiss: (id) => {
|
|
141
|
+
write({ ...read(id), status: 'dismissed', dismissedAt: new Date().toISOString() });
|
|
142
|
+
},
|
|
143
|
+
complete: (id) => {
|
|
144
|
+
write({ ...read(id), status: 'completed', completedAt: new Date().toISOString() });
|
|
145
|
+
},
|
|
146
|
+
resetAll: () => {
|
|
147
|
+
for (const id of [...states.keys(), ...configs.keys()]) {
|
|
148
|
+
states.delete(id);
|
|
149
|
+
try {
|
|
150
|
+
storage.removeItem(`${prefix}${id}`);
|
|
151
|
+
}
|
|
152
|
+
catch {
|
|
153
|
+
/* storage refused; the in-memory reset still stands */
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
},
|
|
157
|
+
};
|
|
158
|
+
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TipKit types — contextual, dismissible guidance, after Apple's TipKit.
|
|
3
|
+
*
|
|
4
|
+
* @module
|
|
5
|
+
*/
|
|
6
|
+
/** How often a tip may reappear once it has been shown. */
|
|
7
|
+
export type TipFrequency = 'immediate' | 'hourly' | 'daily' | 'weekly' | 'once';
|
|
8
|
+
/** Where a tip has got to in its life. */
|
|
9
|
+
export type TipStatus = 'pending' | 'available' | 'dismissed' | 'completed';
|
|
10
|
+
/** Which side of its anchor a popover tip sits on. */
|
|
11
|
+
export type TipPlacement = 'top' | 'bottom' | 'left' | 'right';
|
|
12
|
+
/** A condition that must hold for a tip to show. */
|
|
13
|
+
export interface TipRule {
|
|
14
|
+
readonly id: string;
|
|
15
|
+
/** Synchronous on purpose — see the note in `tip-store.svelte.ts`. */
|
|
16
|
+
readonly evaluate: () => boolean;
|
|
17
|
+
}
|
|
18
|
+
/** Something the reader can do from the tip. */
|
|
19
|
+
export interface TipAction {
|
|
20
|
+
readonly id: string;
|
|
21
|
+
readonly title: string;
|
|
22
|
+
readonly handler: () => void | Promise<void>;
|
|
23
|
+
}
|
|
24
|
+
/** A tip, as declared by the page that owns it. */
|
|
25
|
+
export interface TipConfig {
|
|
26
|
+
readonly id: string;
|
|
27
|
+
readonly title: string;
|
|
28
|
+
readonly message?: string | undefined;
|
|
29
|
+
readonly actions?: readonly TipAction[] | undefined;
|
|
30
|
+
/** Every rule must hold. */
|
|
31
|
+
readonly rules?: readonly TipRule[] | undefined;
|
|
32
|
+
/** Defaults to `immediate` — shown every time the page is visited. */
|
|
33
|
+
readonly frequency?: TipFrequency | undefined;
|
|
34
|
+
/** Never shown more than this many times, whatever the frequency says. */
|
|
35
|
+
readonly maxDisplayCount?: number | undefined;
|
|
36
|
+
}
|
|
37
|
+
/** What is remembered about a tip between visits. */
|
|
38
|
+
export interface TipState {
|
|
39
|
+
readonly id: string;
|
|
40
|
+
readonly status: TipStatus;
|
|
41
|
+
readonly displayCount: number;
|
|
42
|
+
/** ISO 8601; absent until the tip has been shown once. */
|
|
43
|
+
readonly lastShown?: string | undefined;
|
|
44
|
+
readonly dismissedAt?: string | undefined;
|
|
45
|
+
readonly completedAt?: string | undefined;
|
|
46
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
<!-- @component
|
|
2
|
+
The daisyUI `alert`: a tone-coloured notice with an icon, an optional lead-in and an optional dismiss button.
|
|
3
|
+
-->
|
|
4
|
+
<!--
|
|
5
|
+
Alert — the daisyUI `alert`: a tone-coloured notice with an icon, an
|
|
6
|
+
optional bold lead-in and an optional dismiss button.
|
|
7
|
+
|
|
8
|
+
ARIA contract, and the reason `live` is a prop rather than a constant: an
|
|
9
|
+
alert that is on the page when it loads must NOT be announced, an alert
|
|
10
|
+
that appears in response to something the reader did must be, and the
|
|
11
|
+
urgency depends on what happened. `live` picks between `role="alert"`
|
|
12
|
+
(assertive — interrupt, the default for `error`), `role="status"` (polite —
|
|
13
|
+
wait for a pause, the default for everything else) and `off`, which renders
|
|
14
|
+
no role at all. Static page furniture wants `off`; a form failure wants
|
|
15
|
+
`assertive`.
|
|
16
|
+
|
|
17
|
+
A live region must be IN the document before its content changes, so an app
|
|
18
|
+
that swaps an alert's text keeps the component mounted; one that mounts the
|
|
19
|
+
alert on failure gets the announcement from the insertion.
|
|
20
|
+
|
|
21
|
+
The tone icon is `aria-hidden` — it restates the tone, which the text
|
|
22
|
+
already carries — and the dismiss button is a labelled `Button`, not a
|
|
23
|
+
bare glyph.
|
|
24
|
+
-->
|
|
25
|
+
<script lang="ts">import { CircleCheck, CircleX, Info, TriangleAlert, X } from "@lucide/svelte";
|
|
26
|
+
import Button from "./Button.svelte";
|
|
27
|
+
import { alertTone, alertVariant } from "./tones.js";
|
|
28
|
+
import VisuallyHidden from "./VisuallyHidden.svelte";
|
|
29
|
+
let { tone = "info", variant = "soft", title, live, icon, onDismiss, dismissLabel = "Dismiss", children, class: className, ...rest } = $props();
|
|
30
|
+
const liveness = $derived(live ?? (tone === "error" ? "assertive" : "polite"));
|
|
31
|
+
// `role="alert"` already implies `aria-live="assertive"` and `role="status"`
|
|
32
|
+
// implies `polite`; spelling the second one out as well is how a region ends
|
|
33
|
+
// up announced twice.
|
|
34
|
+
const role = $derived(liveness === "assertive" ? "alert" : liveness === "polite" ? "status" : undefined);
|
|
35
|
+
</script>
|
|
36
|
+
|
|
37
|
+
<div {...rest} {role} class={['alert', alertTone[tone], alertVariant[variant], className]}>
|
|
38
|
+
{#if icon}
|
|
39
|
+
{@render icon()}
|
|
40
|
+
{:else if tone === 'success'}
|
|
41
|
+
<CircleCheck class="size-5 shrink-0" aria-hidden="true" />
|
|
42
|
+
{:else if tone === 'warning'}
|
|
43
|
+
<TriangleAlert class="size-5 shrink-0" aria-hidden="true" />
|
|
44
|
+
{:else if tone === 'error'}
|
|
45
|
+
<CircleX class="size-5 shrink-0" aria-hidden="true" />
|
|
46
|
+
{:else}
|
|
47
|
+
<Info class="size-5 shrink-0" aria-hidden="true" />
|
|
48
|
+
{/if}
|
|
49
|
+
<div>
|
|
50
|
+
{#if title !== undefined}
|
|
51
|
+
<p class="font-semibold">{title}</p>
|
|
52
|
+
{/if}
|
|
53
|
+
{@render children()}
|
|
54
|
+
</div>
|
|
55
|
+
{#if onDismiss}
|
|
56
|
+
<Button variant="ghost" size="sm" class="btn-square" onclick={onDismiss}>
|
|
57
|
+
<X class="size-4" aria-hidden="true" />
|
|
58
|
+
<VisuallyHidden>{dismissLabel}</VisuallyHidden>
|
|
59
|
+
</Button>
|
|
60
|
+
{/if}
|
|
61
|
+
</div>
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
3
|
+
import { type AlertVariant, type StatusTone } from './tones.ts';
|
|
4
|
+
interface Props extends HTMLAttributes<HTMLDivElement> {
|
|
5
|
+
/**
|
|
6
|
+
* Status colour and icon.
|
|
7
|
+
* @defaultValue `'info'`
|
|
8
|
+
*/
|
|
9
|
+
tone?: StatusTone | undefined;
|
|
10
|
+
/**
|
|
11
|
+
* daisyUI alert style.
|
|
12
|
+
* @defaultValue `'soft'`
|
|
13
|
+
*/
|
|
14
|
+
variant?: AlertVariant | undefined;
|
|
15
|
+
/** Bold lead-in rendered above the body. */
|
|
16
|
+
title?: string | undefined;
|
|
17
|
+
/**
|
|
18
|
+
* How — and whether — assistive technology announces this alert.
|
|
19
|
+
* Defaults to `assertive` for `error` and `polite` for every other tone.
|
|
20
|
+
*/
|
|
21
|
+
live?: 'assertive' | 'polite' | 'off' | undefined;
|
|
22
|
+
/** Replace the tone's default icon. */
|
|
23
|
+
icon?: Snippet<[]> | undefined;
|
|
24
|
+
/** When set, renders a dismiss button that calls it. */
|
|
25
|
+
onDismiss?: (() => void) | undefined;
|
|
26
|
+
/**
|
|
27
|
+
* Accessible name of the dismiss button.
|
|
28
|
+
* @defaultValue `'Dismiss'`
|
|
29
|
+
*/
|
|
30
|
+
dismissLabel?: string | undefined;
|
|
31
|
+
/** The message, after the optional bold lead-in. */
|
|
32
|
+
children: Snippet<[]>;
|
|
33
|
+
}
|
|
34
|
+
/** The daisyUI `alert`: a tone-coloured notice with an icon, an optional lead-in and an optional dismiss button. */
|
|
35
|
+
declare const Alert: import("svelte").Component<Props, {}, "">;
|
|
36
|
+
type Alert = ReturnType<typeof Alert>;
|
|
37
|
+
export default Alert;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
<!-- @component
|
|
2
|
+
The daisyUI `badge`: a short inline label for a status, a count or a category.
|
|
3
|
+
-->
|
|
4
|
+
<!--
|
|
5
|
+
Badge — the daisyUI `badge`: a short, inline label for a status, a count or
|
|
6
|
+
a category.
|
|
7
|
+
|
|
8
|
+
Two colour paths. A semantic `tone` picks the badge's daisyUI colour, which
|
|
9
|
+
is what a status ("Draft", "Overdue") wants. A `colourToken` instead names a
|
|
10
|
+
custom property the consuming app's theme declares, which is what a
|
|
11
|
+
subject-coloured label (a party, a rank, a project) wants: the badge then
|
|
12
|
+
tints its border and a translucent fill from that one variable and keeps
|
|
13
|
+
`base-content` text, so it stays legible on either theme whatever the hue.
|
|
14
|
+
`colourToken` wins over `tone` — a subject's colour is not a tone.
|
|
15
|
+
|
|
16
|
+
ARIA contract: none, deliberately. A badge is a decorative frame around
|
|
17
|
+
text, so the text has to carry the meaning: a status badge showing only a
|
|
18
|
+
colour is a status nobody can read. If the badge's own text is not enough
|
|
19
|
+
in context, label the thing it sits on, not the badge.
|
|
20
|
+
-->
|
|
21
|
+
<script lang="ts">import { BADGE_COLOUR_PROPERTY, colourTokenStyle } from "./colour-token.js";
|
|
22
|
+
import { badgeSize, badgeTone, badgeVariant } from "./tones.js";
|
|
23
|
+
let { tone = "neutral", variant = "soft", size = "md", colourToken, children, class: className, ...rest } = $props();
|
|
24
|
+
const classes = $derived(colourToken === undefined ? [
|
|
25
|
+
"badge",
|
|
26
|
+
badgeTone[tone],
|
|
27
|
+
badgeVariant[variant],
|
|
28
|
+
badgeSize[size],
|
|
29
|
+
className
|
|
30
|
+
] : [
|
|
31
|
+
"badge border-(--badge-colour) bg-(--badge-colour)/15 text-base-content",
|
|
32
|
+
badgeSize[size],
|
|
33
|
+
className
|
|
34
|
+
]);
|
|
35
|
+
</script>
|
|
36
|
+
|
|
37
|
+
<span {...rest} class={classes} style={colourTokenStyle(BADGE_COLOUR_PROPERTY, colourToken)}>
|
|
38
|
+
{@render children()}
|
|
39
|
+
</span>
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
3
|
+
import { type BadgeVariant, type Size, type Tone } from './tones.ts';
|
|
4
|
+
interface Props extends HTMLAttributes<HTMLSpanElement> {
|
|
5
|
+
/**
|
|
6
|
+
* Semantic colour.
|
|
7
|
+
* @defaultValue `'neutral'`
|
|
8
|
+
*/
|
|
9
|
+
tone?: Tone | undefined;
|
|
10
|
+
/**
|
|
11
|
+
* daisyUI badge style.
|
|
12
|
+
* @defaultValue `'soft'`
|
|
13
|
+
*/
|
|
14
|
+
variant?: BadgeVariant | undefined;
|
|
15
|
+
/**
|
|
16
|
+
* daisyUI size step.
|
|
17
|
+
* @defaultValue `'md'`
|
|
18
|
+
*/
|
|
19
|
+
size?: Size | undefined;
|
|
20
|
+
/**
|
|
21
|
+
* A CSS custom-property name the app's theme declares (`--party-alp`).
|
|
22
|
+
* Overrides `tone` and `variant`; falls back to the theme's neutral
|
|
23
|
+
* colour when the token is undeclared.
|
|
24
|
+
*/
|
|
25
|
+
colourToken?: string | undefined;
|
|
26
|
+
/** The badge text; keep it to a word or a count. */
|
|
27
|
+
children: Snippet<[]>;
|
|
28
|
+
}
|
|
29
|
+
/** The daisyUI `badge`: a short inline label for a status, a count or a category. */
|
|
30
|
+
declare const Badge: import("svelte").Component<Props, {}, "">;
|
|
31
|
+
type Badge = ReturnType<typeof Badge>;
|
|
32
|
+
export default Badge;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
<!-- @component
|
|
2
|
+
The daisyUI `btn` with tone, variant and size; `loading` disables it and sets `aria-busy` without changing its name. `type` defaults to `button`, not the native `submit`.
|
|
3
|
+
-->
|
|
4
|
+
<!--
|
|
5
|
+
Button — the daisyUI `btn`, with tone / variant / size chosen from literal
|
|
6
|
+
lookup tables and a `loading` state that is a real busy state rather than a
|
|
7
|
+
spinner drawn over a live control.
|
|
8
|
+
|
|
9
|
+
ARIA contract: a native `<button>`, so role and keyboard (Enter and Space)
|
|
10
|
+
come from the platform and nothing is hand-rolled. While `loading` the
|
|
11
|
+
button is `disabled` AND `aria-busy="true"` — disabled alone says "you may
|
|
12
|
+
not", busy says "it is already happening" — and the spinner that replaces
|
|
13
|
+
the leading icon is `aria-hidden`, because the accessible name must not
|
|
14
|
+
change when the state does.
|
|
15
|
+
|
|
16
|
+
`type` defaults to `button`: a button inside a form that does not say what
|
|
17
|
+
it is submits it, which is almost never what a call site means.
|
|
18
|
+
-->
|
|
19
|
+
<script lang="ts">import { buttonSize, buttonTone, buttonVariant } from "./tones.js";
|
|
20
|
+
let { tone, variant = "solid", size = "md", loading = false, block = false, icon, children, type = "button", disabled = false, class: className, ...rest } = $props();
|
|
21
|
+
</script>
|
|
22
|
+
|
|
23
|
+
<button
|
|
24
|
+
{...rest}
|
|
25
|
+
{type}
|
|
26
|
+
disabled={disabled || loading}
|
|
27
|
+
aria-busy={loading ? 'true' : undefined}
|
|
28
|
+
class={[
|
|
29
|
+
'btn',
|
|
30
|
+
tone !== undefined && buttonTone[tone],
|
|
31
|
+
buttonVariant[variant],
|
|
32
|
+
buttonSize[size],
|
|
33
|
+
block && 'btn-block',
|
|
34
|
+
className,
|
|
35
|
+
]}
|
|
36
|
+
>
|
|
37
|
+
{#if loading}
|
|
38
|
+
<span class="loading loading-spinner loading-sm" aria-hidden="true"></span>
|
|
39
|
+
{:else if icon}
|
|
40
|
+
{@render icon()}
|
|
41
|
+
{/if}
|
|
42
|
+
{@render children()}
|
|
43
|
+
</button>
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { HTMLButtonAttributes } from 'svelte/elements';
|
|
3
|
+
import { type ButtonVariant, type Size, type Tone } from './tones.ts';
|
|
4
|
+
interface Props extends HTMLButtonAttributes {
|
|
5
|
+
/** Semantic colour. Omitted, the button is daisyUI's neutral `btn`. */
|
|
6
|
+
tone?: Tone | undefined;
|
|
7
|
+
/**
|
|
8
|
+
* daisyUI button style.
|
|
9
|
+
* @defaultValue `'solid'`
|
|
10
|
+
*/
|
|
11
|
+
variant?: ButtonVariant | undefined;
|
|
12
|
+
/**
|
|
13
|
+
* daisyUI size step.
|
|
14
|
+
* @defaultValue `'md'`
|
|
15
|
+
*/
|
|
16
|
+
size?: Size | undefined;
|
|
17
|
+
/** Disables the button, marks it busy and shows a spinner. */
|
|
18
|
+
loading?: boolean | undefined;
|
|
19
|
+
/** Full-width. */
|
|
20
|
+
block?: boolean | undefined;
|
|
21
|
+
/** Leading icon, replaced by the spinner while `loading`. */
|
|
22
|
+
icon?: Snippet<[]> | undefined;
|
|
23
|
+
/** The button text; its accessible name. */
|
|
24
|
+
children: Snippet<[]>;
|
|
25
|
+
}
|
|
26
|
+
/** The daisyUI `btn` with tone, variant and size; `loading` disables it and sets `aria-busy` without changing its name. `type` defaults to `button`, not the native `submit`. */
|
|
27
|
+
declare const Button: import("svelte").Component<Props, {}, "">;
|
|
28
|
+
type Button = ReturnType<typeof Button>;
|
|
29
|
+
export default Button;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
<!-- @component
|
|
2
|
+
The bordered `base-100` surface: a padded body with an optional heading, a figure above and an actions row below.
|
|
3
|
+
-->
|
|
4
|
+
<!--
|
|
5
|
+
Card — the surface. One bordered box on `base-100` with a padded body, an
|
|
6
|
+
optional heading, an optional figure above it and an optional actions row
|
|
7
|
+
under it.
|
|
8
|
+
|
|
9
|
+
This component exists to END the hand-written
|
|
10
|
+
`card card-border bg-base-100 p-6 shadow-sm`, of which the four apps carried
|
|
11
|
+
56 copies. A bare `<Card>` renders exactly that look, so adopting it is a
|
|
12
|
+
substitution and never a redesign; every knob below is an opt-in for a site
|
|
13
|
+
that wanted something slightly different and previously got it by editing
|
|
14
|
+
the class list in place.
|
|
15
|
+
|
|
16
|
+
ARIA contract: `title` is a REAL heading at `headingLevel`, not a styled
|
|
17
|
+
`<p>`, so a card in a list is reachable by heading navigation and the
|
|
18
|
+
document outline stays true. The default is `h2` — a card under the page's
|
|
19
|
+
`<h1>`. `as` picks the element that carries the semantics: `article` for a
|
|
20
|
+
self-contained item (the default), `section` for a labelled region of the
|
|
21
|
+
page, `div` for pure grouping.
|
|
22
|
+
-->
|
|
23
|
+
<script lang="ts">import { cardSize, cardVariant } from "./tones.js";
|
|
24
|
+
let { as = "article", title, headingLevel = 2, variant = "border", size = "md", padded = true, bodyClass, figure, actions, children, class: className, ...rest } = $props();
|
|
25
|
+
</script>
|
|
26
|
+
|
|
27
|
+
<svelte:element
|
|
28
|
+
this={as}
|
|
29
|
+
{...rest}
|
|
30
|
+
class={[
|
|
31
|
+
'card bg-base-100 shadow-sm',
|
|
32
|
+
cardVariant[variant],
|
|
33
|
+
cardSize[size],
|
|
34
|
+
className,
|
|
35
|
+
]}
|
|
36
|
+
>
|
|
37
|
+
{#if figure}
|
|
38
|
+
<figure>{@render figure()}</figure>
|
|
39
|
+
{/if}
|
|
40
|
+
<div class={['card-body', !padded && 'p-0', bodyClass]}>
|
|
41
|
+
{#if title !== undefined}
|
|
42
|
+
<svelte:element this={`h${headingLevel}`} class="card-title">{title}</svelte:element>
|
|
43
|
+
{/if}
|
|
44
|
+
{@render children()}
|
|
45
|
+
{#if actions}
|
|
46
|
+
<div class="card-actions justify-end">{@render actions()}</div>
|
|
47
|
+
{/if}
|
|
48
|
+
</div>
|
|
49
|
+
</svelte:element>
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
3
|
+
import { type CardVariant, type HeadingLevel, type Size } from './tones.ts';
|
|
4
|
+
interface Props extends HTMLAttributes<HTMLElement> {
|
|
5
|
+
/**
|
|
6
|
+
* The element that carries the semantics.
|
|
7
|
+
* @defaultValue `'article'`
|
|
8
|
+
*/
|
|
9
|
+
as?: 'article' | 'section' | 'div' | undefined;
|
|
10
|
+
/** Heading rendered as `card-title` at the top of the body. */
|
|
11
|
+
title?: string | undefined;
|
|
12
|
+
/**
|
|
13
|
+
* Heading level for `title`.
|
|
14
|
+
* @defaultValue `2`
|
|
15
|
+
*/
|
|
16
|
+
headingLevel?: HeadingLevel | undefined;
|
|
17
|
+
/**
|
|
18
|
+
* daisyUI card style.
|
|
19
|
+
* @defaultValue `'border'`
|
|
20
|
+
*/
|
|
21
|
+
variant?: CardVariant | undefined;
|
|
22
|
+
/**
|
|
23
|
+
* daisyUI size step.
|
|
24
|
+
* @defaultValue `'md'`
|
|
25
|
+
*/
|
|
26
|
+
size?: Size | undefined;
|
|
27
|
+
/**
|
|
28
|
+
* Drop the body padding — for a card whose content is edge to edge.
|
|
29
|
+
* @defaultValue `true`
|
|
30
|
+
*/
|
|
31
|
+
padded?: boolean | undefined;
|
|
32
|
+
/** Extra classes on the `card-body`, for layout the caller owns. */
|
|
33
|
+
bodyClass?: string | undefined;
|
|
34
|
+
/** Media block rendered above the body. */
|
|
35
|
+
figure?: Snippet<[]> | undefined;
|
|
36
|
+
/** Buttons or links rendered in the `card-actions` row. */
|
|
37
|
+
actions?: Snippet<[]> | undefined;
|
|
38
|
+
/** The body, inside the padded `card-body`, under the heading. */
|
|
39
|
+
children: Snippet<[]>;
|
|
40
|
+
}
|
|
41
|
+
/** The bordered `base-100` surface: a padded body with an optional heading, a figure above and an actions row below. */
|
|
42
|
+
declare const Card: import("svelte").Component<Props, {}, "">;
|
|
43
|
+
type Card = ReturnType<typeof Card>;
|
|
44
|
+
export default Card;
|