@vertebr/react 0.2.0 → 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 (59) hide show
  1. package/dist/components/admin/VertebrAdmin.d.ts.map +1 -1
  2. package/dist/components/admin/VertebrAdmin.js +25 -3
  3. package/dist/components/config/ConfigForm.d.ts +2 -18
  4. package/dist/components/config/ConfigForm.d.ts.map +1 -1
  5. package/dist/components/config/ConfigForm.js +1 -281
  6. package/dist/components/config/DescriptionInput.d.ts +18 -0
  7. package/dist/components/config/DescriptionInput.d.ts.map +1 -0
  8. package/dist/components/config/DescriptionInput.js +152 -0
  9. package/dist/components/config/ImagePickerModal.d.ts +18 -0
  10. package/dist/components/config/ImagePickerModal.d.ts.map +1 -0
  11. package/dist/components/config/ImagePickerModal.js +120 -0
  12. package/dist/components/config/PriceInput.d.ts +12 -0
  13. package/dist/components/config/PriceInput.d.ts.map +1 -0
  14. package/dist/components/config/PriceInput.js +20 -0
  15. package/dist/components/config/form/ConfigField.d.ts +21 -0
  16. package/dist/components/config/form/ConfigField.d.ts.map +1 -0
  17. package/dist/components/config/form/ConfigField.js +48 -0
  18. package/dist/components/config/form/ConfigForm.d.ts +4 -0
  19. package/dist/components/config/form/ConfigForm.d.ts.map +1 -0
  20. package/dist/components/config/form/ConfigForm.js +70 -0
  21. package/dist/components/config/form/ConfigFormActions.d.ts +10 -0
  22. package/dist/components/config/form/ConfigFormActions.d.ts.map +1 -0
  23. package/dist/components/config/form/ConfigFormActions.js +4 -0
  24. package/dist/components/config/form/RecordConfigForm.d.ts +21 -0
  25. package/dist/components/config/form/RecordConfigForm.d.ts.map +1 -0
  26. package/dist/components/config/form/RecordConfigForm.js +37 -0
  27. package/dist/components/config/form/SettingsConfigForm.d.ts +21 -0
  28. package/dist/components/config/form/SettingsConfigForm.d.ts.map +1 -0
  29. package/dist/components/config/form/SettingsConfigForm.js +7 -0
  30. package/dist/components/config/form/helpers.d.ts +16 -0
  31. package/dist/components/config/form/helpers.d.ts.map +1 -0
  32. package/dist/components/config/form/helpers.js +186 -0
  33. package/dist/components/config/form/types.d.ts +27 -0
  34. package/dist/components/config/form/types.d.ts.map +1 -0
  35. package/dist/components/config/form/types.js +1 -0
  36. package/dist/components/resources/ResourceCell.d.ts.map +1 -1
  37. package/dist/components/resources/ResourceCell.js +5 -19
  38. package/dist/components/resources/ResourceTable.d.ts +5 -2
  39. package/dist/components/resources/ResourceTable.d.ts.map +1 -1
  40. package/dist/components/resources/ResourceTable.js +54 -5
  41. package/dist/hooks/useVertebrControls.d.ts.map +1 -1
  42. package/dist/hooks/useVertebrControls.js +5 -4
  43. package/dist/index.d.ts +7 -2
  44. package/dist/index.d.ts.map +1 -1
  45. package/dist/index.js +4 -1
  46. package/dist/lib/description.d.ts +18 -0
  47. package/dist/lib/description.d.ts.map +1 -0
  48. package/dist/lib/description.js +85 -0
  49. package/dist/lib/images.d.ts +14 -0
  50. package/dist/lib/images.d.ts.map +1 -0
  51. package/dist/lib/images.js +63 -0
  52. package/dist/lib/price.d.ts +15 -0
  53. package/dist/lib/price.d.ts.map +1 -0
  54. package/dist/lib/price.js +50 -0
  55. package/dist/styles/form.css +275 -0
  56. package/dist/styles/modal.css +115 -0
  57. package/dist/styles/panel.css +12 -1
  58. package/dist/styles/table.css +10 -1
  59. package/package.json +9 -2
