@lateralus-ai/shipping-ui 2.0.0-dev.15 → 2.0.0-dev.17
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/components/PageHeader.d.ts +41 -0
- package/dist/components/index.d.ts +1 -0
- package/dist/index.cjs +12 -12
- package/dist/index.d.ts +2 -2
- package/dist/index.esm.js +2182 -2112
- package/dist/patterns/Sidebar/sidebar-styles.d.ts +2 -1
- package/package.json +1 -1
- package/src/components/PageHeader.tsx +132 -0
- package/src/components/index.ts +7 -0
- package/src/index.ts +5 -0
- package/src/patterns/Sidebar/CollapsibleNavGroup.tsx +0 -1
- package/src/patterns/Sidebar/SidebarAction.tsx +14 -10
- package/src/patterns/Sidebar/SidebarEntry.tsx +1 -3
- package/src/patterns/Sidebar/sidebar-styles.ts +2 -1
- package/src/stories/canvases/ContentCanvas.tsx +76 -10
|
@@ -15,7 +15,8 @@ export declare const sidebarSectionShellCollapsed = "relative box-border flex w-
|
|
|
15
15
|
export declare const sidebarUnreadOverlay = "pointer-events-none absolute left-0 top-0 size-6";
|
|
16
16
|
/** Figma unread dot — ~6px, top-right of the 24px indicator overlay. */
|
|
17
17
|
export declare const sidebarUnreadDot = "absolute right-0 top-0 size-1.5 rounded-full bg-[#802c20]";
|
|
18
|
-
|
|
18
|
+
/** Icon + label row inside a section shell — no extra inset (shell already has p-2). */
|
|
19
|
+
export declare const sidebarSectionContent = "flex w-full min-w-0 items-center gap-2";
|
|
19
20
|
export declare const sidebarSectionIconClass = "shrink-0 [&>svg]:size-4";
|
|
20
21
|
export declare const sidebarSectionLabelClass = "min-w-0 flex-1 truncate text-caption-2 tracking-[0.01em]";
|
|
21
22
|
/** Figma sidebar icon buttons — transparent idle, grey hover (no white fill). */
|
package/package.json
CHANGED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type ComponentPropsWithoutRef,
|
|
3
|
+
type MouseEvent,
|
|
4
|
+
type ReactNode,
|
|
5
|
+
} from "react";
|
|
6
|
+
import { cn } from "../utils/cn";
|
|
7
|
+
|
|
8
|
+
export type PageHeaderCrumb = {
|
|
9
|
+
label: string;
|
|
10
|
+
/** Renders a real `<a href>` so native browser controls work. */
|
|
11
|
+
href: string;
|
|
12
|
+
/**
|
|
13
|
+
* Optional SPA handler. Callers should `preventDefault` only for unmodified
|
|
14
|
+
* primary clicks; modifier / middle-click keep native anchor behavior.
|
|
15
|
+
*/
|
|
16
|
+
onClick?: (event: MouseEvent<HTMLAnchorElement>) => void;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export type PageHeaderProps = {
|
|
20
|
+
/** Leading icon (Figma 24×24). */
|
|
21
|
+
icon?: ReactNode;
|
|
22
|
+
/** Standard title, or current (last) segment when `crumbs` is set. */
|
|
23
|
+
title: ReactNode;
|
|
24
|
+
/**
|
|
25
|
+
* Parent breadcrumb links. When present, renders Nested: icon + parents + `/`
|
|
26
|
+
* + current `title` (non-link).
|
|
27
|
+
*/
|
|
28
|
+
crumbs?: PageHeaderCrumb[];
|
|
29
|
+
/** Right-hand actions slot (buttons, menus, etc.). */
|
|
30
|
+
actions?: ReactNode;
|
|
31
|
+
className?: string;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export type PageHeaderShellProps = ComponentPropsWithoutRef<"div">;
|
|
35
|
+
|
|
36
|
+
export type PageHeaderBodyProps = ComponentPropsWithoutRef<"div">;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Page title row — Figma `Header` (389:8850).
|
|
40
|
+
* Variants: Standard (icon + title) | Nested (icon + crumb trail).
|
|
41
|
+
*/
|
|
42
|
+
function PageHeaderRoot({
|
|
43
|
+
icon,
|
|
44
|
+
title,
|
|
45
|
+
crumbs,
|
|
46
|
+
actions,
|
|
47
|
+
className,
|
|
48
|
+
}: PageHeaderProps) {
|
|
49
|
+
const isNested = Boolean(crumbs?.length);
|
|
50
|
+
|
|
51
|
+
return (
|
|
52
|
+
<div
|
|
53
|
+
data-variant={isNested ? "nested" : "standard"}
|
|
54
|
+
className={cn(
|
|
55
|
+
"flex w-full shrink-0 items-center justify-between gap-4",
|
|
56
|
+
"min-h-10",
|
|
57
|
+
className,
|
|
58
|
+
)}
|
|
59
|
+
>
|
|
60
|
+
<div className="flex min-w-0 flex-1 items-center gap-2">
|
|
61
|
+
{icon != null && (
|
|
62
|
+
<span className="inline-flex size-6 shrink-0 items-center justify-center [&>svg]:size-6">
|
|
63
|
+
{icon}
|
|
64
|
+
</span>
|
|
65
|
+
)}
|
|
66
|
+
|
|
67
|
+
{isNested ? (
|
|
68
|
+
<nav aria-label="Breadcrumb" className="min-w-0">
|
|
69
|
+
<ol className="flex min-w-0 flex-wrap items-center gap-2">
|
|
70
|
+
{crumbs!.map((crumb) => (
|
|
71
|
+
<li key={`${crumb.href}-${crumb.label}`} className="contents">
|
|
72
|
+
<a
|
|
73
|
+
href={crumb.href}
|
|
74
|
+
onClick={crumb.onClick}
|
|
75
|
+
className="truncate text-caption-1-em text-display-on-light-secondary transition-colors hover:text-display-on-light-primary"
|
|
76
|
+
>
|
|
77
|
+
{crumb.label}
|
|
78
|
+
</a>
|
|
79
|
+
<span
|
|
80
|
+
aria-hidden
|
|
81
|
+
className="text-caption-1-em text-display-on-light-secondary"
|
|
82
|
+
>
|
|
83
|
+
/
|
|
84
|
+
</span>
|
|
85
|
+
</li>
|
|
86
|
+
))}
|
|
87
|
+
<li
|
|
88
|
+
aria-current="page"
|
|
89
|
+
className="min-w-0 truncate text-caption-1-em text-display-on-light-primary"
|
|
90
|
+
>
|
|
91
|
+
{title}
|
|
92
|
+
</li>
|
|
93
|
+
</ol>
|
|
94
|
+
</nav>
|
|
95
|
+
) : (
|
|
96
|
+
<h1 className="min-w-0 truncate text-caption-1-em text-display-on-light-primary">
|
|
97
|
+
{title}
|
|
98
|
+
</h1>
|
|
99
|
+
)}
|
|
100
|
+
</div>
|
|
101
|
+
|
|
102
|
+
{actions != null && (
|
|
103
|
+
<div className="flex shrink-0 items-center gap-2">{actions}</div>
|
|
104
|
+
)}
|
|
105
|
+
</div>
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** Flex column shell — pin header, scroll body (avoids brittle CSS sticky). */
|
|
110
|
+
function PageHeaderShell({ className, ...props }: PageHeaderShellProps) {
|
|
111
|
+
return (
|
|
112
|
+
<div
|
|
113
|
+
className={cn("flex h-full min-h-0 flex-col", className)}
|
|
114
|
+
{...props}
|
|
115
|
+
/>
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** Scrollable page content under a pinned PageHeader. */
|
|
120
|
+
function PageHeaderBody({ className, ...props }: PageHeaderBodyProps) {
|
|
121
|
+
return (
|
|
122
|
+
<div
|
|
123
|
+
className={cn("min-h-0 flex-1 overflow-y-auto", className)}
|
|
124
|
+
{...props}
|
|
125
|
+
/>
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export const PageHeader = Object.assign(PageHeaderRoot, {
|
|
130
|
+
Shell: PageHeaderShell,
|
|
131
|
+
Body: PageHeaderBody,
|
|
132
|
+
});
|
package/src/components/index.ts
CHANGED
|
@@ -18,6 +18,13 @@ export {
|
|
|
18
18
|
type TabsAppearance,
|
|
19
19
|
} from "./Tabs";
|
|
20
20
|
export { Header, type HeaderProps, type HeaderVariant } from "./Header";
|
|
21
|
+
export {
|
|
22
|
+
PageHeader,
|
|
23
|
+
type PageHeaderProps,
|
|
24
|
+
type PageHeaderCrumb,
|
|
25
|
+
type PageHeaderShellProps,
|
|
26
|
+
type PageHeaderBodyProps,
|
|
27
|
+
} from "./PageHeader";
|
|
21
28
|
export { Entry, type EntryProps, type EntryType, type EntryState, type EntryVariant } from "./Entry";
|
|
22
29
|
export { EmptyState, type EmptyStateProps } from "./EmptyState";
|
|
23
30
|
export {
|
package/src/index.ts
CHANGED
|
@@ -19,6 +19,7 @@ export {
|
|
|
19
19
|
TabsTrigger,
|
|
20
20
|
TabsContent,
|
|
21
21
|
Header,
|
|
22
|
+
PageHeader,
|
|
22
23
|
Entry,
|
|
23
24
|
EmptyState,
|
|
24
25
|
Modal,
|
|
@@ -59,6 +60,10 @@ export type {
|
|
|
59
60
|
TabsAppearance,
|
|
60
61
|
HeaderProps,
|
|
61
62
|
HeaderVariant,
|
|
63
|
+
PageHeaderProps,
|
|
64
|
+
PageHeaderCrumb,
|
|
65
|
+
PageHeaderShellProps,
|
|
66
|
+
PageHeaderBodyProps,
|
|
62
67
|
EntryProps,
|
|
63
68
|
EntryType,
|
|
64
69
|
EntryState,
|
|
@@ -4,9 +4,10 @@ import { cn } from "../../utils/cn";
|
|
|
4
4
|
import {
|
|
5
5
|
sidebarLinkActive,
|
|
6
6
|
sidebarLinkIdle,
|
|
7
|
-
sidebarRowClass,
|
|
8
7
|
sidebarRowInteractiveHover,
|
|
9
8
|
sidebarSectionIconClass,
|
|
9
|
+
sidebarSectionShell,
|
|
10
|
+
sidebarSectionShellCollapsed,
|
|
10
11
|
} from "./sidebar-styles";
|
|
11
12
|
|
|
12
13
|
export type SidebarActionProps = {
|
|
@@ -33,20 +34,23 @@ export const SidebarAction = ({
|
|
|
33
34
|
type="button"
|
|
34
35
|
onClick={onClick}
|
|
35
36
|
aria-label={label}
|
|
36
|
-
className={
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
),
|
|
37
|
+
className={cn(
|
|
38
|
+
collapsed ? sidebarSectionShellCollapsed : sidebarSectionShell,
|
|
39
|
+
"cursor-pointer text-left",
|
|
40
|
+
active ? sidebarLinkActive : sidebarLinkIdle,
|
|
41
|
+
!active && sidebarRowInteractiveHover,
|
|
42
|
+
!collapsed && "gap-2",
|
|
43
|
+
className,
|
|
44
44
|
)}
|
|
45
45
|
data-active={active}
|
|
46
46
|
data-collapsed={collapsed}
|
|
47
47
|
>
|
|
48
48
|
{icon && <span className={sidebarSectionIconClass}>{icon}</span>}
|
|
49
|
-
{!collapsed &&
|
|
49
|
+
{!collapsed && (
|
|
50
|
+
<span className="min-w-0 flex-1 truncate text-caption-2 tracking-[0.01em]">
|
|
51
|
+
{label}
|
|
52
|
+
</span>
|
|
53
|
+
)}
|
|
50
54
|
</button>
|
|
51
55
|
);
|
|
52
56
|
|
|
@@ -75,9 +75,7 @@ export const SidebarEntry = ({
|
|
|
75
75
|
/>
|
|
76
76
|
|
|
77
77
|
<div className="pointer-events-none relative z-10 flex min-w-0 flex-1 items-center gap-2 text-caption-2">
|
|
78
|
-
{icon &&
|
|
79
|
-
<span className="shrink-0 px-1 py-0.5 [&>svg]:size-4">{icon}</span>
|
|
80
|
-
)}
|
|
78
|
+
{icon && <span className="shrink-0 [&>svg]:size-4">{icon}</span>}
|
|
81
79
|
<span className="truncate">{label}</span>
|
|
82
80
|
</div>
|
|
83
81
|
|
|
@@ -30,7 +30,8 @@ export const sidebarUnreadOverlay =
|
|
|
30
30
|
export const sidebarUnreadDot =
|
|
31
31
|
"absolute right-0 top-0 size-1.5 rounded-full bg-[#802c20]";
|
|
32
32
|
|
|
33
|
-
|
|
33
|
+
/** Icon + label row inside a section shell — no extra inset (shell already has p-2). */
|
|
34
|
+
export const sidebarSectionContent = "flex w-full min-w-0 items-center gap-2";
|
|
34
35
|
|
|
35
36
|
export const sidebarSectionIconClass = "shrink-0 [&>svg]:size-4";
|
|
36
37
|
|
|
@@ -1,15 +1,16 @@
|
|
|
1
|
-
import { Badge, Button } from "../../primitives";
|
|
1
|
+
import { Badge, Button, IconButton } from "../../primitives";
|
|
2
2
|
import {
|
|
3
3
|
EmptyState,
|
|
4
4
|
Entry,
|
|
5
5
|
Header,
|
|
6
|
+
PageHeader,
|
|
6
7
|
Tab,
|
|
7
8
|
Tabs,
|
|
8
9
|
TabsContent,
|
|
9
10
|
TabsList,
|
|
10
11
|
TabsTrigger,
|
|
11
12
|
} from "../../components";
|
|
12
|
-
import { TickIcon } from "../../icons";
|
|
13
|
+
import { MoreIcon, ShipIcon, TickIcon } from "../../icons";
|
|
13
14
|
import { WorkflowsIllustration } from "../../illustrations";
|
|
14
15
|
import {
|
|
15
16
|
FigmaContent,
|
|
@@ -20,6 +21,15 @@ import {
|
|
|
20
21
|
} from "../_layout";
|
|
21
22
|
import { FIGMA_WIDTHS } from "./figma-widths";
|
|
22
23
|
|
|
24
|
+
const demoActions = (
|
|
25
|
+
<>
|
|
26
|
+
<Button hierarchy="secondary">Button</Button>
|
|
27
|
+
<IconButton hierarchy="tertiary" size="small" aria-label="More">
|
|
28
|
+
<MoreIcon size="small" />
|
|
29
|
+
</IconButton>
|
|
30
|
+
</>
|
|
31
|
+
);
|
|
32
|
+
|
|
23
33
|
export const ContentCanvas = () => (
|
|
24
34
|
<FigmaPage title="Content" width={FIGMA_WIDTHS.content}>
|
|
25
35
|
<FigmaContent>
|
|
@@ -40,13 +50,22 @@ export const ContentCanvas = () => (
|
|
|
40
50
|
<TabsTrigger value="reports">Reports</TabsTrigger>
|
|
41
51
|
<TabsTrigger value="issues">Issues</TabsTrigger>
|
|
42
52
|
</TabsList>
|
|
43
|
-
<TabsContent
|
|
53
|
+
<TabsContent
|
|
54
|
+
value="all"
|
|
55
|
+
className="pt-3 text-caption-2 text-display-on-light-secondary"
|
|
56
|
+
>
|
|
44
57
|
All content
|
|
45
58
|
</TabsContent>
|
|
46
|
-
<TabsContent
|
|
59
|
+
<TabsContent
|
|
60
|
+
value="reports"
|
|
61
|
+
className="pt-3 text-caption-2 text-display-on-light-secondary"
|
|
62
|
+
>
|
|
47
63
|
Reports content
|
|
48
64
|
</TabsContent>
|
|
49
|
-
<TabsContent
|
|
65
|
+
<TabsContent
|
|
66
|
+
value="issues"
|
|
67
|
+
className="pt-3 text-caption-2 text-display-on-light-secondary"
|
|
68
|
+
>
|
|
50
69
|
Issues content
|
|
51
70
|
</TabsContent>
|
|
52
71
|
</Tabs>
|
|
@@ -60,10 +79,16 @@ export const ContentCanvas = () => (
|
|
|
60
79
|
<Badge color="blue">3</Badge>
|
|
61
80
|
</TabsTrigger>
|
|
62
81
|
</TabsList>
|
|
63
|
-
<TabsContent
|
|
82
|
+
<TabsContent
|
|
83
|
+
value="active"
|
|
84
|
+
className="pt-3 text-caption-2 text-display-on-light-secondary"
|
|
85
|
+
>
|
|
64
86
|
Active content
|
|
65
87
|
</TabsContent>
|
|
66
|
-
<TabsContent
|
|
88
|
+
<TabsContent
|
|
89
|
+
value="completed"
|
|
90
|
+
className="pt-3 text-caption-2 text-display-on-light-secondary"
|
|
91
|
+
>
|
|
67
92
|
Completed content
|
|
68
93
|
</TabsContent>
|
|
69
94
|
</Tabs>
|
|
@@ -92,7 +117,44 @@ export const ContentCanvas = () => (
|
|
|
92
117
|
</div>
|
|
93
118
|
</FigmaSection>
|
|
94
119
|
|
|
95
|
-
<FigmaSection label="
|
|
120
|
+
<FigmaSection label="PageHeader">
|
|
121
|
+
<div className="max-w-[1120px] space-y-8">
|
|
122
|
+
<FigmaVariant label="Standard">
|
|
123
|
+
<PageHeader
|
|
124
|
+
icon={<ShipIcon size="large" />}
|
|
125
|
+
title="Title"
|
|
126
|
+
actions={demoActions}
|
|
127
|
+
/>
|
|
128
|
+
</FigmaVariant>
|
|
129
|
+
<FigmaVariant label="Nested">
|
|
130
|
+
<PageHeader
|
|
131
|
+
icon={<ShipIcon size="large" />}
|
|
132
|
+
title="Title"
|
|
133
|
+
crumbs={[{ label: "Parent", href: "#parent" }]}
|
|
134
|
+
actions={demoActions}
|
|
135
|
+
/>
|
|
136
|
+
</FigmaVariant>
|
|
137
|
+
<FigmaVariant label="Shell (pinned header)">
|
|
138
|
+
<div className="h-64 overflow-hidden rounded-control border border-divider-primary">
|
|
139
|
+
<PageHeader.Shell className="bg-background-primary p-4">
|
|
140
|
+
<PageHeader
|
|
141
|
+
icon={<ShipIcon size="large" />}
|
|
142
|
+
title="Fleet"
|
|
143
|
+
crumbs={[{ label: "Ships", href: "#ships" }]}
|
|
144
|
+
actions={demoActions}
|
|
145
|
+
/>
|
|
146
|
+
<PageHeader.Body className="mt-4 space-y-2 text-caption-2 text-display-on-light-secondary">
|
|
147
|
+
{Array.from({ length: 20 }, (_, i) => (
|
|
148
|
+
<p key={i}>Scrollable body line {i + 1}</p>
|
|
149
|
+
))}
|
|
150
|
+
</PageHeader.Body>
|
|
151
|
+
</PageHeader.Shell>
|
|
152
|
+
</div>
|
|
153
|
+
</FigmaVariant>
|
|
154
|
+
</div>
|
|
155
|
+
</FigmaSection>
|
|
156
|
+
|
|
157
|
+
<FigmaSection label="Header (legacy)">
|
|
96
158
|
<div className="max-w-xl space-y-4">
|
|
97
159
|
<Header title="Fleet overview" />
|
|
98
160
|
<Header
|
|
@@ -115,7 +177,9 @@ export const ContentCanvas = () => (
|
|
|
115
177
|
state="active"
|
|
116
178
|
title="Ballast Pump Not Starting"
|
|
117
179
|
subtitle="The fuel pump won’t start consistently."
|
|
118
|
-
trailing={
|
|
180
|
+
trailing={
|
|
181
|
+
<span className="text-display-on-light-secondary">···</span>
|
|
182
|
+
}
|
|
119
183
|
/>
|
|
120
184
|
<Entry
|
|
121
185
|
variant="issue"
|
|
@@ -145,7 +209,9 @@ export const ContentCanvas = () => (
|
|
|
145
209
|
<div className="w-80">
|
|
146
210
|
<EmptyState
|
|
147
211
|
title="No reports"
|
|
148
|
-
illustration={
|
|
212
|
+
illustration={
|
|
213
|
+
<WorkflowsIllustration className="mx-auto h-24 w-32" />
|
|
214
|
+
}
|
|
149
215
|
action={{ children: "Create report", hierarchy: "primary" }}
|
|
150
216
|
/>
|
|
151
217
|
</div>
|