@smicolon/fasla-ui 0.3.0 → 0.4.0
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 +43 -20
- package/dist/index.cjs +319 -0
- package/dist/index.d.cts +5 -0
- package/dist/index.js +1 -1
- package/dist/lib/utils.cjs +11 -0
- package/dist/lib/utils.d.cts +9 -0
- package/dist/motion/index.cjs +61 -0
- package/dist/motion/index.d.cts +33 -0
- package/dist/registry/index.cjs +179 -0
- package/dist/registry/index.d.cts +17 -0
- package/dist/registry/index.js +1 -1
- package/dist/tokens/index.cjs +77 -0
- package/dist/tokens/index.d.cts +124 -0
- package/package.json +14 -7
- package/registry/blocks/empty-state/EmptyState.test.tsx +11 -0
- package/registry/blocks/empty-state/EmptyState.tsx +10 -2
- package/registry/blocks/sidebar/Sidebar.test.tsx +39 -0
- package/registry/blocks/sidebar/Sidebar.tsx +28 -11
- package/registry/blocks/stats-card/StatsCard.tsx +3 -2
- package/registry/effects/animated-gradient/animated-gradient.tsx +3 -7
- package/registry/effects/text-reveal/text-reveal.tsx +3 -1
- package/registry/next14-tsconfig.test.ts +43 -0
- package/registry/ui/checkbox/checkbox.test.tsx +23 -0
- package/registry/ui/checkbox/checkbox.tsx +3 -1
- package/registry/ui/combobox/combobox.test.tsx +30 -1
- package/registry/ui/combobox/combobox.tsx +7 -4
- package/registry/ui/input/input.test.tsx +1 -1
- package/registry/ui/input/input.tsx +1 -1
- package/registry/ui/skeleton/index.ts +1 -0
- package/registry/ui/skeleton/skeleton.test.tsx +113 -29
- package/registry/ui/skeleton/skeleton.tsx +101 -41
- package/registry.json +1 -1
package/README.md
CHANGED
|
@@ -19,12 +19,25 @@ A professional React UI component library built on **shadcn/ui** primitives. Pro
|
|
|
19
19
|
|
|
20
20
|
## Installation
|
|
21
21
|
|
|
22
|
+
Components are copied into your project with the CLI, as with shadcn/ui:
|
|
23
|
+
|
|
22
24
|
```bash
|
|
23
|
-
#
|
|
24
|
-
|
|
25
|
-
npx @smicolon/cli add button
|
|
25
|
+
npx @smicolon/cli init # once per project: writes components.json and lib/utils.ts,
|
|
26
|
+
# and installs clsx and tailwind-merge, which lib/utils.ts imports
|
|
27
|
+
npx @smicolon/cli add button card shimmer-button
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
`add` ends by printing the install command for the packages the components
|
|
31
|
+
need, in your project's package manager. For `button card shimmer-button` with npm it is:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
npm install @radix-ui/react-slot class-variance-authority lucide-react framer-motion
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
This package holds the design tokens, the Tailwind preset and the motion
|
|
38
|
+
presets, not the components. Install it when you want those:
|
|
26
39
|
|
|
27
|
-
|
|
40
|
+
```bash
|
|
28
41
|
npm install @smicolon/fasla-ui
|
|
29
42
|
# or
|
|
30
43
|
yarn add @smicolon/fasla-ui
|
|
@@ -83,9 +96,9 @@ Animated components for visual flair (powered by Framer Motion):
|
|
|
83
96
|
## Quick Start
|
|
84
97
|
|
|
85
98
|
```tsx
|
|
86
|
-
import { Button } from "
|
|
87
|
-
import { Card, CardHeader, CardTitle, CardContent } from "
|
|
88
|
-
import { ShimmerButton } from "
|
|
99
|
+
import { Button } from "@/components/ui/button"
|
|
100
|
+
import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card"
|
|
101
|
+
import { ShimmerButton } from "@/components/ui/shimmer-button"
|
|
89
102
|
|
|
90
103
|
export function MyComponent() {
|
|
91
104
|
return (
|
|
@@ -106,26 +119,36 @@ export function MyComponent() {
|
|
|
106
119
|
|
|
107
120
|
```json
|
|
108
121
|
{
|
|
109
|
-
"react": "^18.0.0",
|
|
110
|
-
"react-dom": "^18.0.0",
|
|
111
|
-
"tailwindcss": "^3.4.0",
|
|
112
|
-
"framer-motion": "^11.0.0" // optional, required for effects
|
|
122
|
+
"react": "^18.0.0 || ^19.0.0",
|
|
123
|
+
"react-dom": "^18.0.0 || ^19.0.0",
|
|
124
|
+
"tailwindcss": "^3.4.0 || ^4.1.4",
|
|
125
|
+
"framer-motion": "^11.0.0 || ^12.0.0 || ^13.0.0 || ^14.0.0" // optional, required for effects
|
|
113
126
|
}
|
|
114
127
|
```
|
|
115
128
|
|
|
116
|
-
##
|
|
129
|
+
## Theme
|
|
117
130
|
|
|
118
|
-
|
|
131
|
+
Components take every colour from your theme. `init` asks which of two layers
|
|
132
|
+
you want; both include the base layer.
|
|
119
133
|
|
|
120
|
-
```
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
134
|
+
```bash
|
|
135
|
+
# Starting from scratch: use Fasla's colours (palette, radius, Geist).
|
|
136
|
+
# On Next.js 14, leave out the font-geist.json URL: next/font there has no Geist.
|
|
137
|
+
npx shadcn@latest add https://ui.smicolon.com/r/theme.json https://ui.smicolon.com/r/font-geist.json
|
|
138
|
+
|
|
139
|
+
# I have a brand: keep my colours (base layer only)
|
|
140
|
+
npx shadcn@latest add https://ui.smicolon.com/r/theme-base.json
|
|
127
141
|
```
|
|
128
142
|
|
|
143
|
+
The base layer adds only what shadcn doesn't have: the success, warning and
|
|
144
|
+
info tokens and the soft tints, the type scale with Arabic line heights, the
|
|
145
|
+
Arabic setting, and the Cairo font. It never changes a colour you already
|
|
146
|
+
have. Arabic text uses `--font-arabic`; point it at another font to swap Cairo.
|
|
147
|
+
|
|
148
|
+
Fasla's base components replace shadcn's components of the same name in
|
|
149
|
+
`components/ui`; `add` asks before replacing a file that isn't Fasla's. Blocks
|
|
150
|
+
install to `components/blocks`.
|
|
151
|
+
|
|
129
152
|
## Documentation
|
|
130
153
|
|
|
131
154
|
Visit [ui.smicolon.com](https://ui.smicolon.com) for full documentation, examples, and API reference.
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var clsx = require('clsx');
|
|
4
|
+
var tailwindMerge = require('tailwind-merge');
|
|
5
|
+
var framerMotion = require('framer-motion');
|
|
6
|
+
|
|
7
|
+
// src/lib/utils.ts
|
|
8
|
+
function cn(...inputs) {
|
|
9
|
+
return tailwindMerge.twMerge(clsx.clsx(inputs));
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// src/tokens/model.ts
|
|
13
|
+
var baseTokens = {
|
|
14
|
+
radius: { sm: "0.5rem", md: "0.75rem", lg: "1rem" },
|
|
15
|
+
spacing: { densityMultiplier: { compact: 0.9, comfortable: 1, spacious: 1.1 } },
|
|
16
|
+
// Brand V2.5 §14 — Geist is the LTR face. Arabic is set in Cairo, which the
|
|
17
|
+
// Tailwind preset selects per direction; this scaffold carries only the base.
|
|
18
|
+
// 16/24 is the `Base` rung, identical in both Figma ramps.
|
|
19
|
+
typography: { fontFamily: "Geist", baseFontSizePx: 16, lineHeight: 1.5 },
|
|
20
|
+
motion: { durationsMs: { fast: 120, base: 180, slow: 260 } }
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
// src/tokens/tailwind.ts
|
|
24
|
+
var semanticColor = (name) => `color-mix(in oklch, var(--${name}) calc(<alpha-value> * 100%), transparent)`;
|
|
25
|
+
var c = semanticColor;
|
|
26
|
+
var tailwindSemanticColors = {
|
|
27
|
+
// Surfaces
|
|
28
|
+
background: c("background"),
|
|
29
|
+
foreground: c("foreground"),
|
|
30
|
+
card: { DEFAULT: c("card"), foreground: c("card-foreground") },
|
|
31
|
+
popover: { DEFAULT: c("popover"), foreground: c("popover-foreground") },
|
|
32
|
+
// Actions. `primary` is Figma's strong neutral (#0a0a0a light, #fafafa dark),
|
|
33
|
+
// identical to `foreground` in both modes. It is NOT a brand accent — an app
|
|
34
|
+
// that wants its brand colour should define its own token for it.
|
|
35
|
+
primary: { DEFAULT: c("primary"), foreground: c("primary-foreground") },
|
|
36
|
+
secondary: { DEFAULT: c("secondary"), foreground: c("secondary-foreground") },
|
|
37
|
+
muted: { DEFAULT: c("muted"), foreground: c("muted-foreground") },
|
|
38
|
+
accent: { DEFAULT: c("accent"), foreground: c("accent-foreground") },
|
|
39
|
+
destructive: {
|
|
40
|
+
DEFAULT: c("destructive"),
|
|
41
|
+
foreground: c("destructive-foreground")
|
|
42
|
+
},
|
|
43
|
+
// Lines and focus
|
|
44
|
+
border: c("border"),
|
|
45
|
+
input: c("input"),
|
|
46
|
+
ring: c("ring"),
|
|
47
|
+
// Status
|
|
48
|
+
success: { DEFAULT: c("success"), foreground: c("success-foreground") },
|
|
49
|
+
warning: { DEFAULT: c("warning"), foreground: c("warning-foreground") },
|
|
50
|
+
info: { DEFAULT: c("info"), foreground: c("info-foreground") },
|
|
51
|
+
// Soft tints — Figma's `opacity/*` variables, already translucent. The Badge
|
|
52
|
+
// Soft type lays one over `card`, exactly as Figma stacks the two fills, so
|
|
53
|
+
// the chip reads the same on any surface. Only `primary` changes in Dark.
|
|
54
|
+
soft: {
|
|
55
|
+
primary: c("soft-primary"),
|
|
56
|
+
secondary: c("soft-secondary"),
|
|
57
|
+
info: c("soft-info"),
|
|
58
|
+
success: c("soft-success"),
|
|
59
|
+
warning: c("soft-warning"),
|
|
60
|
+
destructive: c("soft-destructive")
|
|
61
|
+
},
|
|
62
|
+
// Inverse — a fixed contrast band, not a mode-reactive surface.
|
|
63
|
+
"background-inverse": c("background-inverse"),
|
|
64
|
+
"foreground-inverse": c("foreground-inverse"),
|
|
65
|
+
"muted-foreground-inverse": c("muted-foreground-inverse"),
|
|
66
|
+
// Charts
|
|
67
|
+
"chart-1": c("chart-1"),
|
|
68
|
+
"chart-2": c("chart-2"),
|
|
69
|
+
"chart-3": c("chart-3"),
|
|
70
|
+
"chart-4": c("chart-4"),
|
|
71
|
+
"chart-5": c("chart-5"),
|
|
72
|
+
sidebar: {
|
|
73
|
+
DEFAULT: c("sidebar"),
|
|
74
|
+
foreground: c("sidebar-foreground"),
|
|
75
|
+
primary: c("sidebar-primary"),
|
|
76
|
+
"primary-foreground": c("sidebar-primary-foreground"),
|
|
77
|
+
accent: c("sidebar-accent"),
|
|
78
|
+
"accent-foreground": c("sidebar-accent-foreground"),
|
|
79
|
+
border: c("sidebar-border"),
|
|
80
|
+
ring: c("sidebar-ring")
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
function useMotionPresets() {
|
|
84
|
+
const prefersReduced = framerMotion.useReducedMotion();
|
|
85
|
+
const durations = {
|
|
86
|
+
fast: 0.12,
|
|
87
|
+
base: 0.18,
|
|
88
|
+
slow: 0.26
|
|
89
|
+
};
|
|
90
|
+
const easing = {
|
|
91
|
+
standard: [0.2, 0, 0, 1],
|
|
92
|
+
emphasized: [0.2, 0.8, 0.2, 1]
|
|
93
|
+
};
|
|
94
|
+
const safe = (preset) => {
|
|
95
|
+
if (!prefersReduced) return preset;
|
|
96
|
+
return {
|
|
97
|
+
initial: { opacity: 0 },
|
|
98
|
+
animate: { opacity: 1 },
|
|
99
|
+
exit: { opacity: 0 },
|
|
100
|
+
transition: { duration: durations.fast }
|
|
101
|
+
};
|
|
102
|
+
};
|
|
103
|
+
return {
|
|
104
|
+
fadeIn: safe({
|
|
105
|
+
initial: { opacity: 0 },
|
|
106
|
+
animate: { opacity: 1 },
|
|
107
|
+
exit: { opacity: 0 },
|
|
108
|
+
transition: { duration: durations.base, ease: easing.standard }
|
|
109
|
+
}),
|
|
110
|
+
slideUp: safe({
|
|
111
|
+
initial: { opacity: 0, y: 10 },
|
|
112
|
+
animate: { opacity: 1, y: 0 },
|
|
113
|
+
exit: { opacity: 0, y: 6 },
|
|
114
|
+
transition: { duration: durations.base, ease: easing.standard }
|
|
115
|
+
}),
|
|
116
|
+
modalScale: safe({
|
|
117
|
+
initial: { opacity: 0, scale: 0.98 },
|
|
118
|
+
animate: { opacity: 1, scale: 1 },
|
|
119
|
+
exit: { opacity: 0, scale: 0.98 },
|
|
120
|
+
transition: { duration: durations.base, ease: easing.emphasized }
|
|
121
|
+
}),
|
|
122
|
+
stagger: {
|
|
123
|
+
container: safe({
|
|
124
|
+
initial: { opacity: 0 },
|
|
125
|
+
animate: { opacity: 1 },
|
|
126
|
+
transition: { staggerChildren: 0.06, delayChildren: 0.02 }
|
|
127
|
+
}),
|
|
128
|
+
item: safe({
|
|
129
|
+
initial: { opacity: 0, y: 8 },
|
|
130
|
+
animate: { opacity: 1, y: 0 },
|
|
131
|
+
transition: { duration: durations.base, ease: easing.standard }
|
|
132
|
+
})
|
|
133
|
+
},
|
|
134
|
+
meta: { prefersReduced, durations, easing }
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// src/registry/index.ts
|
|
139
|
+
var registry = [
|
|
140
|
+
// UI Primitives
|
|
141
|
+
{
|
|
142
|
+
name: "button",
|
|
143
|
+
type: "ui",
|
|
144
|
+
title: "Button",
|
|
145
|
+
description: "A button component with multiple variants, sizes, and loading state.",
|
|
146
|
+
sourcePath: "registry/ui/button",
|
|
147
|
+
dependencies: ["@radix-ui/react-slot", "class-variance-authority", "lucide-react"],
|
|
148
|
+
categories: ["primitives", "forms"]
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
name: "input",
|
|
152
|
+
type: "ui",
|
|
153
|
+
title: "Input",
|
|
154
|
+
description: "An input component with variants for validation states and icon support.",
|
|
155
|
+
sourcePath: "registry/ui/input",
|
|
156
|
+
dependencies: ["class-variance-authority"],
|
|
157
|
+
categories: ["primitives", "forms"]
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
name: "card",
|
|
161
|
+
type: "ui",
|
|
162
|
+
title: "Card",
|
|
163
|
+
description: "A card component with header, content, and footer sections.",
|
|
164
|
+
sourcePath: "registry/ui/card",
|
|
165
|
+
categories: ["primitives", "layout"]
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
name: "badge",
|
|
169
|
+
type: "ui",
|
|
170
|
+
title: "Badge",
|
|
171
|
+
description: "A compact inline label for status, category or metadata: solid, soft and outline types in six semantic tones, with an optional icon, avatar and close button.",
|
|
172
|
+
sourcePath: "registry/ui/badge",
|
|
173
|
+
dependencies: ["class-variance-authority", "lucide-react"],
|
|
174
|
+
categories: ["primitives", "data-display"]
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
name: "avatar",
|
|
178
|
+
type: "ui",
|
|
179
|
+
title: "Avatar",
|
|
180
|
+
description: "A user's photo, initials or icon, in three sizes and two radii, with an optional border and presence dot. Falls back from photo to initials to icon on its own.",
|
|
181
|
+
sourcePath: "registry/ui/avatar",
|
|
182
|
+
dependencies: ["class-variance-authority", "lucide-react"],
|
|
183
|
+
registryDependencies: ["status-indicator"],
|
|
184
|
+
categories: ["primitives", "data-display"]
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
name: "status-indicator",
|
|
188
|
+
type: "ui",
|
|
189
|
+
title: "Status Indicator",
|
|
190
|
+
description: "A presence dot \u2014 online, away, busy or offline \u2014 with an accessible name in the page's language, so colour never carries the status alone.",
|
|
191
|
+
sourcePath: "registry/ui/status-indicator",
|
|
192
|
+
dependencies: ["class-variance-authority"],
|
|
193
|
+
categories: ["primitives", "data-display"]
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
name: "skeleton",
|
|
197
|
+
type: "ui",
|
|
198
|
+
title: "Skeleton",
|
|
199
|
+
description: "Loading placeholders that replace shadcn's Skeleton: a plain block plus text, avatar, list item and card shapes that stay still under reduced motion.",
|
|
200
|
+
sourcePath: "registry/ui/skeleton",
|
|
201
|
+
categories: ["primitives", "feedback"]
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
name: "radio",
|
|
205
|
+
type: "ui",
|
|
206
|
+
title: "Radio",
|
|
207
|
+
description: "An accessible radio for choosing one option from a set, with a plain and a bordered layout variant.",
|
|
208
|
+
sourcePath: "registry/ui/radio",
|
|
209
|
+
dependencies: ["class-variance-authority"],
|
|
210
|
+
categories: ["primitives", "forms"]
|
|
211
|
+
},
|
|
212
|
+
// Blocks
|
|
213
|
+
{
|
|
214
|
+
name: "app-shell",
|
|
215
|
+
type: "block",
|
|
216
|
+
title: "App Shell",
|
|
217
|
+
description: "Application shell with sidebar, header, and content area.",
|
|
218
|
+
sourcePath: "registry/blocks/app-shell",
|
|
219
|
+
categories: ["layout", "application"]
|
|
220
|
+
},
|
|
221
|
+
{
|
|
222
|
+
name: "page-header",
|
|
223
|
+
type: "block",
|
|
224
|
+
title: "Page Header",
|
|
225
|
+
description: "Page header with title, breadcrumb, description, and actions.",
|
|
226
|
+
sourcePath: "registry/blocks/page-header",
|
|
227
|
+
categories: ["layout", "navigation"]
|
|
228
|
+
},
|
|
229
|
+
{
|
|
230
|
+
name: "empty-state",
|
|
231
|
+
type: "block",
|
|
232
|
+
title: "Empty State",
|
|
233
|
+
description: "Empty state component with icon, title, description, and actions.",
|
|
234
|
+
sourcePath: "registry/blocks/empty-state",
|
|
235
|
+
dependencies: ["lucide-react"],
|
|
236
|
+
categories: ["feedback", "data-display"]
|
|
237
|
+
},
|
|
238
|
+
{
|
|
239
|
+
name: "form-section",
|
|
240
|
+
type: "block",
|
|
241
|
+
title: "Form Section",
|
|
242
|
+
description: "Form section with title, description, fields, and actions.",
|
|
243
|
+
sourcePath: "registry/blocks/form-section",
|
|
244
|
+
categories: ["forms", "layout"]
|
|
245
|
+
},
|
|
246
|
+
{
|
|
247
|
+
name: "data-table",
|
|
248
|
+
type: "block",
|
|
249
|
+
title: "Data Table",
|
|
250
|
+
description: "Data table with sorting, pagination, and custom cell rendering.",
|
|
251
|
+
sourcePath: "registry/blocks/data-table",
|
|
252
|
+
categories: ["data-display", "tables"]
|
|
253
|
+
},
|
|
254
|
+
// Effects
|
|
255
|
+
{
|
|
256
|
+
name: "shimmer-button",
|
|
257
|
+
type: "effect",
|
|
258
|
+
title: "Shimmer Button",
|
|
259
|
+
description: "A button with animated shimmer effect.",
|
|
260
|
+
sourcePath: "registry/effects/shimmer-button",
|
|
261
|
+
dependencies: ["framer-motion"],
|
|
262
|
+
categories: ["effects", "buttons"]
|
|
263
|
+
},
|
|
264
|
+
{
|
|
265
|
+
name: "animated-gradient",
|
|
266
|
+
type: "effect",
|
|
267
|
+
title: "Animated Gradient",
|
|
268
|
+
description: "Animated gradient background and text effects.",
|
|
269
|
+
sourcePath: "registry/effects/animated-gradient",
|
|
270
|
+
dependencies: ["framer-motion"],
|
|
271
|
+
categories: ["effects", "backgrounds"]
|
|
272
|
+
},
|
|
273
|
+
{
|
|
274
|
+
name: "text-reveal",
|
|
275
|
+
type: "effect",
|
|
276
|
+
title: "Text Reveal",
|
|
277
|
+
description: "Character and word reveal animations.",
|
|
278
|
+
sourcePath: "registry/effects/text-reveal",
|
|
279
|
+
dependencies: ["framer-motion"],
|
|
280
|
+
categories: ["effects", "text"]
|
|
281
|
+
},
|
|
282
|
+
{
|
|
283
|
+
name: "border-beam",
|
|
284
|
+
type: "effect",
|
|
285
|
+
title: "Border Beam",
|
|
286
|
+
description: "Animated border beam and glowing border effects.",
|
|
287
|
+
sourcePath: "registry/effects/border-beam",
|
|
288
|
+
dependencies: ["framer-motion"],
|
|
289
|
+
categories: ["effects", "borders"]
|
|
290
|
+
},
|
|
291
|
+
{
|
|
292
|
+
name: "spotlight",
|
|
293
|
+
type: "effect",
|
|
294
|
+
title: "Spotlight",
|
|
295
|
+
description: "Cursor-following spotlight effect.",
|
|
296
|
+
sourcePath: "registry/effects/spotlight",
|
|
297
|
+
dependencies: ["framer-motion"],
|
|
298
|
+
categories: ["effects", "interactive"]
|
|
299
|
+
}
|
|
300
|
+
];
|
|
301
|
+
function getRegistryItem(name) {
|
|
302
|
+
return registry.find((item) => item.name === name);
|
|
303
|
+
}
|
|
304
|
+
function getRegistryItemsByType(type) {
|
|
305
|
+
return registry.filter((item) => item.type === type);
|
|
306
|
+
}
|
|
307
|
+
function getRegistryItemsByCategory(category) {
|
|
308
|
+
return registry.filter((item) => item.categories?.includes(category));
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
exports.baseTokens = baseTokens;
|
|
312
|
+
exports.cn = cn;
|
|
313
|
+
exports.getRegistryItem = getRegistryItem;
|
|
314
|
+
exports.getRegistryItemsByCategory = getRegistryItemsByCategory;
|
|
315
|
+
exports.getRegistryItemsByType = getRegistryItemsByType;
|
|
316
|
+
exports.registry = registry;
|
|
317
|
+
exports.semanticColor = semanticColor;
|
|
318
|
+
exports.tailwindSemanticColors = tailwindSemanticColors;
|
|
319
|
+
exports.useMotionPresets = useMotionPresets;
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { cn } from './lib/utils.cjs';
|
|
2
|
+
export { Density, TokenSet, baseTokens, semanticColor, tailwindSemanticColors } from './tokens/index.cjs';
|
|
3
|
+
export { MotionPreset, useMotionPresets } from './motion/index.cjs';
|
|
4
|
+
export { RegistryItem, RegistryItemType, getRegistryItem, getRegistryItemsByCategory, getRegistryItemsByType, registry } from './registry/index.cjs';
|
|
5
|
+
import 'clsx';
|
package/dist/index.js
CHANGED
|
@@ -194,7 +194,7 @@ var registry = [
|
|
|
194
194
|
name: "skeleton",
|
|
195
195
|
type: "ui",
|
|
196
196
|
title: "Skeleton",
|
|
197
|
-
description: "Loading
|
|
197
|
+
description: "Loading placeholders that replace shadcn's Skeleton: a plain block plus text, avatar, list item and card shapes that stay still under reduced motion.",
|
|
198
198
|
sourcePath: "registry/ui/skeleton",
|
|
199
199
|
categories: ["primitives", "feedback"]
|
|
200
200
|
},
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var framerMotion = require('framer-motion');
|
|
4
|
+
|
|
5
|
+
// src/motion/presets.ts
|
|
6
|
+
function useMotionPresets() {
|
|
7
|
+
const prefersReduced = framerMotion.useReducedMotion();
|
|
8
|
+
const durations = {
|
|
9
|
+
fast: 0.12,
|
|
10
|
+
base: 0.18,
|
|
11
|
+
slow: 0.26
|
|
12
|
+
};
|
|
13
|
+
const easing = {
|
|
14
|
+
standard: [0.2, 0, 0, 1],
|
|
15
|
+
emphasized: [0.2, 0.8, 0.2, 1]
|
|
16
|
+
};
|
|
17
|
+
const safe = (preset) => {
|
|
18
|
+
if (!prefersReduced) return preset;
|
|
19
|
+
return {
|
|
20
|
+
initial: { opacity: 0 },
|
|
21
|
+
animate: { opacity: 1 },
|
|
22
|
+
exit: { opacity: 0 },
|
|
23
|
+
transition: { duration: durations.fast }
|
|
24
|
+
};
|
|
25
|
+
};
|
|
26
|
+
return {
|
|
27
|
+
fadeIn: safe({
|
|
28
|
+
initial: { opacity: 0 },
|
|
29
|
+
animate: { opacity: 1 },
|
|
30
|
+
exit: { opacity: 0 },
|
|
31
|
+
transition: { duration: durations.base, ease: easing.standard }
|
|
32
|
+
}),
|
|
33
|
+
slideUp: safe({
|
|
34
|
+
initial: { opacity: 0, y: 10 },
|
|
35
|
+
animate: { opacity: 1, y: 0 },
|
|
36
|
+
exit: { opacity: 0, y: 6 },
|
|
37
|
+
transition: { duration: durations.base, ease: easing.standard }
|
|
38
|
+
}),
|
|
39
|
+
modalScale: safe({
|
|
40
|
+
initial: { opacity: 0, scale: 0.98 },
|
|
41
|
+
animate: { opacity: 1, scale: 1 },
|
|
42
|
+
exit: { opacity: 0, scale: 0.98 },
|
|
43
|
+
transition: { duration: durations.base, ease: easing.emphasized }
|
|
44
|
+
}),
|
|
45
|
+
stagger: {
|
|
46
|
+
container: safe({
|
|
47
|
+
initial: { opacity: 0 },
|
|
48
|
+
animate: { opacity: 1 },
|
|
49
|
+
transition: { staggerChildren: 0.06, delayChildren: 0.02 }
|
|
50
|
+
}),
|
|
51
|
+
item: safe({
|
|
52
|
+
initial: { opacity: 0, y: 8 },
|
|
53
|
+
animate: { opacity: 1, y: 0 },
|
|
54
|
+
transition: { duration: durations.base, ease: easing.standard }
|
|
55
|
+
})
|
|
56
|
+
},
|
|
57
|
+
meta: { prefersReduced, durations, easing }
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
exports.useMotionPresets = 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 };
|