@miozu/jera 0.8.2 → 0.8.3

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.
package/package.json CHANGED
@@ -1,11 +1,8 @@
1
1
  {
2
2
  "name": "@miozu/jera",
3
- "version": "0.8.2",
3
+ "version": "0.8.3",
4
4
  "description": "Zero-dependency, AI-first component library for Svelte 5",
5
5
  "type": "module",
6
- "scripts": {
7
- "prepublishOnly": "echo 'Publishing @miozu/jera...' && test -f src/index.js"
8
- },
9
6
  "svelte": "./src/index.js",
10
7
  "exports": {
11
8
  ".": {
@@ -58,5 +55,6 @@
58
55
  "bugs": {
59
56
  "url": "https://github.com/miozu-com/jera/issues"
60
57
  },
61
- "dependencies": {}
62
- }
58
+ "dependencies": {},
59
+ "scripts": {}
60
+ }
@@ -2,28 +2,24 @@
2
2
  @component SettingCard
3
3
 
4
4
  A card container for settings sections with optional danger variant.
5
- Provides consistent layout for settings items with labels, descriptions, and actions.
5
+ Use with SettingItem for structured setting rows.
6
6
 
7
7
  @example Basic settings card
8
8
  <SettingCard title="Account Settings">
9
- <div class="setting-item">
10
- <div class="setting-content">
11
- <h4 class="setting-label">Display Name</h4>
12
- <p class="setting-description">Your public display name</p>
13
- </div>
14
- <Input value={name} />
15
- </div>
9
+ <SettingItem label="Display Name" description="Your public display name">
10
+ {#snippet action()}
11
+ <Input value={name} />
12
+ {/snippet}
13
+ </SettingItem>
16
14
  </SettingCard>
17
15
 
18
16
  @example Danger zone
19
17
  <SettingCard title="Danger Zone" variant="danger">
20
- <div class="setting-item">
21
- <div class="setting-content">
22
- <h4 class="setting-label">Delete Account</h4>
23
- <p class="setting-description">This cannot be undone</p>
24
- </div>
25
- <Button variant="danger">Delete</Button>
26
- </div>
18
+ <SettingItem label="Delete Account" description="This cannot be undone">
19
+ {#snippet action()}
20
+ <Button variant="danger">Delete</Button>
21
+ {/snippet}
22
+ </SettingItem>
27
23
  </SettingCard>
28
24
  -->
29
25
  <script>
@@ -83,71 +79,4 @@
83
79
  display: flex;
84
80
  flex-direction: column;
85
81
  }
86
-
87
- /* Utility classes for setting items - exposed globally */
88
- :global(.setting-item) {
89
- display: flex;
90
- align-items: center;
91
- justify-content: space-between;
92
- gap: var(--space-4);
93
- padding: var(--space-4) 0;
94
- border-bottom: 1px solid var(--color-base02);
95
- }
96
-
97
- :global(.setting-item:last-child) {
98
- border-bottom: none;
99
- padding-bottom: 0;
100
- }
101
-
102
- :global(.setting-item:first-child) {
103
- padding-top: 0;
104
- }
105
-
106
- :global(.setting-content) {
107
- flex: 1;
108
- min-width: 0;
109
- }
110
-
111
- :global(.setting-label) {
112
- margin: 0 0 var(--space-1);
113
- font-size: var(--text-sm);
114
- font-weight: 500;
115
- color: var(--color-base06);
116
- }
117
-
118
- :global(.setting-description) {
119
- margin: 0;
120
- font-size: var(--text-xs);
121
- color: var(--color-base04);
122
- line-height: 1.5;
123
- }
124
-
125
- :global(.setting-item-icon) {
126
- display: flex;
127
- align-items: center;
128
- gap: var(--space-3);
129
- color: color-mix(in srgb, var(--color-base04) 80%, transparent);
130
- }
131
-
132
- :global(.setting-item-with-icon) {
133
- display: flex;
134
- align-items: flex-start;
135
- gap: var(--space-3);
136
- padding: var(--space-4) 0;
137
- border-bottom: 1px solid var(--color-base02);
138
- }
139
-
140
- :global(.setting-item-with-icon:last-child) {
141
- border-bottom: none;
142
- padding-bottom: 0;
143
- }
144
-
145
- /* Responsive */
146
- @media (max-width: 640px) {
147
- :global(.setting-item) {
148
- flex-direction: column;
149
- align-items: flex-start;
150
- gap: var(--space-3);
151
- }
152
- }
153
82
  </style>
@@ -0,0 +1,116 @@
1
+ <!--
2
+ @component SettingItem
3
+
4
+ A structured setting row for use inside SettingCard.
5
+ Replaces magic class names with a composable component API.
6
+
7
+ @example Basic setting
8
+ <SettingItem label="Display Name" description="Your public display name">
9
+ {#snippet action()}
10
+ <Input value={name} />
11
+ {/snippet}
12
+ </SettingItem>
13
+
14
+ @example With icon
15
+ <SettingItem label="Active Sessions" description="Manage your devices">
16
+ {#snippet leading()}
17
+ <Monitor size={16} />
18
+ {/snippet}
19
+ {#snippet action()}
20
+ <Button size="sm">Manage</Button>
21
+ {/snippet}
22
+ </SettingItem>
23
+ -->
24
+ <script>
25
+ let {
26
+ label = '',
27
+ description = '',
28
+ leading,
29
+ action,
30
+ class: className = ''
31
+ } = $props();
32
+ </script>
33
+
34
+ <div class="setting-item {className}" class:has-leading={leading}>
35
+ {#if leading}
36
+ <div class="setting-leading">
37
+ {@render leading()}
38
+ </div>
39
+ {/if}
40
+ <div class="setting-content">
41
+ {#if label}
42
+ <h4 class="setting-label">{label}</h4>
43
+ {/if}
44
+ {#if description}
45
+ <p class="setting-description">{description}</p>
46
+ {/if}
47
+ </div>
48
+ {#if action}
49
+ <div class="setting-action">
50
+ {@render action()}
51
+ </div>
52
+ {/if}
53
+ </div>
54
+
55
+ <style>
56
+ .setting-item {
57
+ display: flex;
58
+ align-items: center;
59
+ justify-content: space-between;
60
+ gap: var(--space-4);
61
+ padding: var(--space-4) 0;
62
+ border-bottom: 1px solid var(--color-base02);
63
+ }
64
+
65
+ .setting-item:last-child {
66
+ border-bottom: none;
67
+ padding-bottom: 0;
68
+ }
69
+
70
+ .setting-item:first-child {
71
+ padding-top: 0;
72
+ }
73
+
74
+ .has-leading {
75
+ justify-content: flex-start;
76
+ }
77
+
78
+ .setting-leading {
79
+ display: flex;
80
+ align-items: center;
81
+ color: color-mix(in srgb, var(--color-base04) 80%, transparent);
82
+ flex-shrink: 0;
83
+ }
84
+
85
+ .setting-content {
86
+ flex: 1;
87
+ min-width: 0;
88
+ }
89
+
90
+ .setting-label {
91
+ margin: 0 0 var(--space-1);
92
+ font-size: var(--text-sm);
93
+ font-weight: 500;
94
+ color: var(--color-base06);
95
+ }
96
+
97
+ .setting-description {
98
+ margin: 0;
99
+ font-size: var(--text-xs);
100
+ color: var(--color-base04);
101
+ line-height: 1.5;
102
+ }
103
+
104
+ .setting-action {
105
+ flex-shrink: 0;
106
+ }
107
+
108
+ /* Responsive */
109
+ @media (max-width: 640px) {
110
+ .setting-item:not(.has-leading) {
111
+ flex-direction: column;
112
+ align-items: flex-start;
113
+ gap: var(--space-3);
114
+ }
115
+ }
116
+ </style>
@@ -45,10 +45,34 @@
45
45
  active = tab.id;
46
46
  onchange?.(tab);
47
47
  }
48
+
49
+ function handleKeydown(e, index) {
50
+ let nextIndex = index;
51
+
52
+ if (e.key === 'ArrowRight') {
53
+ nextIndex = (index + 1) % tabs.length;
54
+ } else if (e.key === 'ArrowLeft') {
55
+ nextIndex = (index - 1 + tabs.length) % tabs.length;
56
+ } else if (e.key === 'Home') {
57
+ nextIndex = 0;
58
+ } else if (e.key === 'End') {
59
+ nextIndex = tabs.length - 1;
60
+ } else {
61
+ return;
62
+ }
63
+
64
+ e.preventDefault();
65
+ const nextTab = tabs[nextIndex];
66
+ if (!nextTab.disabled) {
67
+ handleTabClick(nextTab);
68
+ const buttons = e.currentTarget.parentElement.querySelectorAll('[role="tab"]');
69
+ buttons[nextIndex]?.focus();
70
+ }
71
+ }
48
72
  </script>
49
73
 
50
74
  <nav class="tab-nav tab-nav-{variant} tab-nav-{size} {className}" role="tablist">
51
- {#each tabs as tab}
75
+ {#each tabs as tab, index}
52
76
  <button
53
77
  type="button"
54
78
  class="tab-item"
@@ -57,7 +81,9 @@
57
81
  role="tab"
58
82
  aria-selected={active === tab.id}
59
83
  aria-disabled={tab.disabled}
84
+ tabindex={active === tab.id ? 0 : -1}
60
85
  onclick={() => handleTabClick(tab)}
86
+ onkeydown={(e) => handleKeydown(e, index)}
61
87
  >
62
88
  {#if tab.icon}
63
89
  <span class="tab-icon">
@@ -1,7 +1,7 @@
1
1
  <!--
2
2
  @component Tabs
3
3
 
4
- Tabbed navigation component.
4
+ Tabbed navigation component with keyboard navigation.
5
5
 
6
6
  @example Basic usage
7
7
  <Tabs
@@ -13,7 +13,7 @@
13
13
  bind:active={activeTab}
14
14
  />
15
15
 
16
- @example With icons
16
+ @example With icons (component reference)
17
17
  <Tabs
18
18
  tabs={[
19
19
  { id: 'home', label: 'Home', icon: HomeIcon },
@@ -64,7 +64,6 @@
64
64
  const nextTab = tabs[nextIndex];
65
65
  if (!nextTab.disabled) {
66
66
  selectTab(nextTab);
67
- // Focus the next tab button
68
67
  const buttons = e.currentTarget.parentElement.querySelectorAll('[role="tab"]');
69
68
  buttons[nextIndex]?.focus();
70
69
  }
@@ -90,8 +89,9 @@
90
89
  onkeydown={(e) => handleKeydown(e, tab, index)}
91
90
  >
92
91
  {#if tab.icon}
92
+ {@const Icon = tab.icon}
93
93
  <span class="tab-icon">
94
- <svelte:component this={tab.icon} size={16} />
94
+ <Icon size={16} />
95
95
  </span>
96
96
  {/if}
97
97
  {#if tab.label}
@@ -107,10 +107,10 @@
107
107
  <style>
108
108
  .tabs {
109
109
  display: inline-flex;
110
- gap: 0.25rem;
110
+ gap: var(--space-1);
111
111
  background: var(--color-base01);
112
- border-radius: 0.5rem;
113
- padding: 0.25rem;
112
+ border-radius: var(--radius-lg);
113
+ padding: var(--space-1);
114
114
  }
115
115
 
116
116
  .tabs-full-width {
@@ -126,12 +126,12 @@
126
126
  .tab {
127
127
  display: inline-flex;
128
128
  align-items: center;
129
- gap: 0.5rem;
130
- padding: 0.5rem 1rem;
129
+ gap: var(--space-2);
130
+ padding: var(--space-2) var(--space-4);
131
131
  background: transparent;
132
132
  border: none;
133
- border-radius: 0.375rem;
134
- font-size: 0.875rem;
133
+ border-radius: var(--radius-md);
134
+ font-size: var(--text-sm);
135
135
  font-weight: 500;
136
136
  color: var(--color-base05);
137
137
  cursor: pointer;
@@ -141,13 +141,13 @@
141
141
 
142
142
  /* Size variants */
143
143
  .tabs-sm .tab {
144
- padding: 0.375rem 0.75rem;
145
- font-size: 0.75rem;
144
+ padding: var(--space-1) var(--space-3);
145
+ font-size: var(--text-xs);
146
146
  }
147
147
 
148
148
  .tabs-lg .tab {
149
- padding: 0.625rem 1.25rem;
150
- font-size: 1rem;
149
+ padding: var(--space-3) var(--space-5);
150
+ font-size: var(--text-base);
151
151
  }
152
152
 
153
153
  .tab:hover:not(.tab-disabled) {
@@ -180,12 +180,12 @@
180
180
  display: inline-flex;
181
181
  align-items: center;
182
182
  justify-content: center;
183
- min-width: 1.25rem;
184
- height: 1.25rem;
185
- padding: 0 0.375rem;
183
+ min-width: var(--space-5);
184
+ height: var(--space-5);
185
+ padding: 0 var(--space-1);
186
186
  background: var(--color-base03);
187
187
  border-radius: 9999px;
188
- font-size: 0.75rem;
188
+ font-size: var(--text-xs);
189
189
  font-weight: 600;
190
190
  }
191
191
 
@@ -224,7 +224,7 @@
224
224
  .tabs-pills {
225
225
  background: transparent;
226
226
  padding: 0;
227
- gap: 0.5rem;
227
+ gap: var(--space-2);
228
228
  }
229
229
 
230
230
  .tabs-pills .tab {
package/src/index.js CHANGED
@@ -141,6 +141,7 @@ export { default as DropdownContainer } from './components/navigation/DropdownCo
141
141
 
142
142
  export { default as PageHeader } from './components/layout/PageHeader.svelte';
143
143
  export { default as SettingCard } from './components/layout/SettingCard.svelte';
144
+ export { default as SettingItem } from './components/layout/SettingItem.svelte';
144
145
 
145
146
  // ============================================
146
147
  // COMPONENTS - Documentation