@elsapiens/cli 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,3 +1,4 @@
1
+ import { useEffect, useState } from 'react';
1
2
  import {
2
3
  Button,
3
4
  Input,
@@ -6,110 +7,286 @@ import {
6
7
  Card,
7
8
  CardHeader,
8
9
  CardTitle,
10
+ CardDescription,
9
11
  CardContent,
12
+ Form,
13
+ FormField,
14
+ Separator,
10
15
  } from '@elsapiens/ui';
11
- import { User, Bell, Palette } from 'lucide-react';
12
- import { useState } from 'react';
16
+ import { PageHeaderWithBreadcrumbs } from '@elsapiens/layout';
17
+ import { User, Bell, Shield, Palette, Mail, Globe, Moon, Save, Maximize2, Sparkles } from 'lucide-react';
18
+ import { Link } from 'react-router-dom';
19
+ import { useTheme, useLocale, usePageHeader, SUPPORTED_LOCALES, BACKGROUND_STYLES, type Theme, type Locale, type SpacingMode, type BackgroundStyle } from '@elsapiens/providers';
20
+ import { loadSettings } from '../translations/settings/index';
21
+
22
+ const languageOptions = Object.values(SUPPORTED_LOCALES).map((config) => ({
23
+ value: config.code,
24
+ label: `${config.nativeName} (${config.name})`,
25
+ }));
13
26
 
14
27
  export default function Settings() {
28
+ const { theme, setTheme, spacing, setSpacing, backgroundStyle, setBackgroundStyle } = useTheme();
29
+ const { locale, setLocale, t, loadTranslations } = useLocale();
15
30
  const [notifications, setNotifications] = useState(true);
16
31
  const [emailUpdates, setEmailUpdates] = useState(false);
17
32
 
33
+ // Load settings translations
34
+ useEffect(() => {
35
+ loadTranslations(loadSettings);
36
+ }, [locale, loadTranslations]);
37
+
38
+ // Set page header info
39
+ usePageHeader({
40
+ title: t('settings.title'),
41
+ description: t('settings.description'),
42
+ breadcrumbs: [
43
+ { label: t('nav.home'), href: '/', as: Link },
44
+ { label: t('settings.title') },
45
+ ],
46
+ });
47
+
48
+ // Theme options with translations
49
+ const themeOptions = [
50
+ { value: 'light', label: t('settings.theme.light') },
51
+ { value: 'dark', label: t('settings.theme.dark') },
52
+ { value: 'system', label: t('settings.theme.system') },
53
+ ];
54
+
55
+ // Spacing options with translations
56
+ const spacingOptions = [
57
+ { value: 'compact', label: t('settings.spacing.compact') },
58
+ { value: 'normal', label: t('settings.spacing.normal') },
59
+ { value: 'spacious', label: t('settings.spacing.spacious') },
60
+ ];
61
+
62
+ // Background style options
63
+ const backgroundOptions = BACKGROUND_STYLES.map((bg) => ({
64
+ value: bg.value,
65
+ label: bg.label,
66
+ }));
67
+
18
68
  return (
19
- <div className="p-6 max-w-4xl">
20
- {/* Page Header - pl-6 to align with card content */}
21
- <div className="mb-8 pl-6">
22
- <h1 className="text-2xl font-bold text-foreground">Settings</h1>
23
- <p className="text-muted-foreground">Manage your account and preferences</p>
24
- </div>
69
+ <div className="flex flex-col">
70
+ <PageHeaderWithBreadcrumbs variant="card" linkComponent={Link} />
25
71
 
26
- <div className="space-y-6">
27
- {/* Profile Section */}
28
- <Card padding="none">
29
- <CardHeader className="p-6">
30
- <CardTitle className="flex items-center gap-3">
31
- <User className="w-5 h-5 text-muted-foreground" />
32
- Profile
33
- </CardTitle>
34
- </CardHeader>
35
- <CardContent className="p-6 pt-0 space-y-4">
36
- <div className="grid grid-cols-2 gap-4">
37
- <div>
38
- <label className="block text-sm font-medium text-foreground mb-1">
39
- First Name
40
- </label>
41
- <Input defaultValue="John" hideMessage />
72
+ <div className="el-p-lg" style={{ overflowX: 'clip' }}>
73
+ <div className="el-space-y-section max-w-3xl">
74
+ {/* Profile Section */}
75
+ <Card>
76
+ <CardHeader>
77
+ <div className="flex items-center el-gap-sm">
78
+ <div className="w-10 h-10 rounded-lg bg-primary/10 flex items-center justify-center">
79
+ <User className="w-5 h-5 text-primary" />
80
+ </div>
81
+ <div>
82
+ <CardTitle>{t('settings.profile')}</CardTitle>
83
+ <CardDescription>{t('settings.profileDescription')}</CardDescription>
84
+ </div>
42
85
  </div>
43
- <div>
44
- <label className="block text-sm font-medium text-foreground mb-1">
45
- Last Name
46
- </label>
47
- <Input defaultValue="Doe" hideMessage />
86
+ </CardHeader>
87
+ <CardContent className="el-space-y-content">
88
+ <div className="grid grid-cols-1 sm:grid-cols-2 el-gap-field">
89
+ <FormField label={t('settings.firstName')}>
90
+ <Input defaultValue="John" />
91
+ </FormField>
92
+ <FormField label={t('settings.lastName')}>
93
+ <Input defaultValue="Doe" />
94
+ </FormField>
48
95
  </div>
49
- </div>
50
- <div>
51
- <label className="block text-sm font-medium text-foreground mb-1">
52
- Email
53
- </label>
54
- <Input type="email" defaultValue="john.doe@example.com" hideMessage />
55
- </div>
56
- <div className="pt-2">
57
- <Button>Save Changes</Button>
58
- </div>
59
- </CardContent>
60
- </Card>
61
-
62
- {/* Notifications Section */}
63
- <Card padding="none">
64
- <CardHeader className="p-6">
65
- <CardTitle className="flex items-center gap-3">
66
- <Bell className="w-5 h-5 text-muted-foreground" />
67
- Notifications
68
- </CardTitle>
69
- </CardHeader>
70
- <CardContent className="p-6 pt-0 space-y-4">
71
- <div className="flex items-center justify-between">
72
- <div>
73
- <p className="font-medium text-foreground">Push Notifications</p>
74
- <p className="text-sm text-muted-foreground">Receive push notifications</p>
96
+ <div className="el-gap-field">
97
+ <FormField label={t('settings.email')}>
98
+ <Input type="email" defaultValue="john.doe@example.com" />
99
+ </FormField>
100
+ </div>
101
+ <div className="el-gap-field flex justify-end">
102
+ <Button>
103
+ <Save className="w-4 h-4 el-mr-xs" />
104
+ {t('settings.saveChanges')}
105
+ </Button>
106
+ </div>
107
+ </CardContent>
108
+ </Card>
109
+
110
+ {/* Appearance Section */}
111
+ <Card>
112
+ <CardHeader>
113
+ <div className="flex items-center el-gap-sm">
114
+ <div className="w-10 h-10 rounded-lg bg-primary/10 flex items-center justify-center">
115
+ <Palette className="w-5 h-5 text-primary" />
116
+ </div>
117
+ <div>
118
+ <CardTitle>{t('settings.appearance')}</CardTitle>
119
+ <CardDescription>{t('settings.appearanceDescription')}</CardDescription>
120
+ </div>
121
+ </div>
122
+ </CardHeader>
123
+ <CardContent className="el-space-y-content">
124
+ {/* Spacing Mode Setting */}
125
+ <div className="flex flex-col sm:flex-row sm:items-center sm:justify-between el-gap-md el-py-sm">
126
+ <div className="flex items-start el-gap-sm">
127
+ <Maximize2 className="w-5 h-5 text-muted-foreground el-mt-xs flex-shrink-0" />
128
+ <div>
129
+ <p className="font-medium text-foreground">{t('settings.spacing')}</p>
130
+ <p className="text-sm text-muted-foreground">{t('settings.spacingDescription')}</p>
131
+ </div>
132
+ </div>
133
+ <div className="sm:w-48 flex-shrink-0">
134
+ <Select
135
+ options={spacingOptions}
136
+ value={spacing}
137
+ onChange={(e) => setSpacing(e.target.value as SpacingMode)}
138
+ />
139
+ </div>
140
+ </div>
141
+
142
+ <Separator />
143
+
144
+ {/* Theme Setting */}
145
+ <div className="flex flex-col sm:flex-row sm:items-center sm:justify-between el-gap-md el-py-sm">
146
+ <div className="flex items-start el-gap-sm">
147
+ <Moon className="w-5 h-5 text-muted-foreground el-mt-xs flex-shrink-0" />
148
+ <div>
149
+ <p className="font-medium text-foreground">{t('settings.theme')}</p>
150
+ <p className="text-sm text-muted-foreground">{t('settings.themeDescription')}</p>
151
+ </div>
152
+ </div>
153
+ <div className="sm:w-48 flex-shrink-0">
154
+ <Select
155
+ options={themeOptions}
156
+ value={theme}
157
+ onChange={(e) => setTheme(e.target.value as Theme)}
158
+ />
159
+ </div>
75
160
  </div>
76
- <Toggle checked={notifications} onChange={setNotifications} />
77
- </div>
78
- <div className="flex items-center justify-between">
79
- <div>
80
- <p className="font-medium text-foreground">Email Updates</p>
81
- <p className="text-sm text-muted-foreground">Receive email summaries</p>
161
+
162
+ <Separator />
163
+
164
+ {/* Background Effect Setting */}
165
+ <div className="flex flex-col sm:flex-row sm:items-center sm:justify-between el-gap-md el-py-sm">
166
+ <div className="flex items-start el-gap-sm">
167
+ <Sparkles className="w-5 h-5 text-muted-foreground el-mt-xs flex-shrink-0" />
168
+ <div>
169
+ <p className="font-medium text-foreground">{t('settings.background')}</p>
170
+ <p className="text-sm text-muted-foreground">{t('settings.backgroundDescription')}</p>
171
+ </div>
172
+ </div>
173
+ <div className="sm:w-48 flex-shrink-0">
174
+ <Select
175
+ options={backgroundOptions}
176
+ value={backgroundStyle}
177
+ onChange={(e) => setBackgroundStyle(e.target.value as BackgroundStyle)}
178
+ />
179
+ </div>
180
+ </div>
181
+
182
+ <Separator />
183
+
184
+ {/* Language Setting */}
185
+ <div className="flex flex-col sm:flex-row sm:items-center sm:justify-between el-gap-md el-py-sm">
186
+ <div className="flex items-start el-gap-sm">
187
+ <Globe className="w-5 h-5 text-muted-foreground el-mt-xs flex-shrink-0" />
188
+ <div>
189
+ <p className="font-medium text-foreground">{t('settings.language')}</p>
190
+ <p className="text-sm text-muted-foreground">{t('settings.languageDescription')}</p>
191
+ </div>
192
+ </div>
193
+ <div className="sm:w-48 flex-shrink-0">
194
+ <Select
195
+ options={languageOptions}
196
+ value={locale}
197
+ onChange={(e) => setLocale(e.target.value as Locale)}
198
+ />
199
+ </div>
200
+ </div>
201
+ </CardContent>
202
+ </Card>
203
+
204
+ {/* Notifications Section */}
205
+ <Card>
206
+ <CardHeader>
207
+ <div className="flex items-center el-gap-sm">
208
+ <div className="w-10 h-10 rounded-lg bg-primary/10 flex items-center justify-center">
209
+ <Bell className="w-5 h-5 text-primary" />
210
+ </div>
211
+ <div>
212
+ <CardTitle>{t('settings.notifications')}</CardTitle>
213
+ <CardDescription>{t('settings.notificationsDescription')}</CardDescription>
214
+ </div>
215
+ </div>
216
+ </CardHeader>
217
+ <CardContent className="el-space-y-content">
218
+ {/* Push Notifications */}
219
+ <div className="flex flex-col sm:flex-row sm:items-center sm:justify-between el-gap-md el-py-sm">
220
+ <div className="flex items-start el-gap-sm">
221
+ <Bell className="w-5 h-5 text-muted-foreground el-mt-xs flex-shrink-0" />
222
+ <div>
223
+ <p className="font-medium text-foreground">{t('settings.pushNotifications')}</p>
224
+ <p className="text-sm text-muted-foreground">{t('settings.pushNotificationsDesc')}</p>
225
+ </div>
226
+ </div>
227
+ <div className="flex-shrink-0">
228
+ <Toggle checked={notifications} onChange={setNotifications} />
229
+ </div>
230
+ </div>
231
+
232
+ <Separator />
233
+
234
+ {/* Email Updates */}
235
+ <div className="flex flex-col sm:flex-row sm:items-center sm:justify-between el-gap-md el-py-sm">
236
+ <div className="flex items-start el-gap-sm">
237
+ <Mail className="w-5 h-5 text-muted-foreground el-mt-xs flex-shrink-0" />
238
+ <div>
239
+ <p className="font-medium text-foreground">{t('settings.emailUpdates')}</p>
240
+ <p className="text-sm text-muted-foreground">{t('settings.emailUpdatesDesc')}</p>
241
+ </div>
242
+ </div>
243
+ <div className="flex-shrink-0">
244
+ <Toggle checked={emailUpdates} onChange={setEmailUpdates} />
245
+ </div>
246
+ </div>
247
+ </CardContent>
248
+ </Card>
249
+
250
+ {/* Security Section */}
251
+ <Card>
252
+ <CardHeader>
253
+ <div className="flex items-center el-gap-sm">
254
+ <div className="w-10 h-10 rounded-lg bg-primary/10 flex items-center justify-center">
255
+ <Shield className="w-5 h-5 text-primary" />
256
+ </div>
257
+ <div>
258
+ <CardTitle>{t('settings.security')}</CardTitle>
259
+ <CardDescription>{t('settings.securityDescription')}</CardDescription>
260
+ </div>
82
261
  </div>
83
- <Toggle checked={emailUpdates} onChange={setEmailUpdates} />
84
- </div>
85
- </CardContent>
86
- </Card>
87
-
88
- {/* Appearance Section */}
89
- <Card padding="none">
90
- <CardHeader className="p-6">
91
- <CardTitle className="flex items-center gap-3">
92
- <Palette className="w-5 h-5 text-muted-foreground" />
93
- Appearance
94
- </CardTitle>
95
- </CardHeader>
96
- <CardContent className="p-6 pt-0 space-y-4">
97
- <div>
98
- <label className="block text-sm font-medium text-foreground mb-1">
99
- Theme
100
- </label>
101
- <Select
102
- options={[
103
- { value: 'light', label: 'Light' },
104
- { value: 'dark', label: 'Dark' },
105
- { value: 'system', label: 'System' },
106
- ]}
107
- value="light"
108
- hideMessage
109
- />
110
- </div>
111
- </CardContent>
112
- </Card>
262
+ </CardHeader>
263
+ <CardContent>
264
+ <Form onSubmit={(e) => e.preventDefault()} className="el-space-y-content">
265
+ {/* Hidden username field for accessibility - helps password managers */}
266
+ <input type="text" name="username" autoComplete="username" className="sr-only" aria-hidden="true" tabIndex={-1} />
267
+ <div className="el-gap-field">
268
+ <FormField label={t('settings.currentPassword')}>
269
+ <Input type="password" placeholder={t('settings.currentPasswordPlaceholder')} autoComplete="current-password" />
270
+ </FormField>
271
+ </div>
272
+ <div className="grid grid-cols-1 sm:grid-cols-2 el-gap-field">
273
+ <FormField label={t('settings.newPassword')}>
274
+ <Input type="password" placeholder={t('settings.newPasswordPlaceholder')} autoComplete="new-password" />
275
+ </FormField>
276
+ <FormField label={t('settings.confirmPassword')}>
277
+ <Input type="password" placeholder={t('settings.confirmPasswordPlaceholder')} autoComplete="new-password" />
278
+ </FormField>
279
+ </div>
280
+ <div className="el-gap-field flex justify-end">
281
+ <Button type="submit">
282
+ <Shield className="w-4 h-4 el-mr-xs" />
283
+ {t('settings.updatePassword')}
284
+ </Button>
285
+ </div>
286
+ </Form>
287
+ </CardContent>
288
+ </Card>
289
+ </div>
113
290
  </div>
114
291
  </div>
115
292
  );
@@ -0,0 +1,6 @@
1
+ export default {
2
+ plugins: {
3
+ tailwindcss: {},
4
+ autoprefixer: {},
5
+ },
6
+ };
@@ -0,0 +1,158 @@
1
+ import {
2
+ createShortcutManager,
3
+ createLogger,
4
+ createApiClient,
5
+ type ShortcutManager,
6
+ type Logger,
7
+ type ApiClient,
8
+ } from '@elsapiens/services';
9
+
10
+ /**
11
+ * Application Services
12
+ *
13
+ * This file initializes and exports the core services used throughout the application.
14
+ * Import these services where needed in your components.
15
+ */
16
+
17
+ // Configuration from environment
18
+ const API_BASE_URL = import.meta.env.VITE_API_URL || '/api';
19
+
20
+ /**
21
+ * Logger instance
22
+ * Use for consistent logging throughout the application
23
+ *
24
+ * @example
25
+ * logger.info('User logged in', { userId: '123' });
26
+ * logger.error('Failed to fetch data', error);
27
+ */
28
+ export const logger: Logger = createLogger({
29
+ level: import.meta.env.DEV ? 'debug' : 'info',
30
+ context: '<%= pascalName %>',
31
+ });
32
+
33
+ /**
34
+ * API Client instance
35
+ * Use for making HTTP requests to your backend
36
+ *
37
+ * @example
38
+ * const response = await apiClient.get('/users');
39
+ * const data = await apiClient.post('/users', { name: 'John' });
40
+ */
41
+ export const apiClient: ApiClient = createApiClient({
42
+ baseUrl: API_BASE_URL,
43
+ timeout: 30000,
44
+ onError: (error) => {
45
+ logger.error('API Error', error);
46
+ },
47
+ });
48
+
49
+ /**
50
+ * Shortcut Manager instance
51
+ *
52
+ * Uses a stack-based context system for handling shortcuts in modals/popups.
53
+ * Only the topmost context receives keyboard events for context-scoped shortcuts.
54
+ * Global shortcuts (scope: 'global') always receive events regardless of context.
55
+ *
56
+ * ## Shortcut Scopes
57
+ *
58
+ * - `scope: 'global'` - Always active, receives events regardless of context stack
59
+ * - `scope: 'context'` - Only active when its context is at the top of the stack
60
+ *
61
+ * ## Context Stack Usage
62
+ *
63
+ * When opening a modal/popup:
64
+ * ```ts
65
+ * shortcutManager.pushContext('my-modal');
66
+ * ```
67
+ *
68
+ * When closing a modal/popup:
69
+ * ```ts
70
+ * shortcutManager.popContext('my-modal');
71
+ * ```
72
+ *
73
+ * ## Example: Modal with context-specific shortcuts
74
+ *
75
+ * ```tsx
76
+ * function MyModal({ onClose }: { onClose: () => void }) {
77
+ * useEffect(() => {
78
+ * // Push context when modal opens
79
+ * shortcutManager.pushContext('my-modal');
80
+ *
81
+ * // Register modal-specific shortcuts
82
+ * const unregister = shortcutManager.register({
83
+ * id: 'close-modal',
84
+ * keys: 'escape',
85
+ * label: 'Close',
86
+ * category: 'modal',
87
+ * scope: 'context',
88
+ * context: 'my-modal',
89
+ * action: () => onClose(),
90
+ * });
91
+ *
92
+ * return () => {
93
+ * unregister();
94
+ * shortcutManager.popContext('my-modal');
95
+ * };
96
+ * }, [onClose]);
97
+ *
98
+ * return <div>Modal content</div>;
99
+ * }
100
+ * ```
101
+ */
102
+ export const shortcutManager: ShortcutManager = createShortcutManager();
103
+
104
+ // Register global shortcuts (always active regardless of context)
105
+ shortcutManager.register({
106
+ id: 'help',
107
+ keys: '?',
108
+ label: 'Open Help',
109
+ category: 'General',
110
+ scope: 'global',
111
+ action: () => {
112
+ // Toggle help panel - implement based on your HelpProvider
113
+ logger.debug('Help shortcut triggered');
114
+ },
115
+ });
116
+
117
+ shortcutManager.register({
118
+ id: 'search',
119
+ keys: 'ctrl+k',
120
+ label: 'Open Search',
121
+ category: 'General',
122
+ scope: 'global',
123
+ action: () => {
124
+ // Open search/command palette - implement based on your needs
125
+ logger.debug('Search shortcut triggered');
126
+ },
127
+ });
128
+
129
+ // Shortcut manager detach function (set when attached)
130
+ let detachShortcuts: (() => void) | null = null;
131
+
132
+ /**
133
+ * Attach shortcut manager to document
134
+ * Call this after the app mounts
135
+ */
136
+ export function attachShortcuts(): void {
137
+ detachShortcuts = shortcutManager.attach();
138
+ }
139
+
140
+ /**
141
+ * Initialize all services
142
+ * Call this in your main.tsx before rendering the app
143
+ */
144
+ export function initializeServices(): void {
145
+ logger.info('Services initialized');
146
+ }
147
+
148
+ /**
149
+ * Cleanup all services
150
+ * Call this when unmounting the app (e.g., in useEffect cleanup)
151
+ */
152
+ export function cleanupServices(): void {
153
+ if (detachShortcuts) {
154
+ detachShortcuts();
155
+ detachShortcuts = null;
156
+ }
157
+ logger.info('Services cleaned up');
158
+ }
@@ -0,0 +1,105 @@
1
+ /** @type {import('tailwindcss').Config} */
2
+ export default {
3
+ content: [
4
+ './index.html',
5
+ './src/**/*.{js,ts,jsx,tsx}',
6
+ './node_modules/@elsapiens/*/dist/**/*.{js,ts,jsx,tsx}',
7
+ ],
8
+ safelist: [
9
+ 'hide-native-scrollbar',
10
+ 'scrollbar-visible',
11
+ 'scrollbar-hide',
12
+ 'scrollbar-custom',
13
+ ],
14
+ darkMode: 'class',
15
+ theme: {
16
+ extend: {
17
+ colors: {
18
+ // Wisteria palette (for accent colors - changes with color scheme)
19
+ wisteria: {
20
+ 50: 'hsl(var(--wisteria-50))',
21
+ 100: 'hsl(var(--wisteria-100))',
22
+ 200: 'hsl(var(--wisteria-200))',
23
+ 300: 'hsl(var(--wisteria-300))',
24
+ 400: 'hsl(var(--wisteria-400))',
25
+ 500: 'hsl(var(--wisteria-500))',
26
+ 600: 'hsl(var(--wisteria-600))',
27
+ 700: 'hsl(var(--wisteria-700))',
28
+ 800: 'hsl(var(--wisteria-800))',
29
+ 850: 'hsl(var(--wisteria-850))',
30
+ 900: 'hsl(var(--wisteria-900))',
31
+ 950: 'hsl(var(--wisteria-950))',
32
+ },
33
+ // Neutral gray scale (never changes)
34
+ neutral: {
35
+ 50: 'hsl(var(--neutral-50))',
36
+ 100: 'hsl(var(--neutral-100))',
37
+ 200: 'hsl(var(--neutral-200))',
38
+ 300: 'hsl(var(--neutral-300))',
39
+ 400: 'hsl(var(--neutral-400))',
40
+ 500: 'hsl(var(--neutral-500))',
41
+ 600: 'hsl(var(--neutral-600))',
42
+ 700: 'hsl(var(--neutral-700))',
43
+ 800: 'hsl(var(--neutral-800))',
44
+ 900: 'hsl(var(--neutral-900))',
45
+ 950: 'hsl(var(--neutral-950))',
46
+ },
47
+ // Semantic colors
48
+ border: 'hsl(var(--border))',
49
+ input: 'hsl(var(--input))',
50
+ ring: 'hsl(var(--ring))',
51
+ background: 'hsl(var(--background))',
52
+ foreground: 'hsl(var(--foreground))',
53
+ primary: {
54
+ 50: 'hsl(var(--wisteria-50))',
55
+ 100: 'hsl(var(--wisteria-100))',
56
+ 200: 'hsl(var(--wisteria-200))',
57
+ 300: 'hsl(var(--wisteria-300))',
58
+ 400: 'hsl(var(--wisteria-400))',
59
+ 500: 'hsl(var(--wisteria-500))',
60
+ 600: 'hsl(var(--wisteria-600))',
61
+ 700: 'hsl(var(--wisteria-700))',
62
+ 800: 'hsl(var(--wisteria-800))',
63
+ 900: 'hsl(var(--wisteria-900))',
64
+ 950: 'hsl(var(--wisteria-950))',
65
+ DEFAULT: 'hsl(var(--primary))',
66
+ foreground: 'hsl(var(--primary-foreground))',
67
+ },
68
+ secondary: {
69
+ DEFAULT: 'hsl(var(--secondary))',
70
+ foreground: 'hsl(var(--secondary-foreground))',
71
+ },
72
+ destructive: {
73
+ DEFAULT: 'hsl(var(--destructive))',
74
+ foreground: 'hsl(var(--destructive-foreground))',
75
+ },
76
+ muted: {
77
+ DEFAULT: 'hsl(var(--muted))',
78
+ foreground: 'hsl(var(--muted-foreground))',
79
+ },
80
+ accent: {
81
+ DEFAULT: 'hsl(var(--accent))',
82
+ foreground: 'hsl(var(--accent-foreground))',
83
+ },
84
+ popover: {
85
+ DEFAULT: 'hsl(var(--popover))',
86
+ foreground: 'hsl(var(--popover-foreground))',
87
+ },
88
+ card: {
89
+ DEFAULT: 'hsl(var(--card))',
90
+ foreground: 'hsl(var(--card-foreground))',
91
+ },
92
+ },
93
+ borderRadius: {
94
+ lg: 'var(--radius)',
95
+ md: 'calc(var(--radius) - 2px)',
96
+ sm: 'calc(var(--radius) - 4px)',
97
+ },
98
+ fontFamily: {
99
+ sans: ['Inter', 'system-ui', '-apple-system', 'sans-serif'],
100
+ mono: ['JetBrains Mono', 'ui-monospace', 'monospace'],
101
+ },
102
+ },
103
+ },
104
+ plugins: [],
105
+ };
@@ -0,0 +1 @@
1
+ import '@testing-library/jest-dom';