@miozu/jera 0.0.2 → 0.4.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.
Files changed (82) hide show
  1. package/CLAUDE.md +734 -0
  2. package/README.md +219 -1
  3. package/llms.txt +97 -0
  4. package/package.json +54 -14
  5. package/src/actions/index.js +375 -0
  6. package/src/components/docs/CodeBlock.svelte +203 -0
  7. package/src/components/docs/DocSection.svelte +120 -0
  8. package/src/components/docs/PropsTable.svelte +136 -0
  9. package/src/components/docs/SplitPane.svelte +98 -0
  10. package/src/components/docs/index.js +14 -0
  11. package/src/components/feedback/Alert.svelte +234 -0
  12. package/src/components/feedback/EmptyState.svelte +179 -0
  13. package/src/components/feedback/ProgressBar.svelte +116 -0
  14. package/src/components/feedback/Skeleton.svelte +107 -0
  15. package/src/components/feedback/Spinner.svelte +77 -0
  16. package/src/components/feedback/Toast.svelte +261 -0
  17. package/src/components/forms/Checkbox.svelte +147 -0
  18. package/src/components/forms/Dropzone.svelte +248 -0
  19. package/src/components/forms/FileUpload.svelte +266 -0
  20. package/src/components/forms/IconInput.svelte +184 -0
  21. package/src/components/forms/Input.svelte +121 -0
  22. package/src/components/forms/NumberInput.svelte +225 -0
  23. package/src/components/forms/PinInput.svelte +169 -0
  24. package/src/components/forms/Radio.svelte +143 -0
  25. package/src/components/forms/RadioGroup.svelte +62 -0
  26. package/src/components/forms/RangeSlider.svelte +212 -0
  27. package/src/components/forms/SearchInput.svelte +175 -0
  28. package/src/components/forms/Select.svelte +324 -0
  29. package/src/components/forms/Switch.svelte +159 -0
  30. package/src/components/forms/Textarea.svelte +122 -0
  31. package/src/components/navigation/Accordion.svelte +65 -0
  32. package/src/components/navigation/AccordionItem.svelte +146 -0
  33. package/src/components/navigation/NavigationContainer.svelte +344 -0
  34. package/src/components/navigation/Sidebar.svelte +334 -0
  35. package/src/components/navigation/SidebarAccountGroup.svelte +495 -0
  36. package/src/components/navigation/SidebarAccountItem.svelte +492 -0
  37. package/src/components/navigation/SidebarGroup.svelte +230 -0
  38. package/src/components/navigation/SidebarGroupSwitcher.svelte +262 -0
  39. package/src/components/navigation/SidebarItem.svelte +210 -0
  40. package/src/components/navigation/SidebarNavigationItem.svelte +470 -0
  41. package/src/components/navigation/SidebarPopover.svelte +145 -0
  42. package/src/components/navigation/SidebarSearch.svelte +236 -0
  43. package/src/components/navigation/SidebarSection.svelte +158 -0
  44. package/src/components/navigation/SidebarToggle.svelte +86 -0
  45. package/src/components/navigation/Tabs.svelte +239 -0
  46. package/src/components/navigation/WorkspaceMenu.svelte +416 -0
  47. package/src/components/navigation/blocks/NavigationAccountGroup.svelte +396 -0
  48. package/src/components/navigation/blocks/NavigationCustomBlock.svelte +74 -0
  49. package/src/components/navigation/blocks/NavigationGroupSwitcher.svelte +277 -0
  50. package/src/components/navigation/blocks/NavigationSearch.svelte +300 -0
  51. package/src/components/navigation/blocks/NavigationSection.svelte +230 -0
  52. package/src/components/navigation/index.js +22 -0
  53. package/src/components/overlays/ConfirmDialog.svelte +272 -0
  54. package/src/components/overlays/Dropdown.svelte +153 -0
  55. package/src/components/overlays/DropdownDivider.svelte +23 -0
  56. package/src/components/overlays/DropdownItem.svelte +97 -0
  57. package/src/components/overlays/Modal.svelte +232 -0
  58. package/src/components/overlays/Popover.svelte +206 -0
  59. package/src/components/primitives/Avatar.svelte +132 -0
  60. package/src/components/primitives/Badge.svelte +118 -0
  61. package/src/components/primitives/Button.svelte +214 -0
  62. package/src/components/primitives/Card.svelte +104 -0
  63. package/src/components/primitives/Divider.svelte +105 -0
  64. package/src/components/primitives/LazyImage.svelte +104 -0
  65. package/src/components/primitives/Link.svelte +122 -0
  66. package/src/components/primitives/Stat.svelte +197 -0
  67. package/src/components/primitives/StatusBadge.svelte +122 -0
  68. package/src/index.js +183 -0
  69. package/src/tokens/colors.css +157 -0
  70. package/src/tokens/effects.css +128 -0
  71. package/src/tokens/index.css +81 -0
  72. package/src/tokens/spacing.css +49 -0
  73. package/src/tokens/typography.css +79 -0
  74. package/src/utils/cn.svelte.js +175 -0
  75. package/src/utils/highlighter.js +124 -0
  76. package/src/utils/index.js +22 -0
  77. package/src/utils/navigation.svelte.js +423 -0
  78. package/src/utils/reactive.svelte.js +328 -0
  79. package/src/utils/sidebar.svelte.js +211 -0
  80. package/jera.js +0 -135
  81. package/www/components/jera/Input/Input.svelte +0 -63
  82. package/www/components/jera/Input/index.js +0 -1