@@ -0,0 +1,63 @@
1
+ function looksLikeUrl(value) {
2
+ return /^https?:\/\//i.test(value) || value.startsWith("//");
3
+ }
4
+ export function isImageField(key, field) {
5
+ if (field?.type === "file[]")
6
+ return true;
7
+ const hay = `${key} ${field?.label ?? ""}`;
8
+ return /(image|img|photo|avatar|thumbnail|thumb|cover)/i.test(hay);
9
+ }
10
+ function urlFromUnknown(value) {
11
+ if (typeof value === "string" && looksLikeUrl(value)) {
12
+ return { url: value };
13
+ }
14
+ if (value && typeof value === "object" && "url" in value) {
15
+ const url = value.url;
16
+ const name = value.name;
17
+ if (typeof url === "string" && looksLikeUrl(url)) {
18
+ return {
19
+ url,
20
+ name: typeof name === "string" ? name : undefined,
21
+ };
22
+ }
23
+ }
24
+ return null;
25
+ }
26
+ /** Flatten record image fields (single URL or URL lists) for the editor picker. */
27
+ export function collectRecordImages(schema, values) {
28
+ const images = [];
29
+ for (const [key, field] of Object.entries(schema)) {
30
+ if (!isImageField(key, field))
31
+ continue;
32
+ const label = field.label?.trim() || key;
33
+ const value = values?.[key];
34
+ const items = Array.isArray(value) ? value : [value];
35
+ items.forEach((item, index) => {
36
+ const parsed = urlFromUnknown(item);
37
+ if (!parsed)
38
+ return;
39
+ images.push({
40
+ id: `${key}:${index}`,
41
+ url: parsed.url,
42
+ name: parsed.name || (items.length > 1 ? `${label} ${index + 1}` : label),
43
+ field: key,
44
+ });
45
+ });
46
+ }
47
+ return images;
48
+ }
49
+ export function urlsFromValue(value) {
50
+ const items = Array.isArray(value)
51
+ ? value
52
+ : value == null || value === ""
53
+ ? []
54
+ : [value];
55
+ const urls = [];
56
+ for (const item of items) {
57
+ const parsed = urlFromUnknown(item);
58
+ if (parsed)
59
+ urls.push(parsed.url);
60
+ }
61
+ return urls;
62
+ }
63
+ export { looksLikeUrl };
@@ -0,0 +1,15 @@
1
+ import type { FieldSchema } from "@vertebr/client";
2
+ export declare function isCentsField(key: string, field?: Pick<FieldSchema, "label">): boolean;
3
+ export declare function isPriceField(key: string, field?: Pick<FieldSchema, "label">): boolean;
4
+ export declare function dollarsFromStored(value: number, asCents: boolean): number;
5
+ export declare function storedFromDollars(dollars: number, asCents: boolean): number;
6
+ /** Format a stored number for display (`3800` cents → `$38.00`). */
7
+ export declare function formatPrice(value: number, options?: {
8
+ asCents?: boolean;
9
+ currency?: string;
10
+ }): string;
11
+ /** Value shown in an editable money input (`3800` → `38.00`). */
12
+ export declare function formatPriceInput(value: unknown, asCents?: boolean): string;
13
+ /** Parse typed money (`$38`, `38.00`, `38`) back to stored units. */
14
+ export declare function parsePriceInput(raw: string, asCents?: boolean): number | undefined;
15
+ //# sourceMappingURL=price.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"price.d.ts","sourceRoot":"","sources":["../../src/lib/price.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAKnD,wBAAgB,YAAY,CAC1B,GAAG,EAAE,MAAM,EACX,KAAK,CAAC,EAAE,IAAI,CAAC,WAAW,EAAE,OAAO,CAAC,GACjC,OAAO,CAET;AAED,wBAAgB,YAAY,CAC1B,GAAG,EAAE,MAAM,EACX,KAAK,CAAC,EAAE,IAAI,CAAC,WAAW,EAAE,OAAO,CAAC,GACjC,OAAO,CAGT;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,MAAM,CAEzE;AAED,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,MAAM,CAE3E;AAED,oEAAoE;AACpE,wBAAgB,WAAW,CACzB,KAAK,EAAE,MAAM,EACb,OAAO,CAAC,EAAE;IAAE,OAAO,CAAC,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,GACjD,MAAM,CAcR;AAED,iEAAiE;AACjE,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,OAAO,EACd,OAAO,UAAO,GACb,MAAM,CAGR;AAED,qEAAqE;AACrE,wBAAgB,eAAe,CAC7B,GAAG,EAAE,MAAM,EACX,OAAO,UAAO,GACb,MAAM,GAAG,SAAS,CAQpB"}
@@ -0,0 +1,50 @@
1
+ const CENTS_KEY = /(cents|priceCents|amountCents)/i;
2
+ const PRICE_KEY = /(price|amount|cost|total|msrp)/i;
3
+ export function isCentsField(key, field) {
4
+ return CENTS_KEY.test(key) || /cents/i.test(field?.label ?? "");
5
+ }
6
+ export function isPriceField(key, field) {
7
+ if (isCentsField(key, field))
8
+ return true;
9
+ return PRICE_KEY.test(key) || PRICE_KEY.test(field?.label ?? "");
10
+ }
11
+ export function dollarsFromStored(value, asCents) {
12
+ return asCents ? value / 100 : value;
13
+ }
14
+ export function storedFromDollars(dollars, asCents) {
15
+ return asCents ? Math.round(dollars * 100) : dollars;
16
+ }
17
+ /** Format a stored number for display (`3800` cents → `$38.00`). */
18
+ export function formatPrice(value, options) {
19
+ const asCents = options?.asCents ?? true;
20
+ const currency = options?.currency ?? "USD";
21
+ const amount = dollarsFromStored(value, asCents);
22
+ try {
23
+ return new Intl.NumberFormat(undefined, {
24
+ style: "currency",
25
+ currency,
26
+ minimumFractionDigits: 2,
27
+ maximumFractionDigits: 2,
28
+ }).format(amount);
29
+ }
30
+ catch {
31
+ return `$${amount.toFixed(2)}`;
32
+ }
33
+ }
34
+ /** Value shown in an editable money input (`3800` → `38.00`). */
35
+ export function formatPriceInput(value, asCents = true) {
36
+ if (typeof value !== "number" || !Number.isFinite(value))
37
+ return "";
38
+ return dollarsFromStored(value, asCents).toFixed(2);
39
+ }
40
+ /** Parse typed money (`$38`, `38.00`, `38`) back to stored units. */
41
+ export function parsePriceInput(raw, asCents = true) {
42
+ const cleaned = raw.replace(/[^0-9.-]/g, "");
43
+ if (!cleaned || cleaned === "-" || cleaned === "." || cleaned === "-.") {
44
+ return undefined;
45
+ }
46
+ const dollars = Number(cleaned);
47
+ if (!Number.isFinite(dollars))
48
+ return undefined;
49
+ return storedFromDollars(dollars, asCents);
50
+ }
@@ -234,6 +234,252 @@
234
234
  padding-top: 0.65rem;
