diff --git a/toast-notification/README.md b/toast-notification/README.md new file mode 100644 index 0000000..f1a5835 --- /dev/null +++ b/toast-notification/README.md @@ -0,0 +1,117 @@ +# ToastNotification + +A toast notification system that displays temporary messages in a configurable screen corner with smooth animations and auto-dismiss functionality. + +## Getting Started + +Install dependencies: +```bash +npm install +``` + +Share the component to your Webflow workspace: +```bash +npx webflow library share +``` + +For local development: +```bash +npm run dev +``` + +## Designer Properties + +| Property | Type | Default | Description | +|----------|------|---------|-------------| +| ID | Id | — | HTML ID attribute for the toast container | +| Position | Variant | top-right | Screen corner position where toasts appear (top-right, top-left, bottom-right, bottom-left) | +| Variant | Variant | info | Visual style and color scheme of the toast (success, error, warning, info) | +| Message | TextNode | Notification message | Primary message text displayed prominently | +| Description | Text | Additional details about this notification | Optional secondary description text below the message | +| Show Description | Visibility | — | Show or hide the description text | +| Duration | Number | 5000 | Auto-dismiss duration in milliseconds | +| Show Progress Bar | Boolean | true | Display animated progress bar showing time until auto-dismiss | +| Show Close Button | Boolean | true | Display close button for manual dismissal | +| Max Toasts | Number | 5 | Maximum number of toasts visible simultaneously | +| Enable Animations | Boolean | true | Enable smooth slide and fade animations | +| Toast 1 Message | Text | Changes saved successfully | Message text for first demo toast | +| Toast 1 Description | Text | Your profile has been updated | Description text for first demo toast | +| Toast 1 Variant | Variant | success | Variant style for first demo toast | +| Toast 1 Visible | Visibility | — | Show or hide the first demo toast | +| Toast 2 Message | Text | Connection error | Message text for second demo toast | +| Toast 2 Description | Text | Unable to reach the server | Description text for second demo toast | +| Toast 2 Variant | Variant | error | Variant style for second demo toast | +| Toast 2 Visible | Visibility | — | Show or hide the second demo toast | +| Toast 3 Message | Text | Storage almost full | Message text for third demo toast | +| Toast 3 Description | Text | You have used 90% of your storage | Description text for third demo toast | +| Toast 3 Variant | Variant | warning | Variant style for third demo toast | +| Toast 3 Visible | Visibility | — | Show or hide the third demo toast | + +## Styling + +This component automatically adapts to your Webflow site's design system through site variables and inherited properties. + +### Site Variables + +To match your site's design system, define these CSS variables in your Webflow project settings. The component will use the fallback values shown below until you configure them. + +| Site Variable | What It Controls | Fallback | +|---------------|------------------|----------| +| --background-primary | Toast card background color | #ffffff | +| --background-secondary | Close button hover state background | #f5f5f5 | +| --text-primary | Message text color | #1a1a1a | +| --text-secondary | Description text and close button color | #737373 | +| --border-color | Toast card border and progress bar background | #e5e5e5 | +| --border-radius | Toast card and button corner rounding | 8px | +| --success-color | Success variant border, icon, and progress bar color | #22c55e | +| --success-bg | Success variant background tint | #f0fdf4 | +| --error-color | Error variant border, icon, and progress bar color | #ef4444 | +| --error-bg | Error variant background tint | #fef2f2 | +| --warning-color | Warning variant border, icon, and progress bar color | #f59e0b | +| --warning-bg | Warning variant background tint | #fffbeb | +| --info-color | Info variant border, icon, and progress bar color | #3b82f6 | +| --info-bg | Info variant background tint | #eff6ff | + +### Inherited Properties + +The component inherits these CSS properties from its parent element: +- `font-family` — Typography style +- `color` — Text color +- `line-height` — Text spacing + +## Extending in Code + +### Triggering Toasts Programmatically + +To show toasts dynamically based on user actions or application events: + +```javascript +// Access the toast container element +const toastContainer = document.querySelector('[data-component="toast-notification"]'); + +// Create and dispatch a custom event to trigger a new toast +const showToast = (message, description, variant = 'info') => { + const event = new CustomEvent('showToast', { + detail: { message, description, variant } + }); + toastContainer.dispatchEvent(event); +}; + +// Usage examples +showToast('Form submitted', 'Your data has been saved', 'success'); +showToast('Network error', 'Please check your connection', 'error'); +``` + +### Customizing Animation Duration + +Adjust the slide-in animation speed for different timing preferences: + +```css +.wf-toastnotification-animated .wf-toastnotification-card { + animation-duration: 0.5s; /* Slower entrance */ +} +``` + +## Dependencies + +No external dependencies. \ No newline at end of file diff --git a/toast-notification/index.html b/toast-notification/index.html new file mode 100644 index 0000000..90bfefd --- /dev/null +++ b/toast-notification/index.html @@ -0,0 +1,17 @@ + + + + + + ToastNotification + + + +
+ + + diff --git a/toast-notification/metadata.json b/toast-notification/metadata.json new file mode 100644 index 0000000..b57d731 --- /dev/null +++ b/toast-notification/metadata.json @@ -0,0 +1,5 @@ +{ + "name": "Toast Notifications", + "description": "Temporary notification messages with success, error, warning, and info variants. Auto-dismiss with progress bar.", + "category": "Feedback" +} diff --git a/toast-notification/package.json b/toast-notification/package.json new file mode 100644 index 0000000..66e6d89 --- /dev/null +++ b/toast-notification/package.json @@ -0,0 +1,25 @@ +{ + "name": "toast-notification", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc -b && vite build", + "preview": "vite preview" + }, + "dependencies": { + "react": "^19.1.1", + "react-dom": "^19.1.1" + }, + "devDependencies": { + "@types/react": "^19.1.13", + "@types/react-dom": "^19.1.9", + "@vitejs/plugin-react": "^5.0.3", + "@webflow/data-types": "^1.0.1", + "@webflow/react": "^1.0.1", + "@webflow/webflow-cli": "^1.8.44", + "typescript": "~5.8.3", + "vite": "^7.1.7" + } +} \ No newline at end of file diff --git a/toast-notification/screenshot-brand.png b/toast-notification/screenshot-brand.png new file mode 100644 index 0000000..3c0baad Binary files /dev/null and b/toast-notification/screenshot-brand.png differ diff --git a/toast-notification/screenshot-dark.png b/toast-notification/screenshot-dark.png new file mode 100644 index 0000000..fe92b5e Binary files /dev/null and b/toast-notification/screenshot-dark.png differ diff --git a/toast-notification/screenshot-light.png b/toast-notification/screenshot-light.png new file mode 100644 index 0000000..4394e44 Binary files /dev/null and b/toast-notification/screenshot-light.png differ diff --git a/toast-notification/src/components/ToastNotification/ToastNotification.css b/toast-notification/src/components/ToastNotification/ToastNotification.css new file mode 100644 index 0000000..31a4dc8 --- /dev/null +++ b/toast-notification/src/components/ToastNotification/ToastNotification.css @@ -0,0 +1,294 @@ +/* + * Webflow Site Variables Used: + * - --background-primary: Toast card background + * - --background-secondary: Close button hover state + * - --text-primary: Message text color + * - --text-secondary: Description text color + * - --border-color: Toast card border + * - --border-radius: Toast card and button rounding + * - --success-color: Success variant border and icon color + * - --success-bg: Success variant background tint + * - --error-color: Error variant border and icon color + * - --error-bg: Error variant background tint + * - --warning-color: Warning variant border and icon color + * - --warning-bg: Warning variant background tint + * - --info-color: Info variant border and icon color + * - --info-bg: Info variant background tint + */ + +/* Box sizing reset */ +.wf-toastnotification *, +.wf-toastnotification *::before, +.wf-toastnotification *::after { + box-sizing: border-box; +} + +/* Root container - fixed positioning for toast notifications */ +.wf-toastnotification { + font-family: inherit; + color: inherit; + line-height: inherit; + position: fixed; + z-index: 9999; + display: flex; + flex-direction: column; + gap: 12px; + pointer-events: none; + max-width: 420px; + width: calc(100% - 48px); +} + +/* Position variants */ +.wf-toastnotification-position-top-right { + top: 24px; + right: 24px; +} + +.wf-toastnotification-position-top-left { + top: 24px; + left: 24px; +} + +.wf-toastnotification-position-bottom-right { + bottom: 24px; + right: 24px; +} + +.wf-toastnotification-position-bottom-left { + bottom: 24px; + left: 24px; +} + +/* Toast card */ +.wf-toastnotification-card { + background: var(--background-primary, #ffffff); + border: 1px solid var(--border-color, #e5e5e5); + border-radius: var(--border-radius, 8px); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); + overflow: hidden; + pointer-events: auto; + position: relative; + border-left-width: 4px; +} + +/* Animation states */ +.wf-toastnotification-animated .wf-toastnotification-card { + animation: wf-toast-slide-in 0.3s ease-out; +} + +.wf-toastnotification-position-top-right.wf-toastnotification-animated .wf-toastnotification-card, +.wf-toastnotification-position-bottom-right.wf-toastnotification-animated .wf-toastnotification-card { + animation: wf-toast-slide-in-right 0.3s ease-out; +} + +.wf-toastnotification-position-top-left.wf-toastnotification-animated .wf-toastnotification-card, +.wf-toastnotification-position-bottom-left.wf-toastnotification-animated .wf-toastnotification-card { + animation: wf-toast-slide-in-left 0.3s ease-out; +} + +@keyframes wf-toast-slide-in-right { + from { + transform: translateX(100%); + opacity: 0; + } + to { + transform: translateX(0); + opacity: 1; + } +} + +@keyframes wf-toast-slide-in-left { + from { + transform: translateX(-100%); + opacity: 0; + } + to { + transform: translateX(0); + opacity: 1; + } +} + +/* Variant colors - success */ +.wf-toastnotification-variant-success { + border-left-color: var(--success-color, #22c55e); + background: var(--success-bg, #f0fdf4); +} + +.wf-toastnotification-variant-success .wf-toastnotification-icon { + color: var(--success-color, #22c55e); +} + +.wf-toastnotification-variant-success .wf-toastnotification-progress-bar { + background: var(--success-color, #22c55e); +} + +/* Variant colors - error */ +.wf-toastnotification-variant-error { + border-left-color: var(--error-color, #ef4444); + background: var(--error-bg, #fef2f2); +} + +.wf-toastnotification-variant-error .wf-toastnotification-icon { + color: var(--error-color, #ef4444); +} + +.wf-toastnotification-variant-error .wf-toastnotification-progress-bar { + background: var(--error-color, #ef4444); +} + +/* Variant colors - warning */ +.wf-toastnotification-variant-warning { + border-left-color: var(--warning-color, #f59e0b); + background: var(--warning-bg, #fffbeb); +} + +.wf-toastnotification-variant-warning .wf-toastnotification-icon { + color: var(--warning-color, #f59e0b); +} + +.wf-toastnotification-variant-warning .wf-toastnotification-progress-bar { + background: var(--warning-color, #f59e0b); +} + +/* Variant colors - info */ +.wf-toastnotification-variant-info { + border-left-color: var(--info-color, #3b82f6); + background: var(--info-bg, #eff6ff); +} + +.wf-toastnotification-variant-info .wf-toastnotification-icon { + color: var(--info-color, #3b82f6); +} + +.wf-toastnotification-variant-info .wf-toastnotification-progress-bar { + background: var(--info-color, #3b82f6); +} + +/* Content container */ +.wf-toastnotification-content { + display: flex; + align-items: flex-start; + gap: 12px; + padding: 16px; +} + +/* Icon wrapper */ +.wf-toastnotification-icon-wrapper { + flex-shrink: 0; + display: flex; + align-items: center; + justify-content: center; + width: 20px; + height: 20px; +} + +.wf-toastnotification-icon { + width: 20px; + height: 20px; + display: block; +} + +/* Text content */ +.wf-toastnotification-text { + flex: 1; + min-width: 0; +} + +.wf-toastnotification-message { + font-weight: 600; + font-size: 14px; + line-height: 1.4; + color: var(--text-primary, #1a1a1a); + margin: 0; +} + +.wf-toastnotification-description { + font-size: 13px; + line-height: 1.4; + color: var(--text-secondary, #737373); + margin-top: 4px; +} + +/* Close button */ +.wf-toastnotification-close { + flex-shrink: 0; + background: transparent; + border: none; + padding: 4px; + cursor: pointer; + border-radius: var(--border-radius, 8px); + color: var(--text-secondary, #737373); + transition: background-color 0.2s, color 0.2s; + display: flex; + align-items: center; + justify-content: center; + width: 24px; + height: 24px; + margin-top: -2px; +} + +.wf-toastnotification-close:hover { + background: var(--background-secondary, #f5f5f5); + color: var(--text-primary, #1a1a1a); +} + +.wf-toastnotification-close:focus-visible { + outline: 2px solid var(--info-color, #3b82f6); + outline-offset: 2px; +} + +.wf-toastnotification-close:active { + transform: scale(0.95); +} + +.wf-toastnotification-close svg { + width: 14px; + height: 14px; + display: block; +} + +/* Progress bar container */ +.wf-toastnotification-progress-container { + height: 4px; + background: var(--border-color, #e5e5e5); + overflow: hidden; +} + +.wf-toastnotification-progress-bar { + height: 100%; + transition: width 0.1s linear; +} + +/* Responsive adjustments */ +@media (max-width: 640px) { + .wf-toastnotification { + width: calc(100% - 32px); + max-width: none; + } + + .wf-toastnotification-position-top-right, + .wf-toastnotification-position-top-left { + top: 16px; + left: 16px; + right: 16px; + } + + .wf-toastnotification-position-bottom-right, + .wf-toastnotification-position-bottom-left { + bottom: 16px; + left: 16px; + right: 16px; + } + + .wf-toastnotification-content { + padding: 12px; + } + + .wf-toastnotification-message { + font-size: 13px; + } + + .wf-toastnotification-description { + font-size: 12px; + } +} \ No newline at end of file diff --git a/toast-notification/src/components/ToastNotification/ToastNotification.tsx b/toast-notification/src/components/ToastNotification/ToastNotification.tsx new file mode 100644 index 0000000..6026e05 --- /dev/null +++ b/toast-notification/src/components/ToastNotification/ToastNotification.tsx @@ -0,0 +1,207 @@ +import { useState, useEffect, useRef } from "react"; + +export interface ToastNotificationProps { + id?: string; + position?: "top-right" | "top-left" | "bottom-right" | "bottom-left"; + variant?: "success" | "error" | "warning" | "info"; + message?: React.ReactNode; + description?: string; + showDescription?: boolean; + duration?: number; + showProgressBar?: boolean; + showCloseButton?: boolean; + maxToasts?: number; + enableAnimations?: boolean; + toast1Message?: string; + toast1Description?: string; + toast1Variant?: "success" | "error" | "warning" | "info"; + toast1Visible?: boolean; + toast2Message?: string; + toast2Description?: string; + toast2Variant?: "success" | "error" | "warning" | "info"; + toast2Visible?: boolean; + toast3Message?: string; + toast3Description?: string; + toast3Variant?: "success" | "error" | "warning" | "info"; + toast3Visible?: boolean; +} + +interface Toast { + id: string; + message: React.ReactNode; + description?: string; + variant: "success" | "error" | "warning" | "info"; + progress: number; +} + +const variantIcons = { + success: ( + + + + ), + error: ( + + + + ), + warning: ( + + + + ), + info: ( + + + + ), +}; + +export default function ToastNotification({ + id, + position = "top-right", + variant = "info", + message = "Notification message", + description = "Additional details about this notification", + showDescription = true, + duration = 5000, + showProgressBar = true, + showCloseButton = true, + maxToasts = 5, + enableAnimations = true, + toast1Message = "Changes saved successfully", + toast1Description = "Your profile has been updated", + toast1Variant = "success", + toast1Visible = false, + toast2Message = "Connection error", + toast2Description = "Unable to reach the server", + toast2Variant = "error", + toast2Visible = false, + toast3Message = "Storage almost full", + toast3Description = "You have used 90% of your storage", + toast3Variant = "warning", + toast3Visible = false, +}: ToastNotificationProps) { + const [toasts, setToasts] = useState([]); + const progressIntervals = useRef>(new Map()); + const dismissTimeouts = useRef>(new Map()); + + const addToast = (toastMessage: React.ReactNode, toastDescription: string | undefined, toastVariant: "success" | "error" | "warning" | "info") => { + const newToast: Toast = { + id: `toast-${Date.now()}-${Math.random()}`, + message: toastMessage, + description: toastDescription, + variant: toastVariant, + progress: 100, + }; + + setToasts((prev) => { + const updated = [...prev, newToast]; + if (updated.length > maxToasts) { + const removed = updated.shift(); + if (removed) { + clearInterval(progressIntervals.current.get(removed.id)); + clearTimeout(dismissTimeouts.current.get(removed.id)); + progressIntervals.current.delete(removed.id); + dismissTimeouts.current.delete(removed.id); + } + } + return updated; + }); + + const progressInterval = setInterval(() => { + setToasts((prev) => + prev.map((t) => + t.id === newToast.id + ? { ...t, progress: Math.max(0, t.progress - (100 / (duration / 100))) } + : t + ) + ); + }, 100); + + const dismissTimeout = setTimeout(() => { + removeToast(newToast.id); + }, duration); + + progressIntervals.current.set(newToast.id, progressInterval); + dismissTimeouts.current.set(newToast.id, dismissTimeout); + }; + + const removeToast = (toastId: string) => { + clearInterval(progressIntervals.current.get(toastId)); + clearTimeout(dismissTimeouts.current.get(toastId)); + progressIntervals.current.delete(toastId); + dismissTimeouts.current.delete(toastId); + setToasts((prev) => prev.filter((t) => t.id !== toastId)); + }; + + useEffect(() => { + return () => { + progressIntervals.current.forEach((interval) => clearInterval(interval)); + dismissTimeouts.current.forEach((timeout) => clearTimeout(timeout)); + }; + }, []); + + useEffect(() => { + if (toast1Visible) { + addToast(toast1Message, toast1Description, toast1Variant); + } + }, [toast1Visible, toast1Message, toast1Description, toast1Variant]); + + useEffect(() => { + if (toast2Visible) { + addToast(toast2Message, toast2Description, toast2Variant); + } + }, [toast2Visible, toast2Message, toast2Description, toast2Variant]); + + useEffect(() => { + if (toast3Visible) { + addToast(toast3Message, toast3Description, toast3Variant); + } + }, [toast3Visible, toast3Message, toast3Description, toast3Variant]); + + const positionClass = `wf-toastnotification-position-${position}`; + const animationClass = enableAnimations ? "wf-toastnotification-animated" : ""; + + return ( +
+ {toasts.map((toast) => ( +
+
+
+ {variantIcons[toast.variant]} +
+
+
{toast.message}
+ {toast.description && ( +
{toast.description}
+ )} +
+ {showCloseButton && ( + + )} +
+ {showProgressBar && ( +
+
+
+ )} +
+ ))} +
+ ); +} \ No newline at end of file diff --git a/toast-notification/src/components/ToastNotification/ToastNotification.webflow.tsx b/toast-notification/src/components/ToastNotification/ToastNotification.webflow.tsx new file mode 100644 index 0000000..d7a4995 --- /dev/null +++ b/toast-notification/src/components/ToastNotification/ToastNotification.webflow.tsx @@ -0,0 +1,154 @@ +import ToastNotification from "./ToastNotification"; +import { props } from "@webflow/data-types"; +import { declareComponent } from "@webflow/react"; +import "./ToastNotification.css"; + +export default declareComponent(ToastNotification, { + name: "ToastNotification", + description: "A toast notification system that displays temporary messages in a configurable screen corner (top-right, top-left, bottom-right, bottom-left). Each toast card features a colored left border and icon matching the variant (success with green checkmark, error with red X, warning with yellow exclamation, info with blue info icon), followed by a bold message title and optional lighter description text below. A close button (X) appears on the right. A progress bar animates along the bottom edge, depleting over the auto-dismiss duration. Toasts stack vertically with 12px gaps, slide in smoothly from their corner direction, and fade out when dismissed. Maximum 5 toasts visible simultaneously; oldest auto-dismiss when the limit is exceeded. Fully responsive with appropriate padding and max-width constraints.", + group: "Interactive", + options: { + ssr: false, + applyTagSelectors: true + }, + props: { + id: props.Id({ + name: "Element ID", + group: "Settings", + tooltip: "HTML ID attribute for the toast container" + }), + position: props.Variant({ + name: "Position", + options: ["top-right", "top-left", "bottom-right", "bottom-left"], + defaultValue: "top-right", + group: "Style", + tooltip: "Screen corner position where toasts appear" + }), + variant: props.Variant({ + name: "Variant", + options: ["success", "error", "warning", "info"], + defaultValue: "info", + group: "Style", + tooltip: "Visual style and color scheme of the toast" + }), + message: props.TextNode({ + name: "Message", + defaultValue: "Notification message", + group: "Content", + tooltip: "Primary message text displayed prominently" + }), + description: props.Text({ + name: "Description", + defaultValue: "Additional details about this notification", + group: "Content", + tooltip: "Optional secondary description text below the message" + }), + showDescription: props.Visibility({ + name: "Show Description", + group: "Display", + tooltip: "Show or hide the description text" + }), + duration: props.Number({ + name: "Duration", + defaultValue: 5000, + group: "Behavior", + tooltip: "Auto-dismiss duration in milliseconds" + }), + showProgressBar: props.Boolean({ + name: "Show Progress Bar", + defaultValue: true, + group: "Display", + tooltip: "Display animated progress bar showing time until auto-dismiss" + }), + showCloseButton: props.Boolean({ + name: "Show Close Button", + defaultValue: true, + group: "Display", + tooltip: "Display close button for manual dismissal" + }), + maxToasts: props.Number({ + name: "Max Toasts", + defaultValue: 5, + group: "Behavior", + tooltip: "Maximum number of toasts visible simultaneously" + }), + enableAnimations: props.Boolean({ + name: "Enable Animations", + defaultValue: true, + group: "Behavior", + tooltip: "Enable smooth slide and fade animations" + }), + toast1Message: props.Text({ + name: "Toast 1 Message", + defaultValue: "Changes saved successfully", + group: "Demo Toast 1", + tooltip: "Message text for first demo toast" + }), + toast1Description: props.Text({ + name: "Toast 1 Description", + defaultValue: "Your profile has been updated", + group: "Demo Toast 1", + tooltip: "Description text for first demo toast" + }), + toast1Variant: props.Variant({ + name: "Toast 1 Variant", + options: ["success", "error", "warning", "info"], + defaultValue: "success", + group: "Demo Toast 1", + tooltip: "Variant style for first demo toast" + }), + toast1Visible: props.Visibility({ + name: "Toast 1 Visible", + group: "Demo Toast 1", + tooltip: "Show or hide the first demo toast" + }), + toast2Message: props.Text({ + name: "Toast 2 Message", + defaultValue: "Connection error", + group: "Demo Toast 2", + tooltip: "Message text for second demo toast" + }), + toast2Description: props.Text({ + name: "Toast 2 Description", + defaultValue: "Unable to reach the server", + group: "Demo Toast 2", + tooltip: "Description text for second demo toast" + }), + toast2Variant: props.Variant({ + name: "Toast 2 Variant", + options: ["success", "error", "warning", "info"], + defaultValue: "error", + group: "Demo Toast 2", + tooltip: "Variant style for second demo toast" + }), + toast2Visible: props.Visibility({ + name: "Toast 2 Visible", + group: "Demo Toast 2", + tooltip: "Show or hide the second demo toast" + }), + toast3Message: props.Text({ + name: "Toast 3 Message", + defaultValue: "Storage almost full", + group: "Demo Toast 3", + tooltip: "Message text for third demo toast" + }), + toast3Description: props.Text({ + name: "Toast 3 Description", + defaultValue: "You have used 90% of your storage", + group: "Demo Toast 3", + tooltip: "Description text for third demo toast" + }), + toast3Variant: props.Variant({ + name: "Toast 3 Variant", + options: ["success", "error", "warning", "info"], + defaultValue: "warning", + group: "Demo Toast 3", + tooltip: "Variant style for third demo toast" + }), + toast3Visible: props.Visibility({ + name: "Toast 3 Visible", + group: "Demo Toast 3", + tooltip: "Show or hide the third demo toast" + }) + } +}); \ No newline at end of file diff --git a/toast-notification/src/components/ToastNotification/ToastNotificationSimple.webflow.tsx b/toast-notification/src/components/ToastNotification/ToastNotificationSimple.webflow.tsx new file mode 100644 index 0000000..8fecdfd --- /dev/null +++ b/toast-notification/src/components/ToastNotification/ToastNotificationSimple.webflow.tsx @@ -0,0 +1,75 @@ +import ToastNotification from "./ToastNotification"; +import { props } from "@webflow/data-types"; +import { declareComponent } from "@webflow/react"; +import "./ToastNotification.css"; + +export default declareComponent(ToastNotification, { + name: "ToastNotification (Simple)", + description: "A toast notification system that displays temporary messages in a configurable screen corner (top-right, top-left, bottom-right, bottom-left). Each toast card features a colored left border and icon matching the variant (success with green checkmark, error with red X, warning with yellow exclamation, info with blue info icon), followed by a bold message title and optional lighter description text below. A close button (X) appears on the right. A progress bar animates along the bottom edge, depleting over the auto-dismiss duration. Toasts stack vertically with 12px gaps, slide in smoothly from their corner direction, and fade out when dismissed. Maximum 5 toasts visible simultaneously; oldest auto-dismiss when the limit is exceeded. Fully responsive with appropriate padding and max-width constraints.", + group: "Interactive", + options: { + ssr: false, + applyTagSelectors: true + }, + props: { + id: props.Id({ + name: "Element ID", + group: "Settings", + tooltip: "HTML ID attribute for the toast container" + }), + toast1Message: props.Text({ + name: "Toast 1 Message", + defaultValue: "Changes saved successfully", + group: "Demo Toast 1", + tooltip: "Message text for first demo toast" + }), + toast1Variant: props.Variant({ + name: "Toast 1 Variant", + options: ["success", "error", "warning", "info"], + defaultValue: "success", + group: "Demo Toast 1", + tooltip: "Variant style for first demo toast" + }), + toast1Visible: props.Visibility({ + name: "Toast 1 Visible", + group: "Demo Toast 1", + tooltip: "Show or hide the first demo toast" + }), + toast2Message: props.Text({ + name: "Toast 2 Message", + defaultValue: "Connection error", + group: "Demo Toast 2", + tooltip: "Message text for second demo toast" + }), + toast2Variant: props.Variant({ + name: "Toast 2 Variant", + options: ["success", "error", "warning", "info"], + defaultValue: "error", + group: "Demo Toast 2", + tooltip: "Variant style for second demo toast" + }), + toast2Visible: props.Visibility({ + name: "Toast 2 Visible", + group: "Demo Toast 2", + tooltip: "Show or hide the second demo toast" + }), + toast3Message: props.Text({ + name: "Toast 3 Message", + defaultValue: "Storage almost full", + group: "Demo Toast 3", + tooltip: "Message text for third demo toast" + }), + toast3Variant: props.Variant({ + name: "Toast 3 Variant", + options: ["success", "error", "warning", "info"], + defaultValue: "warning", + group: "Demo Toast 3", + tooltip: "Variant style for third demo toast" + }), + toast3Visible: props.Visibility({ + name: "Toast 3 Visible", + group: "Demo Toast 3", + tooltip: "Show or hide the third demo toast" + }) + } +}); \ No newline at end of file diff --git a/toast-notification/src/main.tsx b/toast-notification/src/main.tsx new file mode 100644 index 0000000..aa90ebf --- /dev/null +++ b/toast-notification/src/main.tsx @@ -0,0 +1,334 @@ +import { StrictMode, useState } from "react" +import { createRoot } from "react-dom/client" +import ToastNotification from "./components/ToastNotification/ToastNotification" +import "./components/ToastNotification/ToastNotification.css" + +type ThemeVars = { + "--background-primary": string + "--background-secondary": string + "--text-primary": string + "--text-secondary": string + "--border-color": string + "--accent-color": string + "--accent-text-color": string + "--border-radius": string +} + +const themes: Record = { + light: { + "--background-primary": "#ffffff", + "--background-secondary": "#f5f5f5", + "--text-primary": "#1a1a1a", + "--text-secondary": "#737373", + "--border-color": "#e5e5e5", + "--accent-color": "#2563eb", + "--accent-text-color": "#ffffff", + "--border-radius": "8px", + }, + dark: { + "--background-primary": "#0a0a0a", + "--background-secondary": "#1a1a1a", + "--text-primary": "#fafafa", + "--text-secondary": "#a3a3a3", + "--border-color": "#2a2a2a", + "--accent-color": "#3b82f6", + "--accent-text-color": "#ffffff", + "--border-radius": "8px", + }, + brand: { + "--background-primary": "#fef7f0", + "--background-secondary": "#fde8d0", + "--text-primary": "#1c1917", + "--text-secondary": "#78716c", + "--border-color": "#e7e5e4", + "--accent-color": "#ea580c", + "--accent-text-color": "#ffffff", + "--border-radius": "12px", + }, +} + +function App() { + const [activeTheme, setActiveTheme] = useState<"light" | "dark" | "brand" | "custom">("light") + const [customVars, setCustomVars] = useState(themes.light) + + const currentVars = activeTheme === "custom" ? customVars : themes[activeTheme] + + const handleThemeChange = (theme: "light" | "dark" | "brand" | "custom") => { + setActiveTheme(theme) + if (theme !== "custom") { + setCustomVars(themes[theme]) + } + } + + const handleCustomVarChange = (key: keyof ThemeVars, value: string) => { + setCustomVars((prev) => ({ ...prev, [key]: value })) + } + + const pageBackground = activeTheme === "dark" ? "#000000" : activeTheme === "brand" ? "#fef3e8" : "#f9fafb" + + return ( +
+
+
+

+ Toast Notification Preview +

+ +
+
+ {(["light", "dark", "brand", "custom"] as const).map((theme) => ( + + ))} +
+
+ + {activeTheme === "custom" && ( +
+

+ Custom Theme Editor +

+
+ {Object.entries(customVars).map(([key, value]) => ( +
+ + handleCustomVarChange(key as keyof ThemeVars, e.target.value)} + style={{ + width: "100%", + padding: "8px 12px", + border: `1px solid ${currentVars["--border-color"]}`, + borderRadius: "4px", + background: currentVars["--background-primary"], + color: currentVars["--text-primary"], + fontSize: "13px", + fontFamily: "monospace" + }} + /> +
+ ))} +
+
+ )} +
+ +
+
+

+ Default Configuration +

+ +
+ +
+

+ Bottom Left Position - Minimal Style +

+ +
+ +
+

+ Multiple Error Toasts - Top Left +

+ +
+ +
+

+ Long Duration - No Animations +

+ +
+
+
+
+ ) +} + +createRoot(document.getElementById("root")!).render( + + + +) \ No newline at end of file diff --git a/toast-notification/src/vite-env.d.ts b/toast-notification/src/vite-env.d.ts new file mode 100644 index 0000000..151aa68 --- /dev/null +++ b/toast-notification/src/vite-env.d.ts @@ -0,0 +1 @@ +/// \ No newline at end of file diff --git a/toast-notification/tsconfig.app.json b/toast-notification/tsconfig.app.json new file mode 100644 index 0000000..d775f2a --- /dev/null +++ b/toast-notification/tsconfig.app.json @@ -0,0 +1,27 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsBuildInfo", + "target": "ES2022", + "useDefineForClassFields": true, + "lib": [ + "ES2022", + "DOM", + "DOM.Iterable" + ], + "module": "ESNext", + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "noEmit": true, + "jsx": "react-jsx", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true + }, + "include": [ + "src" + ] +} \ No newline at end of file diff --git a/toast-notification/tsconfig.json b/toast-notification/tsconfig.json new file mode 100644 index 0000000..65f670c --- /dev/null +++ b/toast-notification/tsconfig.json @@ -0,0 +1,11 @@ +{ + "files": [], + "references": [ + { + "path": "./tsconfig.app.json" + }, + { + "path": "./tsconfig.node.json" + } + ] +} \ No newline at end of file diff --git a/toast-notification/tsconfig.node.json b/toast-notification/tsconfig.node.json new file mode 100644 index 0000000..c4a9a48 --- /dev/null +++ b/toast-notification/tsconfig.node.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsBuildInfo", + "target": "ES2023", + "lib": [ + "ES2023" + ], + "module": "ESNext", + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "noEmit": true, + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true + }, + "include": [ + "vite.config.ts" + ] +} \ No newline at end of file diff --git a/toast-notification/vite.config.ts b/toast-notification/vite.config.ts new file mode 100644 index 0000000..c7a4f78 --- /dev/null +++ b/toast-notification/vite.config.ts @@ -0,0 +1,6 @@ +import { defineConfig } from "vite"; +import react from "@vitejs/plugin-react"; + +export default defineConfig({ + plugins: [react()], +}); \ No newline at end of file diff --git a/toast-notification/webflow.json b/toast-notification/webflow.json new file mode 100644 index 0000000..7429d5e --- /dev/null +++ b/toast-notification/webflow.json @@ -0,0 +1,10 @@ +{ + "library": { + "name": "ToastNotification", + "components": [ + "./src/**/*.webflow.@(js|jsx|mjs|ts|tsx)" + ], + "description": "A toast notification system that displays temporary messages in a configurable screen corner (top-right, top-left, bottom-right, bottom-left). Each toast card features a colored left border and icon matching the variant (success with green checkmark, error with red X, warning with yellow exclamation, info with blue info icon), followed by a bold message title and optional lighter description text below. A close button (X) appears on the right. A progress bar animates along the bottom edge, depleting over the auto-dismiss duration. Toasts stack vertically with 12px gaps, slide in smoothly from their corner direction, and fade out when dismissed. Maximum 5 toasts visible simultaneously; oldest auto-dismiss when the limit is exceeded. Fully responsive with appropriate padding and max-width constraints.", + "id": "toast-notification" + } +} \ No newline at end of file