@@ -0,0 +1,328 @@
1
+ /**
2
+ * Reactive State Utilities for Svelte 5
3
+ *
4
+ * Advanced patterns for creating shareable, reactive state.
5
+ * Uses Svelte 5 runes ($state, $derived, $effect) internally.
6
+ */
7
+
8
+ /**
9
+ * Create a reactive store-like object with Svelte 5 runes
10
+ * Unlike stores, these work seamlessly with runes and don't need $ prefix
11
+ *
12
+ * @template T
13
+ * @param {T} initial
14
+ * @returns {{ value: T, set: (v: T) => void, update: (fn: (v: T) => T) => void }}
15
+ *
16
+ * @example
17
+ * const count = createReactive(0);
18
+ * count.value; // read
19
+ * count.set(5); // write
20
+ * count.update(n => n + 1); // update
21
+ */
22
+ export function createReactive(initial) {
23
+ let value = $state(initial);
24
+
25
+ return {
26
+ get value() {
27
+ return value;
28
+ },
29
+ set value(v) {
30
+ value = v;
31
+ },
32
+ set(v) {
33
+ value = v;
34
+ },
35
+ update(fn) {
36
+ value = fn(value);
37
+ }
38
+ };
39
+ }
40
+
41
+ /**
42
+ * Create a derived reactive value
43
+ *
44
+ * @template T
45
+ * @param {() => T} fn
46
+ * @returns {{ readonly value: T }}
47
+ */
48
+ export function createDerived(fn) {
49
+ const value = $derived.by(fn);
50
+
51
+ return {
52
+ get value() {
53
+ return value;
54
+ }
55
+ };
56
+ }
57
+
58
+ /**
59
+ * Theme Reactive State Class (Singleton Pattern)
60
+ *
61
+ * Manages theme state with persistence and SSR support.
62
+ * Uses singleton pattern for global app-level state.
63
+ *
64
+ * Storage key: 'miozu-theme'
65
+ * Data-theme values: 'miozu-dark' | 'miozu-light'
66
+ *
67
+ * @example
68
+ * import { getTheme } from '@miozu/jera';
69
+ * const theme = getTheme();
70
+ * theme.init(); // Call once in root layout onMount
71
+ * theme.toggle(); // Toggle dark/light
72
+ * theme.set('system'); // Use system preference
73
+ */
74
+ export class ThemeState {
75
+ /** @type {'light' | 'dark' | 'system'} */
76
+ current = $state('system');
77
+
78
+ /** @type {'light' | 'dark'} */
79
+ resolved = $derived.by(() => this.#resolveTheme());
80
+
81
+ /** @type {'miozu-light' | 'miozu-dark'} */
82
+ dataTheme = $derived.by(() => this.resolved === 'dark' ? 'miozu-dark' : 'miozu-light');
83
+
84
+ /**
85
+ * Alias for dataTheme - backwards compatibility
86
+ * @type {'miozu-light' | 'miozu-dark'}
87
+ */
88
+ currentTheme = $derived.by(() => this.dataTheme);
89
+
90
+ /** @type {boolean} */
91
+ isDark = $derived.by(() => this.resolved === 'dark');
92
+
93
+ /** @type {boolean} */
94
+ isLight = $derived.by(() => this.resolved === 'light');
95
+
96
+ /** @type {boolean} */
97
+ #initialized = false;
98
+
99
+ /** @type {MediaQueryList | null} */
100
+ #mediaQuery = null;
101
+
102
+ /** @type {(() => void) | null} */
103
+ #mediaQueryHandler = null;
104
+
105
+ /**
106
+ * Parse theme from cookie string (for SSR)
107
+ * @param {string | null} cookieString - Raw cookie header
108
+ * @returns {'miozu-light' | 'miozu-dark'} - Theme value for data-theme attribute
109
+ */
110
+ static getThemeFromCookieString(cookieString) {
111
+ if (!cookieString) return 'miozu-dark';
112
+
113
+ const match = cookieString.match(/miozu-theme=([^;]+)/);
114
+ const preference = match ? match[1] : null;
115
+
116
+ if (preference === 'light') return 'miozu-light';
117
+ if (preference === 'dark') return 'miozu-dark';
118
+ // 'system' or invalid - default to dark
119
+ return 'miozu-dark';
120
+ }
121
+
122
+ constructor(initial = 'system') {
123
+ this.current = initial;
124
+ }
125
+
126
+ #resolveTheme() {
127
+ if (this.current === 'system') {
128
+ // SSR-safe: default to dark if no window (miozu default)
129
+ if (typeof window === 'undefined') return 'dark';
130
+ return this.#mediaQuery?.matches ? 'dark' : 'light';
131
+ }
132
+ return this.current;
133
+ }
134
+
135
+ /**
136
+ * Initialize theme - call once in root layout onMount
137
+ * Loads from storage and sets up system preference listener
138
+ */
139
+ init() {
140
+ if (typeof window === 'undefined') return;
141
+ if (this.#initialized) return; // Prevent double init
142
+
143
+ // Load from storage
144
+ const stored = localStorage.getItem('miozu-theme');
145
+ if (stored && ['light', 'dark', 'system'].includes(stored)) {
146
+ this.current = stored;
147
+ }
148
+
149
+ // Setup media query listener for system preference
150
+ this.#mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
151
+ this.#mediaQueryHandler = () => {
152
+ // Force re-resolution when system preference changes
153
+ if (this.current === 'system') {
154
+ this.#apply();
155
+ }
156
+ };
157
+ this.#mediaQuery.addEventListener('change', this.#mediaQueryHandler);
158
+
159
+ this.#initialized = true;
160
+ this.#apply();
161
+ }
162
+
163
+ /**
164
+ * Cleanup media query listener
165
+ * Call in onDestroy if needed (usually not necessary for singleton)
166
+ */
167
+ cleanup() {
168
+ if (this.#mediaQuery && this.#mediaQueryHandler) {
169
+ this.#mediaQuery.removeEventListener('change', this.#mediaQueryHandler);
170
+ this.#mediaQueryHandler = null;
171
+ }
172
+ this.#initialized = false;
173
+ }
174
+
175
+ /**
176
+ * Sync state with current DOM attribute (for hydration)
177
+ * Call after SSR to ensure state matches what app.html set
178
+ */
179
+ sync() {
180
+ if (typeof document === 'undefined') return;
181
+
182
+ const domTheme = document.documentElement.getAttribute('data-theme');
183
+ if (domTheme === 'miozu-dark') {
184
+ this.current = 'dark';
185
+ } else if (domTheme === 'miozu-light') {
186
+ this.current = 'light';
187
+ }
188
+ }
189
+
190
+ /**
191
+ * Set theme preference and persist
192
+ * @param {'light' | 'dark' | 'system'} theme
193
+ */
194
+ set(theme) {
195
+ if (!['light', 'dark', 'system'].includes(theme)) {
196
+ console.warn(`Invalid theme: ${theme}`);
197
+ return;
198
+ }
199
+ this.current = theme;
200
+ this.#persist();
201
+ this.#apply();
202
+ }
203
+
204
+ /**
205
+ * Toggle between light and dark (skips system)
206
+ */
207
+ toggle() {
208
+ this.set(this.resolved === 'light' ? 'dark' : 'light');
209
+ }
210
+
211
+ /**
212
+ * Persist to localStorage and cookie
213
+ */
214
+ #persist() {
215
+ if (typeof window === 'undefined') return;
216
+ localStorage.setItem('miozu-theme', this.current);
217
+ document.cookie = `miozu-theme=${this.current};path=/;max-age=31536000;SameSite=Lax`;
218
+ }
219
+
220
+ /**
221
+ * Apply theme to document
222
+ */
223
+ #apply() {
224
+ if (typeof document === 'undefined') return;
225
+ document.documentElement.setAttribute('data-theme', this.dataTheme);
226
+ document.documentElement.style.colorScheme = this.resolved;
227
+ }
228
+ }
229
+
230
+ // ============================================
231
+ // SINGLETON INSTANCE
232
+ // ============================================
233
+
234
+ /** @type {ThemeState | null} */
235
+ let themeInstance = null;
236
+
237
+ /**
238
+ * Get the global theme singleton
239
+ * Creates instance on first call (lazy initialization)
240
+ *
241
+ * @param {'light' | 'dark' | 'system'} [initial] - Initial theme (only used on first call)
242
+ * @returns {ThemeState}
243
+ *
244
+ * @example
245
+ * // In root +layout.svelte
246
+ * import { getTheme } from '@miozu/jera';
247
+ * const theme = getTheme();
248
+ * onMount(() => theme.init());
249
+ *
250
+ * // Anywhere else
251
+ * import { getTheme } from '@miozu/jera';
252
+ * const theme = getTheme();
253
+ * theme.toggle();
254
+ */
255
+ export function getTheme(initial = 'system') {
256
+ if (!themeInstance) {
257
+ themeInstance = new ThemeState(initial);
258
+ }
259
+ return themeInstance;
260
+ }
261
+
262
+ /**
263
+ * Reset theme singleton (for testing)
264
+ */
265
+ export function resetTheme() {
266
+ if (themeInstance) {
267
+ themeInstance.cleanup();
268
+ themeInstance = null;
269
+ }
270
+ }
271
+
272
+ /**
273
+ * Component State Factory
274
+ *
275
+ * Creates encapsulated component state with derived values.
276
+ * Pattern for complex components like Select, Combobox, etc.
277
+ *
278
+ * @template T
279
+ * @param {T} initialState
280
+ * @param {(state: T) => Record<string, any>} [derivedFn]
281
+ */
282
+ export function createComponentState(initialState, derivedFn) {
283
+ const state = $state(initialState);
284
+
285
+ // $derived.by tracks dependencies inside the function
286
+ const deriveFn = derivedFn || (() => ({}));
287
+ const derived = $derived.by(() => deriveFn(state));
288
+
289
+ return {
290
+ get state() {
291
+ return state;
292
+ },
293
+ set state(v) {
294
+ Object.assign(state, v);
295
+ },
296
+ get derived() {
297
+ return derived;
298
+ },
299
+ /**
300
+ * Patch state partially
301
+ * @param {Partial<T>} patch
302
+ */
303
+ patch(patch) {
304
+ Object.assign(state, patch);
305
+ },
306
+ /**
307
+ * Reset to initial state
308
+ */
309
+ reset() {
310
+ Object.assign(state, initialState);
311
+ }
312
+ };
313
+ }
314
+
315
+ /**
316
+ * Create a unique ID generator for accessibility
317
+ * @param {string} [prefix]
318
+ * @returns {() => string}
319
+ */
320
+ export function createIdGenerator(prefix = 'jera') {
321
+ let counter = 0;
322
+ return () => `${prefix}-${++counter}-${Math.random().toString(36).slice(2, 7)}`;
323
+ }
324
+
325
+ /**
326
+ * Shared ID generator instance
327
+ */
328
+ export const generateId = createIdGenerator();
@@ -0,0 +1,211 @@
1
+ /**
2
+ * Sidebar State Management
3
+ *
4
+ * Provides reactive state management for sidebar components.
5
+ * Uses Svelte 5 runes for fine-grained reactivity.
6
+ *
7
+ * @example
8
+ * const sidebar = createSidebarState({ persistKey: 'my-sidebar' });
9
+ *
10
+ * // In component
11
+ * sidebar.toggle();
12
+ * console.log(sidebar.collapsed); // true/false
13
+ */
14
+
15
+ /**
16
+ * Create a reactive sidebar state manager
17
+ *
18
+ * @param {Object} options
19
+ * @param {boolean} [options.collapsed=false] - Initial collapsed state
20
+ * @param {string|null} [options.persistKey=null] - localStorage key for persistence
21
+ * @returns {Object} Sidebar state object with reactive properties and methods
22
+ */
23
+ export function createSidebarState(options = {}) {
24
+ const { collapsed: initial = false, persistKey = null } = options;
25
+
26
+ // Core state
27
+ let _collapsed = $state(initial);
28
+ let _expandedGroups = $state(new Set());
29
+ let _hoverPopover = $state({
30
+ item: null,
31
+ position: { top: 0, left: 0 }
32
+ });
33
+ let _hoverTimeout = null;
34
+
35
+ // Load from localStorage on init (client-side only)
36
+ if (persistKey && typeof localStorage !== 'undefined') {
37
+ try {
38
+ const saved = localStorage.getItem(persistKey);
39
+ if (saved !== null) {
40
+ _collapsed = saved === 'true';
41
+ }
42
+
43
+ const savedGroups = localStorage.getItem(`${persistKey}-groups`);
44
+ if (savedGroups) {
45
+ _expandedGroups = new Set(JSON.parse(savedGroups));
46
+ }
47
+ } catch (e) {
48
+ // Ignore localStorage errors
49
+ }
50
+ }
51
+
52
+ // Persist to localStorage when state changes
53
+ function persist() {
54
+ if (persistKey && typeof localStorage !== 'undefined') {
55
+ try {
56
+ localStorage.setItem(persistKey, String(_collapsed));
57
+ localStorage.setItem(`${persistKey}-groups`, JSON.stringify([..._expandedGroups]));
58
+ } catch (e) {
59
+ // Ignore localStorage errors
60
+ }
61
+ }
62
+ }
63
+
64
+ return {
65
+ // Collapsed state
66
+ get collapsed() {
67
+ return _collapsed;
68
+ },
69
+ set collapsed(value) {
70
+ _collapsed = value;
71
+ persist();
72
+ },
73
+
74
+ /**
75
+ * Toggle collapsed state
76
+ */
77
+ toggle() {
78
+ _collapsed = !_collapsed;
79
+ persist();
80
+ },
81
+
82
+ /**
83
+ * Expand the sidebar
84
+ */
85
+ expand() {
86
+ _collapsed = false;
87
+ persist();
88
+ },
89
+
90
+ /**
91
+ * Collapse the sidebar
92
+ */
93
+ collapse() {
94
+ _collapsed = true;
95
+ persist();
96
+ },
97
+
98
+ // Group expansion
99
+ get expandedGroups() {
100
+ return _expandedGroups;
101
+ },
102
+
103
+ /**
104
+ * Check if a group is expanded
105
+ * @param {string} groupId
106
+ * @returns {boolean}
107
+ */
108
+ isGroupExpanded(groupId) {
109
+ return _expandedGroups.has(groupId);
110
+ },
111
+
112
+ /**
113
+ * Toggle group expansion
114
+ * @param {string} groupId
115
+ */
116
+ toggleGroup(groupId) {
117
+ if (_expandedGroups.has(groupId)) {
118
+ _expandedGroups = new Set([..._expandedGroups].filter(id => id !== groupId));
119
+ } else {
120
+ _expandedGroups = new Set([..._expandedGroups, groupId]);
121
+ }
122
+ persist();
123
+ },
124
+
125
+ /**
126
+ * Expand a group
127
+ * @param {string} groupId
128
+ */
129
+ expandGroup(groupId) {
130
+ if (!_expandedGroups.has(groupId)) {
131
+ _expandedGroups = new Set([..._expandedGroups, groupId]);
132
+ persist();
133
+ }
134
+ },
135
+
136
+ /**
137
+ * Collapse a group
138
+ * @param {string} groupId
139
+ */
140
+ collapseGroup(groupId) {
141
+ if (_expandedGroups.has(groupId)) {
142
+ _expandedGroups = new Set([..._expandedGroups].filter(id => id !== groupId));
143
+ persist();
144
+ }
145
+ },
146
+
147
+ // Hover popover state
148
+ get hoverPopover() {
149
+ return _hoverPopover;
150
+ },
151
+
152
+ /**
153
+ * Show hover popover for an item
154
+ * @param {string} itemId
155
+ * @param {{top: number, left: number}} position
156
+ */
157
+ showPopover(itemId, position) {
158
+ if (!_collapsed) return;
159
+
160
+ if (_hoverTimeout) {
161
+ clearTimeout(_hoverTimeout);
162
+ _hoverTimeout = null;
163
+ }
164
+
165
+ _hoverPopover = {
166
+ item: itemId,
167
+ position
168
+ };
169
+ },
170
+
171
+ /**
172
+ * Hide hover popover with delay
173
+ * @param {number} [delay=150]
174
+ */
175
+ hidePopover(delay = 150) {
176
+ if (_hoverTimeout) {
177
+ clearTimeout(_hoverTimeout);
178
+ }
179
+
180
+ _hoverTimeout = setTimeout(() => {
181
+ _hoverPopover = { item: null, position: { top: 0, left: 0 } };
182
+ }, delay);
183
+ },
184
+
185
+ /**
186
+ * Cancel pending popover hide
187
+ */
188
+ keepPopoverOpen() {
189
+ if (_hoverTimeout) {
190
+ clearTimeout(_hoverTimeout);
191
+ _hoverTimeout = null;
192
+ }
193
+ },
194
+
195
+ /**
196
+ * Immediately hide popover
197
+ */
198
+ hidePopoverImmediate() {
199
+ if (_hoverTimeout) {
200
+ clearTimeout(_hoverTimeout);
201
+ _hoverTimeout = null;
202
+ }
203
+ _hoverPopover = { item: null, position: { top: 0, left: 0 } };
204
+ }
205
+ };
206
+ }
207
+
208
+ /**
209
+ * Sidebar context key for Svelte context API
210
+ */
211
+ export const SIDEBAR_CONTEXT_KEY = 'sidebar';
package/jera.js DELETED
@@ -1,135 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- import { Command } from 'commander';
4
- import fs from 'node:fs';
5
- import path from 'node:path';
6
- import { fileURLToPath } from 'node:url';
7
-
8
- const __filename = fileURLToPath(import.meta.url);
9
- const __dirname = path.dirname(__filename);
10
-
11
- // Find the components directory relative to the package
12
- function findComponentsDir() {
13
- // Try to locate the components directory
14
- const possiblePaths = [
15
- // When running from the installed package
16
- path.join(__dirname, 'src', 'components'),
17
- // When running from the project root
18
- path.join(__dirname, 'components'),
19
- // When running from a cloned repo
20
- path.join(__dirname, '..', 'src', 'components')
21
- ];
22
-
23
- for (const dir of possiblePaths) {
24
- if (fs.existsSync(dir)) {
25
- return dir;
26
- }
27
- }
28
-
29
- // If components directory not found, return null
30
- return null;
31
- }
32
-
33
- const COMPONENTS_DIR = findComponentsDir();
34
-
35
- // List available components in the components directory
36
- function listAvailableComponents() {
37
- if (!COMPONENTS_DIR) {
38
- return [];
39
- }
40
-
41
- try {
42
- return fs.readdirSync(COMPONENTS_DIR)
43
- .filter(file => file.endsWith('.svelte'))
44
- .map(file => file.replace('.svelte', ''));
45
- } catch (error) {
46
- console.error('Error reading components directory:', error);
47
- return [];
48
- }
49
- }
50
-
51
- const program = new Command();
52
-
53
- program
54
- .name('jera')
55
- .description('Install Jera components to your project')
56
- .version('0.0.1');
57
-
58
- program
59
- .command('list')
60
- .description('List all available components')
61
- .action(() => {
62
- const components = listAvailableComponents();
63
-
64
- if (components.length === 0) {
65
- console.log('No components found.');
66
- return;
67
- }
68
-
69
- console.log('Available components:');
70
- components.forEach(comp => console.log(`- ${comp}`));
71
- });
72
-
73
- program
74
- .command('add')
75
- .description('Add a component to your project')
76
- .argument('<component>', 'Component to add (e.g. Input)')
77
- .option('-d, --dir <directory>', 'Target directory', 'src/lib/components')
78
- .action((component, options) => {
79
- console.log(`Adding ${component} to ${options.dir}...`);
80
-
81
- const componentFile = component.endsWith('.svelte')
82
- ? component
83
- : `${component}.svelte`;
84
-
85
- // Check if components directory exists
86
- if (!COMPONENTS_DIR) {
87
- console.error('Error: Components directory not found.');
88
- console.error('This could happen if you\'re running the CLI in development mode');
89
- console.error('or if the package structure has changed.');
90
- return;
91
- }
92
-
93
- // Check if component exists
94
- const sourceComponentPath = path.join(COMPONENTS_DIR, componentFile);
95
- if (!fs.existsSync(sourceComponentPath)) {
96
- console.error(`Error: Component "${component}" not found.`);
97
- console.error('Available components:');
98
- listAvailableComponents().forEach(comp => console.error(`- ${comp}`));
99
- return;
100
- }
101
-
102
- // Create target directory if it doesn't exist
103
- const targetDir = path.resolve(options.dir);
104
- if (!fs.existsSync(targetDir)) {
105
- fs.mkdirSync(targetDir, { recursive: true });
106
- }
107
-
108
- // Copy the component file
109
- const componentContent = fs.readFileSync(sourceComponentPath, 'utf-8');
110
- fs.writeFileSync(path.join(targetDir, componentFile), componentContent);
111
-
112
- console.log(`Component ${component} added to ${options.dir}`);
113
- console.log('\nUsage:');
114
- console.log('\nNOTE: This component uses the Miozu theme.');
115
- console.log('Make sure you have installed @miozu/js-theme and configured it in your Tailwind config.');
116
- });
117
-
118
- // Add init command to set up the repository structure
119
- program
120
- .command('init')
121
- .description('Initialize a new project with Jera components')
122
- .action(() => {
123
- console.log('Initializing Jera components...');
124
-
125
- const componentsDir = path.resolve('src/lib/components');
126
-
127
- if (!fs.existsSync(componentsDir)) {
128
- fs.mkdirSync(componentsDir, { recursive: true });
129
- }
130
-
131
- console.log('Project initialized! You can now add components with:');
132
- console.log(' jera add <component>');
133
- });
134
-
135
- program.parse();