From 7571021822eb8dbc6c379fc20d5fd2fc760651c9 Mon Sep 17 00:00:00 2001 From: bennymi Date: Mon, 20 Jan 2025 20:29:02 +0100 Subject: [PATCH 1/5] wip --- docs/src/api.json | 122 +++++++--- docs/src/content/docs/components/toaster.mdx | 36 +++ docs/src/previews/toaster.svelte | 95 ++++++++ .../melt/src/lib/builders/Toaster.svelte.ts | 211 ++++++++++++++++++ packages/melt/src/lib/builders/index.ts | 3 +- packages/melt/src/lib/utils/is.ts | 4 + 6 files changed, 444 insertions(+), 27 deletions(-) create mode 100644 docs/src/content/docs/components/toaster.mdx create mode 100644 docs/src/previews/toaster.svelte create mode 100644 packages/melt/src/lib/builders/Toaster.svelte.ts diff --git a/docs/src/api.json b/docs/src/api.json index f704656..e1df319 100644 --- a/docs/src/api.json +++ b/docs/src/api.json @@ -16,7 +16,7 @@ { "name": "selected", "type": "MaybeMultiple | undefined", - "description": "The currently selected item(s).\nIf `multiple` is `true`, this should be an `Iterable`.\nOtherwise, it'll be a `string`.", + "description": "The currently selected item(s).\r\nIf `multiple` is `true`, this should be an `Iterable`.\r\nOtherwise, it'll be a `string`.", "defaultValue": "undefined", "optional": true }, @@ -64,37 +64,37 @@ { "name": "isSelected", "type": "(id: string) => boolean", - "description": "Checks if an item is currently selected\n@param id - ID of the item to check" + "description": "Checks if an item is currently selected\r@param id - ID of the item to check" }, { "name": "isExpanded", "type": "(id: string) => boolean", - "description": "Checks if an item is currently expanded\n@param id - ID of the item to check" + "description": "Checks if an item is currently expanded\r@param id - ID of the item to check" }, { "name": "expand", "type": "(id: string) => void", - "description": "Expands a specific item\n@param id - ID of the item to expand" + "description": "Expands a specific item\r@param id - ID of the item to expand" }, { "name": "collapse", "type": "(id: string) => void", - "description": "Collapses a specific item\n@param id - ID of the item to collapse" + "description": "Collapses a specific item\r@param id - ID of the item to collapse" }, { "name": "toggleExpand", "type": "(id: string) => void", - "description": "Toggles the expanded state of an item\n@param id - ID of the item to toggle" + "description": "Toggles the expanded state of an item\r@param id - ID of the item to toggle" }, { "name": "select", "type": "(id: string) => void", - "description": "Selects a specific item\n@param id - ID of the item to select" + "description": "Selects a specific item\r@param id - ID of the item to select" }, { "name": "deselect", "type": "(id: string) => void", - "description": "Deselects a specific item\n@param id - ID of the item to deselect" + "description": "Deselects a specific item\r@param id - ID of the item to deselect" }, { "name": "clearSelection", @@ -104,27 +104,27 @@ { "name": "toggleSelect", "type": "(id: string) => void", - "description": "Toggles the selected state of an item\n@param id - ID of the item to toggle" + "description": "Toggles the selected state of an item\r@param id - ID of the item to toggle" }, { "name": "selectAll", "type": "() => void", - "description": "Selects all visible items.\nIf all items are already selected, clears the selection." + "description": "Selects all visible items.\rIf all items are already selected, clears the selection." }, { "name": "getItemId", "type": "(id: string) => string", - "description": "Gets the DOM ID for a specific tree item\n@param id - ID of the item" + "description": "Gets the DOM ID for a specific tree item\r@param id - ID of the item" }, { "name": "getItemEl", "type": "(id: string) => HTMLElement | null", - "description": "Gets the DOM element for a specific tree item\n@param id - ID of the item" + "description": "Gets the DOM element for a specific tree item\r@param id - ID of the item" }, { "name": "selectUntil", "type": "(id: string) => void", - "description": "Selects all items between the last selected item and the specified item\n@param id - ID of the item to select until" + "description": "Selects all items between the last selected item and the specified item\r@param id - ID of the item to select until" }, { "name": "typeahead", @@ -156,7 +156,7 @@ { "name": "selected", "type": "Multiple extends true ? Set : string | undefined", - "description": "Currently selected item(s)\nFor multiple selection, returns a Set of IDs\nFor single selection, returns a single ID or undefined" + "description": "Currently selected item(s)\rFor multiple selection, returns a Set of IDs\rFor single selection, returns a single ID or undefined" }, { "name": "expanded", @@ -179,14 +179,14 @@ "description": "Array of Child instances representing the top-level items" } ], - "propsAlt": "export type TreeProps = {\n /**\n * If `true`, the user can select multiple items.\n * @default false\n */\n multiple?: MaybeGetter;\n /**\n * The currently selected item(s).\n * If `multiple` is `true`, this should be an `Iterable`.\n * Otherwise, it'll be a `string`.\n * @default undefined\n */\n selected?: MaybeMultiple;\n /**\n * Callback fired when selection changes\n * @param value - For multiple selection, a Set of selected IDs. For single selection, a single ID or undefined\n */\n onSelectedChange?: (value: Multiple extends true ? Set : string | undefined) => void;\n /**\n * The currently expanded items\n *\n * @default undefined\n */\n expanded?: MaybeMultiple;\n /**\n * Callback fired when expanded state changes\n * @param value - Set of expanded item IDs\n */\n onExpandedChange?: (value: Set) => void;\n /**\n * If `true`, groups (items with children) expand on click.\n * @default true\n */\n expandOnClick?: MaybeGetter;\n /**\n * The items contained in the tree.\n * @required\n */\n items: Items;\n /**\n * How many time (in ms) the typeahead string is held before it is cleared\n * @default 500\n */\n typeaheadTimeout?: MaybeGetter;\n};" + "propsAlt": "export type TreeProps = {\r\n /**\r\n * If `true`, the user can select multiple items.\r\n * @default false\r\n */\r\n multiple?: MaybeGetter;\r\n /**\r\n * The currently selected item(s).\r\n * If `multiple` is `true`, this should be an `Iterable`.\r\n * Otherwise, it'll be a `string`.\r\n * @default undefined\r\n */\r\n selected?: MaybeMultiple;\r\n /**\r\n * Callback fired when selection changes\r\n * @param value - For multiple selection, a Set of selected IDs. For single selection, a single ID or undefined\r\n */\r\n onSelectedChange?: (value: Multiple extends true ? Set : string | undefined) => void;\r\n /**\r\n * The currently expanded items\r\n *\r\n * @default undefined\r\n */\r\n expanded?: MaybeMultiple;\r\n /**\r\n * Callback fired when expanded state changes\r\n * @param value - Set of expanded item IDs\r\n */\r\n onExpandedChange?: (value: Set) => void;\r\n /**\r\n * If `true`, groups (items with children) expand on click.\r\n * @default true\r\n */\r\n expandOnClick?: MaybeGetter;\r\n /**\r\n * The items contained in the tree.\r\n * @required\r\n */\r\n items: Items;\r\n /**\r\n * How many time (in ms) the typeahead string is held before it is cleared\r\n * @default 500\r\n */\r\n typeaheadTimeout?: MaybeGetter;\r\n};" }, "Toggle": { "constructorProps": [ { "name": "value", "type": "MaybeGetter | undefined", - "description": "The value for the Toggle.\n\nWhen passing a getter, it will be used as source of truth,\nmeaning that the value only changes when the getter returns a new value.\n\nOtherwise, if passing a static value, it'll serve as the default value.", + "description": "The value for the Toggle.\r\n\r\nWhen passing a getter, it will be used as source of truth,\r\nmeaning that the value only changes when the getter returns a new value.\r\n\r\nOtherwise, if passing a static value, it'll serve as the default value.", "defaultValue": "false", "optional": true }, @@ -227,7 +227,77 @@ "description": "A hidden input field to use within forms." } ], - "propsAlt": "export type ToggleProps = {\n /**\n * The value for the Toggle.\n *\n * When passing a getter, it will be used as source of truth,\n * meaning that the value only changes when the getter returns a new value.\n *\n * Otherwise, if passing a static value, it'll serve as the default value.\n *\n *\n * @default false\n */\n value?: MaybeGetter;\n /**\n * Called when the value is supposed to change.\n */\n onValueChange?: (value: boolean) => void;\n\n /**\n * If `true`, prevents the user from interacting with the input.\n *\n * @default false\n */\n disabled?: MaybeGetter;\n};" + "propsAlt": "export type ToggleProps = {\r\n /**\r\n * The value for the Toggle.\r\n *\r\n * When passing a getter, it will be used as source of truth,\r\n * meaning that the value only changes when the getter returns a new value.\r\n *\r\n * Otherwise, if passing a static value, it'll serve as the default value.\r\n *\r\n *\r\n * @default false\r\n */\r\n value?: MaybeGetter;\r\n /**\r\n * Called when the value is supposed to change.\r\n */\r\n onValueChange?: (value: boolean) => void;\r\n\r\n /**\r\n * If `true`, prevents the user from interacting with the input.\r\n *\r\n * @default false\r\n */\r\n disabled?: MaybeGetter;\r\n};" + }, + "Toaster": { + "constructorProps": [ + { + "name": "closeDelay", + "type": "MaybeGetter", + "description": "The delay in milliseconds before the toast closes. Set to 0 to disable.", + "defaultValue": "5000", + "optional": true + }, + { + "name": "type", + "type": "MaybeGetter<\"foreground\" | \"background\" | undefined>", + "description": "The sensitivity of the toast for accessibility purposes.", + "defaultValue": "'foreground'", + "optional": true + }, + { + "name": "hover", + "type": "MaybeGetter<\"pause\" | \"pause-all\" | null | undefined>", + "description": "The behaviour when a toast is hovered.", + "defaultValue": "'pause'", + "optional": true + } + ], + "methods": [ + { + "name": "addToast", + "type": "(props: AddToastProps) => Toast", + "description": "" + }, + { + "name": "removeToast", + "type": "(id: string) => void", + "description": "" + }, + { + "name": "updateToast", + "type": "(id: string, data: T) => void", + "description": "" + }, + { + "name": "getToastFromToaster", + "type": "(toast: Toast) => ToastItem", + "description": "" + } + ], + "properties": [ + { + "name": "closeDelay", + "type": "number", + "description": "" + }, + { + "name": "type", + "type": "\"foreground\" | \"background\"", + "description": "" + }, + { + "name": "hover", + "type": "\"pause\" | \"pause-all\"", + "description": "" + }, + { + "name": "toasts", + "type": "Toast[]", + "description": "The active toasts." + } + ], + "propsAlt": "export type ToasterProps = {\r\n /**\r\n * The delay in milliseconds before the toast closes. Set to 0 to disable.\r\n * @default 5000\r\n */\r\n closeDelay?: MaybeGetter;\r\n\r\n /**\r\n * The sensitivity of the toast for accessibility purposes.\r\n * @default 'foreground'\r\n */\r\n type?: MaybeGetter<'foreground' | 'background' | undefined>;\r\n\r\n /**\r\n * The behaviour when a toast is hovered.\r\n * @default 'pause'\r\n */\r\n hover?: MaybeGetter<'pause' | 'pause-all' | null | undefined>;\r\n};" }, "Tabs": { "constructorProps": [ @@ -255,7 +325,7 @@ { "name": "value", "type": "MaybeGetter | undefined", - "description": "The default value for `tabs.value`\n\nWhen passing a getter, it will be used as source of truth,\nmeaning that `tabs.value` only changes when the getter returns a new value.\n\nIf omitted, it will use the first tab as default.", + "description": "The default value for `tabs.value`\r\n\r\nWhen passing a getter, it will be used as source of truth,\r\nmeaning that `tabs.value` only changes when the getter returns a new value.\r\n\r\nIf omitted, it will use the first tab as default.", "defaultValue": "undefined", "optional": true }, @@ -305,7 +375,7 @@ "description": "The attributes for the list that contains the tab triggers." } ], - "propsAlt": "export type TabsProps = {\n /**\n * If `true`, the value will be changed whenever a trigger is focused.\n *\n * @default true\n */\n selectWhenFocused?: MaybeGetter;\n /**\n * If the the trigger selection should loop when navigating with the arrow keys.\n *\n * @default true\n */\n loop?: MaybeGetter;\n /**\n * The orientation of the tabs.\n *\n * @default \"horizontal\"\n */\n orientation?: MaybeGetter<\"horizontal\" | \"vertical\">;\n /**\n * The default value for `tabs.value`\n *\n * When passing a getter, it will be used as source of truth,\n * meaning that `tabs.value` only changes when the getter returns a new value.\n *\n * If omitted, it will use the first tab as default.\n *\n * @default undefined\n */\n value?: MaybeGetter;\n /**\n * Called when the `Tabs` instance tries to change the active tab.\n */\n onValueChange?: (active: T) => void;\n};" + "propsAlt": "export type TabsProps = {\r\n /**\r\n * If `true`, the value will be changed whenever a trigger is focused.\r\n *\r\n * @default true\r\n */\r\n selectWhenFocused?: MaybeGetter;\r\n /**\r\n * If the the trigger selection should loop when navigating with the arrow keys.\r\n *\r\n * @default true\r\n */\r\n loop?: MaybeGetter;\r\n /**\r\n * The orientation of the tabs.\r\n *\r\n * @default \"horizontal\"\r\n */\r\n orientation?: MaybeGetter<\"horizontal\" | \"vertical\">;\r\n /**\r\n * The default value for `tabs.value`\r\n *\r\n * When passing a getter, it will be used as source of truth,\r\n * meaning that `tabs.value` only changes when the getter returns a new value.\r\n *\r\n * If omitted, it will use the first tab as default.\r\n *\r\n * @default undefined\r\n */\r\n value?: MaybeGetter;\r\n /**\r\n * Called when the `Tabs` instance tries to change the active tab.\r\n */\r\n onValueChange?: (active: T) => void;\r\n};" }, "Slider": { "constructorProps": [ @@ -340,7 +410,7 @@ { "name": "value", "type": "MaybeGetter", - "description": "The default value for `tabs.value`\n\nWhen passing a getter, it will be used as source of truth,\nmeaning that `tabs.value` only changes when the getter returns a new value.\n\nIf omitted, it will use the first tab as default.", + "description": "The default value for `tabs.value`\r\n\r\nWhen passing a getter, it will be used as source of truth,\r\nmeaning that `tabs.value` only changes when the getter returns a new value.\r\n\r\nIf omitted, it will use the first tab as default.", "defaultValue": "undefined", "optional": true }, @@ -381,7 +451,7 @@ { "name": "root", "type": "{\n readonly \"data-dragging\": \"\" | undefined\n readonly \"data-value\": number\n readonly \"data-orientation\": \"horizontal\" | \"vertical\"\n readonly \"aria-valuenow\": number\n readonly \"aria-valuemin\": number\n readonly \"aria-valuemax\": number\n readonly \"aria-orientation\": \"horizontal\" | \"vertical\"\n readonly style: `--percentage: ${string}; --percentage-inv: ${string}; touch-action: ${string}`\n readonly tabindex: 0\n readonly role: \"slider\"\n readonly \"data-melt-slider-root\": \"\"\n readonly id: string\n readonly onpointerdown: (e: PointerEvent) => void\n readonly onkeydown: (e: KeyboardEvent) => void\n}", - "description": "The root of the slider.\nAny cursor interaction along this element will change the slider's values." + "description": "The root of the slider.\rAny cursor interaction along this element will change the slider's values." }, { "name": "thumb", @@ -389,7 +459,7 @@ "description": "The slider's thumb, positioned at the end of the range." } ], - "propsAlt": "export type SliderProps = {\n /**\n * The minimum value of the slider.\n *\n * @default 0\n */\n min?: MaybeGetter;\n /**\n * The maximum value of the slider.\n *\n * @default 100\n */\n max?: MaybeGetter;\n /**\n * The orientation of the slider.\n *\n * @default \"horizontal\"\n */\n orientation?: MaybeGetter<\"horizontal\" | \"vertical\" | undefined>;\n\n /**\n * The step size of the slider.\n *\n * @default 1\n */\n step?: MaybeGetter;\n /**\n * The default value for `tabs.value`\n *\n * When passing a getter, it will be used as source of truth,\n * meaning that `tabs.value` only changes when the getter returns a new value.\n *\n * If omitted, it will use the first tab as default.\n *\n * @default undefined\n */\n value?: MaybeGetter;\n /**\n * Called when the `Slider` instance tries to change the active tab.\n */\n onValueChange?: (active: number) => void;\n};" + "propsAlt": "export type SliderProps = {\r\n /**\r\n * The minimum value of the slider.\r\n *\r\n * @default 0\r\n */\r\n min?: MaybeGetter;\r\n /**\r\n * The maximum value of the slider.\r\n *\r\n * @default 100\r\n */\r\n max?: MaybeGetter;\r\n /**\r\n * The orientation of the slider.\r\n *\r\n * @default \"horizontal\"\r\n */\r\n orientation?: MaybeGetter<\"horizontal\" | \"vertical\" | undefined>;\r\n\r\n /**\r\n * The step size of the slider.\r\n *\r\n * @default 1\r\n */\r\n step?: MaybeGetter;\r\n /**\r\n * The default value for `tabs.value`\r\n *\r\n * When passing a getter, it will be used as source of truth,\r\n * meaning that `tabs.value` only changes when the getter returns a new value.\r\n *\r\n * If omitted, it will use the first tab as default.\r\n *\r\n * @default undefined\r\n */\r\n value?: MaybeGetter;\r\n /**\r\n * Called when the `Slider` instance tries to change the active tab.\r\n */\r\n onValueChange?: (active: number) => void;\r\n};" }, "Select": { "constructorProps": [], @@ -401,7 +471,7 @@ { "name": "open", "type": "MaybeGetter | undefined", - "description": "If the Popover is open.\n\nWhen passing a getter, it will be used as source of truth,\nmeaning that the value only changes when the getter returns a new value.\n\nOtherwise, if passing a static value, it'll serve as the default value.", + "description": "If the Popover is open.\r\n\r\nWhen passing a getter, it will be used as source of truth,\r\nmeaning that the value only changes when the getter returns a new value.\r\n\r\nOtherwise, if passing a static value, it'll serve as the default value.", "defaultValue": "false", "optional": true }, @@ -453,14 +523,14 @@ "description": "" } ], - "propsAlt": "export type PopoverProps = {\n /**\n * If the Popover is open.\n *\n * When passing a getter, it will be used as source of truth,\n * meaning that the value only changes when the getter returns a new value.\n *\n * Otherwise, if passing a static value, it'll serve as the default value.\n *\n *\n * @default false\n */\n open?: MaybeGetter;\n\n /**\n * Called when the value is supposed to change.\n */\n onOpenChange?: (value: boolean) => void;\n\n /**\n * If the popover visibility should be controlled by the user.\n *\n * @default false\n */\n forceVisible?: MaybeGetter;\n\n /**\n * Options to be passed to Floating UI's `computePosition`\n *\n * @see https://floating-ui.com/docs/computePosition\n */\n computePositionOptions?: MaybeGetter | undefined>;\n};" + "propsAlt": "export type PopoverProps = {\r\n /**\r\n * If the Popover is open.\r\n *\r\n * When passing a getter, it will be used as source of truth,\r\n * meaning that the value only changes when the getter returns a new value.\r\n *\r\n * Otherwise, if passing a static value, it'll serve as the default value.\r\n *\r\n *\r\n * @default false\r\n */\r\n open?: MaybeGetter;\r\n\r\n /**\r\n * Called when the value is supposed to change.\r\n */\r\n onOpenChange?: (value: boolean) => void;\r\n\r\n /**\r\n * If the popover visibility should be controlled by the user.\r\n *\r\n * @default false\r\n */\r\n forceVisible?: MaybeGetter;\r\n\r\n /**\r\n * Options to be passed to Floating UI's `computePosition`\r\n *\r\n * @see https://floating-ui.com/docs/computePosition\r\n */\r\n computePositionOptions?: MaybeGetter | undefined>;\r\n};" }, "PinInput": { "constructorProps": [ { "name": "value", "type": "MaybeGetter", - "description": "The value for the Pin Input.\n\nWhen passing a getter, it will be used as source of truth,\nmeaning that the value only changes when the getter returns a new value.\n\nOtherwise, if passing a static value, it'll serve as the default value.", + "description": "The value for the Pin Input.\r\n\r\nWhen passing a getter, it will be used as source of truth,\r\nmeaning that the value only changes when the getter returns a new value.\r\n\r\nOtherwise, if passing a static value, it'll serve as the default value.", "defaultValue": "''", "optional": true }, @@ -554,6 +624,6 @@ "description": "An array of props that should be spread to the input elements." } ], - "propsAlt": "export type PinInputProps = {\n /**\n * The value for the Pin Input.\n *\n * When passing a getter, it will be used as source of truth,\n * meaning that the value only changes when the getter returns a new value.\n *\n * Otherwise, if passing a static value, it'll serve as the default value.\n *\n *\n * @default ''\n */\n value?: MaybeGetter;\n /**\n * Called when the `PinInput` instance tries to change the value.\n */\n onValueChange?: (value: string) => void;\n\n /**\n * The amount of digits in the Pin Input.\n *\n * @default 4\n */\n maxLength?: MaybeGetter;\n /**\n * An optional placeholder to display when the input is empty.\n *\n * @default '○'\n */\n placeholder?: MaybeGetter;\n\n /**\n * If `true`, prevents the user from interacting with the input.\n *\n * @default false\n */\n disabled?: MaybeGetter;\n\n /**\n * If the input should be masked like a password.\n *\n * @default false\n */\n mask?: MaybeGetter;\n\n /**\n * What characters the input accepts.\n *\n * @default 'text'\n */\n type?: MaybeGetter<\"alphanumeric\" | \"numeric\" | \"text\" | undefined>;\n};" + "propsAlt": "export type PinInputProps = {\r\n /**\r\n * The value for the Pin Input.\r\n *\r\n * When passing a getter, it will be used as source of truth,\r\n * meaning that the value only changes when the getter returns a new value.\r\n *\r\n * Otherwise, if passing a static value, it'll serve as the default value.\r\n *\r\n *\r\n * @default ''\r\n */\r\n value?: MaybeGetter;\r\n /**\r\n * Called when the `PinInput` instance tries to change the value.\r\n */\r\n onValueChange?: (value: string) => void;\r\n\r\n /**\r\n * The amount of digits in the Pin Input.\r\n *\r\n * @default 4\r\n */\r\n maxLength?: MaybeGetter;\r\n /**\r\n * An optional placeholder to display when the input is empty.\r\n *\r\n * @default '○'\r\n */\r\n placeholder?: MaybeGetter;\r\n\r\n /**\r\n * If `true`, prevents the user from interacting with the input.\r\n *\r\n * @default false\r\n */\r\n disabled?: MaybeGetter;\r\n\r\n /**\r\n * If the input should be masked like a password.\r\n *\r\n * @default false\r\n */\r\n mask?: MaybeGetter;\r\n\r\n /**\r\n * What characters the input accepts.\r\n *\r\n * @default 'text'\r\n */\r\n type?: MaybeGetter<\"alphanumeric\" | \"numeric\" | \"text\" | undefined>;\r\n};" } } \ No newline at end of file diff --git a/docs/src/content/docs/components/toaster.mdx b/docs/src/content/docs/components/toaster.mdx new file mode 100644 index 0000000..de768b4 --- /dev/null +++ b/docs/src/content/docs/components/toaster.mdx @@ -0,0 +1,36 @@ +--- +title: Toaster +description: A succinct message that is displayed temporarily. +--- +import ApiTable from "@components/api-table.astro"; +import Preview from "@previews/toaster.svelte"; +import Features from "@components/features.astro"; +import ThemedCode from "@components/themed-code.astro"; +import { Tabs, TabItem } from '@astrojs/starlight/components'; + + + + +## Features + + + - 🎹 + + +## Usage + + + +```svelte + +``` + + + +## API Reference + + diff --git a/docs/src/previews/toaster.svelte b/docs/src/previews/toaster.svelte new file mode 100644 index 0000000..83dde75 --- /dev/null +++ b/docs/src/previews/toaster.svelte @@ -0,0 +1,95 @@ + + + + + + +
+ {#each toaster.toasts as toast (toast.id)} + {@const toastBuilder = toaster.getToastFromToaster(toast)} +
+
+
+

+ {toastBuilder.data.title} + +

+
+ {toastBuilder.data.description} +
+
+ +
+
+ {/each} +
diff --git a/packages/melt/src/lib/builders/Toaster.svelte.ts b/packages/melt/src/lib/builders/Toaster.svelte.ts new file mode 100644 index 0000000..af55dba --- /dev/null +++ b/packages/melt/src/lib/builders/Toaster.svelte.ts @@ -0,0 +1,211 @@ +import type { MaybeGetter } from "$lib/types"; +import { extract } from "$lib/utils/extract"; +import { createDataIds, createIds } from "$lib/utils/identifiers"; +import { isTouch } from "../utils/is"; +import { SvelteMap } from "svelte/reactivity"; + +const dataIds = createDataIds("toaster", ["content", "title", "description", "close"]); +const toastDataIds = createDataIds("toast", ["content", "title", "description"]); + +export type ToasterProps = { + /** + * The delay in milliseconds before the toast closes. Set to 0 to disable. + * @default 5000 + */ + closeDelay?: MaybeGetter; + + /** + * The sensitivity of the toast for accessibility purposes. + * @default 'foreground' + */ + type?: MaybeGetter<'foreground' | 'background' | undefined>; + + /** + * The behaviour when a toast is hovered. + * @default 'pause' + */ + hover?: MaybeGetter<'pause' | 'pause-all' | null | undefined>; +}; + +export type AddToastProps = { + /** + * The delay in milliseconds before the toast closes. Set to 0 to disable. + */ + closeDelay?: number; + + /** + * The sensitivity of the toast for accessibility purposes. + */ + type?: 'foreground' | 'background'; + + /** + * The data passed to the toaster. + */ + data: T; +}; + +export type Toast = { + id: string; + ids: { + content: string; + title: string; + description: string; + }; + closeDelay: number; + type: 'foreground' | 'background'; + data: T; + timeout: number | null; + createdAt: number; + pausedAt?: number; + pauseDuration: number; + getPercentage: () => number; +}; + +export class Toaster { + // Props + #props!: ToasterProps; + closeDelay = $derived(extract(this.#props.closeDelay, 5000)); + type = $derived(extract(this.#props.type, 'foreground')); + hover = $derived(extract(this.#props.hover, 'pause')); + + // State + #toastsMap = new SvelteMap>(); + + /** The active toasts. */ + toasts = $derived(Array.from(this.#toastsMap.values())); + + constructor(props: ToasterProps = {}) { + this.#props = props; + } + + addToast(props: AddToastProps) { + const propsWithDefaults = { + closeDelay: this.closeDelay, + type: this.type, + ...props + } satisfies AddToastProps; + + const ids = createIds(toastDataIds); + + const timeout = propsWithDefaults.closeDelay === 0 + ? null + : window.setTimeout(() => { + this.removeToast(ids.content); + }, propsWithDefaults.closeDelay); + + const getPercentage = () => { + const { createdAt, pauseDuration, closeDelay, pausedAt } = toast; + if (closeDelay === 0) return 0; + + if (pausedAt) { + return (100 * (pausedAt - createdAt - pauseDuration)) / closeDelay; + } else { + const now = performance.now(); + return (100 * (now - createdAt - pauseDuration)) / closeDelay; + } + }; + + const toast = { + id: ids.content, + ids, + ...propsWithDefaults, + timeout, + createdAt: performance.now(), + pauseDuration: 0, + getPercentage, + } as Toast; + + this.#toastsMap.set(ids.content, toast); + + return toast; + } + + removeToast(id: string) { + this.#toastsMap.delete(id); + } + + updateToast(id: string, data: T) { + const toast = this.#toastsMap.get(id); + if (!toast) return; + + this.#toastsMap.set(id, { ...toast, data }); + } + + #pauseToastTimer(currentToast: Toast) { + if (currentToast.timeout !== null) { + window.clearTimeout(currentToast.timeout); + } + currentToast.pausedAt = performance.now(); + } + + #restartToastTimer(currentToast: Toast) { + const pausedAt = currentToast.pausedAt ?? currentToast.createdAt; + const elapsed = pausedAt - currentToast.createdAt - currentToast.pauseDuration; + const remaining = currentToast.closeDelay - elapsed; + + currentToast.timeout = window.setTimeout(() => { + this.removeToast(currentToast.id); + }, remaining); + + currentToast.pauseDuration += performance.now() - pausedAt; + currentToast.pausedAt = undefined; + } + + getToastFromToaster(toast: Toast) { + return new ToastItem({ + toaster: this, + toast + }); + } + + // get content() { + // return { + + // }; + // } +} + +type ToastItemProps = { + toaster: Toaster; + toast: Toast; +}; + +class ToastItem { + #props!: ToastItemProps; + #toaster = $derived(this.#props.toaster); + #toast = $derived(this.#props.toast); + + data = $derived(this.#props.toast.data); + + constructor(props: ToastItemProps) { + this.#props = props; + } + + get content() { + return { + id: this.#toast.id, + role: 'alert', + 'aria-describedby': this.#toast.ids.description, + 'aria-labeldby': this.#toast.ids.title, + 'aria-live': this.#toaster.type === 'foreground' ? 'assertive' : 'polite', + tabindex: -1, + 'onpointerenter': (e: PointerEvent) => { + if (isTouch(e)) return; + + // TODO + } + } as const; + } + + get title() { + return { id: this.#toast.ids.title }; + } + + get description() { + return { id: this.#toast.ids.description }; + } + + get close() { + return {}; + } +} \ No newline at end of file diff --git a/packages/melt/src/lib/builders/index.ts b/packages/melt/src/lib/builders/index.ts index 068e221..455afa5 100644 --- a/packages/melt/src/lib/builders/index.ts +++ b/packages/melt/src/lib/builders/index.ts @@ -3,5 +3,6 @@ export * from "./Popover.svelte"; export * from "./Tabs.svelte"; export * from "./Toggle.svelte"; export * from "./Slider.svelte"; -export * from "./utils.svelte"; +export * from './Toaster.svelte'; export * from "./Tree.svelte"; +export * from "./utils.svelte"; diff --git a/packages/melt/src/lib/utils/is.ts b/packages/melt/src/lib/utils/is.ts index 8afd552..db8a758 100644 --- a/packages/melt/src/lib/utils/is.ts +++ b/packages/melt/src/lib/utils/is.ts @@ -27,3 +27,7 @@ export function isHtmlInputElement(element: unknown): element is HTMLInputElemen export function isString(value: unknown): value is string { return typeof value === "string"; } + +export function isTouch(event: PointerEvent): boolean { + return event.pointerType === 'touch'; +} From 97818da099aad70d5c5fc286d819cd9c58eb64a6 Mon Sep 17 00:00:00 2001 From: bennymi Date: Fri, 24 Jan 2025 14:39:25 +0100 Subject: [PATCH 2/5] wip --- docs/src/api.json | 43 ++++--- docs/src/previews/toaster.svelte | 59 ++++----- .../melt/src/lib/builders/Toaster.svelte.ts | 113 +++++++++++++++--- packages/melt/src/lib/builders/index.ts | 7 +- 4 files changed, 161 insertions(+), 61 deletions(-) diff --git a/docs/src/api.json b/docs/src/api.json index 59a3cac..af8b2b2 100644 --- a/docs/src/api.json +++ b/docs/src/api.json @@ -156,7 +156,7 @@ { "name": "selected", "type": "Value>", - "description": "Currently selected item(s)\nFor multiple selection, returns a Set of IDs\nFor single selection, returns a single ID or undefined" + "description": "Currently selected item(s)\rFor multiple selection, returns a Set of IDs\rFor single selection, returns a single ID or undefined" }, { "name": "expanded", @@ -257,22 +257,32 @@ { "name": "addToast", "type": "(props: AddToastProps) => Toast", - "description": "" + "description": "Adds a toast." }, { "name": "removeToast", "type": "(id: string) => void", - "description": "" + "description": "Removes the toast with the specified ID.\r@param id The id of the toast." }, { "name": "updateToast", "type": "(id: string, data: T) => void", - "description": "" + "description": "Updates a toast's data.\r@param id The id of the toast.\r@param data The updated data." + }, + { + "name": "pauseToastTimer", + "type": "(currentToast: Toast) => void", + "description": "Pauses the clearance timer of a toast.\r@param currentToast The toast." + }, + { + "name": "restartToastTimer", + "type": "(currentToast: Toast) => void", + "description": "Restarts a toast timer.\r@param currentToast The toast." }, { "name": "getToastFromToaster", "type": "(toast: Toast) => ToastItem", - "description": "" + "description": "Returns a ToastItem with the required spread attributes.\r@param toast The toast for which to return a ToastItem." } ], "properties": [ @@ -295,6 +305,11 @@ "name": "toasts", "type": "Toast[]", "description": "The active toasts." + }, + { + "name": "root", + "type": "{ readonly id: string; readonly popover: \"manual\" }", + "description": "Spread attributes for the container of the toasts." } ], "propsAlt": "export type ToasterProps = {\r\n /**\r\n * The delay in milliseconds before the toast closes. Set to 0 to disable.\r\n * @default 5000\r\n */\r\n closeDelay?: MaybeGetter;\r\n\r\n /**\r\n * The sensitivity of the toast for accessibility purposes.\r\n * @default 'foreground'\r\n */\r\n type?: MaybeGetter<'foreground' | 'background' | undefined>;\r\n\r\n /**\r\n * The behaviour when a toast is hovered.\r\n * @default 'pause'\r\n */\r\n hover?: MaybeGetter<'pause' | 'pause-all' | null | undefined>;\r\n};" @@ -325,7 +340,7 @@ { "name": "value", "type": "MaybeGetter", - "description": "The default value for `tabs.value`\n\nWhen passing a getter, it will be used as source of truth,\nmeaning that `tabs.value` only changes when the getter returns a new value.\n\nIf omitted, it will use the first tab as default.", + "description": "The default value for `tabs.value`\r\n\r\nWhen passing a getter, it will be used as source of truth,\r\nmeaning that `tabs.value` only changes when the getter returns a new value.\r\n\r\nIf omitted, it will use the first tab as default.", "defaultValue": "undefined", "optional": true }, @@ -375,7 +390,7 @@ "description": "The attributes for the list that contains the tab triggers." } ], - "propsAlt": "export type TabsProps = {\n /**\n * If `true`, the value will be changed whenever a trigger is focused.\n *\n * @default true\n */\n selectWhenFocused?: MaybeGetter;\n /**\n * If the the trigger selection should loop when navigating with the arrow keys.\n *\n * @default true\n */\n loop?: MaybeGetter;\n /**\n * The orientation of the tabs.\n *\n * @default \"horizontal\"\n */\n orientation?: MaybeGetter<\"horizontal\" | \"vertical\" | undefined>;\n /**\n * The default value for `tabs.value`\n *\n * When passing a getter, it will be used as source of truth,\n * meaning that `tabs.value` only changes when the getter returns a new value.\n *\n * If omitted, it will use the first tab as default.\n *\n * @default undefined\n */\n value?: MaybeGetter;\n /**\n * Called when the `Tabs` instance tries to change the active tab.\n */\n onValueChange?: (active: T) => void;\n};" + "propsAlt": "export type TabsProps = {\r\n /**\r\n * If `true`, the value will be changed whenever a trigger is focused.\r\n *\r\n * @default true\r\n */\r\n selectWhenFocused?: MaybeGetter;\r\n /**\r\n * If the the trigger selection should loop when navigating with the arrow keys.\r\n *\r\n * @default true\r\n */\r\n loop?: MaybeGetter;\r\n /**\r\n * The orientation of the tabs.\r\n *\r\n * @default \"horizontal\"\r\n */\r\n orientation?: MaybeGetter<\"horizontal\" | \"vertical\" | undefined>;\r\n /**\r\n * The default value for `tabs.value`\r\n *\r\n * When passing a getter, it will be used as source of truth,\r\n * meaning that `tabs.value` only changes when the getter returns a new value.\r\n *\r\n * If omitted, it will use the first tab as default.\r\n *\r\n * @default undefined\r\n */\r\n value?: MaybeGetter;\r\n /**\r\n * Called when the `Tabs` instance tries to change the active tab.\r\n */\r\n onValueChange?: (active: T) => void;\r\n};" }, "Slider": { "constructorProps": [ @@ -478,7 +493,7 @@ { "name": "required", "type": "MaybeGetter", - "description": "If `true`, indicates that the user must select a radio button before\nthe owning form can be submitted.", + "description": "If `true`, indicates that the user must select a radio button before\r\nthe owning form can be submitted.", "defaultValue": "false", "optional": true }, @@ -582,7 +597,7 @@ "description": "" } ], - "propsAlt": "export type RadioGroupProps = {\n /**\n * If `true`, prevents the user from interacting with the group.\n *\n * @default false\n */\n disabled?: MaybeGetter;\n /**\n * If `true`, indicates that the user must select a radio button before\n * the owning form can be submitted.\n *\n * @default false\n */\n required?: MaybeGetter;\n /**\n * If the the button selection should loop when navigating with the arrow keys.\n *\n * @default true\n */\n loop?: MaybeGetter;\n /**\n * If `true`, the value will be changed whenever a button is focused.\n *\n * @default true\n */\n selectWhenFocused?: MaybeGetter;\n /**\n * The orientation of the slider.\n *\n * @default \"vertical\"\n */\n orientation?: MaybeGetter<\"horizontal\" | \"vertical\" | undefined>;\n /**\n * Input name for radio group.\n */\n name?: MaybeGetter;\n /**\n * Default value for radio group.\n *\n * @default \"\"\n */\n value?: MaybeGetter;\n /**\n * Called when the radio button is clicked.\n */\n onValueChange?: (active: string) => void;\n};" + "propsAlt": "export type RadioGroupProps = {\r\n /**\r\n * If `true`, prevents the user from interacting with the group.\r\n *\r\n * @default false\r\n */\r\n disabled?: MaybeGetter;\r\n /**\r\n * If `true`, indicates that the user must select a radio button before\r\n * the owning form can be submitted.\r\n *\r\n * @default false\r\n */\r\n required?: MaybeGetter;\r\n /**\r\n * If the the button selection should loop when navigating with the arrow keys.\r\n *\r\n * @default true\r\n */\r\n loop?: MaybeGetter;\r\n /**\r\n * If `true`, the value will be changed whenever a button is focused.\r\n *\r\n * @default true\r\n */\r\n selectWhenFocused?: MaybeGetter;\r\n /**\r\n * The orientation of the slider.\r\n *\r\n * @default \"vertical\"\r\n */\r\n orientation?: MaybeGetter<\"horizontal\" | \"vertical\" | undefined>;\r\n /**\r\n * Input name for radio group.\r\n */\r\n name?: MaybeGetter;\r\n /**\r\n * Default value for radio group.\r\n *\r\n * @default \"\"\r\n */\r\n value?: MaybeGetter;\r\n /**\r\n * Called when the radio button is clicked.\r\n */\r\n onValueChange?: (active: string) => void;\r\n};" }, "Progress": { "constructorProps": [ @@ -626,10 +641,10 @@ { "name": "progress", "type": "{\n \"data-melt-progress-progress\": string\n style: `--progress: ${string}`\n}", - "description": "Spread attributes for the Progress percentage element.\nProvides a --progress CSS variable that can be used to style the progress:\n`transform: translateX(calc(var(--progress) * -1));`" + "description": "Spread attributes for the Progress percentage element.\rProvides a --progress CSS variable that can be used to style the progress:\r`transform: translateX(calc(var(--progress) * -1));`" } ], - "propsAlt": "export type ProgressProps = {\n /**\n * The value for the progress.\n * \n * @default undefined\n */\n value?: MaybeGetter;\n\n /**\n * The maximum value of the progress.\n * \n * @deafult 100\n */\n max?: MaybeGetter;\n\n /**\n * The callback invoked when the value of the progress changes.\n */\n onValueChange?: (value: number) => void;\n};" + "propsAlt": "export type ProgressProps = {\r\n /**\r\n * The value for the progress.\r\n * \r\n * @default undefined\r\n */\r\n value?: MaybeGetter;\r\n\r\n /**\r\n * The maximum value of the progress.\r\n * \r\n * @deafult 100\r\n */\r\n max?: MaybeGetter;\r\n\r\n /**\r\n * The callback invoked when the value of the progress changes.\r\n */\r\n onValueChange?: (value: number) => void;\r\n};" }, "Popover": { "constructorProps": [ @@ -789,7 +804,7 @@ "description": "An array of props that should be spread to the input elements." } ], - "propsAlt": "export type PinInputProps = {\n /**\n * The value for the Pin Input.\n *\n * When passing a getter, it will be used as source of truth,\n * meaning that the value only changes when the getter returns a new value.\n *\n * Otherwise, if passing a static value, it'll serve as the default value.\n *\n *\n * @default ''\n */\n value?: MaybeGetter;\n /**\n * Called when the `PinInput` instance tries to change the value.\n */\n onValueChange?: (value: string) => void;\n\n /**\n * The amount of digits in the Pin Input.\n *\n * @default 4\n */\n maxLength?: MaybeGetter;\n /**\n * An optional placeholder to display when the input is empty.\n *\n * @default '○'\n */\n placeholder?: MaybeGetter;\n\n /**\n * If `true`, prevents the user from interacting with the input.\n *\n * @default false\n */\n disabled?: MaybeGetter;\n\n /**\n * If the input should be masked like a password.\n *\n * @default false\n */\n mask?: MaybeGetter;\n\n /**\n * What characters the input accepts.\n *\n * @default 'text'\n */\n type?: MaybeGetter<\"alphanumeric\" | \"numeric\" | \"text\" | undefined>;\n};" + "propsAlt": "export type PinInputProps = {\r\n /**\r\n * The value for the Pin Input.\r\n *\r\n * When passing a getter, it will be used as source of truth,\r\n * meaning that the value only changes when the getter returns a new value.\r\n *\r\n * Otherwise, if passing a static value, it'll serve as the default value.\r\n *\r\n *\r\n * @default ''\r\n */\r\n value?: MaybeGetter;\r\n /**\r\n * Called when the `PinInput` instance tries to change the value.\r\n */\r\n onValueChange?: (value: string) => void;\r\n\r\n /**\r\n * The amount of digits in the Pin Input.\r\n *\r\n * @default 4\r\n */\r\n maxLength?: MaybeGetter;\r\n /**\r\n * An optional placeholder to display when the input is empty.\r\n *\r\n * @default '○'\r\n */\r\n placeholder?: MaybeGetter;\r\n\r\n /**\r\n * If `true`, prevents the user from interacting with the input.\r\n *\r\n * @default false\r\n */\r\n disabled?: MaybeGetter;\r\n\r\n /**\r\n * If the input should be masked like a password.\r\n *\r\n * @default false\r\n */\r\n mask?: MaybeGetter;\r\n\r\n /**\r\n * What characters the input accepts.\r\n *\r\n * @default 'text'\r\n */\r\n type?: MaybeGetter<\"alphanumeric\" | \"numeric\" | \"text\" | undefined>;\r\n};" }, "Collapsible": { "constructorProps": [ @@ -840,7 +855,7 @@ "description": "The spread attributes for the content element." } ], - "propsAlt": "export type CollapsibleProps = {\n /**\n * Whether the collapsible is disabled which prevents it from being opened.\n */\n disabled?: MaybeGetter;\n\n /**\n * Whether the collapsible is open.\n */\n open?: MaybeGetter;\n\n /**\n * A callback called when the value of `open` changes.\n */\n onOpenChange?: (value: boolean) => void;\n};" + "propsAlt": "export type CollapsibleProps = {\r\n /**\r\n * Whether the collapsible is disabled which prevents it from being opened.\r\n */\r\n disabled?: MaybeGetter;\r\n\r\n /**\r\n * Whether the collapsible is open.\r\n */\r\n open?: MaybeGetter;\r\n\r\n /**\r\n * A callback called when the value of `open` changes.\r\n */\r\n onOpenChange?: (value: boolean) => void;\r\n};" }, "Avatar": { "constructorProps": [ @@ -892,6 +907,6 @@ "description": "" } ], - "propsAlt": "export type AvatarProps = {\n /**\n * The source of the image to display.\n */\n src?: MaybeGetter;\n\n /**\n * The amount of time in milliseconds to wait before displaying the image.\n *\n * @default 0\n */\n delayMs?: MaybeGetter;\n\n /**\n * A callback invoked when the loading status store of the avatar changes.\n */\n onLoadingStatusChange?: (value: ImageLoadingStatus) => void | undefined;\n};" + "propsAlt": "export type AvatarProps = {\r\n /**\r\n * The source of the image to display.\r\n */\r\n src?: MaybeGetter;\r\n\r\n /**\r\n * The amount of time in milliseconds to wait before displaying the image.\r\n *\r\n * @default 0\r\n */\r\n delayMs?: MaybeGetter;\r\n\r\n /**\r\n * A callback invoked when the loading status store of the avatar changes.\r\n */\r\n onLoadingStatusChange?: (value: ImageLoadingStatus) => void | undefined;\r\n};" } } \ No newline at end of file diff --git a/docs/src/previews/toaster.svelte b/docs/src/previews/toaster.svelte index 83dde75..2f055c7 100644 --- a/docs/src/previews/toaster.svelte +++ b/docs/src/previews/toaster.svelte @@ -1,7 +1,8 @@ + + +
+ {#each toaster.toasts as t (t.id)} + {@const toast = toaster.getToastFromToaster(t)} + +
+

{toast.data.title}

+
{toast.data.description}
+ +
+ {/each} +
+``` + +This component should be added to your root `+layout.svelte` or `App.svelte` component. - const toaster = new Toaster(); +```svelte + + + + +{@render children()} +``` + +Finally, you can use the exported `addToast` helper function to add a toast from any component of the application. + +```svelte + + + ``` - - ## API Reference diff --git a/docs/src/previews/toaster.svelte b/docs/src/previews/toaster.svelte index 8db22ff..f86d5a7 100644 --- a/docs/src/previews/toaster.svelte +++ b/docs/src/previews/toaster.svelte @@ -3,7 +3,7 @@ import { fly } from "svelte/transition"; import { usePreviewControls } from "@components/preview-ctx.svelte"; import Preview from "@components/preview.svelte"; - import { Toaster, getters } from "melt/builders"; + import { Progress, Toaster, getters } from "melt/builders"; import Close from "~icons/material-symbols/close-rounded"; const controls = usePreviewControls({ @@ -13,6 +13,13 @@ options: ["pause", "pause-all"], defaultValue: "pause", }, + closeDelay: { + type: "number", + min: 0, + max: 10000, + defaultValue: 5000, + label: "Close Delay", + }, }); type ToastData = { @@ -40,12 +47,10 @@ ]; const toaster = new Toaster({ - closeDelay: 0, ...getters(controls), }); function addRandomToast() { - console.log("add random toast"); toaster.addToast({ data: toastData[Math.floor(Math.random() * toastData.length)], }); @@ -67,10 +72,12 @@
- {#each toaster.toasts as toast (toast.id)} - {@const toastBuilder = toaster.getToastFromToaster(toast)} + {#each toaster.toasts as t (t.id)} + {@const toast = toaster.getToastFromToaster(t)} + {@const progress = new Progress({ value: () => toast.percentage })} +
-

- {toastBuilder.data.title} - +

+ {toast.data.title} +

-
- {toastBuilder.data.description} + +
+ {toast.data.description}
+ + + {#if toast.closeDelay !== 0} +
+
+
+
+
+ {/if}
{/each} diff --git a/packages/melt/src/lib/builders/Toaster.svelte.ts b/packages/melt/src/lib/builders/Toaster.svelte.ts index 726e526..120d8a8 100644 --- a/packages/melt/src/lib/builders/Toaster.svelte.ts +++ b/packages/melt/src/lib/builders/Toaster.svelte.ts @@ -4,6 +4,7 @@ import { extract } from "$lib/utils/extract"; import { createBuilderMetadata } from "$lib/utils/identifiers"; import { isHtmlElement, isTouch } from "../utils/is"; import { SvelteMap } from "svelte/reactivity"; +import { onMount } from "svelte"; const { dataAttrs, createIds } = createBuilderMetadata("toaster", ["root", "content", "title", "description", "close"]); @@ -223,7 +224,15 @@ class ToastItem { #toaster = $derived(this.#props.toaster); #toast = $derived(this.#props.toast); - data = $derived(this.#props.toast.data); + /** The original data you passed to the `addToast` function. */ + readonly data = $derived(this.#props.toast.data); + readonly closeDelay = $derived(this.#toast.closeDelay); + #percentage = $state(0); + + /** Get the toast timer percentage. */ + get percentage() { + return this.#percentage; + } /** Pause toast timer. */ readonly pauseToastTimer = () => { @@ -239,10 +248,26 @@ class ToastItem { constructor(props: ToastItemProps) { this.#props = props; + + onMount(() => { + if (this.#toast.closeDelay === 0) return; + + let frame: number; + const updatePercentage = () => { + this.#percentage = this.#toast.getPercentage(); + frame = requestAnimationFrame(updatePercentage); + }; + + frame = requestAnimationFrame(updatePercentage); + + return () => { + cancelAnimationFrame(frame); + }; + }); } /** - * Spread attributes for a toast's content element. + * Spread attributes for a toast's content (wrapper) element. */ get content() { return { @@ -280,10 +305,16 @@ class ToastItem { } as const; } + /** + * Spread attributes for a toast's title element. + */ get title() { return { id: this.#toast.ids.title }; } + /** + * Soread attributes for a toast's description element. + */ get description() { return { id: this.#toast.ids.description }; } From 1c42548d7376ba37a422c5e56a7d7c709b3f6c04 Mon Sep 17 00:00:00 2001 From: bennymi Date: Sat, 25 Jan 2025 14:03:17 +0100 Subject: [PATCH 5/5] fix preview dark/light mode --- docs/src/previews/toaster.svelte | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/docs/src/previews/toaster.svelte b/docs/src/previews/toaster.svelte index f86d5a7..56c5883 100644 --- a/docs/src/previews/toaster.svelte +++ b/docs/src/previews/toaster.svelte @@ -26,6 +26,7 @@ title: string; description: string; color: string; + shadow: string; }; const toastData: ToastData[] = [ @@ -33,16 +34,19 @@ title: "Success", description: "Congratulations! It worked!", color: "bg-green-500", + shadow: "shadow-green-500/50", }, { title: "Warning", description: "Please check again.", color: "bg-orange-500", + shadow: "shadow-orange-500/50", }, { title: "Error", description: "Something did not work!", color: "bg-red-500", + shadow: "shadow-red-500/50", }, ]; @@ -70,7 +74,7 @@
{#each toaster.toasts as t (t.id)} {@const toast = toaster.getToastFromToaster(t)} @@ -81,7 +85,7 @@ animate:flip={{ duration: 500 }} in:fly={{ duration: 150, x: "100%" }} out:fly={{ duration: 150, x: "100%" }} - class="rounded-lg bg-gray-800 text-white shadow-md" + class="{toast.data.shadow} rounded-lg bg-white shadow-md dark:bg-gray-800" >
@@ -98,13 +102,13 @@ {#if toast.closeDelay !== 0} -
+