@launch77-shared/lib-design-system 0.1.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 +120 -0
- package/docs/README.md +883 -0
- package/package.json +49 -0
- package/src/index.d.ts +3 -0
- package/src/index.js +3 -0
- package/src/preset.js +139 -0
- package/src/tokens.css +66 -0
package/README.md
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# @launch77-shared/lib-design-system
|
|
2
|
+
|
|
3
|
+
Token-driven design system providing a theming foundation for Launch77 applications. Built for seamless integration with shadcn/ui component libraries.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
**Recommended:** Install via Launch77 plugin (handles all configuration automatically):
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
launch77 plugin:install design-system
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
**Manual installation** (not recommended):
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install @launch77-shared/lib-design-system
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
> **Note:** Manual installation requires configuring Tailwind preset and CSS imports yourself. The plugin automates this setup.
|
|
20
|
+
|
|
21
|
+
## Features
|
|
22
|
+
|
|
23
|
+
- 🎨 **Semantic Token System** - CSS custom properties in HSL format with alpha channel support
|
|
24
|
+
- 🌗 **Dark Mode** - Complete dark mode support via `.dark` class
|
|
25
|
+
- ⚡ **Tailwind Preset** - Ready-to-use Tailwind configuration with theme extensions
|
|
26
|
+
- ♿ **Accessibility First** - Auto-injected base styles with focus rings and reduced motion support
|
|
27
|
+
- 🎬 **Animations** - 8 built-in keyframe animations (accordion, fade, slide)
|
|
28
|
+
- 🎯 **Brand Customization** - Simple CSS cascade for per-app theming
|
|
29
|
+
- 🔧 **shadcn Compatible** - Follows shadcn/ui token architecture
|
|
30
|
+
|
|
31
|
+
## What's Included
|
|
32
|
+
|
|
33
|
+
### Semantic Color Tokens
|
|
34
|
+
|
|
35
|
+
```css
|
|
36
|
+
/* Available in light and dark modes */
|
|
37
|
+
--color-background / --color-foreground
|
|
38
|
+
--color-primary / --color-primary-foreground
|
|
39
|
+
--color-secondary / --color-secondary-foreground
|
|
40
|
+
--color-accent / --color-accent-foreground
|
|
41
|
+
--color-muted / --color-muted-foreground
|
|
42
|
+
--color-destructive / --color-destructive-foreground
|
|
43
|
+
--color-card / --color-card-foreground
|
|
44
|
+
--color-border
|
|
45
|
+
--color-input
|
|
46
|
+
--color-ring
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Tailwind Utilities
|
|
50
|
+
|
|
51
|
+
All semantic colors available as Tailwind utilities with alpha channel support:
|
|
52
|
+
|
|
53
|
+
```tsx
|
|
54
|
+
<div className="bg-primary/20 text-foreground border-input" />
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Animations
|
|
58
|
+
|
|
59
|
+
8 pre-built animations: fade-in/out, slide-in (4 directions), accordion-up/down.
|
|
60
|
+
|
|
61
|
+
All animations automatically respect `prefers-reduced-motion` preferences.
|
|
62
|
+
|
|
63
|
+
## Documentation
|
|
64
|
+
|
|
65
|
+
- **[Complete Documentation](./docs/README.md)** - Comprehensive guide with:
|
|
66
|
+
- Design system concepts and philosophy
|
|
67
|
+
- How to build components with tokens
|
|
68
|
+
- Token system reference
|
|
69
|
+
- Architecture overview
|
|
70
|
+
- Dark mode implementation details
|
|
71
|
+
- TypeScript API reference
|
|
72
|
+
|
|
73
|
+
- **Plugin README** - For installation and usage instructions:
|
|
74
|
+
- Quick start guide
|
|
75
|
+
- Usage examples (buttons, cards, forms)
|
|
76
|
+
- Brand customization
|
|
77
|
+
- Troubleshooting
|
|
78
|
+
|
|
79
|
+
## Quick Reference
|
|
80
|
+
|
|
81
|
+
### Using with Tailwind (if manually installed)
|
|
82
|
+
|
|
83
|
+
```js
|
|
84
|
+
// tailwind.config.js
|
|
85
|
+
const preset = require('@launch77-shared/lib-design-system/preset')
|
|
86
|
+
|
|
87
|
+
module.exports = {
|
|
88
|
+
presets: [preset],
|
|
89
|
+
content: ['./src/**/*.{ts,tsx}'],
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Importing Tokens (if manually installed)
|
|
94
|
+
|
|
95
|
+
```css
|
|
96
|
+
/* globals.css */
|
|
97
|
+
@tailwind base;
|
|
98
|
+
@tailwind components;
|
|
99
|
+
@tailwind utilities;
|
|
100
|
+
|
|
101
|
+
@import '@launch77-shared/lib-design-system/tokens.css';
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### HSL Color Format
|
|
105
|
+
|
|
106
|
+
Colors use HSL format for flexibility:
|
|
107
|
+
|
|
108
|
+
```css
|
|
109
|
+
--color-primary: 222 47% 11%; /* Hue Saturation% Lightness% */
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Benefits: Alpha channel support (`bg-primary/20`), easy to adjust, color picker compatible.
|
|
113
|
+
|
|
114
|
+
## TypeScript Support
|
|
115
|
+
|
|
116
|
+
Full TypeScript definitions included for Tailwind configuration.
|
|
117
|
+
|
|
118
|
+
## License
|
|
119
|
+
|
|
120
|
+
UNLICENSED - Internal Launch77 use only.
|
package/docs/README.md
ADDED
|
@@ -0,0 +1,883 @@
|
|
|
1
|
+
# Launch77 Design System - Complete Guide
|
|
2
|
+
|
|
3
|
+
This design system provides a token-driven theming foundation for Launch77 applications and shared component libraries. It enables consistent, accessible, and brand-customizable user interfaces through semantic design tokens and Tailwind CSS integration.
|
|
4
|
+
|
|
5
|
+
> **Installation:** Use the Launch77 plugin for automatic setup: `launch77 plugin:install design-system`
|
|
6
|
+
|
|
7
|
+
## Table of Contents
|
|
8
|
+
|
|
9
|
+
### Conceptual Guide
|
|
10
|
+
|
|
11
|
+
1. [What is a Design System?](#what-is-a-design-system)
|
|
12
|
+
2. [How to Build Components](#how-to-build-components)
|
|
13
|
+
3. [Token Philosophy](#token-philosophy)
|
|
14
|
+
4. [Common Patterns](#common-patterns)
|
|
15
|
+
5. [When to Use Tokens](#when-to-use-tokens-vs-hardcoded-values)
|
|
16
|
+
|
|
17
|
+
### Technical Reference
|
|
18
|
+
|
|
19
|
+
6. [Architecture Overview](#architecture-overview)
|
|
20
|
+
7. [Token System](#token-system)
|
|
21
|
+
8. [Tailwind Preset](#tailwind-preset)
|
|
22
|
+
9. [Dark Mode](#dark-mode)
|
|
23
|
+
10. [Animations](#animations)
|
|
24
|
+
11. [Accessibility Features](#accessibility-features)
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## Conceptual Guide
|
|
29
|
+
|
|
30
|
+
### What is a Design System?
|
|
31
|
+
|
|
32
|
+
A design system is a **theming foundation** that separates visual decisions (colors, spacing) from component structure (buttons, cards). Think of it as a contract between designers and developers:
|
|
33
|
+
|
|
34
|
+
- **Designers** define what "primary" means (your brand color)
|
|
35
|
+
- **Developers** build components that reference "primary" (not hardcoded blue)
|
|
36
|
+
- **Result**: Change the brand color once, and all components update automatically
|
|
37
|
+
|
|
38
|
+
#### Why Use Semantic Tokens?
|
|
39
|
+
|
|
40
|
+
Instead of naming colors by their appearance (`blue-500`, `red-600`), we name them by their **meaning**:
|
|
41
|
+
|
|
42
|
+
```tsx
|
|
43
|
+
// ❌ BAD: Hardcoded appearance
|
|
44
|
+
<button className="bg-blue-500 text-white">Submit</button>
|
|
45
|
+
|
|
46
|
+
// ✅ GOOD: Semantic meaning
|
|
47
|
+
<button className="bg-primary text-primary-foreground">Submit</button>
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
**Benefits:**
|
|
51
|
+
|
|
52
|
+
- **Rebrand easily**: Change `--color-primary` once, all buttons update
|
|
53
|
+
- **Dark mode**: Tokens adapt automatically (no component changes needed)
|
|
54
|
+
- **Consistency**: All "primary" buttons look identical across the app
|
|
55
|
+
- **Maintainability**: Designers control colors, not component code
|
|
56
|
+
|
|
57
|
+
#### Real-World Example
|
|
58
|
+
|
|
59
|
+
Your company rebrands from blue to green:
|
|
60
|
+
|
|
61
|
+
```css
|
|
62
|
+
/* Old brand */
|
|
63
|
+
--color-primary: 220 60% 50%; /* Blue */
|
|
64
|
+
|
|
65
|
+
/* New brand */
|
|
66
|
+
--color-primary: 150 60% 50%; /* Green */
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
All buttons, links, and primary elements update instantly - **no component code changes required**.
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
### How to Build Components
|
|
74
|
+
|
|
75
|
+
When building components with this design system, follow these rules:
|
|
76
|
+
|
|
77
|
+
#### Rule #1: Always Use Semantic Tokens
|
|
78
|
+
|
|
79
|
+
**Every color in your component should use a semantic token:**
|
|
80
|
+
|
|
81
|
+
```tsx
|
|
82
|
+
// ✅ CORRECT
|
|
83
|
+
function Button() {
|
|
84
|
+
return <button className="bg-primary text-primary-foreground hover:bg-primary/90">Click me</button>
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// ❌ WRONG
|
|
88
|
+
function Button() {
|
|
89
|
+
return <button className="bg-blue-500 text-white hover:bg-blue-600">Click me</button>
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
#### Rule #2: Never Hardcode Colors
|
|
94
|
+
|
|
95
|
+
**Don't use Tailwind's default color palette:**
|
|
96
|
+
|
|
97
|
+
- ❌ Never: `bg-blue-500`, `text-gray-600`, `border-red-400`
|
|
98
|
+
- ✅ Always: `bg-primary`, `text-muted-foreground`, `border-destructive`
|
|
99
|
+
|
|
100
|
+
#### Rule #3: Use Semantic Utilities for Meaning
|
|
101
|
+
|
|
102
|
+
**Match the token to the component's purpose:**
|
|
103
|
+
|
|
104
|
+
```tsx
|
|
105
|
+
// Primary action → bg-primary
|
|
106
|
+
<button className="bg-primary text-primary-foreground">Save</button>
|
|
107
|
+
|
|
108
|
+
// Destructive action → bg-destructive
|
|
109
|
+
<button className="bg-destructive text-destructive-foreground">Delete</button>
|
|
110
|
+
|
|
111
|
+
// Secondary action → bg-secondary
|
|
112
|
+
<button className="bg-secondary text-secondary-foreground">Cancel</button>
|
|
113
|
+
|
|
114
|
+
// Error message → text-destructive
|
|
115
|
+
<p className="text-destructive">Error: Invalid input</p>
|
|
116
|
+
|
|
117
|
+
// Subtle hint → text-muted-foreground
|
|
118
|
+
<p className="text-muted-foreground">Optional field</p>
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
#### Simple Decision Tree
|
|
122
|
+
|
|
123
|
+
When building a component, ask yourself:
|
|
124
|
+
|
|
125
|
+
1. **What is this component's purpose?**
|
|
126
|
+
- Primary action? → Use `bg-primary`
|
|
127
|
+
- Secondary action? → Use `bg-secondary`
|
|
128
|
+
- Destructive action? → Use `bg-destructive`
|
|
129
|
+
- Neutral container? → Use `bg-card`
|
|
130
|
+
- Emphasis/highlight? → Use `bg-accent`
|
|
131
|
+
|
|
132
|
+
2. **What is this text's role?**
|
|
133
|
+
- Main content? → Use `text-foreground`
|
|
134
|
+
- Subtle/helper text? → Use `text-muted-foreground`
|
|
135
|
+
- On colored background? → Use `text-primary-foreground` (matches the background)
|
|
136
|
+
|
|
137
|
+
3. **What are these borders/inputs?**
|
|
138
|
+
- Standard border? → Use `border-border`
|
|
139
|
+
- Form input? → Use `border-input`
|
|
140
|
+
- Focus ring? → Use `ring-ring`
|
|
141
|
+
|
|
142
|
+
---
|
|
143
|
+
|
|
144
|
+
### Token Philosophy
|
|
145
|
+
|
|
146
|
+
#### Tokens = Design Decisions
|
|
147
|
+
|
|
148
|
+
Design tokens represent **design decisions**, not specific colors:
|
|
149
|
+
|
|
150
|
+
- `--color-primary` = "What color represents our brand?"
|
|
151
|
+
- `--color-destructive` = "What color means danger/error?"
|
|
152
|
+
- `--color-muted` = "What color is subtle but visible?"
|
|
153
|
+
|
|
154
|
+
These decisions can change (rebrand, dark mode, A/B tests), but the component code stays the same.
|
|
155
|
+
|
|
156
|
+
#### Components = Structure
|
|
157
|
+
|
|
158
|
+
Components define **structure and behavior**, not appearance:
|
|
159
|
+
|
|
160
|
+
```tsx
|
|
161
|
+
// This component is "themeable" - it adapts to any theme
|
|
162
|
+
function Card({ children }) {
|
|
163
|
+
return <div className="bg-card text-card-foreground border border-border rounded-lg p-6">{children}</div>
|
|
164
|
+
}
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
The card works with:
|
|
168
|
+
|
|
169
|
+
- Any brand color (tokens define primary/accent)
|
|
170
|
+
- Light or dark mode (tokens adapt automatically)
|
|
171
|
+
- Any app's brand identity (each app overrides tokens)
|
|
172
|
+
|
|
173
|
+
#### The Power of Separation
|
|
174
|
+
|
|
175
|
+
By separating tokens (design) from components (structure), you get:
|
|
176
|
+
|
|
177
|
+
```tsx
|
|
178
|
+
// Component code (never changes)
|
|
179
|
+
<button className="bg-primary text-primary-foreground">Save</button>
|
|
180
|
+
|
|
181
|
+
// Theme 1: Blue brand
|
|
182
|
+
--color-primary: 220 60% 50%;
|
|
183
|
+
|
|
184
|
+
// Theme 2: Green brand
|
|
185
|
+
--color-primary: 150 60% 50%;
|
|
186
|
+
|
|
187
|
+
// Theme 3: Dark mode blue
|
|
188
|
+
--color-primary: 220 70% 60%;
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
Same component, different visual appearance - **zero code changes**.
|
|
192
|
+
|
|
193
|
+
---
|
|
194
|
+
|
|
195
|
+
### Common Patterns
|
|
196
|
+
|
|
197
|
+
Here's a quick reference for which tokens to use with common component types:
|
|
198
|
+
|
|
199
|
+
| Component Type | Recommended Tokens | Example |
|
|
200
|
+
| ---------------------- | -------------------------------------------------- | -------------------- |
|
|
201
|
+
| **Primary Button** | `bg-primary` `text-primary-foreground` | Save, Submit, Create |
|
|
202
|
+
| **Secondary Button** | `bg-secondary` `text-secondary-foreground` | Cancel, Back |
|
|
203
|
+
| **Destructive Button** | `bg-destructive` `text-destructive-foreground` | Delete, Remove |
|
|
204
|
+
| **Outline Button** | `border-input` `text-foreground` `hover:bg-accent` | Optional actions |
|
|
205
|
+
| **Ghost Button** | `hover:bg-accent` `text-foreground` | Icon buttons |
|
|
206
|
+
| **Card Container** | `bg-card` `text-card-foreground` `border-border` | Content cards |
|
|
207
|
+
| **Main Content** | `bg-background` `text-foreground` | Page layout |
|
|
208
|
+
| **Muted Container** | `bg-muted` `text-muted-foreground` | Subtle backgrounds |
|
|
209
|
+
| **Error Message** | `text-destructive` `border-destructive` | Validation errors |
|
|
210
|
+
| **Success Message** | `text-primary` `border-primary` | Success states |
|
|
211
|
+
| **Helper Text** | `text-muted-foreground` | Labels, hints |
|
|
212
|
+
| **Form Input** | `border-input` `bg-background` `ring-ring` | Text inputs |
|
|
213
|
+
| **Hover States** | `hover:bg-primary/90` (use alpha) | Interactive elements |
|
|
214
|
+
|
|
215
|
+
#### Real Component Examples
|
|
216
|
+
|
|
217
|
+
**Button Component with Variants:**
|
|
218
|
+
|
|
219
|
+
```tsx
|
|
220
|
+
import { cva } from 'class-variance-authority'
|
|
221
|
+
|
|
222
|
+
const button = cva(
|
|
223
|
+
// Base styles (always applied)
|
|
224
|
+
'inline-flex items-center justify-center rounded-lg px-4 py-2 font-medium transition-colors',
|
|
225
|
+
{
|
|
226
|
+
variants: {
|
|
227
|
+
variant: {
|
|
228
|
+
primary: 'bg-primary text-primary-foreground hover:bg-primary/90',
|
|
229
|
+
secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80',
|
|
230
|
+
destructive: 'bg-destructive text-destructive-foreground hover:bg-destructive/90',
|
|
231
|
+
outline: 'border border-input bg-background hover:bg-accent text-foreground',
|
|
232
|
+
ghost: 'hover:bg-accent hover:text-accent-foreground',
|
|
233
|
+
},
|
|
234
|
+
},
|
|
235
|
+
}
|
|
236
|
+
)
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
**Card Component:**
|
|
240
|
+
|
|
241
|
+
```tsx
|
|
242
|
+
function Card({ children, className = '' }) {
|
|
243
|
+
return <div className={`bg-card text-card-foreground border border-border rounded-lg p-6 ${className}`}>{children}</div>
|
|
244
|
+
}
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
**Form Input:**
|
|
248
|
+
|
|
249
|
+
```tsx
|
|
250
|
+
function Input({ className = '', ...props }) {
|
|
251
|
+
return <input className={`w-full px-3 py-2 bg-background text-foreground border border-input rounded-md focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 ${className}`} {...props} />
|
|
252
|
+
}
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
**Alert Component:**
|
|
256
|
+
|
|
257
|
+
```tsx
|
|
258
|
+
function Alert({ variant = 'default', children }) {
|
|
259
|
+
const styles = {
|
|
260
|
+
default: 'bg-card text-card-foreground border-border',
|
|
261
|
+
destructive: 'bg-destructive/10 text-destructive border-destructive',
|
|
262
|
+
success: 'bg-primary/10 text-primary border-primary',
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
return <div className={`border rounded-lg p-4 ${styles[variant]}`}>{children}</div>
|
|
266
|
+
}
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
---
|
|
270
|
+
|
|
271
|
+
### When to Use Tokens vs Hardcoded Values
|
|
272
|
+
|
|
273
|
+
#### ✅ Use Tokens When...
|
|
274
|
+
|
|
275
|
+
**Colors should adapt to theme or brand:**
|
|
276
|
+
|
|
277
|
+
```tsx
|
|
278
|
+
// ✅ Button color should match brand
|
|
279
|
+
<button className="bg-primary text-primary-foreground">Submit</button>
|
|
280
|
+
|
|
281
|
+
// ✅ Text should adapt to light/dark mode
|
|
282
|
+
<p className="text-foreground">Main content</p>
|
|
283
|
+
|
|
284
|
+
// ✅ Card backgrounds should match theme
|
|
285
|
+
<div className="bg-card border-border">...</div>
|
|
286
|
+
|
|
287
|
+
// ✅ Interactive states should use theme colors
|
|
288
|
+
<button className="hover:bg-primary/90">Hover me</button>
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
**Components that will be reused:**
|
|
292
|
+
|
|
293
|
+
- Buttons, cards, inputs, navigation, etc.
|
|
294
|
+
- Any UI element users interact with
|
|
295
|
+
- Layout containers
|
|
296
|
+
- Text content
|
|
297
|
+
|
|
298
|
+
#### ❌ Don't Use Tokens For...
|
|
299
|
+
|
|
300
|
+
**Decorative elements that shouldn't change:**
|
|
301
|
+
|
|
302
|
+
```tsx
|
|
303
|
+
// ❌ Company logo color is part of brand identity
|
|
304
|
+
<svg className="fill-[#FF5816]">Logo</svg>
|
|
305
|
+
|
|
306
|
+
// ❌ Specific illustrations have fixed colors
|
|
307
|
+
<img src="/hero-illustration.svg" alt="Hero" />
|
|
308
|
+
|
|
309
|
+
// ❌ Chart colors need to be distinct (not theme-dependent)
|
|
310
|
+
<Bar data={data} colors={['#ff0000', '#00ff00', '#0000ff']} />
|
|
311
|
+
|
|
312
|
+
// ❌ Code syntax highlighting (needs specific colors)
|
|
313
|
+
<code className="language-js">...</code>
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
**One-off decorative flourishes:**
|
|
317
|
+
|
|
318
|
+
- Gradients in marketing headers
|
|
319
|
+
- Specific icons with fixed colors
|
|
320
|
+
- Photography and illustrations
|
|
321
|
+
- Data visualizations that need precise colors
|
|
322
|
+
- Third-party embedded content
|
|
323
|
+
|
|
324
|
+
#### Simple Rule of Thumb
|
|
325
|
+
|
|
326
|
+
**Ask yourself: "If the user switches to dark mode or we rebrand, should this change color?"**
|
|
327
|
+
|
|
328
|
+
- **Yes** → Use a semantic token
|
|
329
|
+
- **No** → Use a hardcoded value
|
|
330
|
+
|
|
331
|
+
#### Examples in Context
|
|
332
|
+
|
|
333
|
+
```tsx
|
|
334
|
+
function ProductCard({ product }) {
|
|
335
|
+
return (
|
|
336
|
+
{/* ✅ Card uses theme tokens (adapts to theme) */}
|
|
337
|
+
<div className="bg-card text-card-foreground border border-border rounded-lg p-4">
|
|
338
|
+
|
|
339
|
+
{/* ❌ Product image doesn't change with theme */}
|
|
340
|
+
<img src={product.image} alt={product.name} />
|
|
341
|
+
|
|
342
|
+
{/* ✅ Text uses theme tokens (adapts to light/dark) */}
|
|
343
|
+
<h3 className="text-foreground font-bold">{product.name}</h3>
|
|
344
|
+
<p className="text-muted-foreground">{product.description}</p>
|
|
345
|
+
|
|
346
|
+
{/* ❌ Product category badge has fixed brand color */}
|
|
347
|
+
<span className="bg-[#FF5816] text-white px-2 py-1 rounded">
|
|
348
|
+
{product.category}
|
|
349
|
+
</span>
|
|
350
|
+
|
|
351
|
+
{/* ✅ Action button uses theme tokens */}
|
|
352
|
+
<button className="bg-primary text-primary-foreground hover:bg-primary/90">
|
|
353
|
+
Add to Cart
|
|
354
|
+
</button>
|
|
355
|
+
</div>
|
|
356
|
+
)
|
|
357
|
+
}
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
---
|
|
361
|
+
|
|
362
|
+
## Technical Reference
|
|
363
|
+
|
|
364
|
+
### Architecture Overview
|
|
365
|
+
|
|
366
|
+
The design system consists of three layers:
|
|
367
|
+
|
|
368
|
+
#### 1. CSS Tokens (`tokens.css`)
|
|
369
|
+
|
|
370
|
+
Pure semantic CSS custom properties with no framework dependencies. These define the visual language of your application:
|
|
371
|
+
|
|
372
|
+
```css
|
|
373
|
+
:root {
|
|
374
|
+
--color-background: 0 0% 100%;
|
|
375
|
+
--color-foreground: 222 47% 11%;
|
|
376
|
+
--color-primary: 222 47% 11%;
|
|
377
|
+
/* ... more tokens */
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
.dark {
|
|
381
|
+
--color-background: 222 47% 11%;
|
|
382
|
+
--color-foreground: 210 40% 98%;
|
|
383
|
+
--color-primary: 217 91% 60%;
|
|
384
|
+
/* ... dark mode overrides */
|
|
385
|
+
}
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
**Benefits:**
|
|
389
|
+
|
|
390
|
+
- Framework-agnostic (works with any CSS)
|
|
391
|
+
- Cascades naturally (easy to override)
|
|
392
|
+
- No build step required
|
|
393
|
+
|
|
394
|
+
#### 2. Tailwind Preset (`preset.js`)
|
|
395
|
+
|
|
396
|
+
Maps Tailwind utilities to the CSS tokens and includes a plugin that auto-injects base styles:
|
|
397
|
+
|
|
398
|
+
```js
|
|
399
|
+
module.exports = {
|
|
400
|
+
theme: {
|
|
401
|
+
extend: {
|
|
402
|
+
colors: {
|
|
403
|
+
background: 'hsl(var(--color-background) / <alpha-value>)',
|
|
404
|
+
foreground: 'hsl(var(--color-foreground) / <alpha-value>)',
|
|
405
|
+
primary: {
|
|
406
|
+
DEFAULT: 'hsl(var(--color-primary) / <alpha-value>)',
|
|
407
|
+
foreground: 'hsl(var(--color-primary-foreground) / <alpha-value>)',
|
|
408
|
+
},
|
|
409
|
+
// ... more mappings
|
|
410
|
+
},
|
|
411
|
+
},
|
|
412
|
+
},
|
|
413
|
+
plugins: [
|
|
414
|
+
/* Auto-inject base styles */
|
|
415
|
+
],
|
|
416
|
+
}
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
**Benefits:**
|
|
420
|
+
|
|
421
|
+
- Tailwind utilities automatically use tokens
|
|
422
|
+
- Alpha channel support (`bg-primary/20`)
|
|
423
|
+
- No manual configuration needed
|
|
424
|
+
|
|
425
|
+
#### 3. App-Level Customization
|
|
426
|
+
|
|
427
|
+
Each app can override tokens via its own `brand.css` file without touching shared components:
|
|
428
|
+
|
|
429
|
+
```css
|
|
430
|
+
/* app/brand.css */
|
|
431
|
+
:root {
|
|
432
|
+
--color-primary: 158 64% 52%; /* Override with brand color */
|
|
433
|
+
--color-accent: 156 72% 72%;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
.dark {
|
|
437
|
+
--color-primary: 158 64% 52%; /* Dark mode override */
|
|
438
|
+
}
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
**Result:** Components stay neutral and reusable, while each app maintains its unique brand identity.
|
|
442
|
+
|
|
443
|
+
---
|
|
444
|
+
|
|
445
|
+
### Token System
|
|
446
|
+
|
|
447
|
+
#### Understanding HSL Format
|
|
448
|
+
|
|
449
|
+
All color tokens use HSL (Hue, Saturation, Lightness) format without the `hsl()` wrapper:
|
|
450
|
+
|
|
451
|
+
```css
|
|
452
|
+
--color-primary: 222 47% 11%;
|
|
453
|
+
/* ^^^ ^^ ^^
|
|
454
|
+
H S L */
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
**Why no `hsl()` wrapper?**
|
|
458
|
+
|
|
459
|
+
This format enables Tailwind's alpha channel syntax:
|
|
460
|
+
|
|
461
|
+
```tsx
|
|
462
|
+
{/* Tailwind adds hsl() and alpha automatically */}
|
|
463
|
+
<div className="bg-primary/20"> {/* → hsl(222 47% 11% / 0.2) */}
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
**HSL Benefits:**
|
|
467
|
+
|
|
468
|
+
- **Alpha Channel**: Use `/` for opacity (e.g., `bg-primary/20` = 20% opacity)
|
|
469
|
+
- **Human-Readable**: Easy to understand and adjust
|
|
470
|
+
- **Design Tool Compatible**: Most color pickers support HSL
|
|
471
|
+
- **shadcn Standard**: Matches shadcn/ui architecture
|
|
472
|
+
|
|
473
|
+
**Converting from other formats:**
|
|
474
|
+
|
|
475
|
+
```
|
|
476
|
+
Hex #3730A3 → HSL 243 58% 41%
|
|
477
|
+
RGB 55 48 163 → HSL 243 58% 41%
|
|
478
|
+
|
|
479
|
+
Use online tools: hslpicker.com or browser DevTools color picker
|
|
480
|
+
```
|
|
481
|
+
|
|
482
|
+
#### Complete Token Reference
|
|
483
|
+
|
|
484
|
+
##### Base Colors
|
|
485
|
+
|
|
486
|
+
```css
|
|
487
|
+
--color-background /* Main app background (usually white/black) */
|
|
488
|
+
--color-foreground /* Main text color (usually black/white) */
|
|
489
|
+
```
|
|
490
|
+
|
|
491
|
+
**When to use:**
|
|
492
|
+
|
|
493
|
+
- `bg-background` - Page layout, main containers
|
|
494
|
+
- `text-foreground` - Primary text content
|
|
495
|
+
|
|
496
|
+
##### Card
|
|
497
|
+
|
|
498
|
+
```css
|
|
499
|
+
--color-card /* Card background (slightly elevated) */
|
|
500
|
+
--color-card-foreground /* Card text color */
|
|
501
|
+
```
|
|
502
|
+
|
|
503
|
+
**When to use:**
|
|
504
|
+
|
|
505
|
+
- `bg-card` - Content cards, modals, dropdown menus
|
|
506
|
+
- `text-card-foreground` - Text inside cards
|
|
507
|
+
|
|
508
|
+
##### Primary (Brand Color)
|
|
509
|
+
|
|
510
|
+
```css
|
|
511
|
+
--color-primary /* Main brand color */
|
|
512
|
+
--color-primary-foreground /* Text on primary background */
|
|
513
|
+
```
|
|
514
|
+
|
|
515
|
+
**When to use:**
|
|
516
|
+
|
|
517
|
+
- `bg-primary` - Primary action buttons, active states, brand elements
|
|
518
|
+
- `text-primary` - Links, highlighted text
|
|
519
|
+
- `text-primary-foreground` - Text on primary backgrounds
|
|
520
|
+
|
|
521
|
+
##### Secondary
|
|
522
|
+
|
|
523
|
+
```css
|
|
524
|
+
--color-secondary /* Secondary actions/elements */
|
|
525
|
+
--color-secondary-foreground /* Text on secondary background */
|
|
526
|
+
```
|
|
527
|
+
|
|
528
|
+
**When to use:**
|
|
529
|
+
|
|
530
|
+
- `bg-secondary` - Secondary buttons, less important actions
|
|
531
|
+
- `text-secondary-foreground` - Text on secondary backgrounds
|
|
532
|
+
|
|
533
|
+
##### Accent
|
|
534
|
+
|
|
535
|
+
```css
|
|
536
|
+
--color-accent /* Accent color for highlights */
|
|
537
|
+
--color-accent-foreground /* Text on accent background */
|
|
538
|
+
```
|
|
539
|
+
|
|
540
|
+
**When to use:**
|
|
541
|
+
|
|
542
|
+
- `bg-accent` - Hover states, highlights, badges
|
|
543
|
+
- `hover:bg-accent` - Interactive element hover states
|
|
544
|
+
|
|
545
|
+
##### Muted
|
|
546
|
+
|
|
547
|
+
```css
|
|
548
|
+
--color-muted /* Subtle backgrounds */
|
|
549
|
+
--color-muted-foreground /* Subtle text color */
|
|
550
|
+
```
|
|
551
|
+
|
|
552
|
+
**When to use:**
|
|
553
|
+
|
|
554
|
+
- `bg-muted` - Disabled states, subtle containers
|
|
555
|
+
- `text-muted-foreground` - Helper text, labels, secondary info
|
|
556
|
+
|
|
557
|
+
##### Destructive
|
|
558
|
+
|
|
559
|
+
```css
|
|
560
|
+
--color-destructive /* Destructive actions (delete, error) */
|
|
561
|
+
--color-destructive-foreground /* Text on destructive background */
|
|
562
|
+
```
|
|
563
|
+
|
|
564
|
+
**When to use:**
|
|
565
|
+
|
|
566
|
+
- `bg-destructive` - Delete buttons, error alerts
|
|
567
|
+
- `text-destructive` - Error messages, validation failures
|
|
568
|
+
- `border-destructive` - Error input borders
|
|
569
|
+
|
|
570
|
+
##### Form Elements
|
|
571
|
+
|
|
572
|
+
```css
|
|
573
|
+
--color-border /* Standard border color */
|
|
574
|
+
--color-input /* Input border color (slightly darker) */
|
|
575
|
+
--color-ring /* Focus ring color (usually primary) */
|
|
576
|
+
```
|
|
577
|
+
|
|
578
|
+
**When to use:**
|
|
579
|
+
|
|
580
|
+
- `border-border` - Card borders, dividers
|
|
581
|
+
- `border-input` - Form input borders
|
|
582
|
+
- `ring-ring` - Focus rings on interactive elements
|
|
583
|
+
|
|
584
|
+
##### Border Radius
|
|
585
|
+
|
|
586
|
+
```css
|
|
587
|
+
--radius: 1rem /* Base border radius (16px) */;
|
|
588
|
+
```
|
|
589
|
+
|
|
590
|
+
**Tailwind utilities automatically use this:**
|
|
591
|
+
|
|
592
|
+
- `rounded-lg` → `var(--radius)` (16px)
|
|
593
|
+
- `rounded-md` → `calc(var(--radius) - 2px)` (14px)
|
|
594
|
+
- `rounded-sm` → `calc(var(--radius) - 4px)` (12px)
|
|
595
|
+
|
|
596
|
+
---
|
|
597
|
+
|
|
598
|
+
### Tailwind Preset
|
|
599
|
+
|
|
600
|
+
#### What the Preset Provides
|
|
601
|
+
|
|
602
|
+
The `preset.js` file exports a Tailwind configuration object that:
|
|
603
|
+
|
|
604
|
+
1. **Maps all tokens to Tailwind utilities**
|
|
605
|
+
2. **Enables alpha channel support** (`/` syntax)
|
|
606
|
+
3. **Auto-injects base styles** via plugin
|
|
607
|
+
4. **Configures border radius** utilities
|
|
608
|
+
5. **Adds animation keyframes**
|
|
609
|
+
|
|
610
|
+
#### Configuration API
|
|
611
|
+
|
|
612
|
+
```js
|
|
613
|
+
// tailwind.config.js
|
|
614
|
+
const preset = require('@launch77-shared/lib-design-system/preset')
|
|
615
|
+
|
|
616
|
+
/** @type {import('tailwindcss').Config} */
|
|
617
|
+
module.exports = {
|
|
618
|
+
presets: [preset],
|
|
619
|
+
content: ['./src/**/*.{ts,tsx,js,jsx}', './app/**/*.{ts,tsx,js,jsx}'],
|
|
620
|
+
}
|
|
621
|
+
```
|
|
622
|
+
|
|
623
|
+
#### Alpha Channel Support
|
|
624
|
+
|
|
625
|
+
All color tokens support Tailwind's alpha channel syntax:
|
|
626
|
+
|
|
627
|
+
```tsx
|
|
628
|
+
<div className="bg-primary/10">10% opacity</div>
|
|
629
|
+
<div className="bg-primary/20">20% opacity</div>
|
|
630
|
+
<div className="bg-primary/50">50% opacity</div>
|
|
631
|
+
<div className="bg-primary/90">90% opacity</div>
|
|
632
|
+
|
|
633
|
+
<button className="bg-primary/90 hover:bg-primary">
|
|
634
|
+
Hover to full opacity
|
|
635
|
+
</button>
|
|
636
|
+
```
|
|
637
|
+
|
|
638
|
+
**How it works:**
|
|
639
|
+
|
|
640
|
+
The preset maps tokens like this:
|
|
641
|
+
|
|
642
|
+
```js
|
|
643
|
+
colors: {
|
|
644
|
+
primary: 'hsl(var(--color-primary) / <alpha-value>)',
|
|
645
|
+
// ^^^^^^^^^^^^^^
|
|
646
|
+
// Tailwind replaces this
|
|
647
|
+
}
|
|
648
|
+
```
|
|
649
|
+
|
|
650
|
+
When you write `bg-primary/20`, Tailwind generates:
|
|
651
|
+
|
|
652
|
+
```css
|
|
653
|
+
.bg-primary\/20 {
|
|
654
|
+
background-color: hsl(var(--color-primary) / 0.2);
|
|
655
|
+
}
|
|
656
|
+
```
|
|
657
|
+
|
|
658
|
+
#### Auto-Injected Base Styles
|
|
659
|
+
|
|
660
|
+
The preset's plugin automatically adds these base styles (no manual copying needed):
|
|
661
|
+
|
|
662
|
+
```css
|
|
663
|
+
* {
|
|
664
|
+
border-color: hsl(var(--color-border));
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
html {
|
|
668
|
+
scroll-behavior: smooth;
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
body {
|
|
672
|
+
background-color: hsl(var(--color-background));
|
|
673
|
+
color: hsl(var(--color-foreground));
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
/* Plus: selection styles, focus rings, tap targets, reduced motion */
|
|
677
|
+
```
|
|
678
|
+
|
|
679
|
+
---
|
|
680
|
+
|
|
681
|
+
### Dark Mode
|
|
682
|
+
|
|
683
|
+
#### Implementation Strategy
|
|
684
|
+
|
|
685
|
+
Dark mode uses **class-based toggling** on the `<html>` element:
|
|
686
|
+
|
|
687
|
+
```html
|
|
688
|
+
<!-- Light mode (default) -->
|
|
689
|
+
<html>
|
|
690
|
+
...
|
|
691
|
+
</html>
|
|
692
|
+
|
|
693
|
+
<!-- Dark mode (add .dark class) -->
|
|
694
|
+
<html class="dark">
|
|
695
|
+
...
|
|
696
|
+
</html>
|
|
697
|
+
```
|
|
698
|
+
|
|
699
|
+
#### How It Works
|
|
700
|
+
|
|
701
|
+
Tokens are defined twice - once in `:root` (light mode), once in `.dark` (dark mode):
|
|
702
|
+
|
|
703
|
+
```css
|
|
704
|
+
/* tokens.css */
|
|
705
|
+
:root {
|
|
706
|
+
--color-background: 0 0% 100%; /* White */
|
|
707
|
+
--color-foreground: 222 47% 11%; /* Dark gray */
|
|
708
|
+
--color-primary: 222 47% 11%; /* Dark blue */
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
.dark {
|
|
712
|
+
--color-background: 222 47% 11%; /* Dark gray */
|
|
713
|
+
--color-foreground: 210 40% 98%; /* Off-white */
|
|
714
|
+
--color-primary: 217 91% 60%; /* Light blue */
|
|
715
|
+
}
|
|
716
|
+
```
|
|
717
|
+
|
|
718
|
+
When `.dark` is added to `<html>`, the CSS cascade applies dark mode values. Components don't change - they still use `bg-background`, `text-foreground`, etc.
|
|
719
|
+
|
|
720
|
+
#### Technical Details
|
|
721
|
+
|
|
722
|
+
**Why class-based instead of `prefers-color-scheme`?**
|
|
723
|
+
|
|
724
|
+
- User can override system preference
|
|
725
|
+
- Easy to persist in localStorage
|
|
726
|
+
- Simple to toggle programmatically
|
|
727
|
+
- Works in all browsers
|
|
728
|
+
|
|
729
|
+
**Why on `<html>` instead of `<body>`?**
|
|
730
|
+
|
|
731
|
+
- Tailwind's dark mode selector targets `:root.dark`
|
|
732
|
+
- Ensures all elements inherit correct mode
|
|
733
|
+
- Fixes edge cases with portals and modals
|
|
734
|
+
|
|
735
|
+
---
|
|
736
|
+
|
|
737
|
+
### Animations
|
|
738
|
+
|
|
739
|
+
#### Available Keyframes
|
|
740
|
+
|
|
741
|
+
The preset provides 8 animation keyframes:
|
|
742
|
+
|
|
743
|
+
```css
|
|
744
|
+
@keyframes fadeIn
|
|
745
|
+
@keyframes fadeOut
|
|
746
|
+
@keyframes slideInFromTop
|
|
747
|
+
@keyframes slideInFromBottom
|
|
748
|
+
@keyframes slideInFromLeft
|
|
749
|
+
@keyframes slideInFromRight
|
|
750
|
+
@keyframes accordionDown
|
|
751
|
+
@keyframes accordionUp;
|
|
752
|
+
```
|
|
753
|
+
|
|
754
|
+
#### Tailwind Utilities
|
|
755
|
+
|
|
756
|
+
```tsx
|
|
757
|
+
{/* Fade animations */}
|
|
758
|
+
<div className="animate-fade-in">Fades in</div>
|
|
759
|
+
<div className="animate-fade-out">Fades out</div>
|
|
760
|
+
|
|
761
|
+
{/* Slide animations */}
|
|
762
|
+
<div className="animate-slide-in-from-top">Slides from top</div>
|
|
763
|
+
<div className="animate-slide-in-from-bottom">Slides from bottom</div>
|
|
764
|
+
<div className="animate-slide-in-from-left">Slides from left</div>
|
|
765
|
+
<div className="animate-slide-in-from-right">Slides from right</div>
|
|
766
|
+
|
|
767
|
+
{/* Accordion animations (for Radix UI) */}
|
|
768
|
+
<div className="animate-accordion-down">Expands</div>
|
|
769
|
+
<div className="animate-accordion-up">Collapses</div>
|
|
770
|
+
```
|
|
771
|
+
|
|
772
|
+
#### Accessibility
|
|
773
|
+
|
|
774
|
+
All animations automatically respect `prefers-reduced-motion`:
|
|
775
|
+
|
|
776
|
+
```css
|
|
777
|
+
@media (prefers-reduced-motion: reduce) {
|
|
778
|
+
*,
|
|
779
|
+
*::before,
|
|
780
|
+
*::after {
|
|
781
|
+
animation-duration: 0.01ms !important;
|
|
782
|
+
animation-iteration-count: 1 !important;
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
```
|
|
786
|
+
|
|
787
|
+
Users who have enabled reduced motion in their OS settings will see instant state changes instead of animations.
|
|
788
|
+
|
|
789
|
+
---
|
|
790
|
+
|
|
791
|
+
### Accessibility Features
|
|
792
|
+
|
|
793
|
+
The preset auto-injects several accessibility features:
|
|
794
|
+
|
|
795
|
+
#### Focus Rings
|
|
796
|
+
|
|
797
|
+
```css
|
|
798
|
+
/* Automatically applied to all interactive elements */
|
|
799
|
+
*:focus-visible {
|
|
800
|
+
outline: none;
|
|
801
|
+
box-shadow: 0 0 0 2px hsl(var(--color-ring));
|
|
802
|
+
}
|
|
803
|
+
```
|
|
804
|
+
|
|
805
|
+
Override per-component if needed:
|
|
806
|
+
|
|
807
|
+
```tsx
|
|
808
|
+
<button className="focus-visible:ring-2 focus-visible:ring-offset-2">Custom focus ring</button>
|
|
809
|
+
```
|
|
810
|
+
|
|
811
|
+
#### Minimum Tap Targets
|
|
812
|
+
|
|
813
|
+
```css
|
|
814
|
+
/* Interactive elements are at least 44x44px */
|
|
815
|
+
button,
|
|
816
|
+
[role='button'],
|
|
817
|
+
input,
|
|
818
|
+
select,
|
|
819
|
+
textarea,
|
|
820
|
+
a {
|
|
821
|
+
min-height: 44px;
|
|
822
|
+
min-width: 44px;
|
|
823
|
+
}
|
|
824
|
+
```
|
|
825
|
+
|
|
826
|
+
#### Reduced Motion
|
|
827
|
+
|
|
828
|
+
```css
|
|
829
|
+
@media (prefers-reduced-motion: reduce) {
|
|
830
|
+
/* Disables animations for users who prefer reduced motion */
|
|
831
|
+
animation-duration: 0.01ms !important;
|
|
832
|
+
transition-duration: 0.01ms !important;
|
|
833
|
+
}
|
|
834
|
+
```
|
|
835
|
+
|
|
836
|
+
#### Smooth Scrolling
|
|
837
|
+
|
|
838
|
+
```css
|
|
839
|
+
html {
|
|
840
|
+
scroll-behavior: smooth;
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
@media (prefers-reduced-motion: reduce) {
|
|
844
|
+
html {
|
|
845
|
+
scroll-behavior: auto;
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
```
|
|
849
|
+
|
|
850
|
+
---
|
|
851
|
+
|
|
852
|
+
## Additional Resources
|
|
853
|
+
|
|
854
|
+
### Related Documentation
|
|
855
|
+
|
|
856
|
+
- **Plugin README**: Installation and usage guide (in plugin package)
|
|
857
|
+
- **Module README**: Per-app customization guide (installed by plugin)
|
|
858
|
+
- **Test Page**: Visual demonstration of all features (`/design-system-test` route)
|
|
859
|
+
|
|
860
|
+
### External Resources
|
|
861
|
+
|
|
862
|
+
- [Tailwind CSS Documentation](https://tailwindcss.com)
|
|
863
|
+
- [shadcn/ui Components](https://ui.shadcn.com)
|
|
864
|
+
- [HSL Color Picker](https://hslpicker.com)
|
|
865
|
+
- [WebAIM: Color Contrast](https://webaim.org/articles/contrast/)
|
|
866
|
+
|
|
867
|
+
### TypeScript Support
|
|
868
|
+
|
|
869
|
+
The library includes full TypeScript definitions:
|
|
870
|
+
|
|
871
|
+
```ts
|
|
872
|
+
import type { Config } from 'tailwindcss'
|
|
873
|
+
import preset from '@launch77-shared/lib-design-system/preset'
|
|
874
|
+
|
|
875
|
+
const config: Config = {
|
|
876
|
+
presets: [preset],
|
|
877
|
+
// TypeScript knows about all preset options
|
|
878
|
+
}
|
|
879
|
+
```
|
|
880
|
+
|
|
881
|
+
---
|
|
882
|
+
|
|
883
|
+
**Need help?** Check the plugin README for troubleshooting, or ask in the Launch77 community.
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@launch77-shared/lib-design-system",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Launch77 Design System - Token-driven theming foundation for shadcn-based component libraries",
|
|
5
|
+
"license": "UNLICENSED",
|
|
6
|
+
"main": "./src/preset.js",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"default": "./src/preset.js"
|
|
10
|
+
},
|
|
11
|
+
"./tokens.css": "./src/tokens.css",
|
|
12
|
+
"./preset": "./src/preset.js"
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"src/preset.js",
|
|
16
|
+
"src/tokens.css",
|
|
17
|
+
"src/index.js",
|
|
18
|
+
"src/index.d.ts",
|
|
19
|
+
"docs/"
|
|
20
|
+
],
|
|
21
|
+
"publishConfig": {
|
|
22
|
+
"access": "public"
|
|
23
|
+
},
|
|
24
|
+
"scripts": {
|
|
25
|
+
"build": "echo 'No build required for design system package'",
|
|
26
|
+
"typecheck": "tsc --noEmit",
|
|
27
|
+
"test": "echo 'Tests not yet implemented'",
|
|
28
|
+
"release:connect": "launch77-release-connect",
|
|
29
|
+
"release:verify": "launch77-release-verify"
|
|
30
|
+
},
|
|
31
|
+
"peerDependencies": {
|
|
32
|
+
"tailwindcss": "^3.4.0"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@types/node": "^20.0.0",
|
|
36
|
+
"tailwindcss": "^3.4.17",
|
|
37
|
+
"typescript": "^5.3.0"
|
|
38
|
+
},
|
|
39
|
+
"launch77": {
|
|
40
|
+
"installedPlugins": {
|
|
41
|
+
"release": {
|
|
42
|
+
"package": "release",
|
|
43
|
+
"version": "1.0.0",
|
|
44
|
+
"installedAt": "2026-01-22T17:33:45.971Z",
|
|
45
|
+
"source": "local"
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
package/src/index.d.ts
ADDED
package/src/index.js
ADDED
package/src/preset.js
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
const plugin = require('tailwindcss/plugin')
|
|
2
|
+
|
|
3
|
+
// Base styles plugin - automatically injects base styles without requiring CSS imports
|
|
4
|
+
const baseStylesPlugin = plugin(function ({ addBase }) {
|
|
5
|
+
addBase({
|
|
6
|
+
'*': {
|
|
7
|
+
borderColor: 'hsl(var(--color-border))',
|
|
8
|
+
},
|
|
9
|
+
html: {
|
|
10
|
+
scrollBehavior: 'smooth',
|
|
11
|
+
},
|
|
12
|
+
body: {
|
|
13
|
+
backgroundColor: 'hsl(var(--color-background))',
|
|
14
|
+
color: 'hsl(var(--color-foreground))',
|
|
15
|
+
WebkitFontSmoothing: 'antialiased',
|
|
16
|
+
MozOsxFontSmoothing: 'grayscale',
|
|
17
|
+
},
|
|
18
|
+
'::selection': {
|
|
19
|
+
backgroundColor: 'hsl(var(--color-primary) / 0.2)',
|
|
20
|
+
color: 'hsl(var(--color-primary))',
|
|
21
|
+
},
|
|
22
|
+
':focus-visible': {
|
|
23
|
+
outline: 'none',
|
|
24
|
+
boxShadow: '0 0 0 2px hsl(var(--color-background)), 0 0 0 4px hsl(var(--color-ring))',
|
|
25
|
+
},
|
|
26
|
+
'button, a, input, select, textarea': {
|
|
27
|
+
minHeight: '44px',
|
|
28
|
+
minWidth: '44px',
|
|
29
|
+
},
|
|
30
|
+
'@media (prefers-reduced-motion: reduce)': {
|
|
31
|
+
'*, *::before, *::after': {
|
|
32
|
+
animationDuration: '0.01ms !important',
|
|
33
|
+
animationIterationCount: '1 !important',
|
|
34
|
+
transitionDuration: '0.01ms !important',
|
|
35
|
+
scrollBehavior: 'auto !important',
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
})
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
/** @type {import('tailwindcss').Config} */
|
|
42
|
+
module.exports = {
|
|
43
|
+
darkMode: ['class'],
|
|
44
|
+
theme: {
|
|
45
|
+
container: {
|
|
46
|
+
center: true,
|
|
47
|
+
padding: '2rem',
|
|
48
|
+
screens: {
|
|
49
|
+
'2xl': '1400px',
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
extend: {
|
|
53
|
+
colors: {
|
|
54
|
+
border: 'hsl(var(--color-border))',
|
|
55
|
+
input: 'hsl(var(--color-input))',
|
|
56
|
+
ring: 'hsl(var(--color-ring))',
|
|
57
|
+
background: 'hsl(var(--color-background))',
|
|
58
|
+
foreground: 'hsl(var(--color-foreground))',
|
|
59
|
+
primary: {
|
|
60
|
+
DEFAULT: 'hsl(var(--color-primary) / <alpha-value>)',
|
|
61
|
+
foreground: 'hsl(var(--color-primary-foreground))',
|
|
62
|
+
},
|
|
63
|
+
secondary: {
|
|
64
|
+
DEFAULT: 'hsl(var(--color-secondary) / <alpha-value>)',
|
|
65
|
+
foreground: 'hsl(var(--color-secondary-foreground))',
|
|
66
|
+
},
|
|
67
|
+
destructive: {
|
|
68
|
+
DEFAULT: 'hsl(var(--color-destructive) / <alpha-value>)',
|
|
69
|
+
foreground: 'hsl(var(--color-destructive-foreground))',
|
|
70
|
+
},
|
|
71
|
+
muted: {
|
|
72
|
+
DEFAULT: 'hsl(var(--color-muted) / <alpha-value>)',
|
|
73
|
+
foreground: 'hsl(var(--color-muted-foreground))',
|
|
74
|
+
},
|
|
75
|
+
accent: {
|
|
76
|
+
DEFAULT: 'hsl(var(--color-accent) / <alpha-value>)',
|
|
77
|
+
foreground: 'hsl(var(--color-accent-foreground))',
|
|
78
|
+
},
|
|
79
|
+
card: {
|
|
80
|
+
DEFAULT: 'hsl(var(--color-card))',
|
|
81
|
+
foreground: 'hsl(var(--color-card-foreground))',
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
borderRadius: {
|
|
85
|
+
lg: 'var(--radius)',
|
|
86
|
+
md: 'calc(var(--radius) - 2px)',
|
|
87
|
+
sm: 'calc(var(--radius) - 4px)',
|
|
88
|
+
},
|
|
89
|
+
keyframes: {
|
|
90
|
+
'accordion-down': {
|
|
91
|
+
from: { height: '0' },
|
|
92
|
+
to: { height: 'var(--radix-accordion-content-height)' },
|
|
93
|
+
},
|
|
94
|
+
'accordion-up': {
|
|
95
|
+
from: { height: 'var(--radix-accordion-content-height)' },
|
|
96
|
+
to: { height: '0' },
|
|
97
|
+
},
|
|
98
|
+
'fade-in': {
|
|
99
|
+
from: { opacity: '0' },
|
|
100
|
+
to: { opacity: '1' },
|
|
101
|
+
},
|
|
102
|
+
'fade-out': {
|
|
103
|
+
from: { opacity: '1' },
|
|
104
|
+
to: { opacity: '0' },
|
|
105
|
+
},
|
|
106
|
+
'slide-in-from-top': {
|
|
107
|
+
from: { transform: 'translateY(-100%)' },
|
|
108
|
+
to: { transform: 'translateY(0)' },
|
|
109
|
+
},
|
|
110
|
+
'slide-in-from-bottom': {
|
|
111
|
+
from: { transform: 'translateY(100%)' },
|
|
112
|
+
to: { transform: 'translateY(0)' },
|
|
113
|
+
},
|
|
114
|
+
'slide-in-from-left': {
|
|
115
|
+
from: { transform: 'translateX(-100%)' },
|
|
116
|
+
to: { transform: 'translateX(0)' },
|
|
117
|
+
},
|
|
118
|
+
'slide-in-from-right': {
|
|
119
|
+
from: { transform: 'translateX(100%)' },
|
|
120
|
+
to: { transform: 'translateX(0)' },
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
animation: {
|
|
124
|
+
'accordion-down': 'accordion-down 0.2s ease-out',
|
|
125
|
+
'accordion-up': 'accordion-up 0.2s ease-out',
|
|
126
|
+
'fade-in': 'fade-in 0.2s ease-out',
|
|
127
|
+
'fade-out': 'fade-out 0.2s ease-out',
|
|
128
|
+
'slide-in-from-top': 'slide-in-from-top 0.25s ease-out',
|
|
129
|
+
'slide-in-from-bottom': 'slide-in-from-bottom 0.25s ease-out',
|
|
130
|
+
'slide-in-from-left': 'slide-in-from-left 0.25s ease-out',
|
|
131
|
+
'slide-in-from-right': 'slide-in-from-right 0.25s ease-out',
|
|
132
|
+
},
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
plugins: [baseStylesPlugin],
|
|
136
|
+
future: {
|
|
137
|
+
hoverOnlyWhenSupported: true,
|
|
138
|
+
},
|
|
139
|
+
}
|
package/src/tokens.css
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/* Pure CSS tokens - no Tailwind dependencies */
|
|
2
|
+
|
|
3
|
+
:root {
|
|
4
|
+
/* Base colors */
|
|
5
|
+
--color-background: 0 0% 100%; /* white */
|
|
6
|
+
--color-foreground: 222 47% 11%; /* slate-900 */
|
|
7
|
+
|
|
8
|
+
/* Card */
|
|
9
|
+
--color-card: 0 0% 100%; /* white */
|
|
10
|
+
--color-card-foreground: 222 47% 11%; /* slate-900 */
|
|
11
|
+
|
|
12
|
+
/* Primary - Default neutral theme */
|
|
13
|
+
--color-primary: 222 47% 11%; /* slate-900 */
|
|
14
|
+
--color-primary-foreground: 0 0% 100%; /* white */
|
|
15
|
+
|
|
16
|
+
/* Secondary */
|
|
17
|
+
--color-secondary: 210 40% 96%; /* slate-100 */
|
|
18
|
+
--color-secondary-foreground: 222 47% 11%; /* slate-900 */
|
|
19
|
+
|
|
20
|
+
/* Muted */
|
|
21
|
+
--color-muted: 210 40% 96%; /* slate-100 */
|
|
22
|
+
--color-muted-foreground: 215 16% 47%; /* slate-500 */
|
|
23
|
+
|
|
24
|
+
/* Accent */
|
|
25
|
+
--color-accent: 210 40% 96%; /* slate-100 */
|
|
26
|
+
--color-accent-foreground: 222 47% 11%; /* slate-900 */
|
|
27
|
+
|
|
28
|
+
/* Destructive */
|
|
29
|
+
--color-destructive: 0 84% 60%; /* red-500 */
|
|
30
|
+
--color-destructive-foreground: 0 0% 98%; /* white */
|
|
31
|
+
|
|
32
|
+
/* Border */
|
|
33
|
+
--color-border: 214 32% 91%; /* slate-200 */
|
|
34
|
+
--color-input: 214 32% 91%; /* slate-200 */
|
|
35
|
+
--color-ring: 222 47% 11%; /* slate-900 */
|
|
36
|
+
|
|
37
|
+
/* Radius */
|
|
38
|
+
--radius: 1rem; /* rounded-2xl equivalent */
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.dark {
|
|
42
|
+
--color-background: 222 47% 11%; /* slate-900 */
|
|
43
|
+
--color-foreground: 210 40% 98%; /* slate-50 */
|
|
44
|
+
|
|
45
|
+
--color-card: 222 47% 11%; /* slate-900 */
|
|
46
|
+
--color-card-foreground: 210 40% 98%; /* slate-50 */
|
|
47
|
+
|
|
48
|
+
--color-primary: 210 40% 98%; /* slate-50 for dark mode */
|
|
49
|
+
--color-primary-foreground: 222 47% 11%; /* slate-900 */
|
|
50
|
+
|
|
51
|
+
--color-secondary: 217 33% 17%; /* slate-800 */
|
|
52
|
+
--color-secondary-foreground: 210 40% 98%; /* slate-50 */
|
|
53
|
+
|
|
54
|
+
--color-muted: 217 33% 17%; /* slate-800 */
|
|
55
|
+
--color-muted-foreground: 215 20% 65%; /* slate-400 */
|
|
56
|
+
|
|
57
|
+
--color-accent: 217 33% 17%; /* slate-800 */
|
|
58
|
+
--color-accent-foreground: 210 40% 98%; /* slate-50 */
|
|
59
|
+
|
|
60
|
+
--color-destructive: 0 63% 31%; /* dark red */
|
|
61
|
+
--color-destructive-foreground: 210 40% 98%; /* slate-50 */
|
|
62
|
+
|
|
63
|
+
--color-border: 217 33% 17%; /* slate-800 */
|
|
64
|
+
--color-input: 217 33% 17%; /* slate-800 */
|
|
65
|
+
--color-ring: 210 40% 98%; /* slate-50 */
|
|
66
|
+
}
|