235
235
  }
236
236
 
237
+ .vertebr-field-money {
238
+ display: grid;
239
+ grid-template-columns: auto minmax(0, 1fr);
240
+ align-items: stretch;
241
+ }
242
+
243
+ .vertebr-field-money-prefix {
244
+ display: grid;
245
+ place-items: center;
246
+ padding: 0 0.7rem;
247
+ border: 1px solid var(--vb-border);
248
+ border-right: 0;
249
+ border-radius: var(--vb-radius-sm) 0 0 var(--vb-radius-sm);
250
+ background: var(--vb-surface-tint);
251
+ color: var(--vb-text-muted);
252
+ font-family: var(--vb-mono);
253
+ font-size: 0.86rem;
254
+ }
255
+
256
+ .vertebr-field-money input {
257
+ border-top-left-radius: 0;
258
+ border-bottom-left-radius: 0;
259
+ font-variant-numeric: tabular-nums;
260
+ font-family: var(--vb-mono);
261
+ }
262
+
263
+ .vertebr-field-prose {
264
+ display: grid;
265
+ gap: 0;
266
+ border: 1px solid var(--vb-border);
267
+ border-radius: var(--vb-radius-sm);
268
+ background: var(--vb-bg);
269
+ overflow: hidden;
270
+ }
271
+
272
+ .vertebr-md-toolbar {
273
+ display: flex;
274
+ flex-wrap: wrap;
275
+ align-items: center;
276
+ gap: 0.15rem;
277
+ padding: 0.35rem 0.4rem;
278
+ border-bottom: 1px solid var(--vb-border);
279
+ background: var(--vb-surface-tint);
280
+ }
281
+
282
+ .vertebr-md-btn {
283
+ appearance: none;
284
+ border: 0;
285
+ background: transparent;
286
+ color: var(--vb-text-muted);
287
+ border-radius: 5px;
288
+ min-width: 1.7rem;
289
+ height: 1.7rem;
290
+ padding: 0 0.4rem;
291
+ font: inherit;
292
+ font-size: 0.78rem;
293
+ font-weight: 600;
294
+ cursor: pointer;
295
+ }
296
+
297
+ .vertebr-md-btn:hover:not(:disabled) {
298
+ background: var(--vb-bg-hover);
299
+ color: var(--vb-text);
300
+ }
301
+
302
+ .vertebr-md-btn[data-active="true"] {
303
+ background: var(--vb-bg-hover);
304
+ color: var(--vb-text);
305
+ }
306
+
307
+ .vertebr-md-btn:disabled {
308
+ opacity: 0.45;
309
+ cursor: not-allowed;
310
+ }
311
+
312
+ .vertebr-md-glyph {
313
+ font-weight: 700;
314
+ }
315
+
316
+ .vertebr-md-italic {
317
+ font-style: italic;
318
+ font-family: Georgia, "Times New Roman", serif;
319
+ }
320
+
321
+ .vertebr-md-strike {
322
+ text-decoration: line-through;
323
+ }
324
+
325
+ .vertebr-md-sep {
326
+ width: 1px;
327
+ height: 1rem;
328
+ margin: 0 0.25rem;
329
+ background: var(--vb-border);
330
+ }
331
+
332
+ .vertebr-field-prose textarea,
333
+ .vertebr-md-content {
334
+ resize: none;
335
+ min-height: 7.5rem;
336
+ overflow: hidden;
337
+ line-height: 1.55;
338
+ border: 0;
339
+ border-radius: 0;
340
+ box-shadow: none;
341
+ }
342
+
343
+ .vertebr-md-content {
344
+ padding: 0.65rem 0.75rem;
345
+ outline: none;
346
+ color: var(--vb-text);
347
+ }
348
+
349
+ .vertebr-md-content p {
350
+ margin: 0 0 0.65em;
351
+ }
352
+
353
+ .vertebr-md-content p:last-child {
354
+ margin-bottom: 0;
355
+ }
356
+
357
+ .vertebr-md-content h2,
358
+ .vertebr-md-content h3 {
359
+ margin: 0.85em 0 0.4em;
360
+ font-size: 1.05em;
361
+ font-weight: 650;
362
+ line-height: 1.3;
363
+ }
364
+
365
+ .vertebr-md-content ul,
366
+ .vertebr-md-content ol {
367
+ margin: 0.35em 0 0.65em;
368
+ padding-left: 1.3rem;
369
+ }
370
+
371
+ .vertebr-md-content a {
372
+ color: var(--vb-accent);
373
+ }
374
+
375
+ .vertebr-md-content s {
376
+ text-decoration: line-through;
377
+ }
378
+
379
+ .vertebr-md-content code {
380
+ font-family: var(--vb-mono);
381
+ font-size: 0.86em;
382
+ padding: 0.08em 0.32em;
383
+ border-radius: 4px;
384
+ background: var(--vb-surface-tint);
385
+ border: 1px solid var(--vb-border);
386
+ }
387
+
388
+ .vertebr-md-content pre {
389
+ margin: 0.45em 0 0.7em;
390
+ padding: 0.65rem 0.75rem;
391
+ overflow: auto;
392
+ border-radius: 6px;
393
+ background: var(--vb-surface-tint);
394
+ border: 1px solid var(--vb-border);
395
+ }
396
+
397
+ .vertebr-md-content pre code {
398
+ padding: 0;
399
+ border: 0;
400
+ background: transparent;
401
+ }
402
+
403
+ .vertebr-md-content blockquote {
404
+ margin: 0.45em 0 0.7em;
405
+ padding: 0.15rem 0 0.15rem 0.75rem;
406
+ border-left: 2px solid var(--vb-accent-dim);
407
+ color: var(--vb-text-muted);
408
+ }
409
+
410
+ .vertebr-md-content img {
411
+ display: block;
412
+ max-width: 100%;
413
+ height: auto;
414
+ margin: 0.5em 0;
415
+ border-radius: 8px;
416
+ border: 1px solid var(--vb-border);
417
+ }
418
+
419
+ .vertebr-md-content table {
420
+ width: 100%;
421
+ border-collapse: collapse;
422
+ margin: 0.5em 0 0.75em;
423
+ font-size: 0.88em;
424
+ }
425
+
426
+ .vertebr-md-content th,
427
+ .vertebr-md-content td {
428
+ border: 1px solid var(--vb-border);
429
+ padding: 0.35rem 0.5rem;
430
+ vertical-align: top;
431
+ min-width: 4rem;
432
+ }
433
+
434
+ .vertebr-md-content th {
435
+ background: var(--vb-surface-tint);
436
+ font-weight: 650;
437
+ text-align: left;
438
+ }
439
+
440
+ .vertebr-md-content .selectedCell::after {
441
+ content: "";
442
+ position: absolute;
443
+ inset: 0;
444
+ background: var(--vb-accent-soft);
445
+ pointer-events: none;
446
+ }
447
+
448
+ .vertebr-md-content td,
449
+ .vertebr-md-content th {
450
+ position: relative;
451
+ }
452
+
453
+ .vertebr-md-content p.is-editor-empty:first-child::before {
454
+ color: var(--vb-text-subtle);
455
+ content: attr(data-placeholder);
456
+ float: left;
457
+ height: 0;
458
+ pointer-events: none;
459
+ }
460
+
461
+ .vertebr-field-prose textarea:focus,
462
+ .vertebr-md-content:focus {
463
+ box-shadow: none;
464
+ outline: none;
465
+ }
466
+
467
+ .vertebr-field-prose:focus-within {
468
+ border-color: var(--vb-accent-dim);
469
+ box-shadow: 0 0 0 3px var(--vb-accent-soft);
470
+ }
471
+
472
+ .vertebr-field-prose-meta {
473
+ margin: 0;
474
+ padding: 0.35rem 0.7rem 0.45rem;
475
+ border-top: 1px solid var(--vb-border);
476
+ font-family: var(--vb-mono);
477
+ font-size: 0.68rem;
478
+ letter-spacing: 0.04em;
479
+ color: var(--vb-text-subtle);
480
+ background: var(--vb-surface-tint);
481
+ }
482
+
237
483
  .vertebr-field input:hover,
