@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
|
@@ -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 };
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/registry/index.ts
|
|
4
|
+
var registry = [
|
|
5
|
+
// UI Primitives
|
|
6
|
+
{
|
|
7
|
+
name: "button",
|
|
8
|
+
type: "ui",
|
|
9
|
+
title: "Button",
|
|
10
|
+
description: "A button component with multiple variants, sizes, and loading state.",
|
|
11
|
+
sourcePath: "registry/ui/button",
|
|
12
|
+
dependencies: ["@radix-ui/react-slot", "class-variance-authority", "lucide-react"],
|
|
13
|
+
categories: ["primitives", "forms"]
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
name: "input",
|
|
17
|
+
type: "ui",
|
|
18
|
+
title: "Input",
|
|
19
|
+
description: "An input component with variants for validation states and icon support.",
|
|
20
|
+
sourcePath: "registry/ui/input",
|
|
21
|
+
dependencies: ["class-variance-authority"],
|
|
22
|
+
categories: ["primitives", "forms"]
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
name: "card",
|
|
26
|
+
type: "ui",
|
|
27
|
+
title: "Card",
|
|
28
|
+
description: "A card component with header, content, and footer sections.",
|
|
29
|
+
sourcePath: "registry/ui/card",
|
|
30
|
+
categories: ["primitives", "layout"]
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
name: "badge",
|
|
34
|
+
type: "ui",
|
|
35
|
+
title: "Badge",
|
|
36
|
+
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.",
|
|
37
|
+
sourcePath: "registry/ui/badge",
|
|
38
|
+
dependencies: ["class-variance-authority", "lucide-react"],
|
|
39
|
+
categories: ["primitives", "data-display"]
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
name: "avatar",
|
|
43
|
+
type: "ui",
|
|
44
|
+
title: "Avatar",
|
|
45
|
+
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.",
|
|
46
|
+
sourcePath: "registry/ui/avatar",
|
|
47
|
+
dependencies: ["class-variance-authority", "lucide-react"],
|
|
48
|
+
registryDependencies: ["status-indicator"],
|
|
49
|
+
categories: ["primitives", "data-display"]
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
name: "status-indicator",
|
|
53
|
+
type: "ui",
|
|
54
|
+
title: "Status Indicator",
|
|
55
|
+
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.",
|
|
56
|
+
sourcePath: "registry/ui/status-indicator",
|
|
57
|
+
dependencies: ["class-variance-authority"],
|
|
58
|
+
categories: ["primitives", "data-display"]
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
name: "skeleton",
|
|
62
|
+
type: "ui",
|
|
63
|
+
title: "Skeleton",
|
|
64
|
+
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.",
|
|
65
|
+
sourcePath: "registry/ui/skeleton",
|
|
66
|
+
categories: ["primitives", "feedback"]
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
name: "radio",
|
|
70
|
+
type: "ui",
|
|
71
|
+
title: "Radio",
|
|
72
|
+
description: "An accessible radio for choosing one option from a set, with a plain and a bordered layout variant.",
|
|
73
|
+
sourcePath: "registry/ui/radio",
|
|
74
|
+
dependencies: ["class-variance-authority"],
|
|
75
|
+
categories: ["primitives", "forms"]
|
|
76
|
+
},
|
|
77
|
+
// Blocks
|
|
78
|
+
{
|
|
79
|
+
name: "app-shell",
|
|
80
|
+
type: "block",
|
|
81
|
+
title: "App Shell",
|
|
82
|
+
description: "Application shell with sidebar, header, and content area.",
|
|
83
|
+
sourcePath: "registry/blocks/app-shell",
|
|
84
|
+
categories: ["layout", "application"]
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
name: "page-header",
|
|
88
|
+
type: "block",
|
|
89
|
+
title: "Page Header",
|
|
90
|
+
description: "Page header with title, breadcrumb, description, and actions.",
|
|
91
|
+
sourcePath: "registry/blocks/page-header",
|
|
92
|
+
categories: ["layout", "navigation"]
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
name: "empty-state",
|
|
96
|
+
type: "block",
|
|
97
|
+
title: "Empty State",
|
|
98
|
+
description: "Empty state component with icon, title, description, and actions.",
|
|
99
|
+
sourcePath: "registry/blocks/empty-state",
|
|
100
|
+
dependencies: ["lucide-react"],
|
|
101
|
+
categories: ["feedback", "data-display"]
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
name: "form-section",
|
|
105
|
+
type: "block",
|
|
106
|
+
title: "Form Section",
|
|
107
|
+
description: "Form section with title, description, fields, and actions.",
|
|
108
|
+
sourcePath: "registry/blocks/form-section",
|
|
109
|
+
categories: ["forms", "layout"]
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
name: "data-table",
|
|
113
|
+
type: "block",
|
|
114
|
+
title: "Data Table",
|
|
115
|
+
description: "Data table with sorting, pagination, and custom cell rendering.",
|
|
116
|
+
sourcePath: "registry/blocks/data-table",
|
|
117
|
+
categories: ["data-display", "tables"]
|
|
118
|
+
},
|
|
119
|
+
// Effects
|
|
120
|
+
{
|
|
121
|
+
name: "shimmer-button",
|
|
122
|
+
type: "effect",
|
|
123
|
+
title: "Shimmer Button",
|
|
124
|
+
description: "A button with animated shimmer effect.",
|
|
125
|
+
sourcePath: "registry/effects/shimmer-button",
|
|
126
|
+
dependencies: ["framer-motion"],
|
|
127
|
+
categories: ["effects", "buttons"]
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
name: "animated-gradient",
|
|
131
|
+
type: "effect",
|
|
132
|
+
title: "Animated Gradient",
|
|
133
|
+
description: "Animated gradient background and text effects.",
|
|
134
|
+
sourcePath: "registry/effects/animated-gradient",
|
|
135
|
+
dependencies: ["framer-motion"],
|
|
136
|
+
categories: ["effects", "backgrounds"]
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
name: "text-reveal",
|
|
140
|
+
type: "effect",
|
|
141
|
+
title: "Text Reveal",
|
|
142
|
+
description: "Character and word reveal animations.",
|
|
143
|
+
sourcePath: "registry/effects/text-reveal",
|
|
144
|
+
dependencies: ["framer-motion"],
|
|
145
|
+
categories: ["effects", "text"]
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
name: "border-beam",
|
|
149
|
+
type: "effect",
|
|
150
|
+
title: "Border Beam",
|
|
151
|
+
description: "Animated border beam and glowing border effects.",
|
|
152
|
+
sourcePath: "registry/effects/border-beam",
|
|
153
|
+
dependencies: ["framer-motion"],
|
|
154
|
+
categories: ["effects", "borders"]
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
name: "spotlight",
|
|
158
|
+
type: "effect",
|
|
159
|
+
title: "Spotlight",
|
|
160
|
+
description: "Cursor-following spotlight effect.",
|
|
161
|
+
sourcePath: "registry/effects/spotlight",
|
|
162
|
+
dependencies: ["framer-motion"],
|
|
163
|
+
categories: ["effects", "interactive"]
|
|
164
|
+
}
|
|
165
|
+
];
|
|
166
|
+
function getRegistryItem(name) {
|
|
167
|
+
return registry.find((item) => item.name === name);
|
|
168
|
+
}
|
|
169
|
+
function getRegistryItemsByType(type) {
|
|
170
|
+
return registry.filter((item) => item.type === type);
|
|
171
|
+
}
|
|
172
|
+
function getRegistryItemsByCategory(category) {
|
|
173
|
+
return registry.filter((item) => item.categories?.includes(category));
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
exports.getRegistryItem = getRegistryItem;
|
|
177
|
+
exports.getRegistryItemsByCategory = getRegistryItemsByCategory;
|
|
178
|
+
exports.getRegistryItemsByType = getRegistryItemsByType;
|
|
179
|
+
exports.registry = registry;
|
|
@@ -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 };
|
package/dist/registry/index.js
CHANGED
|
@@ -7,7 +7,7 @@ var registry = [
|
|
|
7
7
|
title: "Button",
|
|
8
8
|
description: "A button component with multiple variants, sizes, and loading state.",
|
|
9
9
|
sourcePath: "registry/ui/button",
|
|
10
|
-
dependencies: ["@radix-ui/react-slot", "class-variance-authority"],
|
|
10
|
+
dependencies: ["@radix-ui/react-slot", "class-variance-authority", "lucide-react"],
|
|
11
11
|
categories: ["primitives", "forms"]
|
|
12
12
|
},
|
|
13
13
|
{
|
|
@@ -31,8 +31,27 @@ var registry = [
|
|
|
31
31
|
name: "badge",
|
|
32
32
|
type: "ui",
|
|
33
33
|
title: "Badge",
|
|
34
|
-
description: "A
|
|
34
|
+
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.",
|
|
35
35
|
sourcePath: "registry/ui/badge",
|
|
36
|
+
dependencies: ["class-variance-authority", "lucide-react"],
|
|
37
|
+
categories: ["primitives", "data-display"]
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
name: "avatar",
|
|
41
|
+
type: "ui",
|
|
42
|
+
title: "Avatar",
|
|
43
|
+
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.",
|
|
44
|
+
sourcePath: "registry/ui/avatar",
|
|
45
|
+
dependencies: ["class-variance-authority", "lucide-react"],
|
|
46
|
+
registryDependencies: ["status-indicator"],
|
|
47
|
+
categories: ["primitives", "data-display"]
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
name: "status-indicator",
|
|
51
|
+
type: "ui",
|
|
52
|
+
title: "Status Indicator",
|
|
53
|
+
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.",
|
|
54
|
+
sourcePath: "registry/ui/status-indicator",
|
|
36
55
|
dependencies: ["class-variance-authority"],
|
|
37
56
|
categories: ["primitives", "data-display"]
|
|
38
57
|
},
|
|
@@ -40,10 +59,19 @@ var registry = [
|
|
|
40
59
|
name: "skeleton",
|
|
41
60
|
type: "ui",
|
|
42
61
|
title: "Skeleton",
|
|
43
|
-
description: "Loading
|
|
62
|
+
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.",
|
|
44
63
|
sourcePath: "registry/ui/skeleton",
|
|
45
64
|
categories: ["primitives", "feedback"]
|
|
46
65
|
},
|
|
66
|
+
{
|
|
67
|
+
name: "radio",
|
|
68
|
+
type: "ui",
|
|
69
|
+
title: "Radio",
|
|
70
|
+
description: "An accessible radio for choosing one option from a set, with a plain and a bordered layout variant.",
|
|
71
|
+
sourcePath: "registry/ui/radio",
|
|
72
|
+
dependencies: ["class-variance-authority"],
|
|
73
|
+
categories: ["primitives", "forms"]
|
|
74
|
+
},
|
|
47
75
|
// Blocks
|
|
48
76
|
{
|
|
49
77
|
name: "app-shell",
|
|
@@ -67,6 +95,7 @@ var registry = [
|
|
|
67
95
|
title: "Empty State",
|
|
68
96
|
description: "Empty state component with icon, title, description, and actions.",
|
|
69
97
|
sourcePath: "registry/blocks/empty-state",
|
|
98
|
+
dependencies: ["lucide-react"],
|
|
70
99
|
categories: ["feedback", "data-display"]
|
|
71
100
|
},
|
|
72
101
|
{
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/tokens/model.ts
|
|
4
|
+
var baseTokens = {
|
|
5
|
+
radius: { sm: "0.5rem", md: "0.75rem", lg: "1rem" },
|
|
6
|
+
spacing: { densityMultiplier: { compact: 0.9, comfortable: 1, spacious: 1.1 } },
|
|
7
|
+
// Brand V2.5 §14 — Geist is the LTR face. Arabic is set in Cairo, which the
|
|
8
|
+
// Tailwind preset selects per direction; this scaffold carries only the base.
|
|
9
|
+
// 16/24 is the `Base` rung, identical in both Figma ramps.
|
|
10
|
+
typography: { fontFamily: "Geist", baseFontSizePx: 16, lineHeight: 1.5 },
|
|
11
|
+
motion: { durationsMs: { fast: 120, base: 180, slow: 260 } }
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
// src/tokens/tailwind.ts
|
|
15
|
+
var semanticColor = (name) => `color-mix(in oklch, var(--${name}) calc(<alpha-value> * 100%), transparent)`;
|
|
16
|
+
var c = semanticColor;
|
|
17
|
+
var tailwindSemanticColors = {
|
|
18
|
+
// Surfaces
|
|
19
|
+
background: c("background"),
|
|
20
|
+
foreground: c("foreground"),
|
|
21
|
+
card: { DEFAULT: c("card"), foreground: c("card-foreground") },
|
|
22
|
+
popover: { DEFAULT: c("popover"), foreground: c("popover-foreground") },
|
|
23
|
+
// Actions. `primary` is Figma's strong neutral (#0a0a0a light, #fafafa dark),
|
|
24
|
+
// identical to `foreground` in both modes. It is NOT a brand accent — an app
|
|
25
|
+
// that wants its brand colour should define its own token for it.
|
|
26
|
+
primary: { DEFAULT: c("primary"), foreground: c("primary-foreground") },
|
|
27
|
+
secondary: { DEFAULT: c("secondary"), foreground: c("secondary-foreground") },
|
|
28
|
+
muted: { DEFAULT: c("muted"), foreground: c("muted-foreground") },
|
|
29
|
+
accent: { DEFAULT: c("accent"), foreground: c("accent-foreground") },
|
|
30
|
+
destructive: {
|
|
31
|
+
DEFAULT: c("destructive"),
|
|
32
|
+
foreground: c("destructive-foreground")
|
|
33
|
+
},
|
|
34
|
+
// Lines and focus
|
|
35
|
+
border: c("border"),
|
|
36
|
+
input: c("input"),
|
|
37
|
+
ring: c("ring"),
|
|
38
|
+
// Status
|
|
39
|
+
success: { DEFAULT: c("success"), foreground: c("success-foreground") },
|
|
40
|
+
warning: { DEFAULT: c("warning"), foreground: c("warning-foreground") },
|
|
41
|
+
info: { DEFAULT: c("info"), foreground: c("info-foreground") },
|
|
42
|
+
// Soft tints — Figma's `opacity/*` variables, already translucent. The Badge
|
|
43
|
+
// Soft type lays one over `card`, exactly as Figma stacks the two fills, so
|
|
44
|
+
// the chip reads the same on any surface. Only `primary` changes in Dark.
|
|
45
|
+
soft: {
|
|
46
|
+
primary: c("soft-primary"),
|
|
47
|
+
secondary: c("soft-secondary"),
|
|
48
|
+
info: c("soft-info"),
|
|
49
|
+
success: c("soft-success"),
|
|
50
|
+
warning: c("soft-warning"),
|
|
51
|
+
destructive: c("soft-destructive")
|
|
52
|
+
},
|
|
53
|
+
// Inverse — a fixed contrast band, not a mode-reactive surface.
|
|
54
|
+
"background-inverse": c("background-inverse"),
|
|
55
|
+
"foreground-inverse": c("foreground-inverse"),
|
|
56
|
+
"muted-foreground-inverse": c("muted-foreground-inverse"),
|
|
57
|
+
// Charts
|
|
58
|
+
"chart-1": c("chart-1"),
|
|
59
|
+
"chart-2": c("chart-2"),
|
|
60
|
+
"chart-3": c("chart-3"),
|
|
61
|
+
"chart-4": c("chart-4"),
|
|
62
|
+
"chart-5": c("chart-5"),
|
|
63
|
+
sidebar: {
|
|
64
|
+
DEFAULT: c("sidebar"),
|
|
65
|
+
foreground: c("sidebar-foreground"),
|
|
66
|
+
primary: c("sidebar-primary"),
|
|
67
|
+
"primary-foreground": c("sidebar-primary-foreground"),
|
|
68
|
+
accent: c("sidebar-accent"),
|
|
69
|
+
"accent-foreground": c("sidebar-accent-foreground"),
|
|
70
|
+
border: c("sidebar-border"),
|
|
71
|
+
ring: c("sidebar-ring")
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
exports.baseTokens = baseTokens;
|
|
76
|
+
exports.semanticColor = semanticColor;
|
|
77
|
+
exports.tailwindSemanticColors = tailwindSemanticColors;
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
type Density = "compact" | "comfortable" | "spacious";
|
|
2
|
+
interface TokenSet {
|
|
3
|
+
radius: {
|
|
4
|
+
sm: string;
|
|
5
|
+
md: string;
|
|
6
|
+
lg: string;
|
|
7
|
+
};
|
|
8
|
+
spacing: {
|
|
9
|
+
/** Multipliers for vertical rhythm based on density */
|
|
10
|
+
densityMultiplier: Record<Density, number>;
|
|
11
|
+
};
|
|
12
|
+
typography: {
|
|
13
|
+
fontFamily: string;
|
|
14
|
+
baseFontSizePx: number;
|
|
15
|
+
lineHeight: number;
|
|
16
|
+
};
|
|
17
|
+
motion: {
|
|
18
|
+
durationsMs: {
|
|
19
|
+
fast: number;
|
|
20
|
+
base: number;
|
|
21
|
+
slow: number;
|
|
22
|
+
};
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
declare const baseTokens: TokenSet;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Tailwind mapping for the Fasla semantic colour tokens.
|
|
29
|
+
*
|
|
30
|
+
* These are the 41 `theme/*` variables of the Figma "☾ Mode" collection, plus
|
|
31
|
+
* the six `opacity/*` Soft tints. Figma
|
|
32
|
+
* is the source of truth; `design/tokens/mode.json` holds the committed
|
|
33
|
+
* snapshot, and both apps declare the same values in their own `globals.css`.
|
|
34
|
+
*
|
|
35
|
+
* Spread this into `theme.extend.colors` and define the matching CSS variables
|
|
36
|
+
* as full colours (hex or oklch) on `:root` and `.dark`.
|
|
37
|
+
*/
|
|
38
|
+
/**
|
|
39
|
+
* Resolve a semantic token to a colour Tailwind can apply an opacity modifier to.
|
|
40
|
+
*
|
|
41
|
+
* A bare `var(--x)` does NOT work: in Tailwind 3 the `/opacity` modifier is
|
|
42
|
+
* silently dropped when the colour is an unparseable string, so `bg-primary/10`
|
|
43
|
+
* compiles to no rule at all. Wrapping in `color-mix` with the `<alpha-value>`
|
|
44
|
+
* placeholder keeps both `bg-primary` and `bg-primary/10` working, while letting
|
|
45
|
+
* the CSS variables stay full colours rather than bare channel triplets.
|
|
46
|
+
*
|
|
47
|
+
* Needs Chrome 111+, Safari 16.2+, Firefox 113+.
|
|
48
|
+
*/
|
|
49
|
+
declare const semanticColor: (name: string) => string;
|
|
50
|
+
declare const tailwindSemanticColors: {
|
|
51
|
+
background: string;
|
|
52
|
+
foreground: string;
|
|
53
|
+
card: {
|
|
54
|
+
DEFAULT: string;
|
|
55
|
+
foreground: string;
|
|
56
|
+
};
|
|
57
|
+
popover: {
|
|
58
|
+
DEFAULT: string;
|
|
59
|
+
foreground: string;
|
|
60
|
+
};
|
|
61
|
+
primary: {
|
|
62
|
+
DEFAULT: string;
|
|
63
|
+
foreground: string;
|
|
64
|
+
};
|
|
65
|
+
secondary: {
|
|
66
|
+
DEFAULT: string;
|
|
67
|
+
foreground: string;
|
|
68
|
+
};
|
|
69
|
+
muted: {
|
|
70
|
+
DEFAULT: string;
|
|
71
|
+
foreground: string;
|
|
72
|
+
};
|
|
73
|
+
accent: {
|
|
74
|
+
DEFAULT: string;
|
|
75
|
+
foreground: string;
|
|
76
|
+
};
|
|
77
|
+
destructive: {
|
|
78
|
+
DEFAULT: string;
|
|
79
|
+
foreground: string;
|
|
80
|
+
};
|
|
81
|
+
border: string;
|
|
82
|
+
input: string;
|
|
83
|
+
ring: string;
|
|
84
|
+
success: {
|
|
85
|
+
DEFAULT: string;
|
|
86
|
+
foreground: string;
|
|
87
|
+
};
|
|
88
|
+
warning: {
|
|
89
|
+
DEFAULT: string;
|
|
90
|
+
foreground: string;
|
|
91
|
+
};
|
|
92
|
+
info: {
|
|
93
|
+
DEFAULT: string;
|
|
94
|
+
foreground: string;
|
|
95
|
+
};
|
|
96
|
+
soft: {
|
|
97
|
+
primary: string;
|
|
98
|
+
secondary: string;
|
|
99
|
+
info: string;
|
|
100
|
+
success: string;
|
|
101
|
+
warning: string;
|
|
102
|
+
destructive: string;
|
|
103
|
+
};
|
|
104
|
+
"background-inverse": string;
|
|
105
|
+
"foreground-inverse": string;
|
|
106
|
+
"muted-foreground-inverse": string;
|
|
107
|
+
"chart-1": string;
|
|
108
|
+
"chart-2": string;
|
|
109
|
+
"chart-3": string;
|
|
110
|
+
"chart-4": string;
|
|
111
|
+
"chart-5": string;
|
|
112
|
+
sidebar: {
|
|
113
|
+
DEFAULT: string;
|
|
114
|
+
foreground: string;
|
|
115
|
+
primary: string;
|
|
116
|
+
"primary-foreground": string;
|
|
117
|
+
accent: string;
|
|
118
|
+
"accent-foreground": string;
|
|
119
|
+
border: string;
|
|
120
|
+
ring: string;
|
|
121
|
+
};
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
export { type Density, type TokenSet, baseTokens, semanticColor, tailwindSemanticColors };
|
package/dist/tokens/index.d.ts
CHANGED
|
@@ -25,15 +25,39 @@ interface TokenSet {
|
|
|
25
25
|
declare const baseTokens: TokenSet;
|
|
26
26
|
|
|
27
27
|
/**
|
|
28
|
-
* Tailwind mapping
|
|
29
|
-
*
|
|
28
|
+
* Tailwind mapping for the Fasla semantic colour tokens.
|
|
29
|
+
*
|
|
30
|
+
* These are the 41 `theme/*` variables of the Figma "☾ Mode" collection, plus
|
|
31
|
+
* the six `opacity/*` Soft tints. Figma
|
|
32
|
+
* is the source of truth; `design/tokens/mode.json` holds the committed
|
|
33
|
+
* snapshot, and both apps declare the same values in their own `globals.css`.
|
|
34
|
+
*
|
|
35
|
+
* Spread this into `theme.extend.colors` and define the matching CSS variables
|
|
36
|
+
* as full colours (hex or oklch) on `:root` and `.dark`.
|
|
30
37
|
*/
|
|
38
|
+
/**
|
|
39
|
+
* Resolve a semantic token to a colour Tailwind can apply an opacity modifier to.
|
|
40
|
+
*
|
|
41
|
+
* A bare `var(--x)` does NOT work: in Tailwind 3 the `/opacity` modifier is
|
|
42
|
+
* silently dropped when the colour is an unparseable string, so `bg-primary/10`
|
|
43
|
+
* compiles to no rule at all. Wrapping in `color-mix` with the `<alpha-value>`
|
|
44
|
+
* placeholder keeps both `bg-primary` and `bg-primary/10` working, while letting
|
|
45
|
+
* the CSS variables stay full colours rather than bare channel triplets.
|
|
46
|
+
*
|
|
47
|
+
* Needs Chrome 111+, Safari 16.2+, Firefox 113+.
|
|
48
|
+
*/
|
|
49
|
+
declare const semanticColor: (name: string) => string;
|
|
31
50
|
declare const tailwindSemanticColors: {
|
|
32
|
-
border: string;
|
|
33
|
-
input: string;
|
|
34
|
-
ring: string;
|
|
35
51
|
background: string;
|
|
36
52
|
foreground: string;
|
|
53
|
+
card: {
|
|
54
|
+
DEFAULT: string;
|
|
55
|
+
foreground: string;
|
|
56
|
+
};
|
|
57
|
+
popover: {
|
|
58
|
+
DEFAULT: string;
|
|
59
|
+
foreground: string;
|
|
60
|
+
};
|
|
37
61
|
primary: {
|
|
38
62
|
DEFAULT: string;
|
|
39
63
|
foreground: string;
|
|
@@ -46,10 +70,55 @@ declare const tailwindSemanticColors: {
|
|
|
46
70
|
DEFAULT: string;
|
|
47
71
|
foreground: string;
|
|
48
72
|
};
|
|
73
|
+
accent: {
|
|
74
|
+
DEFAULT: string;
|
|
75
|
+
foreground: string;
|
|
76
|
+
};
|
|
49
77
|
destructive: {
|
|
50
78
|
DEFAULT: string;
|
|
51
79
|
foreground: string;
|
|
52
80
|
};
|
|
81
|
+
border: string;
|
|
82
|
+
input: string;
|
|
83
|
+
ring: string;
|
|
84
|
+
success: {
|
|
85
|
+
DEFAULT: string;
|
|
86
|
+
foreground: string;
|
|
87
|
+
};
|
|
88
|
+
warning: {
|
|
89
|
+
DEFAULT: string;
|
|
90
|
+
foreground: string;
|
|
91
|
+
};
|
|
92
|
+
info: {
|
|
93
|
+
DEFAULT: string;
|
|
94
|
+
foreground: string;
|
|
95
|
+
};
|
|
96
|
+
soft: {
|
|
97
|
+
primary: string;
|
|
98
|
+
secondary: string;
|
|
99
|
+
info: string;
|
|
100
|
+
success: string;
|
|
101
|
+
warning: string;
|
|
102
|
+
destructive: string;
|
|
103
|
+
};
|
|
104
|
+
"background-inverse": string;
|
|
105
|
+
"foreground-inverse": string;
|
|
106
|
+
"muted-foreground-inverse": string;
|
|
107
|
+
"chart-1": string;
|
|
108
|
+
"chart-2": string;
|
|
109
|
+
"chart-3": string;
|
|
110
|
+
"chart-4": string;
|
|
111
|
+
"chart-5": string;
|
|
112
|
+
sidebar: {
|
|
113
|
+
DEFAULT: string;
|
|
114
|
+
foreground: string;
|
|
115
|
+
primary: string;
|
|
116
|
+
"primary-foreground": string;
|
|
117
|
+
accent: string;
|
|
118
|
+
"accent-foreground": string;
|
|
119
|
+
border: string;
|
|
120
|
+
ring: string;
|
|
121
|
+
};
|
|
53
122
|
};
|
|
54
123
|
|
|
55
|
-
export { type Density, type TokenSet, baseTokens, tailwindSemanticColors };
|
|
124
|
+
export { type Density, type TokenSet, baseTokens, semanticColor, tailwindSemanticColors };
|