@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,132 @@
1
+ <!--
2
+ @component Avatar
3
+
4
+ User avatar with image, initials fallback, and status indicator.
5
+
6
+ @example With image
7
+ <Avatar src="/user.jpg" alt="John Doe" />
8
+
9
+ @example With initials fallback
10
+ <Avatar name="John Doe" />
11
+
12
+ @example With status
13
+ <Avatar src="/user.jpg" status="online" />
14
+ -->
15
+ <script>
16
+ import { cv } from '../../utils/cn.svelte.js';
17
+
18
+ let {
19
+ src = '',
20
+ alt = '',
21
+ name = '',
22
+ size = 'md',
23
+ status = null,
24
+ class: className = ''
25
+ } = $props();
26
+
27
+ // Generate initials from name
28
+ const initials = $derived.by(() => {
29
+ if (!name) return '';
30
+ const parts = name.trim().split(/\s+/);
31
+ if (parts.length >= 2) {
32
+ return (parts[0][0] + parts[parts.length - 1][0]).toUpperCase();
33
+ }
34
+ return parts[0].slice(0, 2).toUpperCase();
35
+ });
36
+
37
+ // Generate consistent color from name
38
+ const bgColor = $derived.by(() => {
39
+ if (!name) return 'var(--color-border)';
40
+ let hash = 0;
41
+ for (let i = 0; i < name.length; i++) {
42
+ hash = name.charCodeAt(i) + ((hash << 5) - hash);
43
+ }
44
+ const colors = [
45
+ 'var(--color-error)', // red
46
+ 'var(--orange)', // orange
47
+ 'var(--color-success)', // green
48
+ 'var(--color-warning)', // yellow
49
+ 'var(--color-info)', // blue
50
+ 'var(--color-primary)', // magenta
51
+ 'var(--peach)', // peach
52
+ 'var(--color-accent)' // cyan
53
+ ];
54
+ return colors[Math.abs(hash) % colors.length];
55
+ });
56
+
57
+ let imgError = $state(false);
58
+ const showImage = $derived(src && !imgError);
59
+ </script>
60
+
61
+ <div class="avatar avatar-{size} {className}">
62
+ {#if showImage}
63
+ <img
64
+ {src}
65
+ alt={alt || name}
66
+ class="avatar-image"
67
+ onerror={() => imgError = true}
68
+ />
69
+ {:else}
70
+ <span class="avatar-initials" style="background: {bgColor};">
71
+ {initials}
72
+ </span>
73
+ {/if}
74
+
75
+ {#if status}
76
+ <span class="avatar-status avatar-status-{status}"></span>
77
+ {/if}
78
+ </div>
79
+
80
+ <style>
81
+ .avatar {
82
+ position: relative;
83
+ display: inline-flex;
84
+ align-items: center;
85
+ justify-content: center;
86
+ border-radius: 50%;
87
+ overflow: hidden;
88
+ flex-shrink: 0;
89
+ }
90
+
91
+ /* Size variants */
92
+ .avatar-xs { width: 1.5rem; height: 1.5rem; font-size: 0.625rem; }
93
+ .avatar-sm { width: 2rem; height: 2rem; font-size: 0.75rem; }
94
+ .avatar-md { width: 2.5rem; height: 2.5rem; font-size: 0.875rem; }
95
+ .avatar-lg { width: 3rem; height: 3rem; font-size: 1rem; }
96
+ .avatar-xl { width: 4rem; height: 4rem; font-size: 1.25rem; }
97
+ .avatar-2xl { width: 5rem; height: 5rem; font-size: 1.5rem; }
98
+
99
+ .avatar-image {
100
+ width: 100%;
101
+ height: 100%;
102
+ object-fit: cover;
103
+ }
104
+
105
+ .avatar-initials {
106
+ display: flex;
107
+ align-items: center;
108
+ justify-content: center;
109
+ width: 100%;
110
+ height: 100%;
111
+ color: white;
112
+ font-weight: 600;
113
+ letter-spacing: 0.02em;
114
+ }
115
+
116
+ .avatar-status {
117
+ position: absolute;
118
+ bottom: 0;
119
+ right: 0;
120
+ width: 25%;
121
+ height: 25%;
122
+ min-width: 8px;
123
+ min-height: 8px;
124
+ border-radius: 50%;
125
+ border: 2px solid var(--color-bg);
126
+ }
127
+
128
+ .avatar-status-online { background: var(--color-success); }
129
+ .avatar-status-offline { background: var(--color-text-muted); }
130
+ .avatar-status-busy { background: var(--color-error); }
131
+ .avatar-status-away { background: var(--color-warning); }
132
+ </style>
@@ -0,0 +1,118 @@
1
+ <!--
2
+ @component Badge
3
+
4
+ A versatile badge/tag component for status, labels, and counts.
5
+ Consolidates Badge, StatusBadge, and Status patterns into one component.
6
+
7
+ @example
8
+ <Badge>Default</Badge>
9
+ <Badge variant="success">Active</Badge>
10
+ <Badge variant="error" size="sm">Error</Badge>
11
+
12
+ @example
13
+ // With icon
14
+ <Badge variant="primary">
15
+ {#snippet iconLeft()}<CheckIcon size={12} />{/snippet}
16
+ Verified
17
+ </Badge>
18
+
19
+ @example
20
+ // Clickable badge
21
+ <Badge onclick={() => filter('active')} clickable>Active</Badge>
22
+ -->
23
+ <script module>
24
+ import { cv } from '../../utils/cn.svelte.js';
25
+
26
+ export const badgeStyles = cv({
27
+ base: [
28
+ 'inline-flex items-center justify-center gap-1',
29
+ 'font-medium rounded-full',
30
+ 'transition-colors'
31
+ ].join(' '),
32
+
33
+ variants: {
34
+ variant: {
35
+ default: 'bg-[var(--color-surface-alt)] text-[var(--color-text)]',
36
+ primary: 'bg-[color-mix(in_srgb,var(--color-primary)_15%,transparent)] text-[var(--color-primary)]',
37
+ secondary: 'bg-[color-mix(in_srgb,var(--color-secondary)_15%,transparent)] text-[var(--color-secondary)]',
38
+ success: 'bg-[color-mix(in_srgb,var(--color-success)_15%,transparent)] text-[var(--color-success)]',
39
+ warning: 'bg-[color-mix(in_srgb,var(--color-warning)_15%,transparent)] text-[var(--color-warning)]',
40
+ error: 'bg-[color-mix(in_srgb,var(--color-error)_15%,transparent)] text-[var(--color-error)]',
41
+ info: 'bg-[color-mix(in_srgb,var(--color-info)_15%,transparent)] text-[var(--color-info)]'
42
+ },
43
+
44
+ size: {
45
+ sm: 'px-2 py-0.5 text-xs',
46
+ md: 'px-2.5 py-1 text-xs',
47
+ lg: 'px-3 py-1.5 text-sm'
48
+ },
49
+
50
+ clickable: {
51
+ true: 'cursor-pointer hover:opacity-80',
52
+ false: ''
53
+ }
54
+ },
55
+
56
+ defaults: {
57
+ variant: 'default',
58
+ size: 'md',
59
+ clickable: 'false'
60
+ }
61
+ });
62
+ </script>
63
+
64
+ <script>
65
+ import { cn } from '../../utils/cn.svelte.js';
66
+
67
+ let {
68
+ children,
69
+ iconLeft,
70
+ iconRight,
71
+ variant = 'default',
72
+ size = 'md',
73
+ clickable = false,
74
+ class: className = '',
75
+ onclick,
76
+ ...rest
77
+ } = $props();
78
+
79
+ const badgeClass = $derived(
80
+ badgeStyles({
81
+ variant,
82
+ size,
83
+ clickable: (clickable || onclick) ? 'true' : 'false',
84
+ class: className
85
+ })
86
+ );
87
+ </script>
88
+
89
+ {#if onclick || clickable}
90
+ <button
91
+ type="button"
92
+ class={badgeClass}
93
+ {onclick}
94
+ {...rest}
95
+ >
96
+ {#if iconLeft}
97
+ <span class="shrink-0">{@render iconLeft()}</span>
98
+ {/if}
99
+ {#if children}
100
+ {@render children()}
101
+ {/if}
102
+ {#if iconRight}
103
+ <span class="shrink-0">{@render iconRight()}</span>
104
+ {/if}
105
+ </button>
106
+ {:else}
107
+ <span class={badgeClass} {...rest}>
108
+ {#if iconLeft}
109
+ <span class="shrink-0">{@render iconLeft()}</span>
110
+ {/if}
111
+ {#if children}
112
+ {@render children()}
113
+ {/if}
114
+ {#if iconRight}
115
+ <span class="shrink-0">{@render iconRight()}</span>
116
+ {/if}
117
+ </span>
118
+ {/if}
@@ -0,0 +1,262 @@
1
+ <!--
2
+ @component Button
3
+
4
+ A polymorphic button component with variants, sizes, and loading states.
5
+ Demonstrates advanced Svelte 5 patterns:
6
+
7
+ - Polymorphic rendering (as button, a, or custom element)
8
+ - Reactive class composition with cv()
9
+ - Snippets for icon slots
10
+ - $derived for computed properties
11
+ - TypeScript-style prop definitions
12
+
13
+ @example
14
+ <Button>Click me</Button>
15
+ <Button variant="secondary" size="lg">Large Secondary</Button>
16
+ <Button href="/about">Link Button</Button>
17
+ <Button loading>Loading...</Button>
18
+ <Button disabled>Disabled</Button>
19
+ <Button>
20
+ {#snippet iconLeft()}<IconPlus />{/snippet}
21
+ Add Item
22
+ </Button>
23
+ -->
24
+ <script module>
25
+ import { cv } from '../../utils/cn.svelte.js';
26
+
27
+ /**
28
+ * Button style variants using cv() for type-safe composition
29
+ */
30
+ export const buttonStyles = cv({
31
+ base: [
32
+ 'inline-flex items-center justify-center gap-2',
33
+ 'font-medium rounded-lg',
34
+ 'transition-all duration-150 ease-out',
35
+ 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2',
36
+ 'disabled:opacity-50 disabled:cursor-not-allowed disabled:pointer-events-none',
37
+ 'select-none'
38
+ ].join(' '),
39
+
40
+ variants: {
41
+ variant: {
42
+ primary: [
43
+ 'bg-primary text-white',
44
+ 'hover:brightness-110 active:brightness-95',
45
+ 'focus-visible:ring-primary/50'
46
+ ].join(' '),
47
+
48
+ secondary: [
49
+ 'bg-surface-alt text-text-strong border border-border',
50
+ 'hover:bg-hover hover:border-muted',
51
+ 'focus-visible:ring-border'
52
+ ].join(' '),
53
+
54
+ ghost: [
55
+ 'bg-transparent text-text',
56
+ 'hover:bg-hover',
57
+ 'focus-visible:ring-border'
58
+ ].join(' '),
59
+
60
+ outline: [
61
+ 'bg-transparent text-primary border border-primary/40',
62
+ 'hover:bg-primary/5 hover:border-primary',
63
+ 'focus-visible:ring-primary/50'
64
+ ].join(' '),
65
+
66
+ danger: [
67
+ 'bg-error text-white',
68
+ 'hover:brightness-110 active:brightness-95',
69
+ 'focus-visible:ring-error/50'
70
+ ].join(' '),
71
+
72
+ success: [
73
+ 'bg-success text-white',
74
+ 'hover:brightness-110 active:brightness-95',
75
+ 'focus-visible:ring-success/50'
76
+ ].join(' ')
77
+ },
78
+
79
+ size: {
80
+ xs: 'h-7 px-2.5 text-xs',
81
+ sm: 'h-8 px-3 text-sm',
82
+ md: 'h-10 px-4 text-sm',
83
+ lg: 'h-12 px-6 text-base',
84
+ xl: 'h-14 px-8 text-lg'
85
+ },
86
+
87
+ fullWidth: {
88
+ true: 'w-full',
89
+ false: ''
90
+ },
91
+
92
+ iconOnly: {
93
+ true: 'aspect-square p-0',
94
+ false: ''
95
+ }
96
+ },
97
+
98
+ compounds: [
99
+ // Icon-only buttons need adjusted padding
100
+ { condition: { iconOnly: 'true', size: 'xs' }, class: 'w-7' },
101
+ { condition: { iconOnly: 'true', size: 'sm' }, class: 'w-8' },
102
+ { condition: { iconOnly: 'true', size: 'md' }, class: 'w-10' },
103
+ { condition: { iconOnly: 'true', size: 'lg' }, class: 'w-12' },
104
+ { condition: { iconOnly: 'true', size: 'xl' }, class: 'w-14' }
105
+ ],
106
+
107
+ defaults: {
108
+ variant: 'primary',
109
+ size: 'md',
110
+ fullWidth: 'false',
111
+ iconOnly: 'false'
112
+ }
113
+ });
114
+ </script>
115
+
116
+ <script>
117
+ import { cn } from '../../utils/cn.svelte.js';
118
+
119
+ /**
120
+ * @typedef {import('svelte').Snippet} Snippet
121
+ * @typedef {'primary' | 'secondary' | 'ghost' | 'outline' | 'danger' | 'success'} Variant
122
+ * @typedef {'xs' | 'sm' | 'md' | 'lg' | 'xl'} Size
123
+ */
124
+
125
+ let {
126
+ children,
127
+ iconLeft,
128
+ iconRight,
129
+ variant = 'primary',
130
+ size = 'md',
131
+ disabled = false,
132
+ loading = false,
133
+ fullWidth = false,
134
+ href,
135
+ type = 'button',
136
+ class: className = '',
137
+ onclick,
138
+ ...restProps
139
+ } = $props();
140
+
141
+ // Derived state
142
+ const isLink = $derived(!!href);
143
+ const isIconOnly = $derived(!children && (!!iconLeft || !!iconRight));
144
+ const isDisabled = $derived(disabled || loading);
145
+
146
+ // Reactive class composition
147
+ const buttonClass = $derived(
148
+ buttonStyles({
149
+ variant,
150
+ size,
151
+ fullWidth: fullWidth ? 'true' : 'false',
152
+ iconOnly: isIconOnly ? 'true' : 'false',
153
+ class: className
154
+ })
155
+ );
156
+
157
+ // Loading spinner
158
+ const spinnerSize = $derived({
159
+ xs: 12,
160
+ sm: 14,
161
+ md: 16,
162
+ lg: 18,
163
+ xl: 20
164
+ }[size]);
165
+ </script>
166
+
167
+ <!--
168
+ Polymorphic rendering: renders as <a> if href provided, otherwise <button>
169
+ Uses Svelte 5's element binding with spread props
170
+ -->
171
+ {#if isLink}
172
+ <a
173
+ {href}
174
+ class={buttonClass}
175
+ aria-disabled={isDisabled || undefined}
176
+ role="button"
177
+ {...restProps}
178
+ >
179
+ {#if loading}
180
+ <svg
181
+ class="animate-spin"
182
+ width={spinnerSize}
183
+ height={spinnerSize}
184
+ viewBox="0 0 24 24"
185
+ fill="none"
186
+ stroke="currentColor"
187
+ stroke-width="2"
188
+ >
189
+ <circle cx="12" cy="12" r="10" opacity="0.25" />
190
+ <path d="M12 2a10 10 0 0 1 10 10" opacity="0.75" />
191
+ </svg>
192
+ {:else if iconLeft}
193
+ <span class="inline-flex shrink-0">
194
+ {@render iconLeft()}
195
+ </span>
196
+ {/if}
197
+
198
+ {#if children}
199
+ <span class={cn(loading && 'opacity-0')}>
200
+ {@render children()}
201
+ </span>
202
+ {/if}
203
+
204
+ {#if iconRight && !loading}
205
+ <span class="inline-flex shrink-0">
206
+ {@render iconRight()}
207
+ </span>
208
+ {/if}
209
+ </a>
210
+ {:else}
211
+ <button
212
+ {type}
213
+ class={buttonClass}
214
+ disabled={isDisabled}
215
+ aria-busy={loading || undefined}
216
+ {onclick}
217
+ {...restProps}
218
+ >
219
+ {#if loading}
220
+ <svg
221
+ class="animate-spin"
222
+ width={spinnerSize}
223
+ height={spinnerSize}
224
+ viewBox="0 0 24 24"
225
+ fill="none"
226
+ stroke="currentColor"
227
+ stroke-width="2"
228
+ >
229
+ <circle cx="12" cy="12" r="10" opacity="0.25" />
230
+ <path d="M12 2a10 10 0 0 1 10 10" opacity="0.75" />
231
+ </svg>
232
+ {:else if iconLeft}
233
+ <span class="inline-flex shrink-0">
234
+ {@render iconLeft()}
235
+ </span>
236
+ {/if}
237
+
238
+ {#if children}
239
+ <span class={cn(loading && 'opacity-70')}>
240
+ {@render children()}
241
+ </span>
242
+ {/if}
243
+
244
+ {#if iconRight && !loading}
245
+ <span class="inline-flex shrink-0">
246
+ {@render iconRight()}
247
+ </span>
248
+ {/if}
249
+ </button>
250
+ {/if}
251
+
252
+ <style>
253
+ @keyframes spin {
254
+ to {
255
+ transform: rotate(360deg);
256
+ }
257
+ }
258
+
259
+ .animate-spin {
260
+ animation: spin 1s linear infinite;
261
+ }
262
+ </style>
@@ -0,0 +1,104 @@
1
+ <!--
2
+ @component Card
3
+
4
+ A flexible card container with optional title and variants.
5
+
6
+ @example Basic
7
+ <Card>
8
+ <p>Card content here</p>
9
+ </Card>
10
+
11
+ @example With title
12
+ <Card title="Settings">
13
+ <p>Your settings content</p>
14
+ </Card>
15
+
16
+ @example Danger variant
17
+ <Card title="Danger Zone" variant="danger">
18
+ <Button variant="danger">Delete Account</Button>
19
+ </Card>
20
+ -->
21
+ <script>
22
+ let {
23
+ title = '',
24
+ variant = 'default',
25
+ class: className = '',
26
+ children
27
+ } = $props();
28
+ </script>
29
+
30
+ <div class="card card-{variant} {className}">
31
+ {#if title}
32
+ <h3 class="card-title">{@html title}</h3>
33
+ {/if}
34
+ <div class="card-content">
35
+ {@render children?.()}
36
+ </div>
37
+ </div>
38
+
39
+ <style>
40
+ .card {
41
+ background: transparent;
42
+ border: 1px solid var(--color-border-muted);
43
+ border-radius: var(--radius-xl);
44
+ padding: var(--space-6);
45
+ transition: border-color 0.2s ease;
46
+ }
47
+
48
+ .card:hover {
49
+ border-color: var(--color-border);
50
+ }
51
+
52
+ .card-danger {
53
+ border-color: color-mix(in srgb, var(--color-error) 30%, transparent);
54
+ background: color-mix(in srgb, var(--color-error) 3%, transparent);
55
+ }
56
+
57
+ .card-danger:hover {
58
+ border-color: color-mix(in srgb, var(--color-error) 40%, transparent);
59
+ }
60
+
61
+ .card-warning {
62
+ border-color: color-mix(in srgb, var(--color-warning) 30%, transparent);
63
+ background: color-mix(in srgb, var(--color-warning) 3%, transparent);
64
+ }
65
+
66
+ .card-warning:hover {
67
+ border-color: color-mix(in srgb, var(--color-warning) 40%, transparent);
68
+ }
69
+
70
+ .card-success {
71
+ border-color: color-mix(in srgb, var(--color-success) 30%, transparent);
72
+ background: color-mix(in srgb, var(--color-success) 3%, transparent);
73
+ }
74
+
75
+ .card-success:hover {
76
+ border-color: color-mix(in srgb, var(--color-success) 40%, transparent);
77
+ }
78
+
79
+ .card-title {
80
+ margin: 0 0 var(--space-5) 0;
81
+ font-size: var(--text-base);
82
+ font-weight: 500;
83
+ color: var(--color-text-strong);
84
+ display: flex;
85
+ align-items: center;
86
+ gap: var(--space-2);
87
+ }
88
+
89
+ .card-danger .card-title {
90
+ color: var(--color-error);
91
+ }
92
+
93
+ .card-warning .card-title {
94
+ color: var(--color-warning);
95
+ }
96
+
97
+ .card-success .card-title {
98
+ color: var(--color-success);
99
+ }
100
+
101
+ .card-content {
102
+ /* Allow child components to handle their own spacing */
103
+ }
104
+ </style>