@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,105 @@
1
+ <!--
2
+ @component Divider
3
+
4
+ A visual separator for content sections.
5
+
6
+ @example Horizontal divider
7
+ <Divider />
8
+
9
+ @example Vertical divider
10
+ <Divider orientation="vertical" />
11
+
12
+ @example With label
13
+ <Divider>
14
+ or continue with
15
+ </Divider>
16
+ -->
17
+ <script>
18
+ let {
19
+ orientation = 'horizontal',
20
+ thickness = '1px',
21
+ spacing = '1rem',
22
+ children,
23
+ class: className = ''
24
+ } = $props();
25
+
26
+ const isHorizontal = $derived(orientation === 'horizontal');
27
+ </script>
28
+
29
+ {#if children}
30
+ <div
31
+ class="divider divider-{orientation} divider-labeled {className}"
32
+ style="--divider-spacing: {spacing};"
33
+ role="separator"
34
+ aria-orientation={orientation}
35
+ >
36
+ <span class="divider-line" style="--divider-thickness: {thickness};"></span>
37
+ <span class="divider-label">
38
+ {@render children()}
39
+ </span>
40
+ <span class="divider-line" style="--divider-thickness: {thickness};"></span>
41
+ </div>
42
+ {:else}
43
+ <div
44
+ class="divider divider-{orientation} {className}"
45
+ style="--divider-thickness: {thickness}; --divider-spacing: {spacing};"
46
+ role="separator"
47
+ aria-orientation={orientation}
48
+ ></div>
49
+ {/if}
50
+
51
+ <style>
52
+ .divider {
53
+ background: color-mix(in srgb, var(--color-text-muted) 30%, transparent);
54
+ flex-shrink: 0;
55
+ }
56
+
57
+ .divider-horizontal {
58
+ width: 100%;
59
+ height: var(--divider-thickness, 1px);
60
+ margin: var(--divider-spacing, 1rem) 0;
61
+ }
62
+
63
+ .divider-vertical {
64
+ width: var(--divider-thickness, 1px);
65
+ height: 100%;
66
+ margin: 0 var(--divider-spacing, 1rem);
67
+ }
68
+
69
+ .divider-labeled {
70
+ display: flex;
71
+ align-items: center;
72
+ gap: 1rem;
73
+ background: transparent;
74
+ }
75
+
76
+ .divider-labeled.divider-horizontal {
77
+ height: auto;
78
+ }
79
+
80
+ .divider-labeled.divider-vertical {
81
+ flex-direction: column;
82
+ width: auto;
83
+ }
84
+
85
+ .divider-line {
86
+ flex: 1;
87
+ background: color-mix(in srgb, var(--color-text-muted) 30%, transparent);
88
+ }
89
+
90
+ .divider-horizontal .divider-line {
91
+ height: var(--divider-thickness, 1px);
92
+ }
93
+
94
+ .divider-vertical .divider-line {
95
+ width: var(--divider-thickness, 1px);
96
+ }
97
+
98
+ .divider-label {
99
+ font-size: 0.75rem;
100
+ color: var(--color-text-muted);
101
+ text-transform: uppercase;
102
+ letter-spacing: 0.05em;
103
+ white-space: nowrap;
104
+ }
105
+ </style>
@@ -0,0 +1,104 @@
1
+ <!--
2
+ @component LazyImage
3
+
4
+ Intersection Observer-based lazy loading image with placeholder support.
5
+
6
+ @example Basic
7
+ <LazyImage src="/photo.jpg" alt="Description" />
8
+
9
+ @example With placeholder
10
+ <LazyImage
11
+ src="/large-photo.jpg"
12
+ alt="Photo"
13
+ placeholder="/tiny-blur.jpg"
14
+ />
15
+
16
+ @example Custom threshold
17
+ <LazyImage
18
+ src="/image.jpg"
19
+ alt="Image"
20
+ threshold={0.5}
21
+ rootMargin="100px"
22
+ />
23
+ -->
24
+ <script>
25
+ import { onMount } from 'svelte';
26
+
27
+ let {
28
+ src,
29
+ alt = '',
30
+ width,
31
+ height,
32
+ loading = 'lazy',
33
+ threshold = 0.01,
34
+ rootMargin = '50px',
35
+ placeholder = null,
36
+ class: className = '',
37
+ onload,
38
+ ...rest
39
+ } = $props();
40
+
41
+ let imgElement = $state(null);
42
+ let isLoaded = $state(false);
43
+ let isInView = $state(false);
44
+ let actualSrc = $state(placeholder || '');
45
+
46
+ onMount(() => {
47
+ if ('IntersectionObserver' in window && imgElement) {
48
+ const observer = new IntersectionObserver(
49
+ (entries) => {
50
+ entries.forEach((entry) => {
51
+ if (entry.isIntersecting && !isLoaded) {
52
+ isInView = true;
53
+ actualSrc = src;
54
+ observer.unobserve(imgElement);
55
+ }
56
+ });
57
+ },
58
+ { rootMargin, threshold }
59
+ );
60
+
61
+ observer.observe(imgElement);
62
+
63
+ return () => {
64
+ observer.disconnect();
65
+ };
66
+ } else {
67
+ actualSrc = src;
68
+ isInView = true;
69
+ }
70
+ });
71
+
72
+ function handleLoad() {
73
+ isLoaded = true;
74
+ onload?.();
75
+ }
76
+ </script>
77
+
78
+ <img
79
+ bind:this={imgElement}
80
+ src={actualSrc}
81
+ {alt}
82
+ {width}
83
+ {height}
84
+ {loading}
85
+ class="lazy-image {isLoaded ? 'lazy-loaded' : 'lazy-loading'} {className}"
86
+ onload={handleLoad}
87
+ decoding="async"
88
+ {...rest}
89
+ />
90
+
91
+ <style>
92
+ .lazy-image {
93
+ transition: opacity 0.3s ease;
94
+ }
95
+
96
+ .lazy-loading {
97
+ opacity: 0;
98
+ background: var(--color-surface);
99
+ }
100
+
101
+ .lazy-loaded {
102
+ opacity: 1;
103
+ }
104
+ </style>
@@ -0,0 +1,122 @@
1
+ <!--
2
+ @component Link
3
+
4
+ A minimal link component with optional trailing icon.
5
+ Renders as <a> with href, or <button> without.
6
+
7
+ @example Basic link
8
+ <Link href="/about">Learn more</Link>
9
+
10
+ @example Button mode
11
+ <Link onclick={handleClick}>View details</Link>
12
+
13
+ @example Without icon
14
+ <Link href="/docs" showIcon={false}>Documentation</Link>
15
+
16
+ @example Custom icon
17
+ <Link href="/external" external>
18
+ {#snippet icon()}
19
+ <ExternalIcon size={14} />
20
+ {/snippet}
21
+ External Link
22
+ </Link>
23
+ -->
24
+ <script>
25
+ let {
26
+ href = null,
27
+ external = false,
28
+ showIcon = true,
29
+ class: className = '',
30
+ onclick,
31
+ children,
32
+ icon,
33
+ ...rest
34
+ } = $props();
35
+
36
+ const target = external ? '_blank' : undefined;
37
+ const rel = external ? 'noopener noreferrer' : undefined;
38
+ </script>
39
+
40
+ {#if href}
41
+ <a {href} {target} {rel} class="link {className}" {...rest}>
42
+ {#if children}
43
+ {@render children()}
44
+ {/if}
45
+ {#if showIcon}
46
+ {#if icon}
47
+ {@render icon()}
48
+ {:else}
49
+ <svg
50
+ class="link-icon"
51
+ width="14"
52
+ height="14"
53
+ viewBox="0 0 24 24"
54
+ fill="none"
55
+ stroke="currentColor"
56
+ stroke-width="2"
57
+ stroke-linecap="round"
58
+ stroke-linejoin="round"
59
+ >
60
+ <line x1="7" y1="17" x2="17" y2="7"></line>
61
+ <polyline points="7 7 17 7 17 17"></polyline>
62
+ </svg>
63
+ {/if}
64
+ {/if}
65
+ </a>
66
+ {:else}
67
+ <button type="button" class="link {className}" {onclick} {...rest}>
68
+ {#if children}
69
+ {@render children()}
70
+ {/if}
71
+ {#if showIcon}
72
+ {#if icon}
73
+ {@render icon()}
74
+ {:else}
75
+ <svg
76
+ class="link-icon"
77
+ width="14"
78
+ height="14"
79
+ viewBox="0 0 24 24"
80
+ fill="none"
81
+ stroke="currentColor"
82
+ stroke-width="2"
83
+ stroke-linecap="round"
84
+ stroke-linejoin="round"
85
+ >
86
+ <line x1="7" y1="17" x2="17" y2="7"></line>
87
+ <polyline points="7 7 17 7 17 17"></polyline>
88
+ </svg>
89
+ {/if}
90
+ {/if}
91
+ </button>
92
+ {/if}
93
+
94
+ <style>
95
+ .link {
96
+ display: inline-flex;
97
+ align-items: center;
98
+ gap: 0.375rem;
99
+ font-size: var(--text-xs);
100
+ font-weight: 500;
101
+ color: var(--color-primary);
102
+ text-decoration: none;
103
+ background: transparent;
104
+ border: none;
105
+ padding: 0;
106
+ cursor: pointer;
107
+ transition: color 0.15s ease;
108
+ }
109
+
110
+ .link:hover {
111
+ color: color-mix(in srgb, var(--color-primary) 80%, transparent);
112
+ }
113
+
114
+ .link-icon {
115
+ flex-shrink: 0;
116
+ transition: transform 0.15s ease;
117
+ }
118
+
119
+ .link:hover .link-icon {
120
+ transform: translate(2px, -2px);
121
+ }
122
+ </style>
@@ -0,0 +1,122 @@
1
+ <!--
2
+ @component StatusBadge
3
+
4
+ A status indicator badge with semantic color variants.
5
+
6
+ @example Status variants
7
+ <StatusBadge variant="pending">Pending</StatusBadge>
8
+ <StatusBadge variant="approved">Approved</StatusBadge>
9
+ <StatusBadge variant="rejected">Rejected</StatusBadge>
10
+
11
+ @example Role variants
12
+ <StatusBadge variant="owner">Owner</StatusBadge>
13
+ <StatusBadge variant="admin">Admin</StatusBadge>
14
+ <StatusBadge variant="member">Member</StatusBadge>
15
+
16
+ @example Connection status
17
+ <StatusBadge variant="connected">Connected</StatusBadge>
18
+ <StatusBadge variant="disconnected">Disconnected</StatusBadge>
19
+ -->
20
+ <script>
21
+ let {
22
+ variant = 'default',
23
+ size = 'md',
24
+ class: className = '',
25
+ children
26
+ } = $props();
27
+ </script>
28
+
29
+ <span class="status-badge status-badge-{variant} status-badge-{size} {className}">
30
+ {#if children}
31
+ {@render children()}
32
+ {:else}
33
+ {variant}
34
+ {/if}
35
+ </span>
36
+
37
+ <style>
38
+ .status-badge {
39
+ display: inline-flex;
40
+ align-items: center;
41
+ gap: 0.25rem;
42
+ border-radius: var(--radius-md);
43
+ border: 1px solid;
44
+ font-weight: 500;
45
+ transition: background 0.2s ease;
46
+ }
47
+
48
+ /* Sizes */
49
+ .status-badge-sm {
50
+ padding: 0.125rem 0.5rem;
51
+ font-size: var(--text-xs);
52
+ }
53
+
54
+ .status-badge-md {
55
+ padding: 0.25rem 0.5rem;
56
+ font-size: var(--text-xs);
57
+ }
58
+
59
+ .status-badge-lg {
60
+ padding: 0.375rem 0.75rem;
61
+ font-size: var(--text-sm);
62
+ }
63
+
64
+ /* Default */
65
+ .status-badge-default {
66
+ background: color-mix(in srgb, var(--color-text-muted) 10%, transparent);
67
+ color: var(--color-text-muted);
68
+ border-color: color-mix(in srgb, var(--color-text-muted) 30%, transparent);
69
+ }
70
+
71
+ /* Status variants */
72
+ .status-badge-pending {
73
+ background: color-mix(in srgb, var(--color-warning) 10%, transparent);
74
+ color: var(--color-warning);
75
+ border-color: color-mix(in srgb, var(--color-warning) 30%, transparent);
76
+ }
77
+
78
+ .status-badge-approved,
79
+ .status-badge-success,
80
+ .status-badge-connected {
81
+ background: color-mix(in srgb, var(--color-success) 10%, transparent);
82
+ color: var(--color-success);
83
+ border-color: color-mix(in srgb, var(--color-success) 30%, transparent);
84
+ }
85
+
86
+ .status-badge-rejected,
87
+ .status-badge-error,
88
+ .status-badge-disconnected {
89
+ background: color-mix(in srgb, var(--color-error) 10%, transparent);
90
+ color: var(--color-error);
91
+ border-color: color-mix(in srgb, var(--color-error) 30%, transparent);
92
+ }
93
+
94
+ /* Role variants */
95
+ .status-badge-owner,
96
+ .status-badge-primary,
97
+ .status-badge-public {
98
+ background: color-mix(in srgb, var(--color-primary) 10%, transparent);
99
+ color: var(--color-primary);
100
+ border-color: color-mix(in srgb, var(--color-primary) 30%, transparent);
101
+ }
102
+
103
+ .status-badge-admin {
104
+ background: color-mix(in srgb, var(--color-secondary) 10%, transparent);
105
+ color: var(--color-secondary);
106
+ border-color: color-mix(in srgb, var(--color-secondary) 30%, transparent);
107
+ }
108
+
109
+ .status-badge-member,
110
+ .status-badge-private {
111
+ background: color-mix(in srgb, var(--color-text-muted) 10%, transparent);
112
+ color: var(--color-text-muted);
113
+ border-color: color-mix(in srgb, var(--color-text-muted) 30%, transparent);
114
+ }
115
+
116
+ /* Info variant */
117
+ .status-badge-info {
118
+ background: color-mix(in srgb, var(--color-info) 10%, transparent);
119
+ color: var(--color-info);
120
+ border-color: color-mix(in srgb, var(--color-info) 30%, transparent);
121
+ }
122
+ </style>
package/src/index.js ADDED
@@ -0,0 +1,128 @@
1
+ /**
2
+ * @miozu/jera
3
+ *
4
+ * A minimal, reactive component library for Svelte 5.
5
+ * Designed for elegance, scalability, and AI-assisted development.
6
+ *
7
+ * @author Nicholas Glazer <glazer.nicholas@gmail.com>
8
+ * @license MIT
9
+ *
10
+ * @example
11
+ * // Import components
12
+ * import { Button, Input, IconInput, SearchInput, PinInput, RangeSlider, Toast, Modal } from '@miozu/jera';
13
+ *
14
+ * // Import utilities
15
+ * import { cn, cv } from '@miozu/jera';
16
+ *
17
+ * // Import actions
18
+ * import { clickOutside, focusTrap } from '@miozu/jera/actions';
19
+ *
20
+ * // Import tokens (CSS)
21
+ * import '@miozu/jera/tokens';
22
+ */
23
+
24
+ // ============================================
25
+ // COMPONENTS - Primitives
26
+ // ============================================
27
+
28
+ export { default as Button, buttonStyles } from './components/primitives/Button.svelte';
29
+ export { default as Badge, badgeStyles } from './components/primitives/Badge.svelte';
30
+ export { default as StatusBadge } from './components/primitives/StatusBadge.svelte';
31
+ export { default as Divider } from './components/primitives/Divider.svelte';
32
+ export { default as Avatar } from './components/primitives/Avatar.svelte';
33
+ export { default as Card } from './components/primitives/Card.svelte';
34
+ export { default as Link } from './components/primitives/Link.svelte';
35
+ export { default as LazyImage } from './components/primitives/LazyImage.svelte';
36
+
37
+ // ============================================
38
+ // COMPONENTS - Forms
39
+ // ============================================
40
+
41
+ export { default as Input } from './components/forms/Input.svelte';
42
+ export { default as IconInput } from './components/forms/IconInput.svelte';
43
+ export { default as Textarea } from './components/forms/Textarea.svelte';
44
+ export { default as Select } from './components/forms/Select.svelte';
45
+ export { default as Checkbox } from './components/forms/Checkbox.svelte';
46
+ export { default as Switch } from './components/forms/Switch.svelte';
47
+ export { default as NumberInput } from './components/forms/NumberInput.svelte';
48
+ export { default as Radio } from './components/forms/Radio.svelte';
49
+ export { default as RadioGroup } from './components/forms/RadioGroup.svelte';
50
+ export { default as FileUpload } from './components/forms/FileUpload.svelte';
51
+ export { default as RangeSlider } from './components/forms/RangeSlider.svelte';
52
+ export { default as SearchInput } from './components/forms/SearchInput.svelte';
53
+ export { default as PinInput } from './components/forms/PinInput.svelte';
54
+ export { default as Dropzone } from './components/forms/Dropzone.svelte';
55
+
56
+ // ============================================
57
+ // COMPONENTS - Feedback
58
+ // ============================================
59
+
60
+ export {
61
+ default as Toast,
62
+ ToastController,
63
+ createToastContext,
64
+ getToastContext,
65
+ toastStyles,
66
+ positionStyles
67
+ } from './components/feedback/Toast.svelte';
68
+
69
+ export { default as Skeleton } from './components/feedback/Skeleton.svelte';
70
+ export { default as ProgressBar } from './components/feedback/ProgressBar.svelte';
71
+ export { default as Spinner } from './components/feedback/Spinner.svelte';
72
+ export { default as EmptyState } from './components/feedback/EmptyState.svelte';
73
+
74
+ // ============================================
75
+ // COMPONENTS - Overlays
76
+ // ============================================
77
+
78
+ export { default as Modal } from './components/overlays/Modal.svelte';
79
+ export { default as Popover } from './components/overlays/Popover.svelte';
80
+ export { default as Dropdown } from './components/overlays/Dropdown.svelte';
81
+ export { default as DropdownItem } from './components/overlays/DropdownItem.svelte';
82
+ export { default as DropdownDivider } from './components/overlays/DropdownDivider.svelte';
83
+ export { default as ConfirmDialog } from './components/overlays/ConfirmDialog.svelte';
84
+
85
+ // ============================================
86
+ // COMPONENTS - Navigation
87
+ // ============================================
88
+
89
+ export { default as Tabs } from './components/navigation/Tabs.svelte';
90
+ export { default as Accordion } from './components/navigation/Accordion.svelte';
91
+ export { default as AccordionItem } from './components/navigation/AccordionItem.svelte';
92
+
93
+ // ============================================
94
+ // UTILITIES - Class Composition
95
+ // ============================================
96
+
97
+ export { cn, rcn, cv, mergeClasses, when, match } from './utils/cn.svelte.js';
98
+
99
+ // ============================================
100
+ // UTILITIES - Reactive State
101
+ // ============================================
102
+
103
+ export {
104
+ createReactive,
105
+ createDerived,
106
+ ThemeState,
107
+ createThemeContext,
108
+ getThemeContext,
109
+ createComponentState,
110
+ createIdGenerator,
111
+ generateId
112
+ } from './utils/reactive.svelte.js';
113
+
114
+ // ============================================
115
+ // ACTIONS
116
+ // ============================================
117
+
118
+ export {
119
+ clickOutside,
120
+ focusTrap,
121
+ autoFocus,
122
+ longPress,
123
+ escapeKey,
124
+ portal,
125
+ intersect,
126
+ resize,
127
+ copy
128
+ } from './actions/index.js';