@protolabsai/ui 0.12.0 → 0.13.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/package.json +1 -1
- package/src/AppShell.full.stories.tsx +19 -2
- package/src/app-shell.tsx +44 -0
- package/src/primitives.tsx +16 -0
- package/src/styles/app-shell.css +56 -0
- package/src/styles/primitives.css +7 -0
package/package.json
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import type { Meta, StoryObj } from "@storybook/react";
|
|
2
2
|
import { useState } from "react";
|
|
3
|
-
import { Badge, Button } from "./primitives";
|
|
3
|
+
import { Badge, Button, Logo } from "./primitives";
|
|
4
4
|
import { PanelHeader } from "./navigation";
|
|
5
5
|
import { StatusDot } from "./data";
|
|
6
|
-
import { AppShell, MobileNav, SurfaceRail, UtilityBar } from "./app-shell";
|
|
6
|
+
import { AppShell, Header, MobileNav, SurfaceRail, UtilityBar } from "./app-shell";
|
|
7
7
|
import type { MobileItem, RailItem } from "./app-shell";
|
|
8
8
|
|
|
9
|
+
const LOGO =
|
|
10
|
+
"data:image/svg+xml," +
|
|
11
|
+
encodeURIComponent('<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><rect width="24" height="24" rx="6" fill="#9b87f2"/></svg>');
|
|
12
|
+
|
|
9
13
|
const meta: Meta = { title: "Components/AppShell" };
|
|
10
14
|
export default meta;
|
|
11
15
|
type Story = StoryObj;
|
|
@@ -84,6 +88,19 @@ export const Full: Story = {
|
|
|
84
88
|
{surfaceBody(activeRight)}
|
|
85
89
|
</>
|
|
86
90
|
}
|
|
91
|
+
header={
|
|
92
|
+
<Header
|
|
93
|
+
logo={<Logo src={LOGO} alt="" />}
|
|
94
|
+
name="protoAgent"
|
|
95
|
+
org="protoLabs.studio"
|
|
96
|
+
status={<StatusDot status="success" pulse />}
|
|
97
|
+
actions={
|
|
98
|
+
<Button size="sm" variant="ghost">
|
|
99
|
+
Settings
|
|
100
|
+
</Button>
|
|
101
|
+
}
|
|
102
|
+
/>
|
|
103
|
+
}
|
|
87
104
|
utilityBar={
|
|
88
105
|
<UtilityBar
|
|
89
106
|
start={<StatusDot status="success" pulse label="connected · 3 agents" />}
|
package/src/app-shell.tsx
CHANGED
|
@@ -280,6 +280,9 @@ export type AppShellProps = {
|
|
|
280
280
|
onMobileSelect?: (id: string) => void;
|
|
281
281
|
quickBarIds?: string[];
|
|
282
282
|
mobileBreakpoint?: number;
|
|
283
|
+
/** Top 48px bar — brand lockup + status/actions. Presentation only; the host
|
|
284
|
+
* fills it (compose a `Header`). Renders on both desktop and mobile. */
|
|
285
|
+
header?: ReactNode;
|
|
283
286
|
/** Bottom 40px track — global utility actions / status / tickers. Presentation
|
|
284
287
|
* only; the host fills it (compose a `UtilityBar`). Desktop only. */
|
|
285
288
|
utilityBar?: ReactNode;
|
|
@@ -311,6 +314,7 @@ export function AppShell({
|
|
|
311
314
|
onMobileSelect,
|
|
312
315
|
quickBarIds,
|
|
313
316
|
mobileBreakpoint = 768,
|
|
317
|
+
header,
|
|
314
318
|
utilityBar,
|
|
315
319
|
className,
|
|
316
320
|
}: AppShellProps) {
|
|
@@ -433,6 +437,7 @@ export function AppShell({
|
|
|
433
437
|
if (isMobile && mobileItems && onMobileSelect && quickBarIds) {
|
|
434
438
|
return (
|
|
435
439
|
<div className={cx("pl-appshell", "pl-appshell--mobile", className)}>
|
|
440
|
+
{header != null && <div className="pl-appshell__header">{header}</div>}
|
|
436
441
|
<div className="pl-appshell__mobile-stage">{leftContent}</div>
|
|
437
442
|
<MobileNav
|
|
438
443
|
items={mobileItems}
|
|
@@ -450,6 +455,7 @@ export function AppShell({
|
|
|
450
455
|
|
|
451
456
|
const renderShell = (leftRail: ReactNode, rightRail: ReactNode) => (
|
|
452
457
|
<div className={cx("pl-appshell-frame", className)}>
|
|
458
|
+
{header != null && <div className="pl-appshell__header">{header}</div>}
|
|
453
459
|
<div className="pl-appshell">
|
|
454
460
|
{leftRail}
|
|
455
461
|
<main className="pl-appshell__col pl-appshell__col--left">{leftContent}</main>
|
|
@@ -539,3 +545,41 @@ export function UtilityBar({
|
|
|
539
545
|
</div>
|
|
540
546
|
);
|
|
541
547
|
}
|
|
548
|
+
|
|
549
|
+
/** White-label top-bar chrome (48px header row) — brand lockup (logo · name ·
|
|
550
|
+
* org) on the left, status + actions on the right. Pass to AppShell's `header`
|
|
551
|
+
* slot. `dragRegion` marks it as the desktop window drag region (Tauri). */
|
|
552
|
+
export function Header({
|
|
553
|
+
logo,
|
|
554
|
+
name,
|
|
555
|
+
org,
|
|
556
|
+
status,
|
|
557
|
+
actions,
|
|
558
|
+
dragRegion,
|
|
559
|
+
className,
|
|
560
|
+
}: {
|
|
561
|
+
logo?: ReactNode;
|
|
562
|
+
name?: ReactNode;
|
|
563
|
+
org?: ReactNode;
|
|
564
|
+
status?: ReactNode;
|
|
565
|
+
actions?: ReactNode;
|
|
566
|
+
dragRegion?: boolean;
|
|
567
|
+
className?: string;
|
|
568
|
+
}) {
|
|
569
|
+
return (
|
|
570
|
+
<div className={cx("pl-header", className)} {...(dragRegion ? { "data-tauri-drag-region": "" } : {})}>
|
|
571
|
+
<div className="pl-header__brand">
|
|
572
|
+
{logo}
|
|
573
|
+
{(name != null || org != null) && (
|
|
574
|
+
<div className="pl-header__lockup">
|
|
575
|
+
{name != null && <span className="pl-header__name">{name}</span>}
|
|
576
|
+
{org != null && <span className="pl-header__org">{org}</span>}
|
|
577
|
+
</div>
|
|
578
|
+
)}
|
|
579
|
+
</div>
|
|
580
|
+
<div className="pl-header__spacer" />
|
|
581
|
+
{status != null && <div className="pl-header__status">{status}</div>}
|
|
582
|
+
{actions != null && <div className="pl-header__actions">{actions}</div>}
|
|
583
|
+
</div>
|
|
584
|
+
);
|
|
585
|
+
}
|
package/src/primitives.tsx
CHANGED
|
@@ -154,3 +154,19 @@ export function Tag({
|
|
|
154
154
|
</span>
|
|
155
155
|
);
|
|
156
156
|
}
|
|
157
|
+
|
|
158
|
+
/** Brand mark — a token-sized, contained `<img>` (square by default). The
|
|
159
|
+
* white-label logo primitive (vs Avatar, which is a person/initials affordance). */
|
|
160
|
+
export function Logo({
|
|
161
|
+
src,
|
|
162
|
+
alt = "",
|
|
163
|
+
size = 22,
|
|
164
|
+
className,
|
|
165
|
+
}: {
|
|
166
|
+
src: string;
|
|
167
|
+
alt?: string;
|
|
168
|
+
size?: number;
|
|
169
|
+
className?: string;
|
|
170
|
+
}) {
|
|
171
|
+
return <img className={cx("pl-logo", className)} src={src} alt={alt} width={size} height={size} />;
|
|
172
|
+
}
|
package/src/styles/app-shell.css
CHANGED
|
@@ -274,3 +274,59 @@
|
|
|
274
274
|
box-shadow: var(--pl-shadow-popover);
|
|
275
275
|
cursor: grabbing;
|
|
276
276
|
}
|
|
277
|
+
|
|
278
|
+
/* ── Header (top bar) ─────────────────────────────────────────────────────────── */
|
|
279
|
+
.pl-appshell__header {
|
|
280
|
+
flex: 0 0 48px;
|
|
281
|
+
height: 48px;
|
|
282
|
+
display: flex;
|
|
283
|
+
align-items: center;
|
|
284
|
+
border-bottom: var(--pl-border-width) solid var(--pl-color-border);
|
|
285
|
+
background: var(--pl-color-bg-raised);
|
|
286
|
+
}
|
|
287
|
+
.pl-header {
|
|
288
|
+
display: flex;
|
|
289
|
+
align-items: center;
|
|
290
|
+
gap: var(--pl-space-3);
|
|
291
|
+
width: 100%;
|
|
292
|
+
height: 100%;
|
|
293
|
+
padding: 0 var(--pl-space-3);
|
|
294
|
+
}
|
|
295
|
+
.pl-header__brand {
|
|
296
|
+
display: flex;
|
|
297
|
+
align-items: center;
|
|
298
|
+
gap: var(--pl-space-2);
|
|
299
|
+
min-width: 0;
|
|
300
|
+
}
|
|
301
|
+
.pl-header__lockup {
|
|
302
|
+
display: flex;
|
|
303
|
+
flex-direction: column;
|
|
304
|
+
justify-content: center;
|
|
305
|
+
min-width: 0;
|
|
306
|
+
line-height: 1.15;
|
|
307
|
+
}
|
|
308
|
+
.pl-header__name {
|
|
309
|
+
overflow: hidden;
|
|
310
|
+
font-size: 13px;
|
|
311
|
+
font-weight: var(--pl-font-weight-medium);
|
|
312
|
+
color: var(--pl-color-fg);
|
|
313
|
+
text-overflow: ellipsis;
|
|
314
|
+
white-space: nowrap;
|
|
315
|
+
}
|
|
316
|
+
.pl-header__org {
|
|
317
|
+
font-family: var(--pl-font-mono);
|
|
318
|
+
font-size: 10px;
|
|
319
|
+
color: var(--pl-color-fg-muted);
|
|
320
|
+
}
|
|
321
|
+
.pl-header__spacer {
|
|
322
|
+
flex: 1;
|
|
323
|
+
}
|
|
324
|
+
.pl-header__status {
|
|
325
|
+
display: flex;
|
|
326
|
+
align-items: center;
|
|
327
|
+
}
|
|
328
|
+
.pl-header__actions {
|
|
329
|
+
display: flex;
|
|
330
|
+
align-items: center;
|
|
331
|
+
gap: var(--pl-space-2);
|
|
332
|
+
}
|
|
@@ -285,3 +285,10 @@
|
|
|
285
285
|
color: var(--pl-color-fg);
|
|
286
286
|
background: var(--pl-color-bg-hover);
|
|
287
287
|
}
|
|
288
|
+
|
|
289
|
+
/* ── Logo ────────────────────────────────────────────────────────────────────── */
|
|
290
|
+
.pl-logo {
|
|
291
|
+
display: block;
|
|
292
|
+
flex-shrink: 0;
|
|
293
|
+
object-fit: contain;
|
|
294
|
+
}
|