@protolabsai/ui 0.20.0 → 0.22.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.
- package/dist/plugin-kit.css +379 -0
- package/package.json +2 -1
- package/src/Empty.stories.tsx +32 -0
- package/src/Grid.stories.tsx +37 -0
- package/src/TabBar.stories.tsx +74 -0
- package/src/ToolCard.stories.tsx +71 -0
- package/src/layout.tsx +40 -1
- package/src/navigation.tsx +119 -0
- package/src/primitives.tsx +31 -3
- package/src/styles/layout.css +43 -0
- package/src/styles/navigation.css +131 -0
- package/src/styles/primitives.css +36 -0
- package/src/styles/tool-card.css +167 -0
- package/src/styles.css +1 -0
- package/src/tool-card.tsx +177 -0
package/dist/plugin-kit.css
CHANGED
|
@@ -336,6 +336,42 @@ a:hover {
|
|
|
336
336
|
padding: 1rem 0;
|
|
337
337
|
}
|
|
338
338
|
|
|
339
|
+
/* Slotted form (icon/title/description/action) — centered scaffold. */
|
|
340
|
+
.pl-empty--slotted {
|
|
341
|
+
display: flex;
|
|
342
|
+
flex-direction: column;
|
|
343
|
+
align-items: center;
|
|
344
|
+
justify-content: center;
|
|
345
|
+
gap: var(--pl-space-2);
|
|
346
|
+
padding: var(--pl-space-6) var(--pl-space-4);
|
|
347
|
+
text-align: center;
|
|
348
|
+
}
|
|
349
|
+
.pl-empty__icon {
|
|
350
|
+
display: inline-flex;
|
|
351
|
+
margin-bottom: var(--pl-space-1);
|
|
352
|
+
color: var(--pl-color-fg-subtle);
|
|
353
|
+
}
|
|
354
|
+
.pl-empty__icon svg {
|
|
355
|
+
width: 28px;
|
|
356
|
+
height: 28px;
|
|
357
|
+
}
|
|
358
|
+
.pl-empty__title {
|
|
359
|
+
font-family: var(--pl-font-sans);
|
|
360
|
+
font-size: 14px;
|
|
361
|
+
font-weight: var(--pl-font-weight-medium);
|
|
362
|
+
color: var(--pl-color-fg);
|
|
363
|
+
}
|
|
364
|
+
.pl-empty__desc {
|
|
365
|
+
max-width: 44ch;
|
|
366
|
+
font-family: var(--pl-font-sans);
|
|
367
|
+
font-size: 13px;
|
|
368
|
+
line-height: 1.6;
|
|
369
|
+
color: var(--pl-color-fg-muted);
|
|
370
|
+
}
|
|
371
|
+
.pl-empty__action {
|
|
372
|
+
margin-top: var(--pl-space-2);
|
|
373
|
+
}
|
|
374
|
+
|
|
339
375
|
/* ── divider ── */
|
|
340
376
|
.pl-divider {
|
|
341
377
|
border: 0;
|
|
@@ -572,6 +608,49 @@ a:hover {
|
|
|
572
608
|
}
|
|
573
609
|
}
|
|
574
610
|
|
|
611
|
+
/* ── Grid (responsive card grid) ── */
|
|
612
|
+
.pl-grid {
|
|
613
|
+
display: grid;
|
|
614
|
+
}
|
|
615
|
+
.pl-grid--gap-sm {
|
|
616
|
+
gap: var(--pl-space-2);
|
|
617
|
+
}
|
|
618
|
+
.pl-grid--gap-md {
|
|
619
|
+
gap: var(--pl-space-4);
|
|
620
|
+
}
|
|
621
|
+
.pl-grid--gap-lg {
|
|
622
|
+
gap: var(--pl-space-6);
|
|
623
|
+
}
|
|
624
|
+
/* auto-fill: as many columns of >= --pl-grid-min as fit */
|
|
625
|
+
.pl-grid--auto {
|
|
626
|
+
grid-template-columns: repeat(auto-fill, minmax(var(--pl-grid-min, 14rem), 1fr));
|
|
627
|
+
}
|
|
628
|
+
/* fixed/responsive column count — each breakpoint falls back to the nearest
|
|
629
|
+
smaller value that was set, so `cols={{ base:1, md:2, xl:3 }}` cascades cleanly */
|
|
630
|
+
.pl-grid--cols {
|
|
631
|
+
grid-template-columns: repeat(var(--pl-grid-cols, 1), minmax(0, 1fr));
|
|
632
|
+
}
|
|
633
|
+
@media (min-width: 640px) {
|
|
634
|
+
.pl-grid--cols {
|
|
635
|
+
grid-template-columns: repeat(var(--pl-grid-cols-sm, var(--pl-grid-cols, 1)), minmax(0, 1fr));
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
@media (min-width: 768px) {
|
|
639
|
+
.pl-grid--cols {
|
|
640
|
+
grid-template-columns: repeat(var(--pl-grid-cols-md, var(--pl-grid-cols-sm, var(--pl-grid-cols, 1))), minmax(0, 1fr));
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
@media (min-width: 1024px) {
|
|
644
|
+
.pl-grid--cols {
|
|
645
|
+
grid-template-columns: repeat(var(--pl-grid-cols-lg, var(--pl-grid-cols-md, var(--pl-grid-cols-sm, var(--pl-grid-cols, 1)))), minmax(0, 1fr));
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
@media (min-width: 1280px) {
|
|
649
|
+
.pl-grid--cols {
|
|
650
|
+
grid-template-columns: repeat(var(--pl-grid-cols-xl, var(--pl-grid-cols-lg, var(--pl-grid-cols-md, var(--pl-grid-cols-sm, var(--pl-grid-cols, 1))))), minmax(0, 1fr));
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
|
|
575
654
|
/* ── row (label | body [| status]) ── */
|
|
576
655
|
.pl-row {
|
|
577
656
|
display: grid;
|
|
@@ -1282,6 +1361,137 @@ a.pl-changelog__version:hover {
|
|
|
1282
1361
|
color: var(--pl-color-fg-muted);
|
|
1283
1362
|
}
|
|
1284
1363
|
|
|
1364
|
+
/* ── TabBar (browser-style session tabs) ────────────────────────────────────── */
|
|
1365
|
+
/* On a narrow window session tabs scroll horizontally (browser/VS Code behaviour) —
|
|
1366
|
+
they do NOT collapse to a <select> like Tabs, because a select can't host the
|
|
1367
|
+
per-tab close / inline rename / add-new affordances that are the point of TabBar. */
|
|
1368
|
+
.pl-tabbar {
|
|
1369
|
+
display: flex;
|
|
1370
|
+
align-items: stretch;
|
|
1371
|
+
gap: 2px;
|
|
1372
|
+
overflow-x: auto;
|
|
1373
|
+
scrollbar-width: thin;
|
|
1374
|
+
border-bottom: var(--pl-border-width) solid var(--pl-color-border);
|
|
1375
|
+
}
|
|
1376
|
+
.pl-tabbar__tab {
|
|
1377
|
+
display: inline-flex;
|
|
1378
|
+
align-items: center;
|
|
1379
|
+
gap: 7px;
|
|
1380
|
+
flex: 0 1 auto;
|
|
1381
|
+
min-width: 92px;
|
|
1382
|
+
max-width: 220px;
|
|
1383
|
+
padding: 7px 10px 7px 12px;
|
|
1384
|
+
background: none;
|
|
1385
|
+
border: none;
|
|
1386
|
+
border-bottom: 2px solid transparent;
|
|
1387
|
+
color: var(--pl-color-fg-muted);
|
|
1388
|
+
font-family: var(--pl-font-sans);
|
|
1389
|
+
font-size: 13px;
|
|
1390
|
+
cursor: pointer;
|
|
1391
|
+
border-radius: var(--pl-radius) var(--pl-radius) 0 0;
|
|
1392
|
+
transition:
|
|
1393
|
+
background var(--pl-motion-fast) var(--pl-motion-ease),
|
|
1394
|
+
color var(--pl-motion-fast) var(--pl-motion-ease);
|
|
1395
|
+
}
|
|
1396
|
+
.pl-tabbar__tab:hover {
|
|
1397
|
+
background: var(--pl-color-bg-hover);
|
|
1398
|
+
color: var(--pl-color-fg);
|
|
1399
|
+
}
|
|
1400
|
+
.pl-tabbar__tab:focus-visible {
|
|
1401
|
+
outline: 2px solid var(--pl-color-accent);
|
|
1402
|
+
outline-offset: -2px;
|
|
1403
|
+
}
|
|
1404
|
+
.pl-tabbar__tab--active {
|
|
1405
|
+
color: var(--pl-color-fg);
|
|
1406
|
+
background: var(--pl-color-bg-subtle);
|
|
1407
|
+
border-bottom-color: var(--pl-color-accent);
|
|
1408
|
+
}
|
|
1409
|
+
.pl-tabbar__icon {
|
|
1410
|
+
display: inline-flex;
|
|
1411
|
+
align-items: center;
|
|
1412
|
+
}
|
|
1413
|
+
.pl-tabbar__icon svg {
|
|
1414
|
+
width: 15px;
|
|
1415
|
+
height: 15px;
|
|
1416
|
+
}
|
|
1417
|
+
.pl-tabbar__label {
|
|
1418
|
+
min-width: 0;
|
|
1419
|
+
overflow: hidden;
|
|
1420
|
+
text-overflow: ellipsis;
|
|
1421
|
+
white-space: nowrap;
|
|
1422
|
+
}
|
|
1423
|
+
.pl-tabbar__edit {
|
|
1424
|
+
min-width: 60px;
|
|
1425
|
+
max-width: 140px;
|
|
1426
|
+
padding: 1px 4px;
|
|
1427
|
+
font: inherit;
|
|
1428
|
+
font-size: 13px;
|
|
1429
|
+
color: var(--pl-color-fg);
|
|
1430
|
+
background: var(--pl-color-bg-inset);
|
|
1431
|
+
border: var(--pl-border-width) solid var(--pl-color-accent);
|
|
1432
|
+
border-radius: calc(var(--pl-radius) - 2px);
|
|
1433
|
+
outline: none;
|
|
1434
|
+
}
|
|
1435
|
+
.pl-tabbar__badge {
|
|
1436
|
+
display: inline-flex;
|
|
1437
|
+
align-items: center;
|
|
1438
|
+
justify-content: center;
|
|
1439
|
+
min-width: 16px;
|
|
1440
|
+
height: 16px;
|
|
1441
|
+
padding: 0 5px;
|
|
1442
|
+
font-family: var(--pl-font-mono);
|
|
1443
|
+
font-size: 10px;
|
|
1444
|
+
line-height: 1;
|
|
1445
|
+
color: var(--pl-color-fg-muted);
|
|
1446
|
+
background: var(--pl-color-bg-subtle);
|
|
1447
|
+
border: var(--pl-border-width) solid var(--pl-color-border);
|
|
1448
|
+
border-radius: 999px;
|
|
1449
|
+
}
|
|
1450
|
+
.pl-tabbar__close {
|
|
1451
|
+
display: inline-flex;
|
|
1452
|
+
align-items: center;
|
|
1453
|
+
justify-content: center;
|
|
1454
|
+
width: 18px;
|
|
1455
|
+
height: 18px;
|
|
1456
|
+
margin-right: -3px;
|
|
1457
|
+
padding: 0;
|
|
1458
|
+
color: var(--pl-color-fg-subtle);
|
|
1459
|
+
background: none;
|
|
1460
|
+
border: none;
|
|
1461
|
+
border-radius: var(--pl-radius);
|
|
1462
|
+
cursor: pointer;
|
|
1463
|
+
opacity: 0.7;
|
|
1464
|
+
transition:
|
|
1465
|
+
background var(--pl-motion-fast) var(--pl-motion-ease),
|
|
1466
|
+
color var(--pl-motion-fast) var(--pl-motion-ease),
|
|
1467
|
+
opacity var(--pl-motion-fast) var(--pl-motion-ease);
|
|
1468
|
+
}
|
|
1469
|
+
.pl-tabbar__close:hover {
|
|
1470
|
+
color: var(--pl-color-fg);
|
|
1471
|
+
background: var(--pl-color-bg-hover);
|
|
1472
|
+
opacity: 1;
|
|
1473
|
+
}
|
|
1474
|
+
.pl-tabbar__add {
|
|
1475
|
+
display: inline-flex;
|
|
1476
|
+
align-items: center;
|
|
1477
|
+
justify-content: center;
|
|
1478
|
+
width: 28px;
|
|
1479
|
+
flex-shrink: 0;
|
|
1480
|
+
margin-left: 2px;
|
|
1481
|
+
color: var(--pl-color-fg-muted);
|
|
1482
|
+
background: none;
|
|
1483
|
+
border: none;
|
|
1484
|
+
cursor: pointer;
|
|
1485
|
+
border-radius: var(--pl-radius);
|
|
1486
|
+
transition:
|
|
1487
|
+
background var(--pl-motion-fast) var(--pl-motion-ease),
|
|
1488
|
+
color var(--pl-motion-fast) var(--pl-motion-ease);
|
|
1489
|
+
}
|
|
1490
|
+
.pl-tabbar__add:hover {
|
|
1491
|
+
color: var(--pl-color-fg);
|
|
1492
|
+
background: var(--pl-color-bg-hover);
|
|
1493
|
+
}
|
|
1494
|
+
|
|
1285
1495
|
/* ── ui component: forms.css ───────────────────────────────────────────────── */
|
|
1286
1496
|
/* @protolabsai/ui — forms styles (over @protolabsai/design --pl-* tokens). */
|
|
1287
1497
|
|
|
@@ -2659,6 +2869,175 @@ a.pl-changelog__version:hover {
|
|
|
2659
2869
|
gap: var(--pl-space-2);
|
|
2660
2870
|
}
|
|
2661
2871
|
|
|
2872
|
+
/* ── ui component: tool-card.css ───────────────────────────────────────────── */
|
|
2873
|
+
/* @protolabsai/ui — ToolCard (streamed tool-call disclosure). Ported from the
|
|
2874
|
+
protoAgent console's tool-card family onto --pl-* tokens. The frame only — the
|
|
2875
|
+
body is a host-filled slot (per-tool value rendering stays app-side). */
|
|
2876
|
+
|
|
2877
|
+
.pl-toolcard-list {
|
|
2878
|
+
display: flex;
|
|
2879
|
+
flex-direction: column;
|
|
2880
|
+
gap: var(--pl-space-2);
|
|
2881
|
+
min-width: 0;
|
|
2882
|
+
}
|
|
2883
|
+
.pl-toolcard-group {
|
|
2884
|
+
display: flex;
|
|
2885
|
+
flex-direction: column;
|
|
2886
|
+
gap: 6px;
|
|
2887
|
+
min-width: 0;
|
|
2888
|
+
}
|
|
2889
|
+
|
|
2890
|
+
.pl-toolcard {
|
|
2891
|
+
min-width: 0;
|
|
2892
|
+
max-width: 100%;
|
|
2893
|
+
background: var(--pl-color-bg-raised);
|
|
2894
|
+
border: var(--pl-border-width) solid var(--pl-color-border);
|
|
2895
|
+
border-radius: var(--pl-radius);
|
|
2896
|
+
overflow: hidden;
|
|
2897
|
+
}
|
|
2898
|
+
|
|
2899
|
+
.pl-toolcard__head {
|
|
2900
|
+
display: flex;
|
|
2901
|
+
align-items: center;
|
|
2902
|
+
gap: 7px;
|
|
2903
|
+
width: 100%;
|
|
2904
|
+
padding: 6px 9px;
|
|
2905
|
+
background: none;
|
|
2906
|
+
border: none;
|
|
2907
|
+
color: var(--pl-color-fg-muted);
|
|
2908
|
+
font: inherit;
|
|
2909
|
+
font-size: 12px;
|
|
2910
|
+
text-align: left;
|
|
2911
|
+
cursor: pointer;
|
|
2912
|
+
}
|
|
2913
|
+
.pl-toolcard__head:disabled {
|
|
2914
|
+
cursor: default;
|
|
2915
|
+
}
|
|
2916
|
+
.pl-toolcard__head:hover:not(:disabled) .pl-toolcard__name {
|
|
2917
|
+
color: var(--pl-color-fg);
|
|
2918
|
+
}
|
|
2919
|
+
|
|
2920
|
+
.pl-toolcard__caret {
|
|
2921
|
+
flex: none;
|
|
2922
|
+
display: inline-flex;
|
|
2923
|
+
color: var(--pl-color-fg-subtle);
|
|
2924
|
+
transition: transform var(--pl-motion-fast) var(--pl-motion-ease);
|
|
2925
|
+
}
|
|
2926
|
+
.pl-toolcard__caret--open {
|
|
2927
|
+
transform: rotate(90deg);
|
|
2928
|
+
}
|
|
2929
|
+
.pl-toolcard__caret--hidden {
|
|
2930
|
+
visibility: hidden;
|
|
2931
|
+
}
|
|
2932
|
+
|
|
2933
|
+
.pl-toolcard__icon {
|
|
2934
|
+
flex: none;
|
|
2935
|
+
display: inline-flex;
|
|
2936
|
+
color: var(--pl-color-fg-muted);
|
|
2937
|
+
}
|
|
2938
|
+
.pl-toolcard__icon svg {
|
|
2939
|
+
width: 13px;
|
|
2940
|
+
height: 13px;
|
|
2941
|
+
}
|
|
2942
|
+
|
|
2943
|
+
.pl-toolcard__name {
|
|
2944
|
+
flex: 1 1 auto;
|
|
2945
|
+
min-width: 0;
|
|
2946
|
+
overflow: hidden;
|
|
2947
|
+
text-overflow: ellipsis;
|
|
2948
|
+
white-space: nowrap;
|
|
2949
|
+
font-family: var(--pl-font-mono);
|
|
2950
|
+
color: var(--pl-color-fg);
|
|
2951
|
+
}
|
|
2952
|
+
|
|
2953
|
+
.pl-toolcard__dur {
|
|
2954
|
+
flex: none;
|
|
2955
|
+
font-family: var(--pl-font-mono);
|
|
2956
|
+
font-size: 10px;
|
|
2957
|
+
color: var(--pl-color-fg-muted);
|
|
2958
|
+
font-variant-numeric: tabular-nums;
|
|
2959
|
+
}
|
|
2960
|
+
|
|
2961
|
+
.pl-toolcard__status {
|
|
2962
|
+
flex: none;
|
|
2963
|
+
display: inline-flex;
|
|
2964
|
+
}
|
|
2965
|
+
.pl-toolcard__status--done {
|
|
2966
|
+
color: var(--pl-color-status-success);
|
|
2967
|
+
}
|
|
2968
|
+
.pl-toolcard__status--error {
|
|
2969
|
+
color: var(--pl-color-status-error);
|
|
2970
|
+
}
|
|
2971
|
+
.pl-toolcard__status--running {
|
|
2972
|
+
color: var(--pl-color-status-warning);
|
|
2973
|
+
}
|
|
2974
|
+
.pl-toolcard__spin {
|
|
2975
|
+
animation: pl-toolcard-spin 0.8s linear infinite;
|
|
2976
|
+
}
|
|
2977
|
+
@keyframes pl-toolcard-spin {
|
|
2978
|
+
to {
|
|
2979
|
+
transform: rotate(360deg);
|
|
2980
|
+
}
|
|
2981
|
+
}
|
|
2982
|
+
|
|
2983
|
+
.pl-toolcard__body {
|
|
2984
|
+
display: flex;
|
|
2985
|
+
flex-direction: column;
|
|
2986
|
+
gap: 8px;
|
|
2987
|
+
padding: 8px 9px;
|
|
2988
|
+
border-top: var(--pl-border-width) solid var(--pl-color-border);
|
|
2989
|
+
}
|
|
2990
|
+
|
|
2991
|
+
/* subagent `task` nesting — indented rail of child cards */
|
|
2992
|
+
.pl-toolcard__children {
|
|
2993
|
+
display: flex;
|
|
2994
|
+
flex-direction: column;
|
|
2995
|
+
gap: 6px;
|
|
2996
|
+
min-width: 0;
|
|
2997
|
+
margin-left: 14px;
|
|
2998
|
+
padding-left: 10px;
|
|
2999
|
+
border-left: 2px solid var(--pl-color-border);
|
|
3000
|
+
}
|
|
3001
|
+
|
|
3002
|
+
/* body section (input / result) */
|
|
3003
|
+
.pl-toolcard__section {
|
|
3004
|
+
display: flex;
|
|
3005
|
+
flex-direction: column;
|
|
3006
|
+
gap: 4px;
|
|
3007
|
+
min-width: 0;
|
|
3008
|
+
}
|
|
3009
|
+
.pl-toolcard__section-head {
|
|
3010
|
+
display: flex;
|
|
3011
|
+
align-items: center;
|
|
3012
|
+
justify-content: space-between;
|
|
3013
|
+
gap: 8px;
|
|
3014
|
+
}
|
|
3015
|
+
.pl-toolcard__label {
|
|
3016
|
+
font-family: var(--pl-font-mono);
|
|
3017
|
+
font-size: 10px;
|
|
3018
|
+
text-transform: uppercase;
|
|
3019
|
+
letter-spacing: 0.04em;
|
|
3020
|
+
color: var(--pl-color-fg-subtle);
|
|
3021
|
+
}
|
|
3022
|
+
.pl-toolcard__copy {
|
|
3023
|
+
display: inline-flex;
|
|
3024
|
+
align-items: center;
|
|
3025
|
+
justify-content: center;
|
|
3026
|
+
padding: 2px;
|
|
3027
|
+
background: none;
|
|
3028
|
+
border: none;
|
|
3029
|
+
border-radius: var(--pl-radius);
|
|
3030
|
+
color: var(--pl-color-fg-muted);
|
|
3031
|
+
cursor: pointer;
|
|
3032
|
+
transition:
|
|
3033
|
+
background var(--pl-motion-fast) var(--pl-motion-ease),
|
|
3034
|
+
color var(--pl-motion-fast) var(--pl-motion-ease);
|
|
3035
|
+
}
|
|
3036
|
+
.pl-toolcard__copy:hover {
|
|
3037
|
+
color: var(--pl-color-fg);
|
|
3038
|
+
background: var(--pl-color-bg-hover);
|
|
3039
|
+
}
|
|
3040
|
+
|
|
2662
3041
|
/* ── ui component: theming.css ─────────────────────────────────────────────── */
|
|
2663
3042
|
/* @protolabsai/ui — ThemePanel (live token editor). All --pl-* driven, so the
|
|
2664
3043
|
panel itself re-skins live as you edit. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@protolabsai/ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.22.0",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public",
|
|
6
6
|
"registry": "https://registry.npmjs.org/"
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
"./data": "./src/data.tsx",
|
|
22
22
|
"./menu": "./src/menu.tsx",
|
|
23
23
|
"./app-shell": "./src/app-shell.tsx",
|
|
24
|
+
"./tool-card": "./src/tool-card.tsx",
|
|
24
25
|
"./theming": "./src/theming.tsx",
|
|
25
26
|
"./styles.css": "./src/styles.css",
|
|
26
27
|
"./plugin-kit.css": "./dist/plugin-kit.css",
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
2
|
+
import { Empty, Button } from "./primitives";
|
|
3
|
+
|
|
4
|
+
const meta: Meta<typeof Empty> = { title: "Components/Primitives/Empty" };
|
|
5
|
+
export default meta;
|
|
6
|
+
type Story = StoryObj<typeof Empty>;
|
|
7
|
+
|
|
8
|
+
const Inbox = () => (
|
|
9
|
+
<svg viewBox="0 0 24 24" width="28" height="28" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round">
|
|
10
|
+
<path d="M22 12h-6l-2 3h-4l-2-3H2" />
|
|
11
|
+
<path d="M5.45 5.11 2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3.45-6.89A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z" />
|
|
12
|
+
</svg>
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
/** Slotted: icon + title + description + action — the standard scaffold. */
|
|
16
|
+
export const Slotted: Story = {
|
|
17
|
+
render: () => (
|
|
18
|
+
<div style={{ maxWidth: 420, border: "1px solid var(--pl-color-border)", borderRadius: 8 }}>
|
|
19
|
+
<Empty
|
|
20
|
+
icon={<Inbox />}
|
|
21
|
+
title="No agents yet"
|
|
22
|
+
description="Discover protoAgents on your network to add them as delegates."
|
|
23
|
+
action={<Button>Discover</Button>}
|
|
24
|
+
/>
|
|
25
|
+
</div>
|
|
26
|
+
),
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/** Bare form still works unchanged (back-compat). */
|
|
30
|
+
export const Bare: Story = {
|
|
31
|
+
render: () => <Empty>no open contracts</Empty>,
|
|
32
|
+
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
2
|
+
import { Grid } from "./layout";
|
|
3
|
+
import { Card } from "./primitives";
|
|
4
|
+
|
|
5
|
+
const meta: Meta<typeof Grid> = { title: "Components/Layout/Grid" };
|
|
6
|
+
export default meta;
|
|
7
|
+
type Story = StoryObj<typeof Grid>;
|
|
8
|
+
|
|
9
|
+
const cards = (n: number) =>
|
|
10
|
+
Array.from({ length: n }, (_, i) => (
|
|
11
|
+
<Card key={i} style={{ padding: 16, fontFamily: "var(--pl-font-mono)", fontSize: 13, color: "var(--pl-color-fg-muted)" }}>
|
|
12
|
+
card {i + 1}
|
|
13
|
+
</Card>
|
|
14
|
+
));
|
|
15
|
+
|
|
16
|
+
/** `min` — auto-fill: as many columns of ≥ the floor as fit. Resize the canvas. */
|
|
17
|
+
export const AutoFill: Story = {
|
|
18
|
+
render: () => <Grid min="14rem" gap="md">{cards(8)}</Grid>,
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/** `cols` — responsive count, cascading by breakpoint (1 → 2 at md → 3 at xl). */
|
|
22
|
+
export const ResponsiveCols: Story = {
|
|
23
|
+
render: () => (
|
|
24
|
+
<Grid cols={{ base: 1, md: 2, xl: 3 }} gap="md">
|
|
25
|
+
{cards(6)}
|
|
26
|
+
</Grid>
|
|
27
|
+
),
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/** `cols` — a fixed count. */
|
|
31
|
+
export const FixedCols: Story = {
|
|
32
|
+
render: () => (
|
|
33
|
+
<Grid cols={4} gap="lg">
|
|
34
|
+
{cards(8)}
|
|
35
|
+
</Grid>
|
|
36
|
+
),
|
|
37
|
+
};
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
2
|
+
import { useState } from "react";
|
|
3
|
+
import { TabBar } from "./navigation";
|
|
4
|
+
import type { TabBarItem } from "./navigation";
|
|
5
|
+
|
|
6
|
+
const meta: Meta = { title: "Components/Navigation/TabBar" };
|
|
7
|
+
export default meta;
|
|
8
|
+
type Story = StoryObj;
|
|
9
|
+
|
|
10
|
+
/** Full browser-style session tabs: close ✕, double-click to rename, "+" to add.
|
|
11
|
+
* Drop any callback to hide its affordance. On a narrow canvas the strip scrolls
|
|
12
|
+
* (it does NOT collapse to a select — that can't carry close/rename/add). */
|
|
13
|
+
export const Sessions: Story = {
|
|
14
|
+
render: () => {
|
|
15
|
+
function Demo() {
|
|
16
|
+
const [tabs, setTabs] = useState<TabBarItem[]>([
|
|
17
|
+
{ id: "a", label: "research" },
|
|
18
|
+
{ id: "b", label: "draft", badge: 3 },
|
|
19
|
+
{ id: "c", label: "review" },
|
|
20
|
+
]);
|
|
21
|
+
const [active, setActive] = useState("a");
|
|
22
|
+
let seq = tabs.length;
|
|
23
|
+
return (
|
|
24
|
+
<div style={{ maxWidth: 520 }}>
|
|
25
|
+
<TabBar
|
|
26
|
+
ariaLabel="Sessions"
|
|
27
|
+
items={tabs}
|
|
28
|
+
activeId={active}
|
|
29
|
+
onSelect={setActive}
|
|
30
|
+
onClose={(id) => {
|
|
31
|
+
setTabs((ts) => {
|
|
32
|
+
const next = ts.filter((t) => t.id !== id);
|
|
33
|
+
if (id === active && next[0]) setActive(next[0].id);
|
|
34
|
+
return next;
|
|
35
|
+
});
|
|
36
|
+
}}
|
|
37
|
+
onRename={(id, label) => setTabs((ts) => ts.map((t) => (t.id === id ? { ...t, label } : t)))}
|
|
38
|
+
onAdd={() => {
|
|
39
|
+
const id = `s${++seq}`;
|
|
40
|
+
setTabs((ts) => [...ts, { id, label: `session ${seq}` }]);
|
|
41
|
+
setActive(id);
|
|
42
|
+
}}
|
|
43
|
+
/>
|
|
44
|
+
<p style={{ fontFamily: "var(--pl-font-mono)", fontSize: 12, color: "var(--pl-color-fg-muted)" }}>
|
|
45
|
+
active: {active} · double-click a tab to rename
|
|
46
|
+
</p>
|
|
47
|
+
</div>
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
return <Demo />;
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
/** Read-only: no callbacks beyond select → degrades to a plain strip. */
|
|
55
|
+
export const SelectOnly: Story = {
|
|
56
|
+
render: () => {
|
|
57
|
+
function Demo() {
|
|
58
|
+
const [active, setActive] = useState("a");
|
|
59
|
+
return (
|
|
60
|
+
<TabBar
|
|
61
|
+
ariaLabel="Views"
|
|
62
|
+
items={[
|
|
63
|
+
{ id: "a", label: "overview" },
|
|
64
|
+
{ id: "b", label: "activity" },
|
|
65
|
+
{ id: "c", label: "settings" },
|
|
66
|
+
]}
|
|
67
|
+
activeId={active}
|
|
68
|
+
onSelect={setActive}
|
|
69
|
+
/>
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
return <Demo />;
|
|
73
|
+
},
|
|
74
|
+
};
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
2
|
+
import { ToolCard, ToolCardList, ToolSection } from "./tool-card";
|
|
3
|
+
|
|
4
|
+
const meta: Meta = { title: "Components/ToolCard" };
|
|
5
|
+
export default meta;
|
|
6
|
+
type Story = StoryObj;
|
|
7
|
+
|
|
8
|
+
const Mono = ({ children }: { children: string }) => (
|
|
9
|
+
<pre style={{ margin: 0, fontFamily: "var(--pl-font-mono)", fontSize: 12, color: "var(--pl-color-fg-muted)", whiteSpace: "pre-wrap" }}>{children}</pre>
|
|
10
|
+
);
|
|
11
|
+
|
|
12
|
+
const Globe = () => (
|
|
13
|
+
<svg viewBox="0 0 24 24" width="13" height="13" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
|
|
14
|
+
<circle cx="12" cy="12" r="9" /><path d="M3 12h18M12 3a14 14 0 0 1 0 18M12 3a14 14 0 0 0 0 18" />
|
|
15
|
+
</svg>
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
/** The three states + a collapsed (no-detail) running call. Body is a host slot —
|
|
19
|
+
* here filled with `ToolSection`s; in protoAgent it's the per-tool value renderer. */
|
|
20
|
+
export const States: Story = {
|
|
21
|
+
render: () => (
|
|
22
|
+
<div style={{ maxWidth: 460 }}>
|
|
23
|
+
<ToolCardList>
|
|
24
|
+
<ToolCard name="web_fetch" status="done" icon={<Globe />} duration={1240} defaultOpen>
|
|
25
|
+
<ToolSection label="input" copyText='{"url":"https://protolabs.studio"}'>
|
|
26
|
+
<Mono>{`{ "url": "https://protolabs.studio" }`}</Mono>
|
|
27
|
+
</ToolSection>
|
|
28
|
+
<ToolSection label="result" copyText="200 OK · 18kb">
|
|
29
|
+
<Mono>200 OK · 18kb · text/html</Mono>
|
|
30
|
+
</ToolSection>
|
|
31
|
+
</ToolCard>
|
|
32
|
+
<ToolCard name="web_search" status="running" duration={820}>
|
|
33
|
+
<ToolSection label="input">
|
|
34
|
+
<Mono>protoLabs design system</Mono>
|
|
35
|
+
</ToolSection>
|
|
36
|
+
</ToolCard>
|
|
37
|
+
<ToolCard name="calculator" status="error">
|
|
38
|
+
<ToolSection label="result">
|
|
39
|
+
<Mono>SyntaxError: unexpected token</Mono>
|
|
40
|
+
</ToolSection>
|
|
41
|
+
</ToolCard>
|
|
42
|
+
<ToolCard name="current_time" status="done" duration={12} />
|
|
43
|
+
</ToolCardList>
|
|
44
|
+
</div>
|
|
45
|
+
),
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
/** Subagent `task` with nested child tool calls (the `parentId` grouping). */
|
|
49
|
+
export const Nested: Story = {
|
|
50
|
+
render: () => (
|
|
51
|
+
<div style={{ maxWidth: 460 }}>
|
|
52
|
+
<ToolCard
|
|
53
|
+
name="task"
|
|
54
|
+
status="done"
|
|
55
|
+
duration={4300}
|
|
56
|
+
nested={
|
|
57
|
+
<>
|
|
58
|
+
<ToolCard name="web_search" status="done" duration={910}>
|
|
59
|
+
<ToolSection label="result"><Mono>7 results</Mono></ToolSection>
|
|
60
|
+
</ToolCard>
|
|
61
|
+
<ToolCard name="web_fetch" status="done" duration={1320}>
|
|
62
|
+
<ToolSection label="result"><Mono>200 OK</Mono></ToolSection>
|
|
63
|
+
</ToolCard>
|
|
64
|
+
</>
|
|
65
|
+
}
|
|
66
|
+
>
|
|
67
|
+
<ToolSection label="input"><Mono>research the brand voice</Mono></ToolSection>
|
|
68
|
+
</ToolCard>
|
|
69
|
+
</div>
|
|
70
|
+
),
|
|
71
|
+
};
|
package/src/layout.tsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { HTMLAttributes, ReactNode } from "react";
|
|
1
|
+
import type { CSSProperties, HTMLAttributes, ReactNode } from "react";
|
|
2
2
|
import { cx } from "./internal";
|
|
3
3
|
|
|
4
4
|
export function Stat({ value, label }: { value: ReactNode; label: ReactNode }) {
|
|
@@ -23,6 +23,45 @@ export function Stats({ className, ...rest }: HTMLAttributes<HTMLDivElement>) {
|
|
|
23
23
|
return <div className={cx("pl-stats", className)} {...rest} />;
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
type GridCols = number | { base?: number; sm?: number; md?: number; lg?: number; xl?: number };
|
|
27
|
+
|
|
28
|
+
/** Responsive card grid — the generic `repeat(auto-fill, minmax())` every surface
|
|
29
|
+
* was re-rolling. Pass `min` (auto-fill floor — packs in as many columns as fit)
|
|
30
|
+
* OR `cols` (a fixed count, or a per-breakpoint map). `gap` is on the token scale. */
|
|
31
|
+
export function Grid({
|
|
32
|
+
min,
|
|
33
|
+
cols,
|
|
34
|
+
gap = "md",
|
|
35
|
+
className,
|
|
36
|
+
style,
|
|
37
|
+
...rest
|
|
38
|
+
}: HTMLAttributes<HTMLDivElement> & {
|
|
39
|
+
/** Auto-fill column floor, e.g. "14rem". Columns wrap to fit the container. */
|
|
40
|
+
min?: string;
|
|
41
|
+
/** Fixed column count, or per-breakpoint `{ base, sm, md, lg, xl }`. Ignored if `min` is set. */
|
|
42
|
+
cols?: GridCols;
|
|
43
|
+
gap?: "sm" | "md" | "lg";
|
|
44
|
+
}) {
|
|
45
|
+
const vars: Record<string, string> = {};
|
|
46
|
+
if (min) {
|
|
47
|
+
vars["--pl-grid-min"] = min;
|
|
48
|
+
} else if (cols != null) {
|
|
49
|
+
const c = typeof cols === "number" ? { base: cols } : cols;
|
|
50
|
+
if (c.base != null) vars["--pl-grid-cols"] = String(c.base);
|
|
51
|
+
if (c.sm != null) vars["--pl-grid-cols-sm"] = String(c.sm);
|
|
52
|
+
if (c.md != null) vars["--pl-grid-cols-md"] = String(c.md);
|
|
53
|
+
if (c.lg != null) vars["--pl-grid-cols-lg"] = String(c.lg);
|
|
54
|
+
if (c.xl != null) vars["--pl-grid-cols-xl"] = String(c.xl);
|
|
55
|
+
}
|
|
56
|
+
return (
|
|
57
|
+
<div
|
|
58
|
+
className={cx("pl-grid", min ? "pl-grid--auto" : "pl-grid--cols", `pl-grid--gap-${gap}`, className)}
|
|
59
|
+
style={{ ...vars, ...style } as CSSProperties}
|
|
60
|
+
{...rest}
|
|
61
|
+
/>
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
26
65
|
export type RowProps = {
|
|
27
66
|
/** Left mono label / layer. */
|
|
28
67
|
label: string;
|