@happyvertical/smrt-products 0.34.5 → 0.34.7
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/dist/lib/chunks/{ProductForm-p4-xbubZ.js → ProductForm-BLRguem5.js} +268 -148
- package/dist/lib/chunks/ProductForm-BLRguem5.js.map +1 -0
- package/dist/lib/components.js +1 -1
- package/dist/lib/index.js +1 -1
- package/dist/lib/lib/components/ProductCard.svelte +6 -35
- package/dist/lib/lib/components/ProductCard.svelte.d.ts.map +1 -1
- package/dist/lib/lib/components/ProductForm.svelte +96 -148
- package/dist/lib/lib/components/ProductForm.svelte.d.ts.map +1 -1
- package/dist/lib/lib/components/auto-generated/AutoForm.svelte +30 -56
- package/dist/lib/lib/components/auto-generated/AutoForm.svelte.d.ts.map +1 -1
- package/dist/lib/lib/components/auto-generated/FieldRenderer.svelte +8 -37
- package/dist/lib/lib/components/auto-generated/FieldRenderer.svelte.d.ts.map +1 -1
- package/dist/lib/lib/features/ProductCatalog.svelte +27 -45
- package/dist/lib/lib/features/ProductCatalog.svelte.d.ts.map +1 -1
- package/dist/lib/manifest.json +2 -2
- package/dist/lib/smrt-knowledge.json +4 -4
- package/dist/lib/smrt-products.css +9 -91
- package/package.json +8 -7
- package/dist/lib/chunks/ProductForm-p4-xbubZ.js.map +0 -1
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { Form, Input, Textarea } from '@happyvertical/smrt-ui/forms';
|
|
2
3
|
import { useI18n } from '@happyvertical/smrt-ui/i18n';
|
|
4
|
+
import { Button } from '@happyvertical/smrt-ui/ui';
|
|
3
5
|
import { M } from '../i18n.js';
|
|
4
6
|
import type { ProductData } from '../types';
|
|
5
7
|
|
|
@@ -65,128 +67,126 @@ function handleSubmit(event: Event) {
|
|
|
65
67
|
}
|
|
66
68
|
</script>
|
|
67
69
|
|
|
68
|
-
<
|
|
69
|
-
<
|
|
70
|
-
<label for="name">{t(M['products.product_form.name_label'])}</label>
|
|
71
|
-
<input
|
|
72
|
-
id="name"
|
|
73
|
-
type="text"
|
|
74
|
-
bind:value={formData.name}
|
|
75
|
-
disabled={loading}
|
|
76
|
-
class="form-input"
|
|
77
|
-
class:error={errors.name}
|
|
78
|
-
placeholder={t(M['products.product_form.name_placeholder'])}
|
|
79
|
-
/>
|
|
80
|
-
{#if errors.name}
|
|
81
|
-
<span class="error-message">{errors.name}</span>
|
|
82
|
-
{/if}
|
|
83
|
-
</div>
|
|
84
|
-
|
|
85
|
-
<div class="form-group">
|
|
86
|
-
<label for="description">Description</label>
|
|
87
|
-
<textarea
|
|
88
|
-
id="description"
|
|
89
|
-
bind:value={formData.description}
|
|
90
|
-
disabled={loading}
|
|
91
|
-
class="form-textarea"
|
|
92
|
-
placeholder={t(M['products.product_form.description_placeholder'])}
|
|
93
|
-
rows="3"
|
|
94
|
-
></textarea>
|
|
95
|
-
</div>
|
|
96
|
-
|
|
97
|
-
<div class="form-row">
|
|
70
|
+
<div class="product-form-shell">
|
|
71
|
+
<Form onsubmit={handleSubmit} class="product-form">
|
|
98
72
|
<div class="form-group">
|
|
99
|
-
<label for="
|
|
100
|
-
<
|
|
101
|
-
id="
|
|
102
|
-
type="
|
|
103
|
-
|
|
104
|
-
min="0"
|
|
105
|
-
bind:value={formData.price}
|
|
73
|
+
<label for="name">{t(M['products.product_form.name_label'])}</label>
|
|
74
|
+
<Input
|
|
75
|
+
id="name"
|
|
76
|
+
type="text"
|
|
77
|
+
bind:value={formData.name}
|
|
106
78
|
disabled={loading}
|
|
107
|
-
class=
|
|
108
|
-
|
|
109
|
-
placeholder="0.00"
|
|
79
|
+
class={errors.name ? 'error' : ''}
|
|
80
|
+
placeholder={t(M['products.product_form.name_placeholder'])}
|
|
110
81
|
/>
|
|
111
|
-
{#if errors.
|
|
112
|
-
<span class="error-message">{errors.
|
|
82
|
+
{#if errors.name}
|
|
83
|
+
<span class="error-message">{errors.name}</span>
|
|
113
84
|
{/if}
|
|
114
85
|
</div>
|
|
115
86
|
|
|
116
87
|
<div class="form-group">
|
|
117
|
-
<label for="
|
|
118
|
-
<
|
|
119
|
-
id="
|
|
120
|
-
|
|
121
|
-
bind:value={formData.category}
|
|
88
|
+
<label for="description">Description</label>
|
|
89
|
+
<Textarea
|
|
90
|
+
id="description"
|
|
91
|
+
bind:value={formData.description}
|
|
122
92
|
disabled={loading}
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
93
|
+
placeholder={t(M['products.product_form.description_placeholder'])}
|
|
94
|
+
rows={3}
|
|
95
|
+
></Textarea>
|
|
126
96
|
</div>
|
|
127
|
-
</div>
|
|
128
97
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
98
|
+
<div class="form-row">
|
|
99
|
+
<div class="form-group">
|
|
100
|
+
<label for="price">Price *</label>
|
|
101
|
+
<Input
|
|
102
|
+
id="price"
|
|
103
|
+
type="number"
|
|
104
|
+
step="0.01"
|
|
105
|
+
min="0"
|
|
106
|
+
bind:value={formData.price}
|
|
107
|
+
disabled={loading}
|
|
108
|
+
class={errors.price ? 'error' : ''}
|
|
109
|
+
placeholder="0.00"
|
|
110
|
+
/>
|
|
111
|
+
{#if errors.price}
|
|
112
|
+
<span class="error-message">{errors.price}</span>
|
|
113
|
+
{/if}
|
|
114
|
+
</div>
|
|
115
|
+
|
|
116
|
+
<div class="form-group">
|
|
117
|
+
<label for="category">Category</label>
|
|
118
|
+
<Input
|
|
119
|
+
id="category"
|
|
120
|
+
type="text"
|
|
121
|
+
bind:value={formData.category}
|
|
122
|
+
disabled={loading}
|
|
123
|
+
placeholder={t(M['products.product_form.category_placeholder'])}
|
|
124
|
+
/>
|
|
125
|
+
</div>
|
|
126
|
+
</div>
|
|
141
127
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
<
|
|
145
|
-
|
|
146
|
-
|
|
128
|
+
<div class="form-group">
|
|
129
|
+
<label for="tags">Tags</label>
|
|
130
|
+
<Input
|
|
131
|
+
id="tags"
|
|
132
|
+
type="text"
|
|
133
|
+
bind:value={formData.tags}
|
|
147
134
|
disabled={loading}
|
|
148
|
-
|
|
135
|
+
placeholder={t(M['products.product_form.tags_placeholder'])}
|
|
149
136
|
/>
|
|
150
|
-
{t(M['products.product_form.
|
|
151
|
-
</
|
|
152
|
-
|
|
137
|
+
<small class="form-hint">{t(M['products.product_form.tags_hint'])}</small>
|
|
138
|
+
</div>
|
|
139
|
+
|
|
140
|
+
<div class="form-group">
|
|
141
|
+
<label class="checkbox-label">
|
|
142
|
+
<!-- raw-primitive-allow: native checkbox; no Provider-free checkbox primitive (Toggle is a switch with different semantics, CheckboxInput requires a Provider) -->
|
|
143
|
+
<input
|
|
144
|
+
type="checkbox"
|
|
145
|
+
bind:checked={formData.inStock}
|
|
146
|
+
disabled={loading}
|
|
147
|
+
class="form-checkbox"
|
|
148
|
+
/>
|
|
149
|
+
{t(M['products.product_form.in_stock_label'])}
|
|
150
|
+
</label>
|
|
151
|
+
</div>
|
|
153
152
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
{/if}
|
|
160
|
-
|
|
161
|
-
<button type="submit" disabled={loading} class="submit-btn">
|
|
162
|
-
{#if loading}
|
|
163
|
-
Saving...
|
|
164
|
-
{:else}
|
|
165
|
-
{product.id ? 'Update Product' : 'Create Product'}
|
|
153
|
+
<div class="form-actions">
|
|
154
|
+
{#if onCancel}
|
|
155
|
+
<Button type="button" variant="secondary" onclick={onCancel} disabled={loading}>
|
|
156
|
+
Cancel
|
|
157
|
+
</Button>
|
|
166
158
|
{/if}
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
159
|
+
|
|
160
|
+
<Button type="submit" variant="primary" disabled={loading}>
|
|
161
|
+
{#if loading}
|
|
162
|
+
Saving...
|
|
163
|
+
{:else}
|
|
164
|
+
{product.id ? 'Update Product' : 'Create Product'}
|
|
165
|
+
{/if}
|
|
166
|
+
</Button>
|
|
167
|
+
</div>
|
|
168
|
+
</Form>
|
|
169
|
+
</div>
|
|
170
170
|
|
|
171
171
|
<style>
|
|
172
|
-
.product-form {
|
|
172
|
+
.product-form-shell :global(.product-form) {
|
|
173
173
|
max-width: 500px;
|
|
174
174
|
padding: 1.5rem;
|
|
175
175
|
background: var(--smrt-color-surface, #fff);
|
|
176
176
|
border-radius: var(--smrt-radius-md, 8px);
|
|
177
177
|
border: 1px solid var(--smrt-color-outline-variant, #e2e8f0);
|
|
178
178
|
}
|
|
179
|
-
|
|
179
|
+
|
|
180
180
|
.form-group {
|
|
181
181
|
margin-bottom: 1rem;
|
|
182
182
|
}
|
|
183
|
-
|
|
183
|
+
|
|
184
184
|
.form-row {
|
|
185
185
|
display: grid;
|
|
186
186
|
grid-template-columns: 1fr 1fr;
|
|
187
187
|
gap: 1rem;
|
|
188
188
|
}
|
|
189
|
-
|
|
189
|
+
|
|
190
190
|
label {
|
|
191
191
|
display: block;
|
|
192
192
|
margin-bottom: 0.25rem;
|
|
@@ -195,30 +195,13 @@ function handleSubmit(event: Event) {
|
|
|
195
195
|
font-size: var(--smrt-typography-label-large-size, 0.875rem);
|
|
196
196
|
}
|
|
197
197
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
border-radius: var(--smrt-radius-sm, 4px);
|
|
203
|
-
font-size: var(--smrt-typography-body-medium-size, 0.875rem);
|
|
204
|
-
transition: border-color 0.2s;
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
.form-input:focus, .form-textarea:focus {
|
|
208
|
-
outline: none;
|
|
209
|
-
border-color: var(--smrt-color-primary, #3b82f6);
|
|
210
|
-
box-shadow: 0 0 0 3px color-mix(in srgb, var(--smrt-color-primary, #3b82f6) 10%, transparent);
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
.form-input.error {
|
|
198
|
+
/* Error border on the migrated <Input>. The primitive renders the inner
|
|
199
|
+
<input class="input error"> inside its own component, so the scoped class
|
|
200
|
+
can't reach it without :global (#1589). */
|
|
201
|
+
.product-form-shell :global(.input.error) {
|
|
214
202
|
border-color: var(--smrt-color-error, #dc2626);
|
|
215
203
|
}
|
|
216
|
-
|
|
217
|
-
.form-textarea {
|
|
218
|
-
resize: vertical;
|
|
219
|
-
min-height: 80px;
|
|
220
|
-
}
|
|
221
|
-
|
|
204
|
+
|
|
222
205
|
.checkbox-label {
|
|
223
206
|
display: flex;
|
|
224
207
|
align-items: center;
|
|
@@ -251,39 +234,4 @@ function handleSubmit(event: Event) {
|
|
|
251
234
|
padding-top: 1rem;
|
|
252
235
|
border-top: 1px solid var(--smrt-color-outline-variant, #f3f4f6);
|
|
253
236
|
}
|
|
254
|
-
|
|
255
|
-
.cancel-btn, .submit-btn {
|
|
256
|
-
padding: 0.5rem 1rem;
|
|
257
|
-
border-radius: var(--smrt-radius-sm, 4px);
|
|
258
|
-
font-size: var(--smrt-typography-label-large-size, 0.875rem);
|
|
259
|
-
font-weight: var(--smrt-typography-weight-medium, 500);
|
|
260
|
-
cursor: pointer;
|
|
261
|
-
border: 1px solid;
|
|
262
|
-
transition: all 0.2s;
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
.cancel-btn {
|
|
266
|
-
background: var(--smrt-color-surface, #fff);
|
|
267
|
-
border-color: var(--smrt-color-outline-variant, #d1d5db);
|
|
268
|
-
color: var(--smrt-color-on-surface, #374151);
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
.cancel-btn:hover:not(:disabled) {
|
|
272
|
-
background: var(--smrt-color-surface-container-low, #f9fafb);
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
.submit-btn {
|
|
276
|
-
background: var(--smrt-color-primary, #3b82f6);
|
|
277
|
-
border-color: var(--smrt-color-primary, #3b82f6);
|
|
278
|
-
color: var(--smrt-color-on-primary, white);
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
.submit-btn:hover:not(:disabled) {
|
|
282
|
-
background: var(--smrt-color-primary, #2563eb);
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
.submit-btn:disabled, .cancel-btn:disabled {
|
|
286
|
-
opacity: 0.5;
|
|
287
|
-
cursor: not-allowed;
|
|
288
|
-
}
|
|
289
237
|
</style>
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ProductForm.svelte.d.ts","sourceRoot":"","sources":["../../../../src/lib/components/ProductForm.svelte"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ProductForm.svelte.d.ts","sourceRoot":"","sources":["../../../../src/lib/components/ProductForm.svelte"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAG5C,UAAU,KAAK;IACb,OAAO,CAAC,EAAE,OAAO,CAAC,WAAW,CAAC,CAAC;IAC/B,QAAQ,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,WAAW,CAAC,KAAK,IAAI,CAAC;IAClD,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IACtB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AA+HD,QAAA,MAAM,WAAW,2CAAwC,CAAC;AAC1D,KAAK,WAAW,GAAG,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC;AAClD,eAAe,WAAW,CAAC"}
|
|
@@ -4,7 +4,9 @@
|
|
|
4
4
|
* Demonstrates "Define Once, Consume Everywhere" - form is generated from Product class definition
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
+
import { Form } from '@happyvertical/smrt-ui/forms';
|
|
7
8
|
import { useI18n } from '@happyvertical/smrt-ui/i18n';
|
|
9
|
+
import { Button } from '@happyvertical/smrt-ui/ui';
|
|
8
10
|
import { M } from '../../i18n.js';
|
|
9
11
|
import type { ProductData } from '../../types';
|
|
10
12
|
import FieldRenderer from './FieldRenderer.svelte';
|
|
@@ -112,31 +114,33 @@ function _getFieldType(
|
|
|
112
114
|
</div>
|
|
113
115
|
</header>
|
|
114
116
|
|
|
115
|
-
<
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
<
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
117
|
+
<div class="form-content-shell">
|
|
118
|
+
<Form class="form-content" onsubmit={handleSubmit}>
|
|
119
|
+
{#each fieldSchema as field}
|
|
120
|
+
<FieldRenderer
|
|
121
|
+
fieldName={field.name}
|
|
122
|
+
fieldType={field.type}
|
|
123
|
+
value={formData[field.name]}
|
|
124
|
+
label={field.label}
|
|
125
|
+
placeholder={field.placeholder}
|
|
126
|
+
required={field.required || false}
|
|
127
|
+
{readonly}
|
|
128
|
+
onUpdate={(value) => updateField(field.name, value)}
|
|
129
|
+
/>
|
|
130
|
+
{/each}
|
|
131
|
+
|
|
132
|
+
{#if !readonly}
|
|
133
|
+
<div class="form-actions">
|
|
134
|
+
<Button type="submit" variant="primary">
|
|
135
|
+
{submitLabel}
|
|
136
|
+
</Button>
|
|
137
|
+
<Button type="button" variant="secondary" onclick={() => formData = {}}>
|
|
138
|
+
Reset
|
|
139
|
+
</Button>
|
|
140
|
+
</div>
|
|
141
|
+
{/if}
|
|
142
|
+
</Form>
|
|
143
|
+
</div>
|
|
140
144
|
|
|
141
145
|
<!-- Demo: Show current form state -->
|
|
142
146
|
<details class="form-debug">
|
|
@@ -173,7 +177,7 @@ function _getFieldType(
|
|
|
173
177
|
font-style: italic;
|
|
174
178
|
}
|
|
175
179
|
|
|
176
|
-
.form-content {
|
|
180
|
+
.form-content-shell :global(.form-content) {
|
|
177
181
|
display: flex;
|
|
178
182
|
flex-direction: column;
|
|
179
183
|
gap: 1rem;
|
|
@@ -187,36 +191,6 @@ function _getFieldType(
|
|
|
187
191
|
border-top: 1px solid var(--smrt-color-outline-variant, #e5e7eb);
|
|
188
192
|
}
|
|
189
193
|
|
|
190
|
-
.submit-btn {
|
|
191
|
-
background: var(--smrt-color-primary, #3b82f6);
|
|
192
|
-
color: var(--smrt-color-on-primary, white);
|
|
193
|
-
border: none;
|
|
194
|
-
padding: 0.75rem 1.5rem;
|
|
195
|
-
border-radius: var(--smrt-radius-sm, 0.375rem);
|
|
196
|
-
font-weight: var(--smrt-typography-weight-medium, 500);
|
|
197
|
-
cursor: pointer;
|
|
198
|
-
transition: background-color 0.2s;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
.submit-btn:hover {
|
|
202
|
-
background: var(--smrt-color-primary, #2563eb);
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
.reset-btn {
|
|
206
|
-
background: var(--smrt-color-surface-container, #f3f4f6);
|
|
207
|
-
color: var(--smrt-color-on-surface, #374151);
|
|
208
|
-
border: 1px solid var(--smrt-color-outline-variant, #d1d5db);
|
|
209
|
-
padding: 0.75rem 1.5rem;
|
|
210
|
-
border-radius: var(--smrt-radius-sm, 0.375rem);
|
|
211
|
-
font-weight: var(--smrt-typography-weight-medium, 500);
|
|
212
|
-
cursor: pointer;
|
|
213
|
-
transition: background-color 0.2s;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
.reset-btn:hover {
|
|
217
|
-
background: var(--smrt-color-surface-container-high, #e5e7eb);
|
|
218
|
-
}
|
|
219
|
-
|
|
220
194
|
.form-debug {
|
|
221
195
|
margin-top: 2rem;
|
|
222
196
|
padding: 1rem;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AutoForm.svelte.d.ts","sourceRoot":"","sources":["../../../../../src/lib/components/auto-generated/AutoForm.svelte.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"AutoForm.svelte.d.ts","sourceRoot":"","sources":["../../../../../src/lib/components/auto-generated/AutoForm.svelte.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAI/C,UAAU,KAAK;IACb,IAAI,CAAC,EAAE,WAAW,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,WAAW,KAAK,IAAI,CAAC;IACvC,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,WAAW,KAAK,IAAI,CAAC;CACxC;AAuID,QAAA,MAAM,QAAQ,2CAAwC,CAAC;AACvD,KAAK,QAAQ,GAAG,UAAU,CAAC,OAAO,QAAQ,CAAC,CAAC;AAC5C,eAAe,QAAQ,CAAC"}
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* This demonstrates the "Define Once, Consume Everywhere" vision
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
+
import { Input, Textarea } from '@happyvertical/smrt-ui/forms';
|
|
7
8
|
import { useI18n } from '@happyvertical/smrt-ui/i18n';
|
|
8
9
|
import { M } from '../../i18n.js';
|
|
9
10
|
|
|
@@ -92,10 +93,9 @@ function handleObjectInput(event: Event) {
|
|
|
92
93
|
</label>
|
|
93
94
|
|
|
94
95
|
{#if fieldType === 'string'}
|
|
95
|
-
<
|
|
96
|
+
<Input
|
|
96
97
|
id={fieldId}
|
|
97
98
|
type="text"
|
|
98
|
-
class="field-input"
|
|
99
99
|
{value}
|
|
100
100
|
{placeholder}
|
|
101
101
|
{readonly}
|
|
@@ -103,10 +103,9 @@ function handleObjectInput(event: Event) {
|
|
|
103
103
|
oninput={handleStringInput}
|
|
104
104
|
/>
|
|
105
105
|
{:else if fieldType === 'number'}
|
|
106
|
-
<
|
|
106
|
+
<Input
|
|
107
107
|
id={fieldId}
|
|
108
108
|
type="number"
|
|
109
|
-
class="field-input"
|
|
110
109
|
value={value || 0}
|
|
111
110
|
{placeholder}
|
|
112
111
|
{readonly}
|
|
@@ -114,6 +113,7 @@ function handleObjectInput(event: Event) {
|
|
|
114
113
|
oninput={handleNumberInput}
|
|
115
114
|
/>
|
|
116
115
|
{:else if fieldType === 'boolean'}
|
|
116
|
+
<!-- raw-primitive-allow: native checkbox; no Provider-free checkbox primitive (Toggle is a switch with different semantics, CheckboxInput requires a Provider) -->
|
|
117
117
|
<input
|
|
118
118
|
id={fieldId}
|
|
119
119
|
type="checkbox"
|
|
@@ -123,26 +123,24 @@ function handleObjectInput(event: Event) {
|
|
|
123
123
|
onchange={handleBooleanInput}
|
|
124
124
|
/>
|
|
125
125
|
{:else if fieldType === 'array'}
|
|
126
|
-
<
|
|
126
|
+
<Textarea
|
|
127
127
|
id={fieldId}
|
|
128
|
-
class="field-textarea"
|
|
129
128
|
value={Array.isArray(value) ? value.join(', ') : ''}
|
|
130
129
|
placeholder={placeholder || 'Enter comma-separated values'}
|
|
131
130
|
{readonly}
|
|
132
131
|
{required}
|
|
133
132
|
oninput={handleArrayInput}
|
|
134
|
-
|
|
133
|
+
></Textarea>
|
|
135
134
|
<div class="field-hint">{t(M['products.field_renderer.array_hint'])}</div>
|
|
136
135
|
{:else if fieldType === 'object'}
|
|
137
|
-
<
|
|
136
|
+
<Textarea
|
|
138
137
|
id={fieldId}
|
|
139
|
-
class="field-textarea"
|
|
140
138
|
value={typeof value === 'object' ? JSON.stringify(value, null, 2) : '{}'}
|
|
141
139
|
placeholder={placeholder || 'Enter JSON object'}
|
|
142
140
|
{readonly}
|
|
143
141
|
{required}
|
|
144
142
|
oninput={handleObjectInput}
|
|
145
|
-
|
|
143
|
+
></Textarea>
|
|
146
144
|
<div class="field-hint">{t(M['products.field_renderer.object_hint'])}</div>
|
|
147
145
|
{/if}
|
|
148
146
|
</div>
|
|
@@ -165,27 +163,6 @@ function handleObjectInput(event: Event) {
|
|
|
165
163
|
color: var(--smrt-color-error, #dc2626);
|
|
166
164
|
}
|
|
167
165
|
|
|
168
|
-
.field-input,
|
|
169
|
-
.field-textarea {
|
|
170
|
-
padding: 0.5rem 0.75rem;
|
|
171
|
-
border: 1px solid var(--smrt-color-outline-variant, #d1d5db);
|
|
172
|
-
border-radius: var(--smrt-radius-sm, 0.375rem);
|
|
173
|
-
font-size: var(--smrt-typography-body-medium-size, 0.875rem);
|
|
174
|
-
transition: border-color 0.2s, box-shadow 0.2s;
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
.field-input:focus,
|
|
178
|
-
.field-textarea:focus {
|
|
179
|
-
outline: none;
|
|
180
|
-
border-color: var(--smrt-color-primary, #3b82f6);
|
|
181
|
-
box-shadow: 0 0 0 3px color-mix(in srgb, var(--smrt-color-primary, #3b82f6) 10%, transparent);
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
.field-textarea {
|
|
185
|
-
min-height: 4rem;
|
|
186
|
-
resize: vertical;
|
|
187
|
-
}
|
|
188
|
-
|
|
189
166
|
.field-checkbox {
|
|
190
167
|
width: 1rem;
|
|
191
168
|
height: 1rem;
|
|
@@ -196,10 +173,4 @@ function handleObjectInput(event: Event) {
|
|
|
196
173
|
color: var(--smrt-color-on-surface-variant, #6b7280);
|
|
197
174
|
font-style: italic;
|
|
198
175
|
}
|
|
199
|
-
|
|
200
|
-
.field-input:read-only,
|
|
201
|
-
.field-textarea:read-only {
|
|
202
|
-
background-color: var(--smrt-color-surface-container-low, #f9fafb);
|
|
203
|
-
cursor: not-allowed;
|
|
204
|
-
}
|
|
205
176
|
</style>
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FieldRenderer.svelte.d.ts","sourceRoot":"","sources":["../../../../../src/lib/components/auto-generated/FieldRenderer.svelte.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"FieldRenderer.svelte.d.ts","sourceRoot":"","sources":["../../../../../src/lib/components/auto-generated/FieldRenderer.svelte.ts"],"names":[],"mappings":"AAYA,UAAU,KAAK;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,QAAQ,GAAG,QAAQ,GAAG,SAAS,GAAG,OAAO,GAAG,QAAQ,CAAC;IAChE,KAAK,EAAE,GAAG,CAAC;IACX,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,KAAK,IAAI,CAAC;CACjC;AAqGD,QAAA,MAAM,aAAa,2CAAwC,CAAC;AAC5D,KAAK,aAAa,GAAG,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC;AACtD,eAAe,aAAa,CAAC"}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { Input, Select } from '@happyvertical/smrt-ui/forms';
|
|
2
3
|
import { useI18n } from '@happyvertical/smrt-ui/i18n';
|
|
4
|
+
import { Button } from '@happyvertical/smrt-ui/ui';
|
|
3
5
|
import { onMount } from 'svelte';
|
|
4
6
|
import ProductCard from '../components/ProductCard.svelte';
|
|
5
7
|
import ProductForm from '../components/ProductForm.svelte';
|
|
@@ -102,30 +104,30 @@ function handleCancelForm() {
|
|
|
102
104
|
|
|
103
105
|
<div class="catalog-controls">
|
|
104
106
|
<div class="search-filters">
|
|
105
|
-
<
|
|
107
|
+
<Input
|
|
106
108
|
type="text"
|
|
107
109
|
bind:value={searchQuery}
|
|
108
110
|
placeholder={t(M['products.product_catalog.search_placeholder'])}
|
|
109
111
|
aria-label={t(M['products.product_catalog.search_placeholder'])}
|
|
110
112
|
class="search-input"
|
|
111
113
|
/>
|
|
112
|
-
|
|
113
|
-
<
|
|
114
|
+
|
|
115
|
+
<Select bind:value={selectedCategory} class="category-filter">
|
|
114
116
|
<option value="">{t(M['products.product_catalog.all_categories'])}</option>
|
|
115
117
|
{#each productStore.categories as category}
|
|
116
118
|
<option value={category}>{category}</option>
|
|
117
119
|
{/each}
|
|
118
|
-
</
|
|
120
|
+
</Select>
|
|
119
121
|
</div>
|
|
120
122
|
|
|
121
123
|
{#if !readonly && (showCreateForm || productStore.items.length === 0)}
|
|
122
|
-
<
|
|
123
|
-
type="button"
|
|
124
|
+
<Button
|
|
125
|
+
type="button"
|
|
126
|
+
variant="primary"
|
|
124
127
|
onclick={handleCreateProduct}
|
|
125
|
-
class="create-btn"
|
|
126
128
|
>
|
|
127
129
|
{t(M['products.product_catalog.add_product'])}
|
|
128
|
-
</
|
|
130
|
+
</Button>
|
|
129
131
|
{/if}
|
|
130
132
|
</div>
|
|
131
133
|
|
|
@@ -136,18 +138,18 @@ function handleCancelForm() {
|
|
|
136
138
|
{:else if productStore.error}
|
|
137
139
|
<div class="error-state" role="alert" aria-live="assertive">
|
|
138
140
|
<p>Error: {productStore.error}</p>
|
|
139
|
-
<
|
|
141
|
+
<Button type="button" variant="danger" onclick={() => productStore.loadProducts()}>
|
|
140
142
|
Retry
|
|
141
|
-
</
|
|
143
|
+
</Button>
|
|
142
144
|
</div>
|
|
143
145
|
{:else if filteredProducts.length === 0}
|
|
144
146
|
<div class="empty-state" role="status" aria-live="polite">
|
|
145
147
|
{#if productStore.items.length === 0}
|
|
146
148
|
<p>{t(M['products.product_catalog.empty'])}</p>
|
|
147
149
|
{#if !readonly}
|
|
148
|
-
<
|
|
150
|
+
<Button type="button" variant="primary" onclick={handleCreateProduct}>
|
|
149
151
|
{t(M['products.product_catalog.create_first'])}
|
|
150
|
-
</
|
|
152
|
+
</Button>
|
|
151
153
|
{/if}
|
|
152
154
|
{:else}
|
|
153
155
|
<p>{t(M['products.product_catalog.no_match'])}</p>
|
|
@@ -223,36 +225,18 @@ function handleCancelForm() {
|
|
|
223
225
|
gap: 0.75rem;
|
|
224
226
|
flex: 1;
|
|
225
227
|
}
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
.search-input {
|
|
228
|
+
|
|
229
|
+
/* Layout sizing for the migrated <Input>/<Select>. The primitives render the
|
|
230
|
+
inner element with the forwarded class, so pierce with :global to keep the
|
|
231
|
+
flex sizing the old scoped rules supplied (#1589); padding/border/font come
|
|
232
|
+
from the primitives' tokenised styling now. */
|
|
233
|
+
.search-filters :global(.search-input) {
|
|
235
234
|
flex: 1;
|
|
236
235
|
max-width: 300px;
|
|
237
236
|
}
|
|
238
|
-
|
|
239
|
-
.category-filter {
|
|
240
|
-
min-width: 150px;
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
.create-btn {
|
|
244
|
-
background: var(--smrt-color-primary, #3b82f6);
|
|
245
|
-
color: var(--smrt-color-on-primary, white);
|
|
246
|
-
border: none;
|
|
247
|
-
padding: 0.5rem 1rem;
|
|
248
|
-
border-radius: var(--smrt-radius-sm, 4px);
|
|
249
|
-
font-weight: var(--smrt-typography-weight-medium, 500);
|
|
250
|
-
cursor: pointer;
|
|
251
|
-
transition: background-color 0.2s;
|
|
252
|
-
}
|
|
253
237
|
|
|
254
|
-
.
|
|
255
|
-
|
|
238
|
+
.search-filters :global(.category-filter) {
|
|
239
|
+
min-width: 150px;
|
|
256
240
|
}
|
|
257
241
|
|
|
258
242
|
.products-grid {
|
|
@@ -267,14 +251,12 @@ function handleCancelForm() {
|
|
|
267
251
|
color: var(--smrt-color-on-surface-variant, #6b7280);
|
|
268
252
|
}
|
|
269
253
|
|
|
270
|
-
|
|
254
|
+
/* Restore the retry button's spacing from the error message. The old
|
|
255
|
+
`.error-state button` rule can't reach the <button> rendered inside the
|
|
256
|
+
smrt-ui <Button>, so pierce into it with `:global` (#1589); the danger
|
|
257
|
+
variant already supplies the background/color the old rule set. */
|
|
258
|
+
.error-state :global(.button) {
|
|
271
259
|
margin-top: 0.5rem;
|
|
272
|
-
background: var(--smrt-color-error, #dc2626);
|
|
273
|
-
color: var(--smrt-color-on-error, white);
|
|
274
|
-
border: none;
|
|
275
|
-
padding: 0.5rem 1rem;
|
|
276
|
-
border-radius: var(--smrt-radius-sm, 4px);
|
|
277
|
-
cursor: pointer;
|
|
278
260
|
}
|
|
279
261
|
|
|
280
262
|
.form-overlay {
|