@miozu/jera 0.0.2 → 0.3.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.
Files changed (53) hide show
  1. package/CLAUDE.md +443 -0
  2. package/README.md +211 -1
  3. package/llms.txt +64 -0
  4. package/package.json +44 -14
  5. package/src/actions/index.js +375 -0
  6. package/src/components/feedback/EmptyState.svelte +179 -0
  7. package/src/components/feedback/ProgressBar.svelte +116 -0
  8. package/src/components/feedback/Skeleton.svelte +107 -0
  9. package/src/components/feedback/Spinner.svelte +77 -0
  10. package/src/components/feedback/Toast.svelte +297 -0
  11. package/src/components/forms/Checkbox.svelte +147 -0
  12. package/src/components/forms/Dropzone.svelte +248 -0
  13. package/src/components/forms/FileUpload.svelte +266 -0
  14. package/src/components/forms/IconInput.svelte +184 -0
  15. package/src/components/forms/Input.svelte +121 -0
  16. package/src/components/forms/NumberInput.svelte +225 -0
  17. package/src/components/forms/PinInput.svelte +169 -0
  18. package/src/components/forms/Radio.svelte +143 -0
  19. package/src/components/forms/RadioGroup.svelte +62 -0
  20. package/src/components/forms/RangeSlider.svelte +212 -0
  21. package/src/components/forms/SearchInput.svelte +175 -0
  22. package/src/components/forms/Select.svelte +326 -0
  23. package/src/components/forms/Switch.svelte +159 -0
  24. package/src/components/forms/Textarea.svelte +122 -0
  25. package/src/components/navigation/Accordion.svelte +65 -0
  26. package/src/components/navigation/AccordionItem.svelte +146 -0
  27. package/src/components/navigation/Tabs.svelte +239 -0
  28. package/src/components/overlays/ConfirmDialog.svelte +272 -0
  29. package/src/components/overlays/Dropdown.svelte +153 -0
  30. package/src/components/overlays/DropdownDivider.svelte +23 -0
  31. package/src/components/overlays/DropdownItem.svelte +97 -0
  32. package/src/components/overlays/Modal.svelte +232 -0
  33. package/src/components/overlays/Popover.svelte +206 -0
  34. package/src/components/primitives/Avatar.svelte +132 -0
  35. package/src/components/primitives/Badge.svelte +118 -0
  36. package/src/components/primitives/Button.svelte +262 -0
  37. package/src/components/primitives/Card.svelte +104 -0
  38. package/src/components/primitives/Divider.svelte +105 -0
  39. package/src/components/primitives/LazyImage.svelte +104 -0
  40. package/src/components/primitives/Link.svelte +122 -0
  41. package/src/components/primitives/StatusBadge.svelte +122 -0
  42. package/src/index.js +128 -0
  43. package/src/tokens/colors.css +189 -0
  44. package/src/tokens/effects.css +128 -0
  45. package/src/tokens/index.css +81 -0
  46. package/src/tokens/spacing.css +49 -0
  47. package/src/tokens/typography.css +79 -0
  48. package/src/utils/cn.svelte.js +175 -0
  49. package/src/utils/index.js +17 -0
  50. package/src/utils/reactive.svelte.js +239 -0
  51. package/jera.js +0 -135
  52. package/www/components/jera/Input/Input.svelte +0 -63
  53. package/www/components/jera/Input/index.js +0 -1
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Jera Utilities
3
+ *
4
+ * Core utilities for class composition and reactive state management.
5
+ */
6
+
7
+ export { cn, rcn, cv, mergeClasses, when, match } from './cn.svelte.js';
8
+ export {
9
+ createReactive,
10
+ createDerived,
11
+ ThemeState,
12
+ createThemeContext,
13
+ getThemeContext,
14
+ createComponentState,
15
+ createIdGenerator,
16
+ generateId
17
+ } from './reactive.svelte.js';
@@ -0,0 +1,239 @@
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
+ import { getContext, setContext, onMount } from 'svelte';
9
+
10
+ /**
11
+ * Create a reactive store-like object with Svelte 5 runes
12
+ * Unlike stores, these work seamlessly with runes and don't need $ prefix
13
+ *
14
+ * @template T
15
+ * @param {T} initial
16
+ * @returns {{ value: T, set: (v: T) => void, update: (fn: (v: T) => T) => void }}
17
+ *
18
+ * @example
19
+ * const count = createReactive(0);
20
+ * count.value; // read
21
+ * count.set(5); // write
22
+ * count.update(n => n + 1); // update
23
+ */
24
+ export function createReactive(initial) {
25
+ let value = $state(initial);
26
+
27
+ return {
28
+ get value() {
29
+ return value;
30
+ },
31
+ set value(v) {
32
+ value = v;
33
+ },
34
+ set(v) {
35
+ value = v;
36
+ },
37
+ update(fn) {
38
+ value = fn(value);
39
+ }
40
+ };
41
+ }
42
+
43
+ /**
44
+ * Create a derived reactive value
45
+ *
46
+ * @template T
47
+ * @param {() => T} fn
48
+ * @returns {{ readonly value: T }}
49
+ */
50
+ export function createDerived(fn) {
51
+ const value = $derived.by(fn);
52
+
53
+ return {
54
+ get value() {
55
+ return value;
56
+ }
57
+ };
58
+ }
59
+
60
+ /**
61
+ * Theme Context Key
62
+ */
63
+ const THEME_KEY = Symbol('jera-theme');
64
+
65
+ /**
66
+ * Theme Reactive State Class
67
+ *
68
+ * Manages theme state with persistence and SSR support.
69
+ * Uses class-based reactive state pattern.
70
+ */
71
+ export class ThemeState {
72
+ /** @type {'light' | 'dark' | 'system'} */
73
+ current = $state('system');
74
+
75
+ /** @type {'light' | 'dark'} */
76
+ resolved = $derived.by(() => this.#resolveTheme());
77
+
78
+ /** @type {boolean} */
79
+ #mounted = false;
80
+
81
+ /** @type {MediaQueryList | null} */
82
+ #mediaQuery = null;
83
+
84
+ /** @type {(() => void) | null} */
85
+ #mediaQueryHandler = null;
86
+
87
+ constructor(initial = 'system') {
88
+ this.current = initial;
89
+ }
90
+
91
+ #resolveTheme() {
92
+ if (this.current === 'system') {
93
+ // SSR-safe: default to light if no window
94
+ if (typeof window === 'undefined') return 'light';
95
+ return this.#mediaQuery?.matches ? 'dark' : 'light';
96
+ }
97
+ return this.current;
98
+ }
99
+
100
+ /**
101
+ * Initialize theme - call in onMount
102
+ */
103
+ init() {
104
+ if (typeof window === 'undefined') return;
105
+
106
+ // Load from storage
107
+ const stored = localStorage.getItem('jera-theme');
108
+ if (stored && ['light', 'dark', 'system'].includes(stored)) {
109
+ this.current = stored;
110
+ }
111
+
112
+ // Setup media query listener
113
+ this.#mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
114
+ this.#mediaQueryHandler = () => {
115
+ // Trigger re-resolution by reassigning
116
+ this.current = this.current;
117
+ };
118
+ this.#mediaQuery.addEventListener('change', this.#mediaQueryHandler);
119
+
120
+ this.#mounted = true;
121
+ this.#apply();
122
+ }
123
+
124
+ /**
125
+ * Cleanup - call in onDestroy to prevent memory leaks
126
+ */
127
+ cleanup() {
128
+ if (this.#mediaQuery && this.#mediaQueryHandler) {
129
+ this.#mediaQuery.removeEventListener('change', this.#mediaQueryHandler);
130
+ this.#mediaQueryHandler = null;
131
+ }
132
+ this.#mounted = false;
133
+ }
134
+
135
+ /**
136
+ * Set theme and persist
137
+ * @param {'light' | 'dark' | 'system'} theme
138
+ */
139
+ set(theme) {
140
+ this.current = theme;
141
+ if (typeof window !== 'undefined') {
142
+ localStorage.setItem('jera-theme', theme);
143
+ document.cookie = `jera-theme=${theme};path=/;max-age=31536000`;
144
+ }
145
+ this.#apply();
146
+ }
147
+
148
+ /**
149
+ * Toggle between light and dark
150
+ */
151
+ toggle() {
152
+ this.set(this.resolved === 'light' ? 'dark' : 'light');
153
+ }
154
+
155
+ /**
156
+ * Apply theme to document
157
+ */
158
+ #apply() {
159
+ if (typeof document === 'undefined') return;
160
+ document.documentElement.setAttribute('data-theme', this.resolved);
161
+ }
162
+ }
163
+
164
+ /**
165
+ * Create and provide theme context
166
+ * @param {'light' | 'dark' | 'system'} [initial]
167
+ * @returns {ThemeState}
168
+ */
169
+ export function createThemeContext(initial = 'system') {
170
+ const theme = new ThemeState(initial);
171
+ setContext(THEME_KEY, theme);
172
+ return theme;
173
+ }
174
+
175
+ /**
176
+ * Get theme from context
177
+ * @returns {ThemeState}
178
+ */
179
+ export function getThemeContext() {
180
+ return getContext(THEME_KEY);
181
+ }
182
+
183
+ /**
184
+ * Component State Factory
185
+ *
186
+ * Creates encapsulated component state with derived values.
187
+ * Pattern for complex components like Select, Combobox, etc.
188
+ *
189
+ * @template T
190
+ * @param {T} initialState
191
+ * @param {(state: T) => Record<string, any>} [derivedFn]
192
+ */
193
+ export function createComponentState(initialState, derivedFn) {
194
+ const state = $state(initialState);
195
+
196
+ // $derived.by tracks dependencies inside the function
197
+ const deriveFn = derivedFn || (() => ({}));
198
+ const derived = $derived.by(() => deriveFn(state));
199
+
200
+ return {
201
+ get state() {
202
+ return state;
203
+ },
204
+ set state(v) {
205
+ Object.assign(state, v);
206
+ },
207
+ get derived() {
208
+ return derived;
209
+ },
210
+ /**
211
+ * Patch state partially
212
+ * @param {Partial<T>} patch
213
+ */
214
+ patch(patch) {
215
+ Object.assign(state, patch);
216
+ },
217
+ /**
218
+ * Reset to initial state
219
+ */
220
+ reset() {
221
+ Object.assign(state, initialState);
222
+ }
223
+ };
224
+ }
225
+
226
+ /**
227
+ * Create a unique ID generator for accessibility
228
+ * @param {string} [prefix]
229
+ * @returns {() => string}
230
+ */
231
+ export function createIdGenerator(prefix = 'jera') {
232
+ let counter = 0;
233
+ return () => `${prefix}-${++counter}-${Math.random().toString(36).slice(2, 7)}`;
234
+ }
235
+
236
+ /**
237
+ * Shared ID generator instance
238
+ */
239
+ export const generateId = createIdGenerator();
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();
@@ -1,63 +0,0 @@
1
- <script>
2
- let {
3
- value = '',
4
- ref = $bindable(),
5
- type = 'text',
6
- placeholder = '',
7
- disabled = false,
8
- required = false,
9
- name = '',
10
- id = '',
11
- autocomplete = 'on',
12
- autocorrect = 'off',
13
- autocapitalize = 'off',
14
- spellcheck = 'false',
15
- maxlength = undefined,
16
- minlength = undefined,
17
- inputmode,
18
- class: className = '',
19
- unstyled = false,
20
- disableBrowserFeatures = false,
21
- oninput = () => {},
22
- onchange = () => {},
23
- onkeydown = () => {},
24
- ...others
25
- } = $props();
26
-
27
- const finalAutocomplete = disableBrowserFeatures ? 'new-password' : autocomplete;
28
- const finalClassName = $derived(unstyled ? className : `base-input ${className}`);
29
- </script>
30
-
31
- <input
32
- class={finalClassName}
33
- bind:this={ref}
34
- {id}
35
- {name}
36
- {type}
37
- {value}
38
- {placeholder}
39
- {disabled}
40
- {required}
41
- {inputmode}
42
- {maxlength}
43
- {minlength}
44
- autocomplete={finalAutocomplete}
45
- autocorrect={disableBrowserFeatures ? 'off' : autocorrect}
46
- autocapitalize={disableBrowserFeatures ? 'off' : autocapitalize}
47
- spellcheck={disableBrowserFeatures ? 'false' : spellcheck}
48
- data-form-type={disableBrowserFeatures ? 'other' : undefined}
49
- data-lpignore={disableBrowserFeatures ? 'true' : undefined}
50
- {oninput}
51
- {onchange}
52
- {onkeydown}
53
- {...others}
54
- />
55
-
56
- <style>
57
- .input-wrapper {
58
- @apply relative;
59
- }
60
- .input-label {
61
- @apply font-bold inline-block;
62
- }
63
- </style>
@@ -1 +0,0 @@
1
- export { default } from './Input.svelte';