@smicolon/fasla-ui 0.2.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 +141 -0
- package/bin/fasla-ui.mjs +54 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +241 -0
- package/dist/lib/utils.d.ts +9 -0
- package/dist/lib/utils.js +9 -0
- package/dist/motion/index.d.ts +33 -0
- package/dist/motion/index.js +59 -0
- package/dist/registry/index.d.ts +17 -0
- package/dist/registry/index.js +145 -0
- package/dist/tokens/index.d.ts +55 -0
- package/dist/tokens/index.js +34 -0
- package/package.json +97 -0
- package/registry/blocks/app-shell/AppShell.tsx +66 -0
- package/registry/blocks/app-shell/index.ts +1 -0
- package/registry/blocks/data-table/DataTable.tsx +292 -0
- package/registry/blocks/data-table/index.ts +7 -0
- package/registry/blocks/empty-state/EmptyState.test.tsx +120 -0
- package/registry/blocks/empty-state/EmptyState.tsx +164 -0
- package/registry/blocks/empty-state/index.ts +6 -0
- package/registry/blocks/form-section/FormSection.tsx +140 -0
- package/registry/blocks/form-section/index.ts +8 -0
- package/registry/blocks/navbar/Navbar.tsx +190 -0
- package/registry/blocks/navbar/index.ts +16 -0
- package/registry/blocks/page-header/PageHeader.tsx +64 -0
- package/registry/blocks/page-header/index.ts +1 -0
- package/registry/blocks/sidebar/Sidebar.tsx +202 -0
- package/registry/blocks/sidebar/index.ts +16 -0
- package/registry/blocks/stats-card/StatsCard.tsx +134 -0
- package/registry/blocks/stats-card/index.ts +6 -0
- package/registry/effects/animated-gradient/animated-gradient.tsx +136 -0
- package/registry/effects/animated-gradient/index.ts +6 -0
- package/registry/effects/border-beam/border-beam.tsx +132 -0
- package/registry/effects/border-beam/index.ts +6 -0
- package/registry/effects/glow-card/glow-card.tsx +145 -0
- package/registry/effects/glow-card/index.ts +6 -0
- package/registry/effects/shimmer-button/index.ts +1 -0
- package/registry/effects/shimmer-button/shimmer-button.tsx +91 -0
- package/registry/effects/spotlight/index.ts +6 -0
- package/registry/effects/spotlight/spotlight.tsx +162 -0
- package/registry/effects/text-reveal/index.ts +6 -0
- package/registry/effects/text-reveal/text-reveal.tsx +139 -0
- package/registry/effects/typewriter-text/index.ts +6 -0
- package/registry/effects/typewriter-text/typewriter-text.tsx +220 -0
- package/registry/ui/avatar/avatar.tsx +135 -0
- package/registry/ui/avatar/index.ts +1 -0
- package/registry/ui/badge/badge.test.tsx +51 -0
- package/registry/ui/badge/badge.tsx +39 -0
- package/registry/ui/badge/index.ts +1 -0
- package/registry/ui/button/button.test.tsx +87 -0
- package/registry/ui/button/button.tsx +88 -0
- package/registry/ui/button/index.ts +1 -0
- package/registry/ui/card/card.test.tsx +119 -0
- package/registry/ui/card/card.tsx +75 -0
- package/registry/ui/card/index.ts +8 -0
- package/registry/ui/checkbox/checkbox.tsx +113 -0
- package/registry/ui/checkbox/index.ts +1 -0
- package/registry/ui/combobox/combobox.tsx +410 -0
- package/registry/ui/combobox/index.ts +1 -0
- package/registry/ui/input/index.ts +1 -0
- package/registry/ui/input/input.test.tsx +78 -0
- package/registry/ui/input/input.tsx +76 -0
- package/registry/ui/select/index.ts +1 -0
- package/registry/ui/select/select.tsx +94 -0
- package/registry/ui/skeleton/index.ts +7 -0
- package/registry/ui/skeleton/skeleton.test.tsx +104 -0
- package/registry/ui/skeleton/skeleton.tsx +88 -0
- package/registry/ui/switch/index.ts +1 -0
- package/registry/ui/switch/switch.tsx +89 -0
- package/registry/ui/tabs/index.ts +10 -0
- package/registry/ui/tabs/tabs.tsx +150 -0
- package/registry/ui/textarea/index.ts +1 -0
- package/registry/ui/textarea/textarea.tsx +73 -0
- package/registry.json +247 -0
package/README.md
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# @smicolon/fasla-ui
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@smicolon/fasla-ui)
|
|
4
|
+
[](https://www.npmjs.com/package/@smicolon/fasla-ui)
|
|
5
|
+
[](https://github.com/smicolon/fasla-ui/blob/main/LICENSE)
|
|
6
|
+
|
|
7
|
+
A professional React UI component library built on **shadcn/ui** primitives. Provides animated effects components (like Magic UI), app building blocks, and design tokens - all distributed via a shadcn-compatible registry.
|
|
8
|
+
|
|
9
|
+
## Features
|
|
10
|
+
|
|
11
|
+
- **30+ Components** - UI primitives, app blocks, and animated effects
|
|
12
|
+
- **shadcn Compatible** - Works seamlessly with existing shadcn/ui projects
|
|
13
|
+
- **Tailwind CSS** - Utility-first styling with full customization
|
|
14
|
+
- **Framer Motion** - Smooth animations with `prefers-reduced-motion` support
|
|
15
|
+
- **TypeScript** - Full type safety and IntelliSense
|
|
16
|
+
- **Design Tokens** - Consistent spacing, colors, and density profiles
|
|
17
|
+
- **Accessible** - WCAG compliant with keyboard navigation support
|
|
18
|
+
- **Tree Shakeable** - Import only what you need
|
|
19
|
+
|
|
20
|
+
## Installation
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
# Using the CLI (recommended)
|
|
24
|
+
npx @smicolon/cli init
|
|
25
|
+
npx @smicolon/cli add button
|
|
26
|
+
|
|
27
|
+
# Or install the package directly
|
|
28
|
+
npm install @smicolon/fasla-ui
|
|
29
|
+
# or
|
|
30
|
+
yarn add @smicolon/fasla-ui
|
|
31
|
+
# or
|
|
32
|
+
pnpm add @smicolon/fasla-ui
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Components
|
|
36
|
+
|
|
37
|
+
### UI Primitives
|
|
38
|
+
Core building blocks with CVA variants for consistent styling:
|
|
39
|
+
|
|
40
|
+
| Component | Description |
|
|
41
|
+
|-----------|-------------|
|
|
42
|
+
| **Button** | Multiple variants (default, destructive, outline, secondary, ghost, link) with loading state |
|
|
43
|
+
| **Input** | Text input with validation states and icon support |
|
|
44
|
+
| **Textarea** | Multi-line text input with auto-resize |
|
|
45
|
+
| **Select** | Dropdown selection with search |
|
|
46
|
+
| **Checkbox** | Checkbox with label and description |
|
|
47
|
+
| **Switch** | Toggle switch with multiple sizes |
|
|
48
|
+
| **Tabs** | Tabbed navigation with animations |
|
|
49
|
+
| **Badge** | Status indicators and labels |
|
|
50
|
+
| **Avatar** | User avatars with fallback initials |
|
|
51
|
+
| **Card** | Flexible content containers |
|
|
52
|
+
| **Skeleton** | Loading placeholders |
|
|
53
|
+
| **Combobox** | Searchable select with autocomplete |
|
|
54
|
+
|
|
55
|
+
### App Blocks
|
|
56
|
+
Pre-built page compositions for rapid development:
|
|
57
|
+
|
|
58
|
+
| Component | Description |
|
|
59
|
+
|-----------|-------------|
|
|
60
|
+
| **AppShell** | Application layout with sidebar and header |
|
|
61
|
+
| **PageHeader** | Title, breadcrumbs, and action buttons |
|
|
62
|
+
| **DataTable** | Sortable, filterable tables with pagination |
|
|
63
|
+
| **FormSection** | Grouped form fields with validation |
|
|
64
|
+
| **EmptyState** | Empty content placeholders with CTAs |
|
|
65
|
+
| **Sidebar** | Collapsible navigation sidebar |
|
|
66
|
+
| **Navbar** | Top navigation bar |
|
|
67
|
+
| **StatsCard** | Metric display cards |
|
|
68
|
+
|
|
69
|
+
### Effects
|
|
70
|
+
Animated components for visual flair (powered by Framer Motion):
|
|
71
|
+
|
|
72
|
+
| Component | Description |
|
|
73
|
+
|-----------|-------------|
|
|
74
|
+
| **ShimmerButton** | Button with animated shimmer effect |
|
|
75
|
+
| **AnimatedGradient** | Smooth gradient background animations |
|
|
76
|
+
| **BorderBeam** | Animated border glow effect |
|
|
77
|
+
| **Spotlight** | Cursor-following spotlight |
|
|
78
|
+
| **TextReveal** | Text reveal on scroll |
|
|
79
|
+
| **GlowCard** | Card with hover glow effect |
|
|
80
|
+
| **TypewriterText** | Typewriter text animation |
|
|
81
|
+
|
|
82
|
+
## Quick Start
|
|
83
|
+
|
|
84
|
+
```tsx
|
|
85
|
+
import { Button } from "@smicolon/fasla-ui/registry/ui/button"
|
|
86
|
+
import { Card, CardHeader, CardTitle, CardContent } from "@smicolon/fasla-ui/registry/ui/card"
|
|
87
|
+
import { ShimmerButton } from "@smicolon/fasla-ui/registry/effects/shimmer-button"
|
|
88
|
+
|
|
89
|
+
export function MyComponent() {
|
|
90
|
+
return (
|
|
91
|
+
<Card>
|
|
92
|
+
<CardHeader>
|
|
93
|
+
<CardTitle>Welcome</CardTitle>
|
|
94
|
+
</CardHeader>
|
|
95
|
+
<CardContent className="space-x-4">
|
|
96
|
+
<Button variant="default">Get Started</Button>
|
|
97
|
+
<ShimmerButton>Learn More</ShimmerButton>
|
|
98
|
+
</CardContent>
|
|
99
|
+
</Card>
|
|
100
|
+
)
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## Peer Dependencies
|
|
105
|
+
|
|
106
|
+
```json
|
|
107
|
+
{
|
|
108
|
+
"react": "^18.0.0",
|
|
109
|
+
"react-dom": "^18.0.0",
|
|
110
|
+
"tailwindcss": "^3.4.0",
|
|
111
|
+
"framer-motion": "^11.0.0" // optional, required for effects
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## Tailwind Configuration
|
|
116
|
+
|
|
117
|
+
Add fasla-ui to your `tailwind.config.js`:
|
|
118
|
+
|
|
119
|
+
```js
|
|
120
|
+
module.exports = {
|
|
121
|
+
content: [
|
|
122
|
+
// ... your content paths
|
|
123
|
+
"./node_modules/@smicolon/fasla-ui/**/*.{js,ts,jsx,tsx}",
|
|
124
|
+
],
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## Documentation
|
|
129
|
+
|
|
130
|
+
Visit [ui.smicolon.com](https://ui.smicolon.com) for full documentation, examples, and API reference.
|
|
131
|
+
|
|
132
|
+
## Related
|
|
133
|
+
|
|
134
|
+
- [@smicolon/cli](https://www.npmjs.com/package/@smicolon/cli) - CLI for adding components
|
|
135
|
+
- [shadcn/ui](https://ui.shadcn.com) - The foundation this library builds on
|
|
136
|
+
- [Tailwind CSS](https://tailwindcss.com) - Utility-first CSS framework
|
|
137
|
+
- [Framer Motion](https://www.framer.com/motion/) - Animation library for React
|
|
138
|
+
|
|
139
|
+
## License
|
|
140
|
+
|
|
141
|
+
MIT - See [LICENSE](https://github.com/smicolon/fasla-ui/blob/main/LICENSE) for details.
|
package/bin/fasla-ui.mjs
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
|
|
5
|
+
function usage() {
|
|
6
|
+
console.log(`fasla-ui (scaffold)
|
|
7
|
+
Usage:
|
|
8
|
+
fasla-ui add <registry-path> --to <target-dir>
|
|
9
|
+
|
|
10
|
+
Example:
|
|
11
|
+
fasla-ui add blocks/app-shell --to ../client/src/components/blocks
|
|
12
|
+
`);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function copyDir(src, dest) {
|
|
16
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
17
|
+
for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
|
|
18
|
+
const srcPath = path.join(src, entry.name);
|
|
19
|
+
const destPath = path.join(dest, entry.name);
|
|
20
|
+
if (entry.isDirectory()) copyDir(srcPath, destPath);
|
|
21
|
+
else fs.copyFileSync(srcPath, destPath);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const args = process.argv.slice(2);
|
|
26
|
+
if (args.length === 0 || args.includes("-h") || args.includes("--help")) {
|
|
27
|
+
usage();
|
|
28
|
+
process.exit(0);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const cmd = args[0];
|
|
32
|
+
if (cmd !== "add") {
|
|
33
|
+
usage();
|
|
34
|
+
process.exit(1);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const item = args[1];
|
|
38
|
+
const toIdx = args.findIndex((a) => a === "--to");
|
|
39
|
+
const targetDir = toIdx >= 0 ? args[toIdx + 1] : null;
|
|
40
|
+
if (!item || !targetDir) {
|
|
41
|
+
usage();
|
|
42
|
+
process.exit(1);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const registryRoot = path.resolve(process.cwd(), "packages/fasla-ui/registry");
|
|
46
|
+
const srcDir = path.join(registryRoot, item);
|
|
47
|
+
if (!fs.existsSync(srcDir)) {
|
|
48
|
+
console.error(`Registry path not found: ${srcDir}`);
|
|
49
|
+
process.exit(1);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const destDir = path.resolve(process.cwd(), targetDir, path.basename(item));
|
|
53
|
+
copyDir(srcDir, destDir);
|
|
54
|
+
console.log(`Added ${item} -> ${destDir}`);
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { cn } from './lib/utils.js';
|
|
2
|
+
export { Density, TokenSet, baseTokens, tailwindSemanticColors } from './tokens/index.js';
|
|
3
|
+
export { MotionPreset, useMotionPresets } from './motion/index.js';
|
|
4
|
+
export { RegistryItem, RegistryItemType, getRegistryItem, getRegistryItemsByCategory, getRegistryItemsByType, registry } from './registry/index.js';
|
|
5
|
+
import 'clsx';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import { clsx } from 'clsx';
|
|
2
|
+
import { twMerge } from 'tailwind-merge';
|
|
3
|
+
import { useReducedMotion } from 'framer-motion';
|
|
4
|
+
|
|
5
|
+
// src/lib/utils.ts
|
|
6
|
+
function cn(...inputs) {
|
|
7
|
+
return twMerge(clsx(inputs));
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
// src/tokens/model.ts
|
|
11
|
+
var baseTokens = {
|
|
12
|
+
radius: { sm: "0.5rem", md: "0.75rem", lg: "1rem" },
|
|
13
|
+
spacing: { densityMultiplier: { compact: 0.9, comfortable: 1, spacious: 1.1 } },
|
|
14
|
+
typography: { fontFamily: "Inter", baseFontSizePx: 16, lineHeight: 1.5 },
|
|
15
|
+
motion: { durationsMs: { fast: 120, base: 180, slow: 260 } }
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
// src/tokens/tailwind.ts
|
|
19
|
+
var tailwindSemanticColors = {
|
|
20
|
+
border: "hsl(var(--border))",
|
|
21
|
+
input: "hsl(var(--input))",
|
|
22
|
+
ring: "hsl(var(--ring))",
|
|
23
|
+
background: "hsl(var(--background))",
|
|
24
|
+
foreground: "hsl(var(--foreground))",
|
|
25
|
+
primary: {
|
|
26
|
+
DEFAULT: "hsl(var(--primary))",
|
|
27
|
+
foreground: "hsl(var(--primary-foreground))"
|
|
28
|
+
},
|
|
29
|
+
secondary: {
|
|
30
|
+
DEFAULT: "hsl(var(--secondary))",
|
|
31
|
+
foreground: "hsl(var(--secondary-foreground))"
|
|
32
|
+
},
|
|
33
|
+
muted: {
|
|
34
|
+
DEFAULT: "hsl(var(--muted))",
|
|
35
|
+
foreground: "hsl(var(--muted-foreground))"
|
|
36
|
+
},
|
|
37
|
+
destructive: {
|
|
38
|
+
DEFAULT: "hsl(var(--destructive))",
|
|
39
|
+
foreground: "hsl(var(--destructive-foreground))"
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
function useMotionPresets() {
|
|
43
|
+
const prefersReduced = useReducedMotion();
|
|
44
|
+
const durations = {
|
|
45
|
+
fast: 0.12,
|
|
46
|
+
base: 0.18,
|
|
47
|
+
slow: 0.26
|
|
48
|
+
};
|
|
49
|
+
const easing = {
|
|
50
|
+
standard: [0.2, 0, 0, 1],
|
|
51
|
+
emphasized: [0.2, 0.8, 0.2, 1]
|
|
52
|
+
};
|
|
53
|
+
const safe = (preset) => {
|
|
54
|
+
if (!prefersReduced) return preset;
|
|
55
|
+
return {
|
|
56
|
+
initial: { opacity: 0 },
|
|
57
|
+
animate: { opacity: 1 },
|
|
58
|
+
exit: { opacity: 0 },
|
|
59
|
+
transition: { duration: durations.fast }
|
|
60
|
+
};
|
|
61
|
+
};
|
|
62
|
+
return {
|
|
63
|
+
fadeIn: safe({
|
|
64
|
+
initial: { opacity: 0 },
|
|
65
|
+
animate: { opacity: 1 },
|
|
66
|
+
exit: { opacity: 0 },
|
|
67
|
+
transition: { duration: durations.base, ease: easing.standard }
|
|
68
|
+
}),
|
|
69
|
+
slideUp: safe({
|
|
70
|
+
initial: { opacity: 0, y: 10 },
|
|
71
|
+
animate: { opacity: 1, y: 0 },
|
|
72
|
+
exit: { opacity: 0, y: 6 },
|
|
73
|
+
transition: { duration: durations.base, ease: easing.standard }
|
|
74
|
+
}),
|
|
75
|
+
modalScale: safe({
|
|
76
|
+
initial: { opacity: 0, scale: 0.98 },
|
|
77
|
+
animate: { opacity: 1, scale: 1 },
|
|
78
|
+
exit: { opacity: 0, scale: 0.98 },
|
|
79
|
+
transition: { duration: durations.base, ease: easing.emphasized }
|
|
80
|
+
}),
|
|
81
|
+
stagger: {
|
|
82
|
+
container: safe({
|
|
83
|
+
initial: { opacity: 0 },
|
|
84
|
+
animate: { opacity: 1 },
|
|
85
|
+
transition: { staggerChildren: 0.06, delayChildren: 0.02 }
|
|
86
|
+
}),
|
|
87
|
+
item: safe({
|
|
88
|
+
initial: { opacity: 0, y: 8 },
|
|
89
|
+
animate: { opacity: 1, y: 0 },
|
|
90
|
+
transition: { duration: durations.base, ease: easing.standard }
|
|
91
|
+
})
|
|
92
|
+
},
|
|
93
|
+
meta: { prefersReduced, durations, easing }
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// src/registry/index.ts
|
|
98
|
+
var registry = [
|
|
99
|
+
// UI Primitives
|
|
100
|
+
{
|
|
101
|
+
name: "button",
|
|
102
|
+
type: "ui",
|
|
103
|
+
title: "Button",
|
|
104
|
+
description: "A button component with multiple variants, sizes, and loading state.",
|
|
105
|
+
sourcePath: "registry/ui/button",
|
|
106
|
+
dependencies: ["@radix-ui/react-slot", "class-variance-authority"],
|
|
107
|
+
categories: ["primitives", "forms"]
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
name: "input",
|
|
111
|
+
type: "ui",
|
|
112
|
+
title: "Input",
|
|
113
|
+
description: "An input component with variants for validation states and icon support.",
|
|
114
|
+
sourcePath: "registry/ui/input",
|
|
115
|
+
dependencies: ["class-variance-authority"],
|
|
116
|
+
categories: ["primitives", "forms"]
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
name: "card",
|
|
120
|
+
type: "ui",
|
|
121
|
+
title: "Card",
|
|
122
|
+
description: "A card component with header, content, and footer sections.",
|
|
123
|
+
sourcePath: "registry/ui/card",
|
|
124
|
+
categories: ["primitives", "layout"]
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
name: "badge",
|
|
128
|
+
type: "ui",
|
|
129
|
+
title: "Badge",
|
|
130
|
+
description: "A badge component for status indicators with multiple variants.",
|
|
131
|
+
sourcePath: "registry/ui/badge",
|
|
132
|
+
dependencies: ["class-variance-authority"],
|
|
133
|
+
categories: ["primitives", "data-display"]
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
name: "skeleton",
|
|
137
|
+
type: "ui",
|
|
138
|
+
title: "Skeleton",
|
|
139
|
+
description: "Loading skeleton placeholders with pre-built variants for text, avatars, and cards.",
|
|
140
|
+
sourcePath: "registry/ui/skeleton",
|
|
141
|
+
categories: ["primitives", "feedback"]
|
|
142
|
+
},
|
|
143
|
+
// Blocks
|
|
144
|
+
{
|
|
145
|
+
name: "app-shell",
|
|
146
|
+
type: "block",
|
|
147
|
+
title: "App Shell",
|
|
148
|
+
description: "Application shell with sidebar, header, and content area.",
|
|
149
|
+
sourcePath: "registry/blocks/app-shell",
|
|
150
|
+
categories: ["layout", "application"]
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
name: "page-header",
|
|
154
|
+
type: "block",
|
|
155
|
+
title: "Page Header",
|
|
156
|
+
description: "Page header with title, breadcrumb, description, and actions.",
|
|
157
|
+
sourcePath: "registry/blocks/page-header",
|
|
158
|
+
categories: ["layout", "navigation"]
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
name: "empty-state",
|
|
162
|
+
type: "block",
|
|
163
|
+
title: "Empty State",
|
|
164
|
+
description: "Empty state component with icon, title, description, and actions.",
|
|
165
|
+
sourcePath: "registry/blocks/empty-state",
|
|
166
|
+
categories: ["feedback", "data-display"]
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
name: "form-section",
|
|
170
|
+
type: "block",
|
|
171
|
+
title: "Form Section",
|
|
172
|
+
description: "Form section with title, description, fields, and actions.",
|
|
173
|
+
sourcePath: "registry/blocks/form-section",
|
|
174
|
+
categories: ["forms", "layout"]
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
name: "data-table",
|
|
178
|
+
type: "block",
|
|
179
|
+
title: "Data Table",
|
|
180
|
+
description: "Data table with sorting, pagination, and custom cell rendering.",
|
|
181
|
+
sourcePath: "registry/blocks/data-table",
|
|
182
|
+
categories: ["data-display", "tables"]
|
|
183
|
+
},
|
|
184
|
+
// Effects
|
|
185
|
+
{
|
|
186
|
+
name: "shimmer-button",
|
|
187
|
+
type: "effect",
|
|
188
|
+
title: "Shimmer Button",
|
|
189
|
+
description: "A button with animated shimmer effect.",
|
|
190
|
+
sourcePath: "registry/effects/shimmer-button",
|
|
191
|
+
dependencies: ["framer-motion"],
|
|
192
|
+
categories: ["effects", "buttons"]
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
name: "animated-gradient",
|
|
196
|
+
type: "effect",
|
|
197
|
+
title: "Animated Gradient",
|
|
198
|
+
description: "Animated gradient background and text effects.",
|
|
199
|
+
sourcePath: "registry/effects/animated-gradient",
|
|
200
|
+
dependencies: ["framer-motion"],
|
|
201
|
+
categories: ["effects", "backgrounds"]
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
name: "text-reveal",
|
|
205
|
+
type: "effect",
|
|
206
|
+
title: "Text Reveal",
|
|
207
|
+
description: "Character and word reveal animations.",
|
|
208
|
+
sourcePath: "registry/effects/text-reveal",
|
|
209
|
+
dependencies: ["framer-motion"],
|
|
210
|
+
categories: ["effects", "text"]
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
name: "border-beam",
|
|
214
|
+
type: "effect",
|
|
215
|
+
title: "Border Beam",
|
|
216
|
+
description: "Animated border beam and glowing border effects.",
|
|
217
|
+
sourcePath: "registry/effects/border-beam",
|
|
218
|
+
dependencies: ["framer-motion"],
|
|
219
|
+
categories: ["effects", "borders"]
|
|
220
|
+
},
|
|
221
|
+
{
|
|
222
|
+
name: "spotlight",
|
|
223
|
+
type: "effect",
|
|
224
|
+
title: "Spotlight",
|
|
225
|
+
description: "Cursor-following spotlight effect.",
|
|
226
|
+
sourcePath: "registry/effects/spotlight",
|
|
227
|
+
dependencies: ["framer-motion"],
|
|
228
|
+
categories: ["effects", "interactive"]
|
|
229
|
+
}
|
|
230
|
+
];
|
|
231
|
+
function getRegistryItem(name) {
|
|
232
|
+
return registry.find((item) => item.name === name);
|
|
233
|
+
}
|
|
234
|
+
function getRegistryItemsByType(type) {
|
|
235
|
+
return registry.filter((item) => item.type === type);
|
|
236
|
+
}
|
|
237
|
+
function getRegistryItemsByCategory(category) {
|
|
238
|
+
return registry.filter((item) => item.categories?.includes(category));
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
export { baseTokens, cn, getRegistryItem, getRegistryItemsByCategory, getRegistryItemsByType, registry, tailwindSemanticColors, useMotionPresets };
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Motion presets: use these across the app to keep motion consistent.
|
|
3
|
+
* Rule: do not hardcode durations/easing elsewhere.
|
|
4
|
+
*/
|
|
5
|
+
type MotionPreset = {
|
|
6
|
+
initial?: Record<string, unknown>;
|
|
7
|
+
animate?: Record<string, unknown>;
|
|
8
|
+
exit?: Record<string, unknown>;
|
|
9
|
+
transition?: Record<string, unknown>;
|
|
10
|
+
};
|
|
11
|
+
declare function useMotionPresets(): {
|
|
12
|
+
fadeIn: MotionPreset;
|
|
13
|
+
slideUp: MotionPreset;
|
|
14
|
+
modalScale: MotionPreset;
|
|
15
|
+
stagger: {
|
|
16
|
+
container: MotionPreset;
|
|
17
|
+
item: MotionPreset;
|
|
18
|
+
};
|
|
19
|
+
meta: {
|
|
20
|
+
prefersReduced: boolean | null;
|
|
21
|
+
durations: {
|
|
22
|
+
fast: number;
|
|
23
|
+
base: number;
|
|
24
|
+
slow: number;
|
|
25
|
+
};
|
|
26
|
+
easing: {
|
|
27
|
+
standard: number[];
|
|
28
|
+
emphasized: number[];
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export { type MotionPreset, useMotionPresets };
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { useReducedMotion } from 'framer-motion';
|
|
2
|
+
|
|
3
|
+
// src/motion/presets.ts
|
|
4
|
+
function useMotionPresets() {
|
|
5
|
+
const prefersReduced = useReducedMotion();
|
|
6
|
+
const durations = {
|
|
7
|
+
fast: 0.12,
|
|
8
|
+
base: 0.18,
|
|
9
|
+
slow: 0.26
|
|
10
|
+
};
|
|
11
|
+
const easing = {
|
|
12
|
+
standard: [0.2, 0, 0, 1],
|
|
13
|
+
emphasized: [0.2, 0.8, 0.2, 1]
|
|
14
|
+
};
|
|
15
|
+
const safe = (preset) => {
|
|
16
|
+
if (!prefersReduced) return preset;
|
|
17
|
+
return {
|
|
18
|
+
initial: { opacity: 0 },
|
|
19
|
+
animate: { opacity: 1 },
|
|
20
|
+
exit: { opacity: 0 },
|
|
21
|
+
transition: { duration: durations.fast }
|
|
22
|
+
};
|
|
23
|
+
};
|
|
24
|
+
return {
|
|
25
|
+
fadeIn: safe({
|
|
26
|
+
initial: { opacity: 0 },
|
|
27
|
+
animate: { opacity: 1 },
|
|
28
|
+
exit: { opacity: 0 },
|
|
29
|
+
transition: { duration: durations.base, ease: easing.standard }
|
|
30
|
+
}),
|
|
31
|
+
slideUp: safe({
|
|
32
|
+
initial: { opacity: 0, y: 10 },
|
|
33
|
+
animate: { opacity: 1, y: 0 },
|
|
34
|
+
exit: { opacity: 0, y: 6 },
|
|
35
|
+
transition: { duration: durations.base, ease: easing.standard }
|
|
36
|
+
}),
|
|
37
|
+
modalScale: safe({
|
|
38
|
+
initial: { opacity: 0, scale: 0.98 },
|
|
39
|
+
animate: { opacity: 1, scale: 1 },
|
|
40
|
+
exit: { opacity: 0, scale: 0.98 },
|
|
41
|
+
transition: { duration: durations.base, ease: easing.emphasized }
|
|
42
|
+
}),
|
|
43
|
+
stagger: {
|
|
44
|
+
container: safe({
|
|
45
|
+
initial: { opacity: 0 },
|
|
46
|
+
animate: { opacity: 1 },
|
|
47
|
+
transition: { staggerChildren: 0.06, delayChildren: 0.02 }
|
|
48
|
+
}),
|
|
49
|
+
item: safe({
|
|
50
|
+
initial: { opacity: 0, y: 8 },
|
|
51
|
+
animate: { opacity: 1, y: 0 },
|
|
52
|
+
transition: { duration: durations.base, ease: easing.standard }
|
|
53
|
+
})
|
|
54
|
+
},
|
|
55
|
+
meta: { prefersReduced, durations, easing }
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export { useMotionPresets };
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
type RegistryItemType = "ui" | "block" | "effect" | "hook";
|
|
2
|
+
type RegistryItem = {
|
|
3
|
+
name: string;
|
|
4
|
+
type: RegistryItemType;
|
|
5
|
+
title: string;
|
|
6
|
+
description: string;
|
|
7
|
+
sourcePath: string;
|
|
8
|
+
dependencies?: string[];
|
|
9
|
+
registryDependencies?: string[];
|
|
10
|
+
categories?: string[];
|
|
11
|
+
};
|
|
12
|
+
declare const registry: RegistryItem[];
|
|
13
|
+
declare function getRegistryItem(name: string): RegistryItem | undefined;
|
|
14
|
+
declare function getRegistryItemsByType(type: RegistryItemType): RegistryItem[];
|
|
15
|
+
declare function getRegistryItemsByCategory(category: string): RegistryItem[];
|
|
16
|
+
|
|
17
|
+
export { type RegistryItem, type RegistryItemType, getRegistryItem, getRegistryItemsByCategory, getRegistryItemsByType, registry };
|