238
484
  .vertebr-field textarea:hover {
239
485
  border-color: var(--vb-border-strong);
@@ -269,6 +515,35 @@
269
515
  background: var(--vb-bg-muted);
270
516
  }
271
517
 
518
+ .vertebr-field-files-list {
519
+ margin: 0;
520
+ padding: 0;
521
+ list-style: none;
522
+ display: grid;
523
+ grid-template-columns: repeat(auto-fill, minmax(92px, 1fr));
524
+ gap: 0.55rem;
525
+ }
526
+
527
+ .vertebr-field-files-list li {
528
+ display: grid;
529
+ gap: 0.35rem;
530
+ }
531
+
532
+ .vertebr-field-files-list img {
533
+ width: 100%;
534
+ aspect-ratio: 1;
535
+ object-fit: cover;
536
+ border-radius: var(--vb-radius);
537
+ border: 1px solid var(--vb-border);
538
+ background: var(--vb-bg-muted);
539
+ }
540
+
541
+ .vertebr-field-files-empty {
542
+ margin: 0;
543
+ font-size: 0.82rem;
544
+ color: var(--vb-text-subtle);
545
+ }
546
+
272
547
  .vertebr-field[data-kind="image"] {
273
548
  grid-column: 1 / -1;
274
549
  }
@@ -108,3 +108,118 @@
108
108
  margin: 0;
