@smicolon/fasla-ui 0.2.4 → 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 +44 -20
- package/dist/index.cjs +319 -0
- package/dist/index.d.cts +5 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +92 -24
- 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 +32 -3
- package/dist/tokens/index.cjs +77 -0
- package/dist/tokens/index.d.cts +124 -0
- package/dist/tokens/index.d.ts +75 -6
- package/dist/tokens/index.js +59 -20
- package/package.json +17 -7
- package/registry/blocks/app-shell/AppShell.tsx +1 -1
- package/registry/blocks/data-table/DataTable.test.tsx +61 -0
- package/registry/blocks/data-table/DataTable.tsx +53 -16
- package/registry/blocks/data-table/index.ts +1 -0
- package/registry/blocks/empty-state/EmptyState.test.tsx +23 -0
- package/registry/blocks/empty-state/EmptyState.tsx +32 -33
- package/registry/blocks/form-section/FormSection.tsx +1 -1
- package/registry/blocks/navbar/Navbar.test.tsx +12 -0
- package/registry/blocks/navbar/Navbar.tsx +8 -23
- package/registry/blocks/page-header/PageHeader.tsx +5 -2
- package/registry/blocks/sidebar/Sidebar.test.tsx +39 -0
- package/registry/blocks/sidebar/Sidebar.tsx +36 -26
- package/registry/blocks/stats-card/StatsCard.tsx +12 -29
- package/registry/effects/animated-gradient/animated-gradient.tsx +19 -8
- package/registry/effects/border-beam/border-beam.tsx +77 -53
- package/registry/effects/effects.test.tsx +266 -0
- package/registry/effects/glow-card/glow-card.tsx +68 -43
- package/registry/effects/shimmer-button/shimmer-button.tsx +62 -23
- package/registry/effects/spotlight/spotlight.tsx +73 -29
- package/registry/effects/text-reveal/index.ts +1 -0
- package/registry/effects/text-reveal/text-reveal.test.tsx +66 -0
- package/registry/effects/text-reveal/text-reveal.tsx +70 -21
- package/registry/effects/typewriter-text/typewriter-text.tsx +39 -20
- package/registry/next14-tsconfig.test.ts +43 -0
- package/registry/text-props-undefined.test.tsx +73 -0
- package/registry/ui/avatar/avatar.test.tsx +316 -0
- package/registry/ui/avatar/avatar.tsx +235 -108
- package/registry/ui/avatar/index.ts +1 -1
- package/registry/ui/badge/badge.test.tsx +267 -32
- package/registry/ui/badge/badge.tsx +233 -21
- package/registry/ui/button/button.test.tsx +6 -0
- package/registry/ui/button/button.tsx +6 -23
- package/registry/ui/card/card.tsx +1 -1
- package/registry/ui/checkbox/checkbox.test.tsx +23 -0
- package/registry/ui/checkbox/checkbox.tsx +16 -27
- package/registry/ui/combobox/combobox.test.tsx +72 -0
- package/registry/ui/combobox/combobox.tsx +34 -59
- package/registry/ui/input/input.test.tsx +3 -3
- package/registry/ui/input/input.tsx +5 -5
- package/registry/ui/radio/index.ts +1 -0
- package/registry/ui/radio/radio.test.tsx +248 -0
- package/registry/ui/radio/radio.tsx +208 -0
- package/registry/ui/select/select.tsx +4 -14
- 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/ui/status-indicator/index.ts +6 -0
- package/registry/ui/status-indicator/status-indicator.test.tsx +148 -0
- package/registry/ui/status-indicator/status-indicator.tsx +87 -0
- package/registry/ui/switch/index.ts +1 -1
- package/registry/ui/switch/switch.test.tsx +323 -0
- package/registry/ui/switch/switch.tsx +254 -57
- package/registry/ui/textarea/textarea.tsx +1 -1
- package/registry.json +79 -16
- package/tailwind-preset.ts +143 -0
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
|
|
@@ -48,6 +61,7 @@ Core building blocks with CVA variants for consistent styling:
|
|
|
48
61
|
| **Tabs** | Tabbed navigation with animations |
|
|
49
62
|
| **Badge** | Status indicators and labels |
|
|
50
63
|
| **Avatar** | User avatars with fallback initials |
|
|
64
|
+
| **Status Indicator** | Presence dot with a language-aware accessible name |
|
|
51
65
|
| **Card** | Flexible content containers |
|
|
52
66
|
| **Skeleton** | Loading placeholders |
|
|
53
67
|
| **Combobox** | Searchable select with autocomplete |
|
|
@@ -82,9 +96,9 @@ Animated components for visual flair (powered by Framer Motion):
|
|
|
82
96
|
## Quick Start
|
|
83
97
|
|
|
84
98
|
```tsx
|
|
85
|
-
import { Button } from "
|
|
86
|
-
import { Card, CardHeader, CardTitle, CardContent } from "
|
|
87
|
-
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"
|
|
88
102
|
|
|
89
103
|
export function MyComponent() {
|
|
90
104
|
return (
|
|
@@ -105,26 +119,36 @@ export function MyComponent() {
|
|
|
105
119
|
|
|
106
120
|
```json
|
|
107
121
|
{
|
|
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
|
|
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
|
|
112
126
|
}
|
|
113
127
|
```
|
|
114
128
|
|
|
115
|
-
##
|
|
129
|
+
## Theme
|
|
116
130
|
|
|
117
|
-
|
|
131
|
+
Components take every colour from your theme. `init` asks which of two layers
|
|
132
|
+
you want; both include the base layer.
|
|
118
133
|
|
|
119
|
-
```
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
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
|
|
126
141
|
```
|
|
127
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
|
+
|
|
128
152
|
## Documentation
|
|
129
153
|
|
|
130
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.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { cn } from './lib/utils.js';
|
|
2
|
-
export { Density, TokenSet, baseTokens, tailwindSemanticColors } from './tokens/index.js';
|
|
2
|
+
export { Density, TokenSet, baseTokens, semanticColor, tailwindSemanticColors } from './tokens/index.js';
|
|
3
3
|
export { MotionPreset, useMotionPresets } from './motion/index.js';
|
|
4
4
|
export { RegistryItem, RegistryItemType, getRegistryItem, getRegistryItemsByCategory, getRegistryItemsByType, registry } from './registry/index.js';
|
|
5
5
|
import 'clsx';
|
package/dist/index.js
CHANGED
|
@@ -11,32 +11,71 @@ function cn(...inputs) {
|
|
|
11
11
|
var baseTokens = {
|
|
12
12
|
radius: { sm: "0.5rem", md: "0.75rem", lg: "1rem" },
|
|
13
13
|
spacing: { densityMultiplier: { compact: 0.9, comfortable: 1, spacious: 1.1 } },
|
|
14
|
-
|
|
14
|
+
// Brand V2.5 §14 — Geist is the LTR face. Arabic is set in Cairo, which the
|
|
15
|
+
// Tailwind preset selects per direction; this scaffold carries only the base.
|
|
16
|
+
// 16/24 is the `Base` rung, identical in both Figma ramps.
|
|
17
|
+
typography: { fontFamily: "Geist", baseFontSizePx: 16, lineHeight: 1.5 },
|
|
15
18
|
motion: { durationsMs: { fast: 120, base: 180, slow: 260 } }
|
|
16
19
|
};
|
|
17
20
|
|
|
18
21
|
// src/tokens/tailwind.ts
|
|
22
|
+
var semanticColor = (name) => `color-mix(in oklch, var(--${name}) calc(<alpha-value> * 100%), transparent)`;
|
|
23
|
+
var c = semanticColor;
|
|
19
24
|
var tailwindSemanticColors = {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
foreground: "
|
|
25
|
-
primary
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
},
|
|
29
|
-
secondary: {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
},
|
|
33
|
-
muted: {
|
|
34
|
-
DEFAULT: "hsl(var(--muted))",
|
|
35
|
-
foreground: "hsl(var(--muted-foreground))"
|
|
36
|
-
},
|
|
25
|
+
// Surfaces
|
|
26
|
+
background: c("background"),
|
|
27
|
+
foreground: c("foreground"),
|
|
28
|
+
card: { DEFAULT: c("card"), foreground: c("card-foreground") },
|
|
29
|
+
popover: { DEFAULT: c("popover"), foreground: c("popover-foreground") },
|
|
30
|
+
// Actions. `primary` is Figma's strong neutral (#0a0a0a light, #fafafa dark),
|
|
31
|
+
// identical to `foreground` in both modes. It is NOT a brand accent — an app
|
|
32
|
+
// that wants its brand colour should define its own token for it.
|
|
33
|
+
primary: { DEFAULT: c("primary"), foreground: c("primary-foreground") },
|
|
34
|
+
secondary: { DEFAULT: c("secondary"), foreground: c("secondary-foreground") },
|
|
35
|
+
muted: { DEFAULT: c("muted"), foreground: c("muted-foreground") },
|
|
36
|
+
accent: { DEFAULT: c("accent"), foreground: c("accent-foreground") },
|
|
37
37
|
destructive: {
|
|
38
|
-
DEFAULT: "
|
|
39
|
-
foreground: "
|
|
38
|
+
DEFAULT: c("destructive"),
|
|
39
|
+
foreground: c("destructive-foreground")
|
|
40
|
+
},
|
|
41
|
+
// Lines and focus
|
|
42
|
+
border: c("border"),
|
|
43
|
+
input: c("input"),
|
|
44
|
+
ring: c("ring"),
|
|
45
|
+
// Status
|
|
46
|
+
success: { DEFAULT: c("success"), foreground: c("success-foreground") },
|
|
47
|
+
warning: { DEFAULT: c("warning"), foreground: c("warning-foreground") },
|
|
48
|
+
info: { DEFAULT: c("info"), foreground: c("info-foreground") },
|
|
49
|
+
// Soft tints — Figma's `opacity/*` variables, already translucent. The Badge
|
|
50
|
+
// Soft type lays one over `card`, exactly as Figma stacks the two fills, so
|
|
51
|
+
// the chip reads the same on any surface. Only `primary` changes in Dark.
|
|
52
|
+
soft: {
|
|
53
|
+
primary: c("soft-primary"),
|
|
54
|
+
secondary: c("soft-secondary"),
|
|
55
|
+
info: c("soft-info"),
|
|
56
|
+
success: c("soft-success"),
|
|
57
|
+
warning: c("soft-warning"),
|
|
58
|
+
destructive: c("soft-destructive")
|
|
59
|
+
},
|
|
60
|
+
// Inverse — a fixed contrast band, not a mode-reactive surface.
|
|
61
|
+
"background-inverse": c("background-inverse"),
|
|
62
|
+
"foreground-inverse": c("foreground-inverse"),
|
|
63
|
+
"muted-foreground-inverse": c("muted-foreground-inverse"),
|
|
64
|
+
// Charts
|
|
65
|
+
"chart-1": c("chart-1"),
|
|
66
|
+
"chart-2": c("chart-2"),
|
|
67
|
+
"chart-3": c("chart-3"),
|
|
68
|
+
"chart-4": c("chart-4"),
|
|
69
|
+
"chart-5": c("chart-5"),
|
|
70
|
+
sidebar: {
|
|
71
|
+
DEFAULT: c("sidebar"),
|
|
72
|
+
foreground: c("sidebar-foreground"),
|
|
73
|
+
primary: c("sidebar-primary"),
|
|
74
|
+
"primary-foreground": c("sidebar-primary-foreground"),
|
|
75
|
+
accent: c("sidebar-accent"),
|
|
76
|
+
"accent-foreground": c("sidebar-accent-foreground"),
|
|
77
|
+
border: c("sidebar-border"),
|
|
78
|
+
ring: c("sidebar-ring")
|
|
40
79
|
}
|
|
41
80
|
};
|
|
42
81
|
function useMotionPresets() {
|
|
@@ -103,7 +142,7 @@ var registry = [
|
|
|
103
142
|
title: "Button",
|
|
104
143
|
description: "A button component with multiple variants, sizes, and loading state.",
|
|
105
144
|
sourcePath: "registry/ui/button",
|
|
106
|
-
dependencies: ["@radix-ui/react-slot", "class-variance-authority"],
|
|
145
|
+
dependencies: ["@radix-ui/react-slot", "class-variance-authority", "lucide-react"],
|
|
107
146
|
categories: ["primitives", "forms"]
|
|
108
147
|
},
|
|
109
148
|
{
|
|
@@ -127,8 +166,27 @@ var registry = [
|
|
|
127
166
|
name: "badge",
|
|
128
167
|
type: "ui",
|
|
129
168
|
title: "Badge",
|
|
130
|
-
description: "A
|
|
169
|
+
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.",
|
|
131
170
|
sourcePath: "registry/ui/badge",
|
|
171
|
+
dependencies: ["class-variance-authority", "lucide-react"],
|
|
172
|
+
categories: ["primitives", "data-display"]
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
name: "avatar",
|
|
176
|
+
type: "ui",
|
|
177
|
+
title: "Avatar",
|
|
178
|
+
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.",
|
|
179
|
+
sourcePath: "registry/ui/avatar",
|
|
180
|
+
dependencies: ["class-variance-authority", "lucide-react"],
|
|
181
|
+
registryDependencies: ["status-indicator"],
|
|
182
|
+
categories: ["primitives", "data-display"]
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
name: "status-indicator",
|
|
186
|
+
type: "ui",
|
|
187
|
+
title: "Status Indicator",
|
|
188
|
+
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.",
|
|
189
|
+
sourcePath: "registry/ui/status-indicator",
|
|
132
190
|
dependencies: ["class-variance-authority"],
|
|
133
191
|
categories: ["primitives", "data-display"]
|
|
134
192
|
},
|
|
@@ -136,10 +194,19 @@ var registry = [
|
|
|
136
194
|
name: "skeleton",
|
|
137
195
|
type: "ui",
|
|
138
196
|
title: "Skeleton",
|
|
139
|
-
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.",
|
|
140
198
|
sourcePath: "registry/ui/skeleton",
|
|
141
199
|
categories: ["primitives", "feedback"]
|
|
142
200
|
},
|
|
201
|
+
{
|
|
202
|
+
name: "radio",
|
|
203
|
+
type: "ui",
|
|
204
|
+
title: "Radio",
|
|
205
|
+
description: "An accessible radio for choosing one option from a set, with a plain and a bordered layout variant.",
|
|
206
|
+
sourcePath: "registry/ui/radio",
|
|
207
|
+
dependencies: ["class-variance-authority"],
|
|
208
|
+
categories: ["primitives", "forms"]
|
|
209
|
+
},
|
|
143
210
|
// Blocks
|
|
144
211
|
{
|
|
145
212
|
name: "app-shell",
|
|
@@ -163,6 +230,7 @@ var registry = [
|
|
|
163
230
|
title: "Empty State",
|
|
164
231
|
description: "Empty state component with icon, title, description, and actions.",
|
|
165
232
|
sourcePath: "registry/blocks/empty-state",
|
|
233
|
+
dependencies: ["lucide-react"],
|
|
166
234
|
categories: ["feedback", "data-display"]
|
|
167
235
|
},
|
|
168
236
|
{
|
|
@@ -238,4 +306,4 @@ function getRegistryItemsByCategory(category) {
|
|
|
238
306
|
return registry.filter((item) => item.categories?.includes(category));
|
|
239
307
|
}
|
|
240
308
|
|
|
241
|
-
export { baseTokens, cn, getRegistryItem, getRegistryItemsByCategory, getRegistryItemsByType, registry, tailwindSemanticColors, useMotionPresets };
|
|
309
|
+
export { baseTokens, cn, getRegistryItem, getRegistryItemsByCategory, getRegistryItemsByType, registry, semanticColor, tailwindSemanticColors, useMotionPresets };
|