diff --git a/badge/README.md b/badge/README.md new file mode 100644 index 0000000..ce5542b --- /dev/null +++ b/badge/README.md @@ -0,0 +1,112 @@ +# Badge +A compact badge component that displays a label with visual styling variants for different semantic meanings. + +## 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 targeting and accessibility | +| Label | TextNode | Badge | The text content displayed in the badge | +| Variant | Variant | info | Visual style variant for semantic meaning (success, warning, error, info) | +| Size | Variant | md | Size of the badge (sm, md, lg) | +| Show Close Button | Boolean | false | Whether to display the close button on the right side | +| Close Button Aria Label | Text | Remove badge | Accessible label for the close button | + +## 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 | +|---------------|------------------|----------| +| --border-radius | Badge corner rounding | 8px | + +### Inherited Properties + +The component inherits these CSS properties from its parent element: +- `font-family` — Typography style +- `color` — Text color (overridden by variant colors) +- `line-height` — Text spacing + +## Extending in Code + +### Custom Close Button Handler + +Add custom behavior when the close button is clicked: + +```javascript +document.addEventListener('DOMContentLoaded', () => { + const badges = document.querySelectorAll('.wf-badge'); + + badges.forEach(badge => { + const closeButton = badge.querySelector('.wf-badge-close'); + if (closeButton) { + closeButton.addEventListener('click', () => { + // Fade out animation + badge.style.transition = 'opacity 0.2s'; + badge.style.opacity = '0'; + + // Remove after animation + setTimeout(() => { + badge.remove(); + }, 200); + }); + } + }); +}); +``` + +### Dynamic Badge Creation + +Programmatically create badges with different variants: + +```javascript +function createBadge(label, variant = 'info', showClose = false) { + const badge = document.createElement('div'); + badge.className = `wf-badge wf-badge-${variant} wf-badge-md`; + + const labelSpan = document.createElement('span'); + labelSpan.className = 'wf-badge-label'; + labelSpan.textContent = label; + badge.appendChild(labelSpan); + + if (showClose) { + const closeBtn = document.createElement('button'); + closeBtn.className = 'wf-badge-close'; + closeBtn.setAttribute('aria-label', 'Remove badge'); + closeBtn.innerHTML = ''; + badge.appendChild(closeBtn); + } + + return badge; +} + +// Usage +const container = document.getElementById('badge-container'); +container.appendChild(createBadge('New', 'success', true)); +container.appendChild(createBadge('Pending', 'warning')); +``` + +## Dependencies + +No external dependencies. \ No newline at end of file diff --git a/badge/index.html b/badge/index.html new file mode 100644 index 0000000..f3ff2c4 --- /dev/null +++ b/badge/index.html @@ -0,0 +1,17 @@ + + + + + + Badge + + + +
+ + + diff --git a/badge/metadata.json b/badge/metadata.json new file mode 100644 index 0000000..91c0fc0 --- /dev/null +++ b/badge/metadata.json @@ -0,0 +1,5 @@ +{ + "name": "Badge", + "description": "Small status labels with color variants for tags and counts", + "category": "Data Display" +} diff --git a/badge/package.json b/badge/package.json new file mode 100644 index 0000000..52c3124 --- /dev/null +++ b/badge/package.json @@ -0,0 +1,25 @@ +{ + "name": "badge", + "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/badge/screenshot-brand.png b/badge/screenshot-brand.png new file mode 100644 index 0000000..3e91d9e Binary files /dev/null and b/badge/screenshot-brand.png differ diff --git a/badge/screenshot-dark.png b/badge/screenshot-dark.png new file mode 100644 index 0000000..9123a60 Binary files /dev/null and b/badge/screenshot-dark.png differ diff --git a/badge/screenshot-light.png b/badge/screenshot-light.png new file mode 100644 index 0000000..0dea7c7 Binary files /dev/null and b/badge/screenshot-light.png differ diff --git a/badge/src/components/Badge/Badge.css b/badge/src/components/Badge/Badge.css new file mode 100644 index 0000000..3065e48 --- /dev/null +++ b/badge/src/components/Badge/Badge.css @@ -0,0 +1,128 @@ +/* + * Webflow Site Variables Used: + * - --text-primary: Badge text color + * - --border-radius: Badge corner rounding + * - --background-primary: Base background for badges + * - --background-secondary: Hover states + * - --border-color: Badge borders + */ + +/* Box sizing reset */ +.wf-badge *, +.wf-badge *::before, +.wf-badge *::after { + box-sizing: border-box; +} + +/* Root element - inherit Webflow typography */ +.wf-badge { + font-family: inherit; + color: inherit; + line-height: inherit; + display: inline-flex; + align-items: center; + gap: 6px; + border-radius: var(--border-radius, 8px); + font-weight: 500; + white-space: nowrap; + border: 1px solid transparent; +} + +/* Size variants */ +.wf-badge-sm { + padding: 2px 8px; + font-size: 12px; + gap: 4px; +} + +.wf-badge-md { + padding: 4px 12px; + font-size: 14px; + gap: 6px; +} + +.wf-badge-lg { + padding: 6px 16px; + font-size: 16px; + gap: 8px; +} + +/* Color variants - Success */ +.wf-badge-success { + background: #dcfce7; + color: #166534; + border-color: #bbf7d0; +} + +/* Color variants - Warning */ +.wf-badge-warning { + background: #fef3c7; + color: #92400e; + border-color: #fde68a; +} + +/* Color variants - Error */ +.wf-badge-error { + background: #fee2e2; + color: #991b1b; + border-color: #fecaca; +} + +/* Color variants - Info */ +.wf-badge-info { + background: #dbeafe; + color: #1e40af; + border-color: #bfdbfe; +} + +/* Label */ +.wf-badge-label { + display: inline-block; +} + +/* Close button */ +.wf-badge-close { + display: inline-flex; + align-items: center; + justify-content: center; + background: transparent; + border: none; + padding: 0; + margin: 0; + cursor: pointer; + color: inherit; + opacity: 0.7; + transition: opacity 0.2s; + border-radius: 2px; +} + +.wf-badge-close:hover { + opacity: 1; +} + +.wf-badge-close:focus-visible { + outline: 2px solid currentColor; + outline-offset: 2px; + opacity: 1; +} + +.wf-badge-close:active { + opacity: 0.8; +} + +/* Close icon */ +.wf-badge-close-icon { + display: block; + width: 12px; + height: 12px; +} + +.wf-badge-sm .wf-badge-close-icon { + width: 10px; + height: 10px; +} + +.wf-badge-lg .wf-badge-close-icon { + width: 14px; + height: 14px; +} \ No newline at end of file diff --git a/badge/src/components/Badge/Badge.tsx b/badge/src/components/Badge/Badge.tsx new file mode 100644 index 0000000..06c5abc --- /dev/null +++ b/badge/src/components/Badge/Badge.tsx @@ -0,0 +1,64 @@ +import { useState } from "react"; + +export interface BadgeProps { + id?: string; + label?: string; + variant?: "success" | "warning" | "error" | "info"; + size?: "sm" | "md" | "lg"; + showCloseButton?: boolean; + closeButtonAriaLabel?: string; +} + +export default function Badge({ + id, + label = "Badge", + variant = "info", + size = "md", + showCloseButton = false, + closeButtonAriaLabel = "Remove badge", +}: BadgeProps) { + const [isVisible, setIsVisible] = useState(true); + + const handleClose = () => { + setIsVisible(false); + }; + + if (!isVisible) { + return null; + } + + return ( + + {label} + {showCloseButton && ( + + )} + + ); +} \ No newline at end of file diff --git a/badge/src/components/Badge/Badge.webflow.tsx b/badge/src/components/Badge/Badge.webflow.tsx new file mode 100644 index 0000000..173e973 --- /dev/null +++ b/badge/src/components/Badge/Badge.webflow.tsx @@ -0,0 +1,53 @@ +import Badge from "./Badge"; +import { props } from "@webflow/data-types"; +import { declareComponent } from "@webflow/react"; +import "./Badge.css"; + +export default declareComponent(Badge, { + name: "Badge", + description: "A compact badge component that displays a label with visual styling variants for different semantic meanings. The badge supports four color variants (success, warning, error, info) that apply appropriate background and text colors. Includes an optional close button that appears on the right side when enabled. The component is inline-block by default and works well for tags, status indicators, categories, and labels. The close button includes hover states and can trigger custom actions.", + group: "Content", + options: { + ssr: false, + applyTagSelectors: true + }, + props: { + id: props.Id({ + name: "Element ID", + group: "Settings", + tooltip: "HTML ID attribute for targeting and accessibility" + }), + label: props.TextNode({ + name: "Label", + defaultValue: "Badge", + group: "Content", + tooltip: "The text content displayed in the badge" + }), + variant: props.Variant({ + name: "Variant", + options: ["success", "warning", "error", "info"], + defaultValue: "info", + group: "Style", + tooltip: "Visual style variant for semantic meaning" + }), + size: props.Variant({ + name: "Size", + options: ["sm", "md", "lg"], + defaultValue: "md", + group: "Style", + tooltip: "Size of the badge" + }), + showCloseButton: props.Boolean({ + name: "Show Close Button", + defaultValue: false, + group: "Display", + tooltip: "Whether to display the close button on the right side" + }), + closeButtonAriaLabel: props.Text({ + name: "Close Button Label", + defaultValue: "Remove badge", + group: "Content", + tooltip: "Accessible label for the close button" + }) + } +}); \ No newline at end of file diff --git a/badge/src/components/Badge/BadgeSimple.webflow.tsx b/badge/src/components/Badge/BadgeSimple.webflow.tsx new file mode 100644 index 0000000..77b8295 --- /dev/null +++ b/badge/src/components/Badge/BadgeSimple.webflow.tsx @@ -0,0 +1,33 @@ +import Badge from "./Badge"; +import { props } from "@webflow/data-types"; +import { declareComponent } from "@webflow/react"; +import "./Badge.css"; + +export default declareComponent(Badge, { + name: "Badge (Simple)", + description: "A compact badge component that displays a label with visual styling variants for different semantic meanings. The badge supports four color variants (success, warning, error, info) that apply appropriate background and text colors. Includes an optional close button that appears on the right side when enabled. The component is inline-block by default and works well for tags, status indicators, categories, and labels. The close button includes hover states and can trigger custom actions.", + group: "Content", + options: { + ssr: false, + applyTagSelectors: true + }, + props: { + id: props.Id({ + name: "Element ID", + group: "Settings", + tooltip: "HTML ID attribute for targeting and accessibility" + }), + label: props.TextNode({ + name: "Label", + defaultValue: "Badge", + group: "Content", + tooltip: "The text content displayed in the badge" + }), + showCloseButton: props.Boolean({ + name: "Show Close Button", + defaultValue: false, + group: "Display", + tooltip: "Whether to display the close button on the right side" + }) + } +}); \ No newline at end of file diff --git a/badge/src/main.tsx b/badge/src/main.tsx new file mode 100644 index 0000000..07be837 --- /dev/null +++ b/badge/src/main.tsx @@ -0,0 +1,379 @@ +import { StrictMode, useState } from "react" +import { createRoot } from "react-dom/client" +import Badge from "./components/Badge/Badge" +import "./components/Badge/Badge.css" + +const themes = { + 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 = (varName: string, value: string) => { + setCustomVars((prev) => ({ + ...prev, + [varName]: value, + })) + } + + const pageBackgroundColor = activeTheme === "dark" ? "#000000" : activeTheme === "brand" ? "#fef7f0" : "#f9fafb" + + return ( +
+
+
+

+ Theme Preview +

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

+ Custom Theme Editor +

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

+ Badge Component +

+

+ A compact badge component that displays a label with visual styling variants for different semantic meanings. +

+ +
+

+ Default Badge +

+
+ +
+
+ +
+

+ Variant Examples +

+
+ + + + +
+
+ +
+

+ Size Variations +

+
+ + + +
+
+ +
+

+ With Close Button +

+
+ + + +
+
+
+
+
+ ) +} + +createRoot(document.getElementById("root")!).render( + + + +) \ No newline at end of file diff --git a/badge/src/vite-env.d.ts b/badge/src/vite-env.d.ts new file mode 100644 index 0000000..151aa68 --- /dev/null +++ b/badge/src/vite-env.d.ts @@ -0,0 +1 @@ +/// \ No newline at end of file diff --git a/badge/tsconfig.app.json b/badge/tsconfig.app.json new file mode 100644 index 0000000..d775f2a --- /dev/null +++ b/badge/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/badge/tsconfig.json b/badge/tsconfig.json new file mode 100644 index 0000000..65f670c --- /dev/null +++ b/badge/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/badge/tsconfig.node.json b/badge/tsconfig.node.json new file mode 100644 index 0000000..c4a9a48 --- /dev/null +++ b/badge/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/badge/vite.config.ts b/badge/vite.config.ts new file mode 100644 index 0000000..c7a4f78 --- /dev/null +++ b/badge/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/badge/webflow.json b/badge/webflow.json new file mode 100644 index 0000000..3f54235 --- /dev/null +++ b/badge/webflow.json @@ -0,0 +1,10 @@ +{ + "library": { + "name": "Badge", + "components": [ + "./src/**/*.webflow.@(js|jsx|mjs|ts|tsx)" + ], + "description": "A compact badge component that displays a label with visual styling variants for different semantic meanings. The badge supports four color variants (success, warning, error, info) that apply appropriate background and text colors. Includes an optional close button that appears on the right side when enabled. The component is inline-block by default and works well for tags, status indicators, categories, and labels. The close button includes hover states and can trigger custom actions.", + "id": "badge" + } +} \ No newline at end of file