109
109
  min-width: 0;
110
110
  }
111
+
112
+ .vertebr-picker-body {
113
+ gap: 0.95rem;
114
+ }
115
+
116
+ .vertebr-picker-tabs {
117
+ display: flex;
118
+ gap: 0.35rem;
119
+ padding: 0.2rem;
120
+ border: 1px solid var(--vb-border);
121
+ border-radius: 8px;
122
+ background: var(--vb-surface-tint);
123
+ }
124
+
125
+ .vertebr-picker-tabs button {
126
+ appearance: none;
127
+ flex: 1 1 0;
128
+ display: inline-flex;
129
+ align-items: center;
130
+ justify-content: center;
131
+ gap: 0.4rem;
132
+ border: 0;
133
+ background: transparent;
134
+ color: var(--vb-text-muted);
135
+ border-radius: 6px;
136
+ padding: 0.4rem 0.55rem;
137
+ font: inherit;
138
+ font-size: 0.82rem;
139
+ cursor: pointer;
140
+ }
141
+
142
+ .vertebr-picker-tabs button[data-active="true"] {
143
+ background: var(--vb-bg-elevated);
144
+ color: var(--vb-text);
145
+ box-shadow: 0 1px 0 color-mix(in srgb, var(--vb-text) 8%, transparent);
146
+ }
147
+
148
+ .vertebr-picker-tabs span {
149
+ font-family: var(--vb-mono);
150
+ font-size: 0.68rem;
151
+ color: var(--vb-text-subtle);
152
+ }
153
+
154
+ .vertebr-picker-url {
155
+ display: grid;
156
+ grid-template-columns: minmax(0, 1fr) auto;
157
+ gap: 0.5rem;
158
+ }
159
+
160
+ .vertebr-picker-grid {
161
+ display: grid;
162
+ grid-template-columns: repeat(auto-fill, minmax(128px, 1fr));
163
+ gap: 0.7rem;
164
+ }
165
+
166
+ .vertebr-picker-tile {
167
+ appearance: none;
168
+ display: grid;
169
+ gap: 0.4rem;
170
+ padding: 0.45rem;
171
+ border: 1px solid var(--vb-border);
172
+ border-radius: 10px;
173
+ background: var(--vb-bg);
174
+ color: inherit;
175
+ text-align: left;
176
+ cursor: pointer;
177
+ }
178
+
179
+ .vertebr-picker-tile:hover {
180
+ border-color: var(--vb-border-strong);
181
+ }
182
+
183
+ .vertebr-picker-tile[data-selected="true"] {
184
+ border-color: var(--vb-accent-dim);
185
+ box-shadow: 0 0 0 3px var(--vb-accent-soft);
186
+ }
187
+
188
+ .vertebr-picker-thumb {
189
+ display: block;
190
+ aspect-ratio: 1;
191
+ border-radius: 7px;
192
+ overflow: hidden;
193
+ border: 1px solid var(--vb-border);
194
+ background: var(--vb-bg-muted);
195
+ }
196
+
197
+ .vertebr-picker-thumb img {
198
+ display: block;
199
+ width: 100%;
200
+ height: 100%;
201
+ object-fit: cover;
202
+ }
203
+
204
+ .vertebr-picker-name {
205
+ font-size: 0.75rem;
206
+ overflow: hidden;
207
+ text-overflow: ellipsis;
208
+ white-space: nowrap;
209
+ }
210
+
211
+ .vertebr-picker-foot {
212
+ display: flex;
213
+ align-items: center;
214
+ justify-content: space-between;
215
+ gap: 0.75rem;
216
+ padding: 0.85rem 1.25rem 1.05rem;
217
+ border-top: 1px solid var(--vb-border);
218
+ background: var(--vb-surface-tint);
219
+ }
220
+
221
+ .vertebr-picker-count {
222
+ font-family: var(--vb-mono);
223
+ font-size: 0.72rem;
224
+ color: var(--vb-text-subtle);
225
+ }
@@ -53,7 +53,7 @@
53
53
  flex: 0 0 auto;
