@empathyds/skills 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +51 -0
- package/bin/cli.js +267 -0
- package/package.json +26 -0
- package/skill/SKILL.md +184 -0
- package/skill/references/blocks.md +451 -0
- package/skill/references/component-api.md +374 -0
package/README.md
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# @empathyds/skills
|
|
2
|
+
|
|
3
|
+
Install the Empathy DS skill for Claude — build beautiful Vue 3 apps with AI.
|
|
4
|
+
|
|
5
|
+
## Quick Start
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npx @empathyds/skills
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
This launches an interactive installer that lets you:
|
|
12
|
+
|
|
13
|
+
1. **Install skill files locally** — for Claude Code or local development
|
|
14
|
+
2. **Export a .skill package** — for uploading to claude.ai
|
|
15
|
+
3. **Setup MCP server config** — for live component docs access
|
|
16
|
+
|
|
17
|
+
## Commands
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
# Interactive mode
|
|
21
|
+
npx @empathyds/skills
|
|
22
|
+
|
|
23
|
+
# Install skill files to a directory (default: .agent/skills/empathy-ds)
|
|
24
|
+
npx @empathyds/skills install
|
|
25
|
+
npx @empathyds/skills install ./my-skills/empathy
|
|
26
|
+
|
|
27
|
+
# Generate MCP config
|
|
28
|
+
npx @empathyds/skills mcp
|
|
29
|
+
|
|
30
|
+
# Help
|
|
31
|
+
npx @empathyds/skills help
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## What's Included
|
|
35
|
+
|
|
36
|
+
The skill teaches Claude to build beautiful Vue 3 applications using the Empathy DS component library:
|
|
37
|
+
|
|
38
|
+
- **Design thinking** — Claude picks an aesthetic direction before coding
|
|
39
|
+
- **60+ components** — Full API reference for all Empathy DS components
|
|
40
|
+
- **Page patterns** — Pre-built blocks for login, dashboard, settings, landing pages, and more
|
|
41
|
+
- **Visual polish** — Font pairings, spacing systems, hover states, transitions
|
|
42
|
+
|
|
43
|
+
## Skills vs MCP
|
|
44
|
+
|
|
45
|
+
| | Skill | MCP |
|
|
46
|
+
|---|---|---|
|
|
47
|
+
| **What** | Static knowledge & patterns | Live data & tools |
|
|
48
|
+
| **Use for** | Design principles, templates, aesthetic guidance | Fetching latest docs, validating usage, searching components |
|
|
49
|
+
| **Updates** | Re-install skill file | Automatic (server-side) |
|
|
50
|
+
|
|
51
|
+
For the best experience, use both together.
|
package/bin/cli.js
ADDED
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { existsSync, mkdirSync, copyFileSync, readFileSync, writeFileSync, readdirSync } from "fs";
|
|
4
|
+
import { resolve, join, dirname } from "path";
|
|
5
|
+
import { fileURLToPath } from "url";
|
|
6
|
+
import { createInterface } from "readline";
|
|
7
|
+
import { execSync } from "child_process";
|
|
8
|
+
|
|
9
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
10
|
+
const __dirname = dirname(__filename);
|
|
11
|
+
const SKILL_DIR = resolve(__dirname, "..", "skill");
|
|
12
|
+
|
|
13
|
+
// ── Colors ──────────────────────────────────────────
|
|
14
|
+
const c = {
|
|
15
|
+
reset: "\x1b[0m",
|
|
16
|
+
bold: "\x1b[1m",
|
|
17
|
+
dim: "\x1b[2m",
|
|
18
|
+
green: "\x1b[32m",
|
|
19
|
+
cyan: "\x1b[36m",
|
|
20
|
+
yellow: "\x1b[33m",
|
|
21
|
+
magenta: "\x1b[35m",
|
|
22
|
+
red: "\x1b[31m",
|
|
23
|
+
white: "\x1b[37m",
|
|
24
|
+
bgCyan: "\x1b[46m",
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
function log(msg = "") { console.log(msg); }
|
|
28
|
+
function success(msg) { log(`${c.green}✔${c.reset} ${msg}`); }
|
|
29
|
+
function info(msg) { log(`${c.cyan}ℹ${c.reset} ${msg}`); }
|
|
30
|
+
function warn(msg) { log(`${c.yellow}⚠${c.reset} ${msg}`); }
|
|
31
|
+
function error(msg) { log(`${c.red}✖${c.reset} ${msg}`); }
|
|
32
|
+
|
|
33
|
+
// ── Readline helper ─────────────────────────────────
|
|
34
|
+
function ask(question) {
|
|
35
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
36
|
+
return new Promise((res) => {
|
|
37
|
+
rl.question(question, (answer) => {
|
|
38
|
+
rl.close();
|
|
39
|
+
res(answer.trim());
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// ── Copy directory recursively ──────────────────────
|
|
45
|
+
function copyDirSync(src, dest) {
|
|
46
|
+
if (!existsSync(dest)) mkdirSync(dest, { recursive: true });
|
|
47
|
+
for (const entry of readdirSync(src, { withFileTypes: true })) {
|
|
48
|
+
const srcPath = join(src, entry.name);
|
|
49
|
+
const destPath = join(dest, entry.name);
|
|
50
|
+
if (entry.isDirectory()) {
|
|
51
|
+
copyDirSync(srcPath, destPath);
|
|
52
|
+
} else {
|
|
53
|
+
copyFileSync(srcPath, destPath);
|
|
54
|
+
success(` ${destPath}`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// ── Create .skill zip ───────────────────────────────
|
|
60
|
+
function createSkillPackage(outputDir) {
|
|
61
|
+
const outputPath = join(outputDir, "empathy-ds.skill");
|
|
62
|
+
try {
|
|
63
|
+
execSync(`cd "${resolve(SKILL_DIR, "..")}" && zip -r "${resolve(outputPath)}" skill/ -x "*.DS_Store"`, {
|
|
64
|
+
stdio: "pipe",
|
|
65
|
+
});
|
|
66
|
+
// Rename internal folder structure
|
|
67
|
+
} catch {
|
|
68
|
+
// Fallback: just copy the directory
|
|
69
|
+
warn("zip not available, copying raw skill files instead");
|
|
70
|
+
copyDirSync(SKILL_DIR, join(outputDir, "empathy-ds"));
|
|
71
|
+
return join(outputDir, "empathy-ds");
|
|
72
|
+
}
|
|
73
|
+
return outputPath;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// ── MCP Server config ───────────────────────────────
|
|
77
|
+
function generateMCPConfig(skillPath) {
|
|
78
|
+
return {
|
|
79
|
+
mcpServers: {
|
|
80
|
+
"empathy-ds-docs": {
|
|
81
|
+
command: "npx",
|
|
82
|
+
args: ["-y", "@anthropic-ai/mcp-server-filesystem", skillPath],
|
|
83
|
+
description: "Serves Empathy DS component docs and skill files to Claude"
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// ── Main ────────────────────────────────────────────
|
|
90
|
+
async function main() {
|
|
91
|
+
const args = process.argv.slice(2);
|
|
92
|
+
const command = args[0];
|
|
93
|
+
|
|
94
|
+
log();
|
|
95
|
+
log(`${c.bold}${c.magenta} ✦ Empathy DS — Claude Skill Installer${c.reset}`);
|
|
96
|
+
log(`${c.dim} Build beautiful Vue 3 apps with AI${c.reset}`);
|
|
97
|
+
log();
|
|
98
|
+
|
|
99
|
+
// Direct command mode
|
|
100
|
+
if (command === "install" || command === "i") {
|
|
101
|
+
await installSkill(args[1]);
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
if (command === "mcp") {
|
|
105
|
+
await setupMCP(args[1]);
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
if (command === "help" || command === "--help" || command === "-h") {
|
|
109
|
+
showHelp();
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Interactive mode
|
|
114
|
+
log(` ${c.bold}What would you like to do?${c.reset}`);
|
|
115
|
+
log();
|
|
116
|
+
log(` ${c.cyan}1${c.reset} Install skill files locally ${c.dim}(for Claude Code / local dev)${c.reset}`);
|
|
117
|
+
log(` ${c.cyan}2${c.reset} Export .skill package ${c.dim}(for uploading to claude.ai)${c.reset}`);
|
|
118
|
+
log(` ${c.cyan}3${c.reset} Setup MCP server config ${c.dim}(for live component docs)${c.reset}`);
|
|
119
|
+
log(` ${c.cyan}4${c.reset} All of the above`);
|
|
120
|
+
log();
|
|
121
|
+
|
|
122
|
+
const choice = await ask(` ${c.bold}Choose [1-4]:${c.reset} `);
|
|
123
|
+
log();
|
|
124
|
+
|
|
125
|
+
switch (choice) {
|
|
126
|
+
case "1":
|
|
127
|
+
await installSkill();
|
|
128
|
+
break;
|
|
129
|
+
case "2":
|
|
130
|
+
await exportPackage();
|
|
131
|
+
break;
|
|
132
|
+
case "3":
|
|
133
|
+
await setupMCP();
|
|
134
|
+
break;
|
|
135
|
+
case "4":
|
|
136
|
+
await installSkill();
|
|
137
|
+
log();
|
|
138
|
+
await exportPackage();
|
|
139
|
+
log();
|
|
140
|
+
await setupMCP();
|
|
141
|
+
break;
|
|
142
|
+
default:
|
|
143
|
+
error("Invalid choice. Run again and pick 1-4.");
|
|
144
|
+
process.exit(1);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
log();
|
|
148
|
+
log(`${c.dim} ─────────────────────────────────────────${c.reset}`);
|
|
149
|
+
log(` ${c.green}${c.bold}Done!${c.reset} Happy building ✦`);
|
|
150
|
+
log();
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// ── Install skill files to a directory ──────────────
|
|
154
|
+
async function installSkill(targetDir) {
|
|
155
|
+
if (!targetDir) {
|
|
156
|
+
targetDir = await ask(` ${c.bold}Install to directory${c.reset} ${c.dim}[.agent/skills/empathy-ds]:${c.reset} `);
|
|
157
|
+
if (!targetDir) targetDir = ".agent/skills/empathy-ds";
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const dest = resolve(process.cwd(), targetDir);
|
|
161
|
+
|
|
162
|
+
if (existsSync(dest)) {
|
|
163
|
+
const overwrite = await ask(` ${c.yellow}Directory exists. Overwrite? [y/N]:${c.reset} `);
|
|
164
|
+
if (overwrite.toLowerCase() !== "y") {
|
|
165
|
+
info("Skipped installation.");
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
log(` ${c.bold}Installing skill files...${c.reset}`);
|
|
171
|
+
copyDirSync(SKILL_DIR, dest);
|
|
172
|
+
log();
|
|
173
|
+
success(`Skill installed to ${c.cyan}${dest}${c.reset}`);
|
|
174
|
+
log();
|
|
175
|
+
info(`To use with Claude Code, point your skill config to this directory.`);
|
|
176
|
+
info(`SKILL.md + references/ are ready to go.`);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// ── Export .skill package ───────────────────────────
|
|
180
|
+
async function exportPackage() {
|
|
181
|
+
let outputDir = await ask(` ${c.bold}Export .skill file to${c.reset} ${c.dim}[.]:${c.reset} `);
|
|
182
|
+
if (!outputDir) outputDir = ".";
|
|
183
|
+
|
|
184
|
+
const dest = resolve(process.cwd(), outputDir);
|
|
185
|
+
if (!existsSync(dest)) mkdirSync(dest, { recursive: true });
|
|
186
|
+
|
|
187
|
+
log(` ${c.bold}Creating .skill package...${c.reset}`);
|
|
188
|
+
const outputPath = createSkillPackage(dest);
|
|
189
|
+
log();
|
|
190
|
+
success(`Package created: ${c.cyan}${outputPath}${c.reset}`);
|
|
191
|
+
log();
|
|
192
|
+
info(`Upload this file to ${c.bold}claude.ai → Settings → Skills${c.reset}`);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// ── Setup MCP ───────────────────────────────────────
|
|
196
|
+
async function setupMCP(skillPath) {
|
|
197
|
+
log(` ${c.bold}MCP Server Setup${c.reset}`);
|
|
198
|
+
log();
|
|
199
|
+
info("An MCP server can serve live component docs to Claude,");
|
|
200
|
+
info("so it always has access to the latest API reference.");
|
|
201
|
+
log();
|
|
202
|
+
|
|
203
|
+
if (!skillPath) {
|
|
204
|
+
skillPath = await ask(` ${c.bold}Path to skill directory${c.reset} ${c.dim}[.agent/skills/empathy-ds]:${c.reset} `);
|
|
205
|
+
if (!skillPath) skillPath = ".agent/skills/empathy-ds";
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const resolvedPath = resolve(process.cwd(), skillPath);
|
|
209
|
+
const config = generateMCPConfig(resolvedPath);
|
|
210
|
+
|
|
211
|
+
log();
|
|
212
|
+
log(` ${c.bold}Add this to your Claude MCP config:${c.reset}`);
|
|
213
|
+
log();
|
|
214
|
+
log(c.dim + " ┌──────────────────────────────────────────────────");
|
|
215
|
+
const configStr = JSON.stringify(config, null, 2);
|
|
216
|
+
for (const line of configStr.split("\n")) {
|
|
217
|
+
log(`${c.dim} │${c.reset} ${c.cyan}${line}${c.reset}`);
|
|
218
|
+
}
|
|
219
|
+
log(c.dim + " └──────────────────────────────────────────────────" + c.reset);
|
|
220
|
+
log();
|
|
221
|
+
|
|
222
|
+
const save = await ask(` ${c.bold}Save config to file? [y/N]:${c.reset} `);
|
|
223
|
+
if (save.toLowerCase() === "y") {
|
|
224
|
+
const configPath = resolve(process.cwd(), "mcp-config.json");
|
|
225
|
+
writeFileSync(configPath, configStr + "\n");
|
|
226
|
+
success(`Config saved to ${c.cyan}${configPath}${c.reset}`);
|
|
227
|
+
log();
|
|
228
|
+
info(`Merge this into your Claude Desktop / Claude Code MCP config file.`);
|
|
229
|
+
info(`Typically at: ${c.dim}~/.claude/mcp_servers.json${c.reset} or ${c.dim}claude_desktop_config.json${c.reset}`);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
log();
|
|
233
|
+
info(`${c.bold}For a richer MCP setup${c.reset}, consider building a custom MCP server that:`);
|
|
234
|
+
log(`${c.dim} • Searches components by name, category, or prop${c.reset}`);
|
|
235
|
+
log(`${c.dim} • Fetches live docs from empathyds.com${c.reset}`);
|
|
236
|
+
log(`${c.dim} • Validates component usage in Vue templates${c.reset}`);
|
|
237
|
+
log(`${c.dim} • Serves design tokens dynamically${c.reset}`);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// ── Help ────────────────────────────────────────────
|
|
241
|
+
function showHelp() {
|
|
242
|
+
log(` ${c.bold}Usage:${c.reset}`);
|
|
243
|
+
log();
|
|
244
|
+
log(` ${c.cyan}npx @empathyds/skills${c.reset} ${c.dim}Interactive mode${c.reset}`);
|
|
245
|
+
log(` ${c.cyan}npx @empathyds/skills install${c.reset} ${c.dim}Install skill files locally${c.reset}`);
|
|
246
|
+
log(` ${c.cyan}npx @empathyds/skills mcp${c.reset} ${c.dim}Setup MCP server config${c.reset}`);
|
|
247
|
+
log(` ${c.cyan}npx @empathyds/skills help${c.reset} ${c.dim}Show this help${c.reset}`);
|
|
248
|
+
log();
|
|
249
|
+
log(` ${c.bold}Options:${c.reset}`);
|
|
250
|
+
log();
|
|
251
|
+
log(` ${c.cyan}install [dir]${c.reset} Install skill files to a directory (default: .agent/skills/empathy-ds)`);
|
|
252
|
+
log(` ${c.cyan}mcp [dir]${c.reset} Generate MCP config pointing to skill directory`);
|
|
253
|
+
log();
|
|
254
|
+
log(` ${c.bold}Examples:${c.reset}`);
|
|
255
|
+
log();
|
|
256
|
+
log(` ${c.dim}# Interactive — choose what to do${c.reset}`);
|
|
257
|
+
log(` ${c.cyan}npx @empathyds/skills${c.reset}`);
|
|
258
|
+
log();
|
|
259
|
+
log(` ${c.dim}# Quick install to custom path${c.reset}`);
|
|
260
|
+
log(` ${c.cyan}npx @empathyds/skills install ./my-skills/empathy${c.reset}`);
|
|
261
|
+
log();
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
main().catch((err) => {
|
|
265
|
+
error(err.message);
|
|
266
|
+
process.exit(1);
|
|
267
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@empathyds/skills",
|
|
3
|
+
"version": "1.0.2",
|
|
4
|
+
"description": "Install the Empathy DS skill for Claude — build beautiful Vue 3 apps with AI",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"empathyds-skills": "./bin/cli.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"bin/",
|
|
11
|
+
"skill/",
|
|
12
|
+
"README.md"
|
|
13
|
+
],
|
|
14
|
+
"engines": {
|
|
15
|
+
"node": ">=16.0.0"
|
|
16
|
+
},
|
|
17
|
+
"keywords": [
|
|
18
|
+
"empathy-ds",
|
|
19
|
+
"claude",
|
|
20
|
+
"skill",
|
|
21
|
+
"design-system",
|
|
22
|
+
"vue3",
|
|
23
|
+
"mcp"
|
|
24
|
+
],
|
|
25
|
+
"license": "MIT"
|
|
26
|
+
}
|
package/skill/SKILL.md
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: empathy-ds
|
|
3
|
+
description: Build beautiful, accessible Vue 3 web applications using the Empathy DS design system. Use this skill when users want to create websites, web apps, UI components, or interfaces using Vue 3 with the Empathy DS component library. Triggers include requests to build Vue apps, create modern UIs, design web pages, build dashboards, landing pages, forms, admin panels, or use the organization's design system. Also triggers when styling, beautifying, or improving the look of any Vue 3 web UI using Empathy DS components. Do NOT use for React, plain HTML, or non-Vue projects unless specifically asked to use Empathy DS.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Empathy DS Skill
|
|
7
|
+
|
|
8
|
+
Build professional, visually striking Vue 3 applications using the Empathy DS design system — an opinionated, accessible component library with 60+ production-ready components.
|
|
9
|
+
|
|
10
|
+
## Design Thinking (Do This First)
|
|
11
|
+
|
|
12
|
+
Before writing any code, commit to a clear aesthetic direction. Empathy DS provides the components — **you provide the creative vision**.
|
|
13
|
+
|
|
14
|
+
### 1. Understand the Context
|
|
15
|
+
- **Purpose**: What problem does this interface solve? Who is the audience?
|
|
16
|
+
- **Tone**: Professional/corporate, playful/creative, minimal/editorial, warm/organic, bold/modern?
|
|
17
|
+
- **Density**: Data-heavy dashboard or spacious marketing page?
|
|
18
|
+
|
|
19
|
+
### 2. Choose an Aesthetic Direction
|
|
20
|
+
Pick a cohesive visual identity and execute it with intention:
|
|
21
|
+
- **Color strategy**: Use 1 dominant color + 1-2 accents. Empathy DS tokens (`bg-primary`, `text-muted-foreground`) are available as Tailwind utilities.
|
|
22
|
+
- **Typography**: Pair fonts thoughtfully — a distinctive display font with a clean body font. Import from Google Fonts via `<link>` or `@import`.
|
|
23
|
+
- **Spacing**: Generous whitespace for marketing/editorial; tighter spacing for data-heavy UIs. Use consistent rhythm (multiples of 4px or 8px).
|
|
24
|
+
- **Layout**: Choose asymmetric, grid-breaking, or structured grid layouts depending on context. CSS Grid and Flexbox are your primary tools.
|
|
25
|
+
|
|
26
|
+
### 3. Avoid Generic "AI Slop"
|
|
27
|
+
- Never default to the same fonts, colors, or layouts for every project
|
|
28
|
+
- Avoid: purple-gradient-on-white, Inter/Roboto everywhere, identical card grids
|
|
29
|
+
- Do: Match aesthetic to context, vary between light/dark themes, surprise with layout choices
|
|
30
|
+
|
|
31
|
+
## Setup
|
|
32
|
+
|
|
33
|
+
```ts
|
|
34
|
+
// main.ts
|
|
35
|
+
import { createApp } from "vue";
|
|
36
|
+
import App from "./App.vue";
|
|
37
|
+
import "@empathyds/vue/style.css"; // Required — always import first
|
|
38
|
+
|
|
39
|
+
createApp(App).mount("#app");
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
For monorepo setups with bundler resolution issues, add Vite alias:
|
|
43
|
+
```ts
|
|
44
|
+
// vite.config.ts
|
|
45
|
+
import { defineConfig } from "vite";
|
|
46
|
+
import path from "path";
|
|
47
|
+
export default defineConfig({
|
|
48
|
+
resolve: {
|
|
49
|
+
alias: {
|
|
50
|
+
"@empathyds/vue": path.resolve(__dirname, "node_modules/@empathyds/vue/dist/empathy-ds.es.js"),
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Component Library Overview
|
|
57
|
+
|
|
58
|
+
All imports come from `@empathyds/vue`. The library provides:
|
|
59
|
+
|
|
60
|
+
| Category | Components |
|
|
61
|
+
|---|---|
|
|
62
|
+
| **Forms** | Input, Select, Checkbox, RadioGroup, Switch, Textarea, Form, Field, Label, NumberField, PinInput, TagsInput, InputOtp, InputGroup |
|
|
63
|
+
| **Layout** | Card (+ CardHeader, CardTitle, CardDescription, CardContent, CardFooter), Separator, Sidebar, AspectRatio, Resizable, ScrollArea, Sheet |
|
|
64
|
+
| **Navigation** | Breadcrumb, NavigationMenu, Menubar, Tabs, Stepper, Pagination |
|
|
65
|
+
| **Feedback** | Alert, AlertDialog, Dialog, Drawer, Toast (Sonner), Spinner, Progress, Skeleton, Empty |
|
|
66
|
+
| **Data Display** | Table (+ TableHeader, TableBody, TableRow, TableHead, TableCell), Avatar, Badge, Chart, Carousel, Calendar, RangeCalendar |
|
|
67
|
+
| **Overlay** | Popover, Tooltip, HoverCard, ContextMenu, DropdownMenu, Command |
|
|
68
|
+
| **Interactive** | Button, ButtonGroup, Toggle, ToggleGroup, Slider, Collapsible, Accordion |
|
|
69
|
+
|
|
70
|
+
For detailed component props, slots, and variants: see `references/component-api.md`
|
|
71
|
+
For pre-built page patterns and blocks: see `references/blocks.md`
|
|
72
|
+
|
|
73
|
+
## Critical Rules
|
|
74
|
+
|
|
75
|
+
1. **Import `@empathyds/vue/style.css`** in main entry file — without it, nothing is styled.
|
|
76
|
+
|
|
77
|
+
2. **Use `<script setup>`** syntax (Vue 3 Composition API).
|
|
78
|
+
|
|
79
|
+
3. **Import every component explicitly** — no global registration:
|
|
80
|
+
```vue
|
|
81
|
+
<script setup>
|
|
82
|
+
import { Button, Card, CardContent, CardHeader, CardTitle } from "@empathyds/vue";
|
|
83
|
+
</script>
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
4. **Use Tailwind classes for all styling** — never use inline `style=""` attributes. Empathy DS design tokens are available as Tailwind utilities (`bg-primary`, `text-muted-foreground`, `border-input`, etc.).
|
|
87
|
+
|
|
88
|
+
## Layout & Styling Patterns
|
|
89
|
+
|
|
90
|
+
### Page Shell Pattern
|
|
91
|
+
Every page needs a shell:
|
|
92
|
+
|
|
93
|
+
```vue
|
|
94
|
+
<template>
|
|
95
|
+
<div class="min-h-screen bg-background text-foreground font-sans">
|
|
96
|
+
<header class="px-8 py-4 border-b border-border">
|
|
97
|
+
<slot name="header" />
|
|
98
|
+
</header>
|
|
99
|
+
<main class="max-w-7xl mx-auto p-8">
|
|
100
|
+
<slot />
|
|
101
|
+
</main>
|
|
102
|
+
</div>
|
|
103
|
+
</template>
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### Card Grid Pattern
|
|
107
|
+
Responsive columns with Tailwind grid:
|
|
108
|
+
```vue
|
|
109
|
+
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
|
|
110
|
+
<Card v-for="item in items" :key="item.id">...</Card>
|
|
111
|
+
</div>
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Dashboard Layout Pattern
|
|
115
|
+
Sidebar + content area:
|
|
116
|
+
```vue
|
|
117
|
+
<div class="grid grid-cols-[260px_1fr] min-h-screen">
|
|
118
|
+
<aside class="border-r border-border p-6">
|
|
119
|
+
<!-- Navigation -->
|
|
120
|
+
</aside>
|
|
121
|
+
<div class="p-8 overflow-y-auto">
|
|
122
|
+
<!-- Content -->
|
|
123
|
+
</div>
|
|
124
|
+
</div>
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### Spacing System
|
|
128
|
+
Use Tailwind's spacing scale consistently: `gap-1` (0.25rem), `gap-2` (0.5rem), `gap-3` (0.75rem), `gap-4` (1rem), `gap-6` (1.5rem), `gap-8` (2rem), `gap-12` (3rem), `gap-16` (4rem). Apply via Tailwind utility classes (`p-`, `m-`, `gap-`, `space-y-`, etc.).
|
|
129
|
+
|
|
130
|
+
### Font Loading
|
|
131
|
+
Import distinctive fonts to avoid generic output:
|
|
132
|
+
```html
|
|
133
|
+
<link href="https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;700&family=DM+Serif+Display&display=swap" rel="stylesheet">
|
|
134
|
+
```
|
|
135
|
+
Then apply via CSS:
|
|
136
|
+
```css
|
|
137
|
+
:root {
|
|
138
|
+
--font-heading: 'DM Serif Display', serif;
|
|
139
|
+
--font-body: 'DM Sans', sans-serif;
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
**Vary fonts per project.** Good pairings to rotate between:
|
|
144
|
+
- DM Serif Display + DM Sans (warm editorial)
|
|
145
|
+
- Playfair Display + Source Sans 3 (classic elegance)
|
|
146
|
+
- Space Grotesk + Inter (modern tech)
|
|
147
|
+
- Fraunces + Work Sans (friendly organic)
|
|
148
|
+
- Instrument Serif + Instrument Sans (refined minimal)
|
|
149
|
+
- Sora + Nunito (soft modern)
|
|
150
|
+
- Bebas Neue + Lato (bold impact)
|
|
151
|
+
|
|
152
|
+
## Visual Polish Checklist
|
|
153
|
+
|
|
154
|
+
Before delivering any output, verify:
|
|
155
|
+
|
|
156
|
+
- [ ] Custom fonts loaded (not just system defaults)
|
|
157
|
+
- [ ] Consistent color palette (2-4 colors max, using Tailwind + DS tokens)
|
|
158
|
+
- [ ] All styling uses Tailwind classes (no inline `style=""` attributes)
|
|
159
|
+
- [ ] Spacing is consistent and rhythmic
|
|
160
|
+
- [ ] Hover states on interactive elements (`hover:` utilities)
|
|
161
|
+
- [ ] Transitions on state changes (`transition-all duration-200 ease-in-out`)
|
|
162
|
+
- [ ] Visual hierarchy clear (headings > subheadings > body > muted text)
|
|
163
|
+
- [ ] Empty states handled gracefully (use `Empty` component)
|
|
164
|
+
- [ ] Responsive — works on mobile widths too
|
|
165
|
+
|
|
166
|
+
## Tailwind Integration
|
|
167
|
+
|
|
168
|
+
Tailwind is the default styling approach. Always use Tailwind utility classes instead of inline styles or scoped CSS.
|
|
169
|
+
|
|
170
|
+
```ts
|
|
171
|
+
// main.ts — import order matters
|
|
172
|
+
import "@empathyds/vue/style.css"; // First
|
|
173
|
+
import "./assets/main.css"; // Tailwind second
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
Empathy DS design tokens (`bg-primary`, `text-muted-foreground`, `border-input`, etc.) are available as Tailwind utilities. Use them for consistent theming.
|
|
177
|
+
|
|
178
|
+
## Additional Resources
|
|
179
|
+
|
|
180
|
+
- Full docs: https://empathyds.com/getting-started
|
|
181
|
+
- Components: https://empathyds.com/components/button
|
|
182
|
+
- Pre-built blocks: https://empathyds.com/blocks/login-form
|
|
183
|
+
- Design tokens: https://empathyds.com/tokens/colors
|
|
184
|
+
- LLM context: https://empathyds.com/llms.txt
|
|
@@ -0,0 +1,451 @@
|
|
|
1
|
+
# Pre-Built Blocks & Page Patterns
|
|
2
|
+
|
|
3
|
+
Copy and adapt these patterns as starting points. Always customize colors, fonts, and spacing to match the project's aesthetic direction. Use Tailwind utility classes for all styling.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
- [Login / Auth Form](#login--auth-form)
|
|
7
|
+
- [Dashboard Layout](#dashboard-layout)
|
|
8
|
+
- [Data Table Page](#data-table-page)
|
|
9
|
+
- [Settings Page](#settings-page)
|
|
10
|
+
- [Landing / Marketing Page](#landing--marketing-page)
|
|
11
|
+
- [Profile / Account Page](#profile--account-page)
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Login / Auth Form
|
|
16
|
+
|
|
17
|
+
Centered card with logo, form fields, and social auth options.
|
|
18
|
+
|
|
19
|
+
```vue
|
|
20
|
+
<script setup>
|
|
21
|
+
import { ref } from "vue";
|
|
22
|
+
import {
|
|
23
|
+
Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter,
|
|
24
|
+
Button, Input, Label, Field, Form, Separator, Checkbox
|
|
25
|
+
} from "@empathyds/vue";
|
|
26
|
+
|
|
27
|
+
const email = ref("");
|
|
28
|
+
const password = ref("");
|
|
29
|
+
const remember = ref(false);
|
|
30
|
+
</script>
|
|
31
|
+
|
|
32
|
+
<template>
|
|
33
|
+
<div class="min-h-screen flex items-center justify-center bg-muted p-4">
|
|
34
|
+
<Card class="w-full max-w-[420px]">
|
|
35
|
+
<CardHeader class="text-center">
|
|
36
|
+
<!-- Replace with your logo/brand -->
|
|
37
|
+
<div class="text-2xl font-bold mb-2">✦ Brand</div>
|
|
38
|
+
<CardTitle class="text-xl">Welcome back</CardTitle>
|
|
39
|
+
<CardDescription>Enter your credentials to continue</CardDescription>
|
|
40
|
+
</CardHeader>
|
|
41
|
+
<CardContent>
|
|
42
|
+
<Form @submit.prevent="handleLogin" class="flex flex-col gap-4">
|
|
43
|
+
<Field>
|
|
44
|
+
<Label for="email">Email</Label>
|
|
45
|
+
<Input id="email" v-model="email" type="email" placeholder="you@company.com" />
|
|
46
|
+
</Field>
|
|
47
|
+
<Field>
|
|
48
|
+
<div class="flex justify-between items-center">
|
|
49
|
+
<Label for="password">Password</Label>
|
|
50
|
+
<a href="#" class="text-xs text-primary hover:underline">Forgot?</a>
|
|
51
|
+
</div>
|
|
52
|
+
<Input id="password" v-model="password" type="password" />
|
|
53
|
+
</Field>
|
|
54
|
+
<Field class="flex items-center gap-2">
|
|
55
|
+
<Checkbox id="remember" v-model:checked="remember" />
|
|
56
|
+
<Label for="remember" class="text-sm">Remember me</Label>
|
|
57
|
+
</Field>
|
|
58
|
+
<Button type="submit" class="w-full">Sign in</Button>
|
|
59
|
+
</Form>
|
|
60
|
+
|
|
61
|
+
<div class="flex items-center gap-4 my-6">
|
|
62
|
+
<Separator class="flex-1" />
|
|
63
|
+
<span class="text-xs text-muted-foreground">OR</span>
|
|
64
|
+
<Separator class="flex-1" />
|
|
65
|
+
</div>
|
|
66
|
+
|
|
67
|
+
<Button variant="outline" class="w-full">Continue with Google</Button>
|
|
68
|
+
</CardContent>
|
|
69
|
+
<CardFooter class="justify-center">
|
|
70
|
+
<p class="text-xs text-muted-foreground">
|
|
71
|
+
Don't have an account? <a href="#" class="text-primary font-medium hover:underline">Sign up</a>
|
|
72
|
+
</p>
|
|
73
|
+
</CardFooter>
|
|
74
|
+
</Card>
|
|
75
|
+
</div>
|
|
76
|
+
</template>
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## Dashboard Layout
|
|
82
|
+
|
|
83
|
+
Sidebar navigation + header + content area with stat cards and data table.
|
|
84
|
+
|
|
85
|
+
```vue
|
|
86
|
+
<script setup>
|
|
87
|
+
import { ref } from "vue";
|
|
88
|
+
import {
|
|
89
|
+
Card, CardContent,
|
|
90
|
+
Button, Avatar, AvatarFallback, Badge,
|
|
91
|
+
Table, TableHeader, TableBody, TableRow, TableHead, TableCell,
|
|
92
|
+
DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem,
|
|
93
|
+
Tabs, TabsList, TabsTrigger, TabsContent,
|
|
94
|
+
} from "@empathyds/vue";
|
|
95
|
+
|
|
96
|
+
const navItems = [
|
|
97
|
+
{ label: "Dashboard", icon: "📊", active: true },
|
|
98
|
+
{ label: "Projects", icon: "📁", active: false },
|
|
99
|
+
{ label: "Team", icon: "👥", active: false },
|
|
100
|
+
{ label: "Settings", icon: "⚙️", active: false },
|
|
101
|
+
];
|
|
102
|
+
</script>
|
|
103
|
+
|
|
104
|
+
<template>
|
|
105
|
+
<div class="grid grid-cols-[260px_1fr] min-h-screen font-sans">
|
|
106
|
+
<!-- Sidebar -->
|
|
107
|
+
<aside class="border-r border-border p-6 flex flex-col gap-1">
|
|
108
|
+
<div class="font-bold text-lg px-2 mb-4">✦ AppName</div>
|
|
109
|
+
<button
|
|
110
|
+
v-for="item in navItems"
|
|
111
|
+
:key="item.label"
|
|
112
|
+
class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-sm text-left w-full transition-colors"
|
|
113
|
+
:class="item.active ? 'bg-primary text-primary-foreground' : 'text-foreground hover:bg-muted'"
|
|
114
|
+
>
|
|
115
|
+
<span>{{ item.icon }}</span>
|
|
116
|
+
{{ item.label }}
|
|
117
|
+
</button>
|
|
118
|
+
</aside>
|
|
119
|
+
|
|
120
|
+
<!-- Main Content -->
|
|
121
|
+
<div>
|
|
122
|
+
<!-- Top Header -->
|
|
123
|
+
<header class="flex justify-between items-center px-8 py-4 border-b border-border">
|
|
124
|
+
<h1 class="text-xl font-semibold">Dashboard</h1>
|
|
125
|
+
<Avatar>
|
|
126
|
+
<AvatarFallback>JD</AvatarFallback>
|
|
127
|
+
</Avatar>
|
|
128
|
+
</header>
|
|
129
|
+
|
|
130
|
+
<!-- Dashboard Content -->
|
|
131
|
+
<div class="p-8">
|
|
132
|
+
<!-- Stat Cards Row -->
|
|
133
|
+
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4 mb-8">
|
|
134
|
+
<Card v-for="stat in stats" :key="stat.label">
|
|
135
|
+
<CardContent>
|
|
136
|
+
<div class="text-xs text-muted-foreground mb-1">{{ stat.label }}</div>
|
|
137
|
+
<div class="text-3xl font-bold">{{ stat.value }}</div>
|
|
138
|
+
<div
|
|
139
|
+
class="text-xs mt-1"
|
|
140
|
+
:class="stat.trend > 0 ? 'text-green-600' : 'text-red-600'"
|
|
141
|
+
>
|
|
142
|
+
{{ stat.trend > 0 ? '↑' : '↓' }} {{ Math.abs(stat.trend) }}% from last month
|
|
143
|
+
</div>
|
|
144
|
+
</CardContent>
|
|
145
|
+
</Card>
|
|
146
|
+
</div>
|
|
147
|
+
|
|
148
|
+
<!-- Tabs with Table -->
|
|
149
|
+
<Tabs default-value="recent">
|
|
150
|
+
<TabsList>
|
|
151
|
+
<TabsTrigger value="recent">Recent</TabsTrigger>
|
|
152
|
+
<TabsTrigger value="popular">Popular</TabsTrigger>
|
|
153
|
+
</TabsList>
|
|
154
|
+
<TabsContent value="recent">
|
|
155
|
+
<!-- Insert Table component here -->
|
|
156
|
+
</TabsContent>
|
|
157
|
+
</Tabs>
|
|
158
|
+
</div>
|
|
159
|
+
</div>
|
|
160
|
+
</div>
|
|
161
|
+
</template>
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
---
|
|
165
|
+
|
|
166
|
+
## Data Table Page
|
|
167
|
+
|
|
168
|
+
Full-featured data table with search, filters, and pagination.
|
|
169
|
+
|
|
170
|
+
```vue
|
|
171
|
+
<script setup>
|
|
172
|
+
import { ref, computed } from "vue";
|
|
173
|
+
import {
|
|
174
|
+
Card, CardContent,
|
|
175
|
+
Button, Input, Badge,
|
|
176
|
+
Table, TableHeader, TableBody, TableRow, TableHead, TableCell,
|
|
177
|
+
DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem,
|
|
178
|
+
Select, SelectTrigger, SelectValue, SelectContent, SelectItem,
|
|
179
|
+
} from "@empathyds/vue";
|
|
180
|
+
|
|
181
|
+
const search = ref("");
|
|
182
|
+
const statusFilter = ref("all");
|
|
183
|
+
</script>
|
|
184
|
+
|
|
185
|
+
<template>
|
|
186
|
+
<div class="p-8 max-w-7xl mx-auto">
|
|
187
|
+
<div class="flex justify-between items-center mb-6">
|
|
188
|
+
<h1 class="text-2xl font-bold">Orders</h1>
|
|
189
|
+
<Button>+ New Order</Button>
|
|
190
|
+
</div>
|
|
191
|
+
|
|
192
|
+
<!-- Filters Row -->
|
|
193
|
+
<div class="flex gap-3 mb-4">
|
|
194
|
+
<Input v-model="search" placeholder="Search orders..." class="max-w-xs" />
|
|
195
|
+
<Select v-model="statusFilter">
|
|
196
|
+
<SelectTrigger class="w-40">
|
|
197
|
+
<SelectValue placeholder="Status" />
|
|
198
|
+
</SelectTrigger>
|
|
199
|
+
<SelectContent>
|
|
200
|
+
<SelectItem value="all">All Status</SelectItem>
|
|
201
|
+
<SelectItem value="active">Active</SelectItem>
|
|
202
|
+
<SelectItem value="completed">Completed</SelectItem>
|
|
203
|
+
<SelectItem value="cancelled">Cancelled</SelectItem>
|
|
204
|
+
</SelectContent>
|
|
205
|
+
</Select>
|
|
206
|
+
</div>
|
|
207
|
+
|
|
208
|
+
<!-- Table -->
|
|
209
|
+
<Card>
|
|
210
|
+
<CardContent class="p-0">
|
|
211
|
+
<Table>
|
|
212
|
+
<TableHeader>
|
|
213
|
+
<TableRow>
|
|
214
|
+
<TableHead>Order</TableHead>
|
|
215
|
+
<TableHead>Customer</TableHead>
|
|
216
|
+
<TableHead>Status</TableHead>
|
|
217
|
+
<TableHead class="text-right">Amount</TableHead>
|
|
218
|
+
<TableHead class="w-12"></TableHead>
|
|
219
|
+
</TableRow>
|
|
220
|
+
</TableHeader>
|
|
221
|
+
<TableBody>
|
|
222
|
+
<TableRow v-for="order in filteredOrders" :key="order.id">
|
|
223
|
+
<TableCell class="font-medium">#{{ order.id }}</TableCell>
|
|
224
|
+
<TableCell>{{ order.customer }}</TableCell>
|
|
225
|
+
<TableCell>
|
|
226
|
+
<Badge :variant="order.status === 'active' ? 'default' : order.status === 'completed' ? 'secondary' : 'destructive'">
|
|
227
|
+
{{ order.status }}
|
|
228
|
+
</Badge>
|
|
229
|
+
</TableCell>
|
|
230
|
+
<TableCell class="text-right">{{ order.amount }}</TableCell>
|
|
231
|
+
<TableCell>
|
|
232
|
+
<DropdownMenu>
|
|
233
|
+
<DropdownMenuTrigger as-child>
|
|
234
|
+
<Button variant="ghost" size="icon">⋯</Button>
|
|
235
|
+
</DropdownMenuTrigger>
|
|
236
|
+
<DropdownMenuContent>
|
|
237
|
+
<DropdownMenuItem>View</DropdownMenuItem>
|
|
238
|
+
<DropdownMenuItem>Edit</DropdownMenuItem>
|
|
239
|
+
</DropdownMenuContent>
|
|
240
|
+
</DropdownMenu>
|
|
241
|
+
</TableCell>
|
|
242
|
+
</TableRow>
|
|
243
|
+
</TableBody>
|
|
244
|
+
</Table>
|
|
245
|
+
</CardContent>
|
|
246
|
+
</Card>
|
|
247
|
+
</div>
|
|
248
|
+
</template>
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
---
|
|
252
|
+
|
|
253
|
+
## Settings Page
|
|
254
|
+
|
|
255
|
+
Sectioned form layout with cards for each settings group.
|
|
256
|
+
|
|
257
|
+
```vue
|
|
258
|
+
<script setup>
|
|
259
|
+
import { ref } from "vue";
|
|
260
|
+
import {
|
|
261
|
+
Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter,
|
|
262
|
+
Button, Input, Textarea, Label, Field, Form, Separator, Switch,
|
|
263
|
+
} from "@empathyds/vue";
|
|
264
|
+
|
|
265
|
+
const name = ref("");
|
|
266
|
+
const bio = ref("");
|
|
267
|
+
const emailNotifs = ref(true);
|
|
268
|
+
const pushNotifs = ref(false);
|
|
269
|
+
</script>
|
|
270
|
+
|
|
271
|
+
<template>
|
|
272
|
+
<div class="max-w-2xl mx-auto p-8">
|
|
273
|
+
<h1 class="text-2xl font-bold mb-2">Settings</h1>
|
|
274
|
+
<p class="text-muted-foreground mb-8">Manage your account preferences.</p>
|
|
275
|
+
|
|
276
|
+
<div class="flex flex-col gap-6">
|
|
277
|
+
<!-- Profile Section -->
|
|
278
|
+
<Card>
|
|
279
|
+
<CardHeader>
|
|
280
|
+
<CardTitle class="text-base">Profile</CardTitle>
|
|
281
|
+
<CardDescription>Your public-facing information.</CardDescription>
|
|
282
|
+
</CardHeader>
|
|
283
|
+
<CardContent class="flex flex-col gap-4">
|
|
284
|
+
<Field>
|
|
285
|
+
<Label for="name">Display name</Label>
|
|
286
|
+
<Input id="name" v-model="name" />
|
|
287
|
+
</Field>
|
|
288
|
+
<Field>
|
|
289
|
+
<Label for="bio">Bio</Label>
|
|
290
|
+
<Textarea id="bio" v-model="bio" rows="3" />
|
|
291
|
+
</Field>
|
|
292
|
+
</CardContent>
|
|
293
|
+
<CardFooter class="border-t border-border justify-end">
|
|
294
|
+
<Button>Save changes</Button>
|
|
295
|
+
</CardFooter>
|
|
296
|
+
</Card>
|
|
297
|
+
|
|
298
|
+
<!-- Notifications Section -->
|
|
299
|
+
<Card>
|
|
300
|
+
<CardHeader>
|
|
301
|
+
<CardTitle class="text-base">Notifications</CardTitle>
|
|
302
|
+
</CardHeader>
|
|
303
|
+
<CardContent class="flex flex-col gap-3">
|
|
304
|
+
<div class="flex justify-between items-center">
|
|
305
|
+
<div>
|
|
306
|
+
<div class="text-sm font-medium">Email notifications</div>
|
|
307
|
+
<div class="text-xs text-muted-foreground">Receive updates via email</div>
|
|
308
|
+
</div>
|
|
309
|
+
<Switch v-model:checked="emailNotifs" />
|
|
310
|
+
</div>
|
|
311
|
+
<Separator />
|
|
312
|
+
<div class="flex justify-between items-center">
|
|
313
|
+
<div>
|
|
314
|
+
<div class="text-sm font-medium">Push notifications</div>
|
|
315
|
+
<div class="text-xs text-muted-foreground">Browser push alerts</div>
|
|
316
|
+
</div>
|
|
317
|
+
<Switch v-model:checked="pushNotifs" />
|
|
318
|
+
</div>
|
|
319
|
+
</CardContent>
|
|
320
|
+
</Card>
|
|
321
|
+
</div>
|
|
322
|
+
</div>
|
|
323
|
+
</template>
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
---
|
|
327
|
+
|
|
328
|
+
## Landing / Marketing Page
|
|
329
|
+
|
|
330
|
+
Hero section + feature grid + CTA. Use bold typography and generous spacing.
|
|
331
|
+
|
|
332
|
+
```vue
|
|
333
|
+
<script setup>
|
|
334
|
+
import {
|
|
335
|
+
Card, CardContent, Button, Badge, Separator,
|
|
336
|
+
} from "@empathyds/vue";
|
|
337
|
+
|
|
338
|
+
const features = [
|
|
339
|
+
{ icon: "⚡", title: "Lightning Fast", description: "Built for speed with optimized rendering and lazy loading." },
|
|
340
|
+
{ icon: "🎨", title: "Beautiful by Default", description: "Opinionated design tokens ensure consistency across your app." },
|
|
341
|
+
{ icon: "♿", title: "Accessible", description: "WCAG compliant components that work for everyone." },
|
|
342
|
+
];
|
|
343
|
+
</script>
|
|
344
|
+
|
|
345
|
+
<template>
|
|
346
|
+
<div class="font-sans">
|
|
347
|
+
<!-- Hero -->
|
|
348
|
+
<section class="text-center py-24 px-8 max-w-3xl mx-auto">
|
|
349
|
+
<Badge variant="secondary" class="mb-6">Now in Beta</Badge>
|
|
350
|
+
<h1 class="font-heading text-5xl lg:text-7xl leading-tight font-bold mb-6">
|
|
351
|
+
Build something<br>people remember
|
|
352
|
+
</h1>
|
|
353
|
+
<p class="text-lg text-muted-foreground max-w-xl mx-auto mb-8 leading-relaxed">
|
|
354
|
+
A short, compelling description of what the product does and why it matters.
|
|
355
|
+
</p>
|
|
356
|
+
<div class="flex gap-3 justify-center">
|
|
357
|
+
<Button size="lg">Get Started</Button>
|
|
358
|
+
<Button size="lg" variant="outline">Learn More</Button>
|
|
359
|
+
</div>
|
|
360
|
+
</section>
|
|
361
|
+
|
|
362
|
+
<Separator />
|
|
363
|
+
|
|
364
|
+
<!-- Features Grid -->
|
|
365
|
+
<section class="py-16 px-8 max-w-5xl mx-auto">
|
|
366
|
+
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
|
|
367
|
+
<Card v-for="feature in features" :key="feature.title">
|
|
368
|
+
<CardContent>
|
|
369
|
+
<div class="text-3xl mb-3">{{ feature.icon }}</div>
|
|
370
|
+
<h3 class="font-semibold mb-2">{{ feature.title }}</h3>
|
|
371
|
+
<p class="text-muted-foreground text-sm leading-relaxed">
|
|
372
|
+
{{ feature.description }}
|
|
373
|
+
</p>
|
|
374
|
+
</CardContent>
|
|
375
|
+
</Card>
|
|
376
|
+
</div>
|
|
377
|
+
</section>
|
|
378
|
+
</div>
|
|
379
|
+
</template>
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
---
|
|
383
|
+
|
|
384
|
+
## Profile / Account Page
|
|
385
|
+
|
|
386
|
+
Header with avatar + tabs for different sections.
|
|
387
|
+
|
|
388
|
+
```vue
|
|
389
|
+
<script setup>
|
|
390
|
+
import { ref } from "vue";
|
|
391
|
+
import {
|
|
392
|
+
Avatar, AvatarImage, AvatarFallback, Button,
|
|
393
|
+
Tabs, TabsList, TabsTrigger, TabsContent,
|
|
394
|
+
} from "@empathyds/vue";
|
|
395
|
+
|
|
396
|
+
const user = ref({
|
|
397
|
+
name: "Jane Doe",
|
|
398
|
+
email: "jane@company.com",
|
|
399
|
+
initials: "JD",
|
|
400
|
+
avatar: "",
|
|
401
|
+
});
|
|
402
|
+
</script>
|
|
403
|
+
|
|
404
|
+
<template>
|
|
405
|
+
<div class="max-w-3xl mx-auto p-8">
|
|
406
|
+
<!-- Profile Header -->
|
|
407
|
+
<div class="flex items-center gap-6 mb-8">
|
|
408
|
+
<Avatar class="w-20 h-20 text-2xl">
|
|
409
|
+
<AvatarImage :src="user.avatar" />
|
|
410
|
+
<AvatarFallback>{{ user.initials }}</AvatarFallback>
|
|
411
|
+
</Avatar>
|
|
412
|
+
<div>
|
|
413
|
+
<h1 class="text-2xl font-bold">{{ user.name }}</h1>
|
|
414
|
+
<p class="text-muted-foreground">{{ user.email }}</p>
|
|
415
|
+
</div>
|
|
416
|
+
<Button variant="outline" class="ml-auto">Edit Profile</Button>
|
|
417
|
+
</div>
|
|
418
|
+
|
|
419
|
+
<!-- Tabbed Content -->
|
|
420
|
+
<Tabs default-value="overview">
|
|
421
|
+
<TabsList>
|
|
422
|
+
<TabsTrigger value="overview">Overview</TabsTrigger>
|
|
423
|
+
<TabsTrigger value="activity">Activity</TabsTrigger>
|
|
424
|
+
<TabsTrigger value="billing">Billing</TabsTrigger>
|
|
425
|
+
</TabsList>
|
|
426
|
+
<TabsContent value="overview" class="mt-6">
|
|
427
|
+
<!-- Overview cards -->
|
|
428
|
+
</TabsContent>
|
|
429
|
+
<TabsContent value="activity" class="mt-6">
|
|
430
|
+
<!-- Activity feed -->
|
|
431
|
+
</TabsContent>
|
|
432
|
+
<TabsContent value="billing" class="mt-6">
|
|
433
|
+
<!-- Billing info -->
|
|
434
|
+
</TabsContent>
|
|
435
|
+
</Tabs>
|
|
436
|
+
</div>
|
|
437
|
+
</template>
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
---
|
|
441
|
+
|
|
442
|
+
## Tips for Beautiful Output
|
|
443
|
+
|
|
444
|
+
1. **Custom fonts matter.** Load a distinctive font pair via Google Fonts and apply with Tailwind's `font-heading` / `font-sans` classes.
|
|
445
|
+
2. **Color consistency.** Use Empathy DS design tokens (`bg-primary`, `text-muted-foreground`, `border-border`) throughout.
|
|
446
|
+
3. **Whitespace is design.** When in doubt, add more `p-`, `m-`, and `gap-` values.
|
|
447
|
+
4. **Transitions everywhere.** Add `transition-all duration-150 ease-in-out` to interactive elements.
|
|
448
|
+
5. **Hover states.** Every clickable element needs `hover:` utilities for visual feedback.
|
|
449
|
+
6. **Border radius consistency.** Pick one and use it (`rounded-lg` or `rounded-xl`).
|
|
450
|
+
7. **Muted text for secondary info.** Use `text-muted-foreground` liberally.
|
|
451
|
+
8. **Never use inline styles.** Always use Tailwind utility classes.
|
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
# Component API Reference
|
|
2
|
+
|
|
3
|
+
Quick-reference for Empathy DS component props, slots, and usage. All imports from `@empathyds/vue`.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
- [Button](#button)
|
|
7
|
+
- [Card](#card)
|
|
8
|
+
- [Input & Forms](#input--forms)
|
|
9
|
+
- [Select](#select)
|
|
10
|
+
- [Table](#table)
|
|
11
|
+
- [Dialog & Drawer](#dialog--drawer)
|
|
12
|
+
- [Tabs](#tabs)
|
|
13
|
+
- [Alert](#alert)
|
|
14
|
+
- [Badge](#badge)
|
|
15
|
+
- [Avatar](#avatar)
|
|
16
|
+
- [Navigation Menu](#navigation-menu)
|
|
17
|
+
- [Sidebar](#sidebar)
|
|
18
|
+
- [Accordion](#accordion)
|
|
19
|
+
- [Toast (Sonner)](#toast-sonner)
|
|
20
|
+
- [Tooltip & Popover](#tooltip--popover)
|
|
21
|
+
- [Progress & Skeleton](#progress--skeleton)
|
|
22
|
+
- [Chart](#chart)
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## Button
|
|
27
|
+
|
|
28
|
+
```vue
|
|
29
|
+
<script setup>
|
|
30
|
+
import { Button } from "@empathyds/vue";
|
|
31
|
+
</script>
|
|
32
|
+
<template>
|
|
33
|
+
<Button variant="default" size="default">Click me</Button>
|
|
34
|
+
</template>
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
| Prop | Values | Default |
|
|
38
|
+
|------|--------|---------|
|
|
39
|
+
| `variant` | `default`, `destructive`, `outline`, `secondary`, `ghost`, `link` | `default` |
|
|
40
|
+
| `size` | `default`, `sm`, `lg`, `icon` | `default` |
|
|
41
|
+
| `disabled` | `boolean` | `false` |
|
|
42
|
+
| `as-child` | `boolean` | `false` |
|
|
43
|
+
|
|
44
|
+
**ButtonGroup**: Wrap multiple Buttons in `<ButtonGroup>` for connected button sets.
|
|
45
|
+
|
|
46
|
+
## Card
|
|
47
|
+
|
|
48
|
+
```vue
|
|
49
|
+
<script setup>
|
|
50
|
+
import { Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter } from "@empathyds/vue";
|
|
51
|
+
</script>
|
|
52
|
+
<template>
|
|
53
|
+
<Card>
|
|
54
|
+
<CardHeader>
|
|
55
|
+
<CardTitle>Title</CardTitle>
|
|
56
|
+
<CardDescription>Optional description text</CardDescription>
|
|
57
|
+
</CardHeader>
|
|
58
|
+
<CardContent>
|
|
59
|
+
Content goes here
|
|
60
|
+
</CardContent>
|
|
61
|
+
<CardFooter>
|
|
62
|
+
<Button>Action</Button>
|
|
63
|
+
</CardFooter>
|
|
64
|
+
</Card>
|
|
65
|
+
</template>
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Sub-components: `CardHeader`, `CardTitle`, `CardDescription`, `CardContent`, `CardFooter`.
|
|
69
|
+
|
|
70
|
+
## Input & Forms
|
|
71
|
+
|
|
72
|
+
```vue
|
|
73
|
+
<script setup>
|
|
74
|
+
import { ref } from "vue";
|
|
75
|
+
import { Form, Field, Label, Input, Textarea, Checkbox, Switch, RadioGroup, RadioGroupItem } from "@empathyds/vue";
|
|
76
|
+
|
|
77
|
+
const email = ref("");
|
|
78
|
+
const agreed = ref(false);
|
|
79
|
+
</script>
|
|
80
|
+
<template>
|
|
81
|
+
<Form @submit.prevent="handleSubmit">
|
|
82
|
+
<Field>
|
|
83
|
+
<Label for="email">Email</Label>
|
|
84
|
+
<Input id="email" v-model="email" type="email" placeholder="you@example.com" />
|
|
85
|
+
</Field>
|
|
86
|
+
|
|
87
|
+
<Field class="flex items-center gap-2">
|
|
88
|
+
<Checkbox id="terms" v-model:checked="agreed" />
|
|
89
|
+
<Label for="terms">I agree to terms</Label>
|
|
90
|
+
</Field>
|
|
91
|
+
|
|
92
|
+
<Button type="submit">Submit</Button>
|
|
93
|
+
</Form>
|
|
94
|
+
</template>
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
**Input props**: `type`, `placeholder`, `disabled`, `modelValue` (use `v-model`)
|
|
98
|
+
**Textarea props**: `placeholder`, `rows`, `disabled`, `modelValue`
|
|
99
|
+
**NumberField**: Numeric input with increment/decrement controls
|
|
100
|
+
**PinInput**: OTP-style segmented input
|
|
101
|
+
**TagsInput**: Multi-value tag entry
|
|
102
|
+
**InputGroup**: Group an Input with prefix/suffix elements
|
|
103
|
+
|
|
104
|
+
## Select
|
|
105
|
+
|
|
106
|
+
```vue
|
|
107
|
+
<script setup>
|
|
108
|
+
import { ref } from "vue";
|
|
109
|
+
import {
|
|
110
|
+
Select, SelectTrigger, SelectValue, SelectContent, SelectGroup,
|
|
111
|
+
SelectLabel, SelectItem, SelectSeparator
|
|
112
|
+
} from "@empathyds/vue";
|
|
113
|
+
|
|
114
|
+
const fruit = ref("");
|
|
115
|
+
</script>
|
|
116
|
+
<template>
|
|
117
|
+
<Select v-model="fruit">
|
|
118
|
+
<SelectTrigger class="w-[200px]">
|
|
119
|
+
<SelectValue placeholder="Pick a fruit" />
|
|
120
|
+
</SelectTrigger>
|
|
121
|
+
<SelectContent>
|
|
122
|
+
<SelectGroup>
|
|
123
|
+
<SelectLabel>Fruits</SelectLabel>
|
|
124
|
+
<SelectItem value="apple">Apple</SelectItem>
|
|
125
|
+
<SelectItem value="banana">Banana</SelectItem>
|
|
126
|
+
<SelectItem value="cherry">Cherry</SelectItem>
|
|
127
|
+
</SelectGroup>
|
|
128
|
+
</SelectContent>
|
|
129
|
+
</Select>
|
|
130
|
+
</template>
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## Table
|
|
134
|
+
|
|
135
|
+
```vue
|
|
136
|
+
<script setup>
|
|
137
|
+
import { Table, TableHeader, TableBody, TableRow, TableHead, TableCell } from "@empathyds/vue";
|
|
138
|
+
</script>
|
|
139
|
+
<template>
|
|
140
|
+
<Table>
|
|
141
|
+
<TableHeader>
|
|
142
|
+
<TableRow>
|
|
143
|
+
<TableHead>Name</TableHead>
|
|
144
|
+
<TableHead>Status</TableHead>
|
|
145
|
+
<TableHead class="text-right">Amount</TableHead>
|
|
146
|
+
</TableRow>
|
|
147
|
+
</TableHeader>
|
|
148
|
+
<TableBody>
|
|
149
|
+
<TableRow v-for="item in items" :key="item.id">
|
|
150
|
+
<TableCell class="font-medium">{{ item.name }}</TableCell>
|
|
151
|
+
<TableCell>{{ item.status }}</TableCell>
|
|
152
|
+
<TableCell class="text-right">{{ item.amount }}</TableCell>
|
|
153
|
+
</TableRow>
|
|
154
|
+
</TableBody>
|
|
155
|
+
</Table>
|
|
156
|
+
</template>
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
## Dialog & Drawer
|
|
160
|
+
|
|
161
|
+
```vue
|
|
162
|
+
<script setup>
|
|
163
|
+
import {
|
|
164
|
+
Dialog, DialogTrigger, DialogContent, DialogHeader,
|
|
165
|
+
DialogTitle, DialogDescription, DialogFooter, DialogClose
|
|
166
|
+
} from "@empathyds/vue";
|
|
167
|
+
</script>
|
|
168
|
+
<template>
|
|
169
|
+
<Dialog>
|
|
170
|
+
<DialogTrigger as-child>
|
|
171
|
+
<Button>Open Dialog</Button>
|
|
172
|
+
</DialogTrigger>
|
|
173
|
+
<DialogContent>
|
|
174
|
+
<DialogHeader>
|
|
175
|
+
<DialogTitle>Are you sure?</DialogTitle>
|
|
176
|
+
<DialogDescription>This action cannot be undone.</DialogDescription>
|
|
177
|
+
</DialogHeader>
|
|
178
|
+
<DialogFooter>
|
|
179
|
+
<DialogClose as-child>
|
|
180
|
+
<Button variant="outline">Cancel</Button>
|
|
181
|
+
</DialogClose>
|
|
182
|
+
<Button>Confirm</Button>
|
|
183
|
+
</DialogFooter>
|
|
184
|
+
</DialogContent>
|
|
185
|
+
</Dialog>
|
|
186
|
+
</template>
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
**Drawer** follows same pattern: `Drawer`, `DrawerTrigger`, `DrawerContent`, `DrawerHeader`, `DrawerTitle`, `DrawerDescription`, `DrawerFooter`, `DrawerClose`. Slides in from bottom/side.
|
|
190
|
+
|
|
191
|
+
**Sheet** is similar to Drawer: `Sheet`, `SheetTrigger`, `SheetContent`, `SheetHeader`, `SheetTitle`, `SheetDescription`, `SheetFooter`, `SheetClose`. Use `side` prop on SheetContent: `"top"`, `"right"`, `"bottom"`, `"left"`.
|
|
192
|
+
|
|
193
|
+
## Tabs
|
|
194
|
+
|
|
195
|
+
```vue
|
|
196
|
+
<script setup>
|
|
197
|
+
import { Tabs, TabsList, TabsTrigger, TabsContent } from "@empathyds/vue";
|
|
198
|
+
</script>
|
|
199
|
+
<template>
|
|
200
|
+
<Tabs default-value="overview">
|
|
201
|
+
<TabsList>
|
|
202
|
+
<TabsTrigger value="overview">Overview</TabsTrigger>
|
|
203
|
+
<TabsTrigger value="analytics">Analytics</TabsTrigger>
|
|
204
|
+
<TabsTrigger value="settings">Settings</TabsTrigger>
|
|
205
|
+
</TabsList>
|
|
206
|
+
<TabsContent value="overview">Overview content</TabsContent>
|
|
207
|
+
<TabsContent value="analytics">Analytics content</TabsContent>
|
|
208
|
+
<TabsContent value="settings">Settings content</TabsContent>
|
|
209
|
+
</Tabs>
|
|
210
|
+
</template>
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
## Alert
|
|
214
|
+
|
|
215
|
+
```vue
|
|
216
|
+
<script setup>
|
|
217
|
+
import { Alert, AlertTitle, AlertDescription } from "@empathyds/vue";
|
|
218
|
+
</script>
|
|
219
|
+
<template>
|
|
220
|
+
<Alert variant="destructive">
|
|
221
|
+
<AlertTitle>Error</AlertTitle>
|
|
222
|
+
<AlertDescription>Something went wrong.</AlertDescription>
|
|
223
|
+
</Alert>
|
|
224
|
+
</template>
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
| Prop | Values |
|
|
228
|
+
|------|--------|
|
|
229
|
+
| `variant` | `default`, `destructive` |
|
|
230
|
+
|
|
231
|
+
**AlertDialog**: Modal confirmation dialog. Uses `AlertDialog`, `AlertDialogTrigger`, `AlertDialogContent`, `AlertDialogHeader`, `AlertDialogTitle`, `AlertDialogDescription`, `AlertDialogFooter`, `AlertDialogAction`, `AlertDialogCancel`.
|
|
232
|
+
|
|
233
|
+
## Badge
|
|
234
|
+
|
|
235
|
+
```vue
|
|
236
|
+
<script setup>
|
|
237
|
+
import { Badge } from "@empathyds/vue";
|
|
238
|
+
</script>
|
|
239
|
+
<template>
|
|
240
|
+
<Badge variant="default">Active</Badge>
|
|
241
|
+
<Badge variant="secondary">Pending</Badge>
|
|
242
|
+
<Badge variant="destructive">Failed</Badge>
|
|
243
|
+
<Badge variant="outline">Draft</Badge>
|
|
244
|
+
</template>
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
## Avatar
|
|
248
|
+
|
|
249
|
+
```vue
|
|
250
|
+
<script setup>
|
|
251
|
+
import { Avatar, AvatarImage, AvatarFallback } from "@empathyds/vue";
|
|
252
|
+
</script>
|
|
253
|
+
<template>
|
|
254
|
+
<Avatar>
|
|
255
|
+
<AvatarImage src="https://example.com/photo.jpg" alt="User" />
|
|
256
|
+
<AvatarFallback>JD</AvatarFallback>
|
|
257
|
+
</Avatar>
|
|
258
|
+
</template>
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
## Navigation Menu
|
|
262
|
+
|
|
263
|
+
```vue
|
|
264
|
+
<script setup>
|
|
265
|
+
import {
|
|
266
|
+
NavigationMenu, NavigationMenuList, NavigationMenuItem,
|
|
267
|
+
NavigationMenuTrigger, NavigationMenuContent, NavigationMenuLink
|
|
268
|
+
} from "@empathyds/vue";
|
|
269
|
+
</script>
|
|
270
|
+
<template>
|
|
271
|
+
<NavigationMenu>
|
|
272
|
+
<NavigationMenuList>
|
|
273
|
+
<NavigationMenuItem>
|
|
274
|
+
<NavigationMenuTrigger>Getting Started</NavigationMenuTrigger>
|
|
275
|
+
<NavigationMenuContent>
|
|
276
|
+
<NavigationMenuLink href="/docs">Documentation</NavigationMenuLink>
|
|
277
|
+
</NavigationMenuContent>
|
|
278
|
+
</NavigationMenuItem>
|
|
279
|
+
</NavigationMenuList>
|
|
280
|
+
</NavigationMenu>
|
|
281
|
+
</template>
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
## Sidebar
|
|
285
|
+
|
|
286
|
+
Use `Sidebar`, `SidebarHeader`, `SidebarContent`, `SidebarFooter`, `SidebarGroup`, `SidebarGroupLabel`, `SidebarGroupContent`, `SidebarMenu`, `SidebarMenuItem`, `SidebarMenuButton`.
|
|
287
|
+
|
|
288
|
+
## Accordion
|
|
289
|
+
|
|
290
|
+
```vue
|
|
291
|
+
<script setup>
|
|
292
|
+
import { Accordion, AccordionItem, AccordionTrigger, AccordionContent } from "@empathyds/vue";
|
|
293
|
+
</script>
|
|
294
|
+
<template>
|
|
295
|
+
<Accordion type="single" collapsible>
|
|
296
|
+
<AccordionItem value="item-1">
|
|
297
|
+
<AccordionTrigger>Section 1</AccordionTrigger>
|
|
298
|
+
<AccordionContent>Content for section 1</AccordionContent>
|
|
299
|
+
</AccordionItem>
|
|
300
|
+
</Accordion>
|
|
301
|
+
</template>
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
## Toast (Sonner)
|
|
305
|
+
|
|
306
|
+
```vue
|
|
307
|
+
<script setup>
|
|
308
|
+
import { Toaster } from "@empathyds/vue";
|
|
309
|
+
import { toast } from "sonner";
|
|
310
|
+
|
|
311
|
+
function showToast() {
|
|
312
|
+
toast.success("Changes saved!");
|
|
313
|
+
}
|
|
314
|
+
</script>
|
|
315
|
+
<template>
|
|
316
|
+
<Toaster />
|
|
317
|
+
<Button @click="showToast">Save</Button>
|
|
318
|
+
</template>
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
Place `<Toaster />` once at root level. Use `toast()`, `toast.success()`, `toast.error()`, `toast.warning()`, `toast.info()`.
|
|
322
|
+
|
|
323
|
+
## Tooltip & Popover
|
|
324
|
+
|
|
325
|
+
```vue
|
|
326
|
+
<script setup>
|
|
327
|
+
import { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider } from "@empathyds/vue";
|
|
328
|
+
</script>
|
|
329
|
+
<template>
|
|
330
|
+
<TooltipProvider>
|
|
331
|
+
<Tooltip>
|
|
332
|
+
<TooltipTrigger as-child>
|
|
333
|
+
<Button variant="ghost">Hover me</Button>
|
|
334
|
+
</TooltipTrigger>
|
|
335
|
+
<TooltipContent>Helpful tooltip text</TooltipContent>
|
|
336
|
+
</Tooltip>
|
|
337
|
+
</TooltipProvider>
|
|
338
|
+
</template>
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
**Popover**: Same pattern with `Popover`, `PopoverTrigger`, `PopoverContent`. Stays open on click.
|
|
342
|
+
**HoverCard**: `HoverCard`, `HoverCardTrigger`, `HoverCardContent`. Shows on hover with richer content.
|
|
343
|
+
|
|
344
|
+
## Progress & Skeleton
|
|
345
|
+
|
|
346
|
+
```vue
|
|
347
|
+
<Progress :model-value="66" />
|
|
348
|
+
|
|
349
|
+
<Skeleton class="w-[200px] h-4" />
|
|
350
|
+
<Skeleton class="w-full h-16 rounded-lg" />
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
## Chart
|
|
354
|
+
|
|
355
|
+
Chart component wraps a charting library. Import `Chart` and pass data/config props. Refer to https://empathyds.com/components/chart for detailed configuration options.
|
|
356
|
+
|
|
357
|
+
---
|
|
358
|
+
|
|
359
|
+
## Other Components
|
|
360
|
+
|
|
361
|
+
- **Breadcrumb**: `Breadcrumb`, `BreadcrumbList`, `BreadcrumbItem`, `BreadcrumbLink`, `BreadcrumbSeparator`, `BreadcrumbPage`
|
|
362
|
+
- **Carousel**: `Carousel`, `CarouselContent`, `CarouselItem`, `CarouselPrevious`, `CarouselNext`
|
|
363
|
+
- **Calendar**: `Calendar`, `RangeCalendar` — date picking components
|
|
364
|
+
- **Collapsible**: `Collapsible`, `CollapsibleTrigger`, `CollapsibleContent`
|
|
365
|
+
- **Command**: `Command`, `CommandInput`, `CommandList`, `CommandEmpty`, `CommandGroup`, `CommandItem` — command palette / search
|
|
366
|
+
- **ContextMenu**: `ContextMenu`, `ContextMenuTrigger`, `ContextMenuContent`, `ContextMenuItem`
|
|
367
|
+
- **DropdownMenu**: `DropdownMenu`, `DropdownMenuTrigger`, `DropdownMenuContent`, `DropdownMenuItem`, `DropdownMenuSeparator`, `DropdownMenuLabel`
|
|
368
|
+
- **Menubar**: Full menu bar component system
|
|
369
|
+
- **Pagination**: `Pagination`, `PaginationList`, `PaginationPrev`, `PaginationNext`
|
|
370
|
+
- **Resizable**: `ResizablePanelGroup`, `ResizablePanel`, `ResizableHandle`
|
|
371
|
+
- **ScrollArea**: `ScrollArea`, `ScrollBar` — custom scrollbars
|
|
372
|
+
- **Slider**: Range slider input
|
|
373
|
+
- **Stepper**: Multi-step wizard component
|
|
374
|
+
- **Toggle / ToggleGroup**: On/off toggle buttons
|