54
54
  display: flex;
55
55
  align-items: center;
56
- justify-content: flex-start;
56
+ justify-content: space-between;
57
57
  gap: 0.5rem;
58
58
  padding: 0.55rem 1rem;
59
59
  border-bottom: 1px solid var(--vb-border);
@@ -67,6 +67,17 @@
67
67
  flex-wrap: wrap;
68
68
  }
69
69
 
70
+ .vertebr-table-scroll-controls {
71
+ display: inline-flex;
72
+ align-items: center;
73
+ gap: 0.15rem;
74
+ margin-left: auto;
75
+ }
76
+
77
+ .vertebr-table-scroll-btn {
78
+ padding: 0.3rem 0.45rem;
79
+ }
80
+
70
81
  .vertebr-btn-icon {
71
82
  display: inline-flex;
72
83
  align-items: center;
@@ -3,12 +3,16 @@
3
3
  .vertebr-table-wrap {
4
4
  flex: 1 1 auto;
5
5
  min-height: 0;
6
+ min-width: 0;
6
7
  max-width: 100%;
7
8
  overflow: auto;
9
+ overscroll-behavior-inline: contain;
10
+ scrollbar-gutter: stable;
8
11
  }
9
12
 
10
13
  .vertebr-table {
11
- width: 100%;
14
+ width: max-content;
15
+ min-width: 100%;
12
16
  border-collapse: separate;
13
17
  border-spacing: 0;
14
18
  font-size: 0.9rem;
@@ -24,6 +28,11 @@
24
28
  white-space: nowrap;
25
29
  }
26
30
 
31
+ .vertebr-table th:not(.vertebr-col-actions),
32
+ .vertebr-table td:not(.vertebr-col-actions) {
33
+ min-width: 6.5rem;
34
+ }
35
+
27
36
  .vertebr-table th {
28
37
  position: sticky;
29
38
  top: 0;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vertebr/react",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Vertebr admin dashboard UI",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -36,13 +36,20 @@
36
36
  "react-dom": "^18.0.0 || ^19.0.0"
37
37
  },
38
38
  "dependencies": {
39
- "@vertebr/client": "*",
40
39
  "@hookform/resolvers": "^5.2.1",
41
40
  "@radix-ui/react-label": "^2.1.7",
42
41
  "@radix-ui/react-select": "^2.3.7",
43
42
  "@radix-ui/react-separator": "^1.1.7",
44
43
  "@radix-ui/react-switch": "^1.2.5",
45
44
  "@tanstack/react-table": "^8.21.3",
45
+ "@tiptap/extension-image": "^3.29.2",
46
+ "@tiptap/extension-table": "^3.29.2",
47
+ "@tiptap/extensions": "^3.29.2",
48
+ "@tiptap/markdown": "^3.29.2",
49
+ "@tiptap/pm": "^3.29.2",
50
+ "@tiptap/react": "^3.29.2",
51
+ "@tiptap/starter-kit": "^3.29.2",
52
+ "@vertebr/client": "*",
46
53
  "react-hook-form": "^7.62.0",
47
54
  "zod": "^3.25.76"
48
55
  },