@lateralus-ai/shipping-ui 2.0.0-dev.20 → 2.0.0-dev.21
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/domain/Filters/FilterDropdown.d.ts +93 -0
- package/dist/domain/Filters/FilterPill.d.ts +12 -5
- package/dist/domain/Filters/FilterPills.d.ts +21 -0
- package/dist/domain/Filters/FilteredPill.d.ts +15 -5
- package/dist/domain/Filters/formatActiveFilterChipLabel.d.ts +11 -0
- package/dist/domain/Filters/index.d.ts +8 -3
- package/dist/index.cjs +33 -33
- package/dist/index.esm.js +7299 -5989
- package/package.json +2 -1
- package/src/components/EmptyState.tsx +44 -44
- package/src/components/Modal.tsx +141 -141
- package/src/components/PageHeader.tsx +132 -132
- package/src/components/ScrollableList.tsx +61 -61
- package/src/components/Tabs.tsx +146 -146
- package/src/components/index.ts +56 -56
- package/src/domain/Filters/FilterDropdown.tsx +438 -0
- package/src/domain/Filters/FilterPill.tsx +54 -18
- package/src/domain/Filters/FilterPills.tsx +41 -0
- package/src/domain/Filters/FilteredPill.tsx +40 -23
- package/src/domain/Filters/FiltersBar.tsx +15 -19
- package/src/domain/Filters/formatActiveFilterChipLabel.ts +23 -0
- package/src/domain/Filters/index.ts +35 -3
- package/src/icons/arrow-paths.ts +8 -8
- package/src/icons/chevron-paths.ts +17 -17
- package/src/icons/icons-data.ts +656 -656
- package/src/index.ts +85 -85
- package/src/patterns/Search/ResultRow.tsx +44 -44
- package/src/patterns/Search/SearchModal.tsx +310 -310
- package/src/patterns/Search/index.ts +31 -31
- package/src/patterns/Sidebar/assets/logo-compliance-mark.png +5 -5
- package/src/patterns/Sidebar/assets/logo-compliance-mask.png +13 -13
- package/src/patterns/Sidebar/assets/logo-compliance.svg +17 -17
- package/src/patterns/Sidebar/assets/logo-technical-avatar.svg +17 -17
- package/src/patterns/Sidebar/assets/logo-technical-mark.png +5 -5
- package/src/patterns/Sidebar/assets/logo-technical.svg +16 -16
- package/src/patterns/Skeleton/Skeleton.tsx +56 -56
- package/src/primitives/button-styles.ts +92 -92
- package/src/stories/canvases/ButtonsCanvas.tsx +107 -107
- package/src/stories/canvases/ButtonsMatrixCanvas.tsx +65 -65
- package/src/stories/canvases/ContentCanvas.tsx +353 -353
- package/src/stories/canvases/FiltersCanvas.tsx +156 -30
- package/src/stories/canvases/SearchCanvas.tsx +150 -150
- package/src/stories/canvases/figma-buttons-layout.ts +83 -83
- package/src/stories/canvases/figma-widths.ts +28 -28
- package/src/stories/canvases/helpers.tsx +146 -146
- package/src/stories/components/Buttons.stories.tsx +11 -11
- package/src/stories/components/Chat.stories.tsx +11 -11
- package/src/stories/components/Content.stories.tsx +11 -11
- package/src/stories/components/Core.stories.tsx +11 -11
- package/src/stories/components/DomainForms.stories.tsx +11 -11
- package/src/stories/components/Filters.stories.tsx +11 -11
- package/src/stories/components/Forms.stories.tsx +11 -11
- package/src/stories/components/Icons.stories.tsx +11 -11
- package/src/stories/components/Illustrations.stories.tsx +11 -11
- package/src/stories/components/Library.stories.tsx +11 -11
- package/src/stories/components/Modals.stories.tsx +11 -11
- package/src/stories/components/Report.stories.tsx +11 -11
- package/src/stories/components/ReportLayout.stories.tsx +11 -11
- package/src/stories/components/Search.stories.tsx +11 -11
- package/src/stories/components/Settings.stories.tsx +11 -11
- package/src/stories/components/Ships.stories.tsx +11 -11
- package/src/stories/components/SidebarLayouts.stories.tsx +11 -11
- package/src/stories/components/Skeletons.stories.tsx +11 -11
- package/src/stories/components/Workflows.stories.tsx +11 -11
- package/src/stories/style-guide/Buttons.stories.tsx +11 -11
- package/src/stories/style-guide/ColorTokens.stories.tsx +11 -11
- package/src/stories/style-guide/Colors.stories.tsx +11 -11
- package/src/stories/style-guide/RaiseLevels.stories.tsx +11 -11
- package/src/stories/style-guide/Typography.stories.tsx +11 -11
- package/src/tailwind-theme.ts +186 -186
- package/src/theme-entry.ts +2 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lateralus-ai/shipping-ui",
|
|
3
|
-
"version": "2.0.0-dev.
|
|
3
|
+
"version": "2.0.0-dev.21",
|
|
4
4
|
"description": "Shared UI theme and components for Lateralus shipping applications",
|
|
5
5
|
"main": "dist/index.cjs",
|
|
6
6
|
"module": "dist/index.esm.js",
|
|
@@ -112,6 +112,7 @@
|
|
|
112
112
|
"dependencies": {
|
|
113
113
|
"@radix-ui/react-dialog": "^1.1.19",
|
|
114
114
|
"@radix-ui/react-dropdown-menu": "^2.1.20",
|
|
115
|
+
"@radix-ui/react-popover": "^1.1.15",
|
|
115
116
|
"@radix-ui/react-tabs": "^1.1.17",
|
|
116
117
|
"@radix-ui/react-tooltip": "^1.2.8",
|
|
117
118
|
"@react-hooks-library/core": "^0.6.2",
|
|
@@ -1,44 +1,44 @@
|
|
|
1
|
-
import { type ReactNode } from "react";
|
|
2
|
-
import { Button, type ButtonProps } from "../primitives";
|
|
3
|
-
import { cn } from "../utils/cn";
|
|
4
|
-
|
|
5
|
-
export type EmptyStateProps = {
|
|
6
|
-
illustration?: ReactNode;
|
|
7
|
-
title: string;
|
|
8
|
-
description?: string;
|
|
9
|
-
action?: Pick<ButtonProps, "children" | "onClick" | "hierarchy" | "icon" | "startIcon">;
|
|
10
|
-
className?: string;
|
|
11
|
-
};
|
|
12
|
-
|
|
13
|
-
export const EmptyState = ({
|
|
14
|
-
illustration,
|
|
15
|
-
title,
|
|
16
|
-
description,
|
|
17
|
-
action,
|
|
18
|
-
className,
|
|
19
|
-
}: EmptyStateProps) => (
|
|
20
|
-
<div
|
|
21
|
-
className={cn(
|
|
22
|
-
"flex max-w-sm flex-col items-center gap-4 px-6 py-10 text-center",
|
|
23
|
-
className,
|
|
24
|
-
)}
|
|
25
|
-
>
|
|
26
|
-
{illustration && <div className="shrink-0">{illustration}</div>}
|
|
27
|
-
<div className="flex flex-col gap-1">
|
|
28
|
-
<h3 className="text-subheader-em text-display-on-light-primary">{title}</h3>
|
|
29
|
-
{description && (
|
|
30
|
-
<p className="text-body text-display-on-light-secondary">{description}</p>
|
|
31
|
-
)}
|
|
32
|
-
</div>
|
|
33
|
-
{action && (
|
|
34
|
-
<Button
|
|
35
|
-
hierarchy={action.hierarchy ?? "primary"}
|
|
36
|
-
onClick={action.onClick}
|
|
37
|
-
icon={action.icon}
|
|
38
|
-
startIcon={action.startIcon}
|
|
39
|
-
>
|
|
40
|
-
{action.children}
|
|
41
|
-
</Button>
|
|
42
|
-
)}
|
|
43
|
-
</div>
|
|
44
|
-
);
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
import { Button, type ButtonProps } from "../primitives";
|
|
3
|
+
import { cn } from "../utils/cn";
|
|
4
|
+
|
|
5
|
+
export type EmptyStateProps = {
|
|
6
|
+
illustration?: ReactNode;
|
|
7
|
+
title: string;
|
|
8
|
+
description?: string;
|
|
9
|
+
action?: Pick<ButtonProps, "children" | "onClick" | "hierarchy" | "icon" | "startIcon">;
|
|
10
|
+
className?: string;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export const EmptyState = ({
|
|
14
|
+
illustration,
|
|
15
|
+
title,
|
|
16
|
+
description,
|
|
17
|
+
action,
|
|
18
|
+
className,
|
|
19
|
+
}: EmptyStateProps) => (
|
|
20
|
+
<div
|
|
21
|
+
className={cn(
|
|
22
|
+
"flex max-w-sm flex-col items-center gap-4 px-6 py-10 text-center",
|
|
23
|
+
className,
|
|
24
|
+
)}
|
|
25
|
+
>
|
|
26
|
+
{illustration && <div className="shrink-0">{illustration}</div>}
|
|
27
|
+
<div className="flex flex-col gap-1">
|
|
28
|
+
<h3 className="text-subheader-em text-display-on-light-primary">{title}</h3>
|
|
29
|
+
{description && (
|
|
30
|
+
<p className="text-body text-display-on-light-secondary">{description}</p>
|
|
31
|
+
)}
|
|
32
|
+
</div>
|
|
33
|
+
{action && (
|
|
34
|
+
<Button
|
|
35
|
+
hierarchy={action.hierarchy ?? "primary"}
|
|
36
|
+
onClick={action.onClick}
|
|
37
|
+
icon={action.icon}
|
|
38
|
+
startIcon={action.startIcon}
|
|
39
|
+
>
|
|
40
|
+
{action.children}
|
|
41
|
+
</Button>
|
|
42
|
+
)}
|
|
43
|
+
</div>
|
|
44
|
+
);
|
package/src/components/Modal.tsx
CHANGED
|
@@ -1,141 +1,141 @@
|
|
|
1
|
-
import * as DialogPrimitive from "@radix-ui/react-dialog";
|
|
2
|
-
import {
|
|
3
|
-
forwardRef,
|
|
4
|
-
type ComponentPropsWithoutRef,
|
|
5
|
-
type ElementRef,
|
|
6
|
-
type ReactNode,
|
|
7
|
-
} from "react";
|
|
8
|
-
import { cn } from "../utils/cn";
|
|
9
|
-
|
|
10
|
-
export type ModalProps = ComponentPropsWithoutRef<typeof DialogPrimitive.Root>;
|
|
11
|
-
|
|
12
|
-
/** Generic dialog shell — portal, backdrop, focus trap, Escape. */
|
|
13
|
-
export const Modal = DialogPrimitive.Root;
|
|
14
|
-
|
|
15
|
-
export const ModalTrigger = DialogPrimitive.Trigger;
|
|
16
|
-
|
|
17
|
-
export const ModalClose = DialogPrimitive.Close;
|
|
18
|
-
|
|
19
|
-
export type ModalPortalProps = ComponentPropsWithoutRef<
|
|
20
|
-
typeof DialogPrimitive.Portal
|
|
21
|
-
>;
|
|
22
|
-
|
|
23
|
-
export const ModalPortal = DialogPrimitive.Portal;
|
|
24
|
-
|
|
25
|
-
export type ModalOverlayProps = ComponentPropsWithoutRef<
|
|
26
|
-
typeof DialogPrimitive.Overlay
|
|
27
|
-
>;
|
|
28
|
-
|
|
29
|
-
/** Dark gray backdrop (app convention; Figma search frames omit overlay). */
|
|
30
|
-
export const ModalOverlay = forwardRef<
|
|
31
|
-
ElementRef<typeof DialogPrimitive.Overlay>,
|
|
32
|
-
ModalOverlayProps
|
|
33
|
-
>(({ className, ...props }, ref) => (
|
|
34
|
-
<DialogPrimitive.Overlay
|
|
35
|
-
ref={ref}
|
|
36
|
-
className={cn(
|
|
37
|
-
"fixed inset-0 z-[999] bg-grey-900/65 transition-opacity",
|
|
38
|
-
"data-[state=open]:animate-in data-[state=open]:fade-in",
|
|
39
|
-
"data-[state=closed]:animate-out data-[state=closed]:fade-out",
|
|
40
|
-
className,
|
|
41
|
-
)}
|
|
42
|
-
{...props}
|
|
43
|
-
/>
|
|
44
|
-
));
|
|
45
|
-
ModalOverlay.displayName = DialogPrimitive.Overlay.displayName;
|
|
46
|
-
|
|
47
|
-
export type ModalContentProps = ComponentPropsWithoutRef<
|
|
48
|
-
typeof DialogPrimitive.Content
|
|
49
|
-
> & {
|
|
50
|
-
/** When false, backdrop is omitted. Defaults to true. */
|
|
51
|
-
showOverlay?: boolean;
|
|
52
|
-
overlayClassName?: string;
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
export const ModalContent = forwardRef<
|
|
56
|
-
ElementRef<typeof DialogPrimitive.Content>,
|
|
57
|
-
ModalContentProps
|
|
58
|
-
>(
|
|
59
|
-
(
|
|
60
|
-
{
|
|
61
|
-
className,
|
|
62
|
-
children,
|
|
63
|
-
showOverlay = true,
|
|
64
|
-
overlayClassName,
|
|
65
|
-
...props
|
|
66
|
-
},
|
|
67
|
-
ref,
|
|
68
|
-
) => (
|
|
69
|
-
<ModalPortal>
|
|
70
|
-
{showOverlay && <ModalOverlay className={overlayClassName} />}
|
|
71
|
-
<DialogPrimitive.Content
|
|
72
|
-
ref={ref}
|
|
73
|
-
className={cn(
|
|
74
|
-
"fixed left-1/2 top-1/2 z-[999] w-full max-w-xl -translate-x-1/2 -translate-y-1/2",
|
|
75
|
-
"overflow-hidden rounded-xl border border-divider-primary bg-background-primary shadow-raise3",
|
|
76
|
-
"outline-none",
|
|
77
|
-
"data-[state=open]:animate-in data-[state=open]:fade-in-90",
|
|
78
|
-
"data-[state=closed]:animate-out data-[state=closed]:fade-out",
|
|
79
|
-
className,
|
|
80
|
-
)}
|
|
81
|
-
{...props}
|
|
82
|
-
>
|
|
83
|
-
{children}
|
|
84
|
-
</DialogPrimitive.Content>
|
|
85
|
-
</ModalPortal>
|
|
86
|
-
),
|
|
87
|
-
);
|
|
88
|
-
ModalContent.displayName = DialogPrimitive.Content.displayName;
|
|
89
|
-
|
|
90
|
-
export type ModalTitleProps = ComponentPropsWithoutRef<
|
|
91
|
-
typeof DialogPrimitive.Title
|
|
92
|
-
>;
|
|
93
|
-
|
|
94
|
-
export const ModalTitle = forwardRef<
|
|
95
|
-
ElementRef<typeof DialogPrimitive.Title>,
|
|
96
|
-
ModalTitleProps
|
|
97
|
-
>(({ className, ...props }, ref) => (
|
|
98
|
-
<DialogPrimitive.Title
|
|
99
|
-
ref={ref}
|
|
100
|
-
className={cn("text-subheader-em text-display-on-light-primary", className)}
|
|
101
|
-
{...props}
|
|
102
|
-
/>
|
|
103
|
-
));
|
|
104
|
-
ModalTitle.displayName = DialogPrimitive.Title.displayName;
|
|
105
|
-
|
|
106
|
-
export type ModalDescriptionProps = ComponentPropsWithoutRef<
|
|
107
|
-
typeof DialogPrimitive.Description
|
|
108
|
-
>;
|
|
109
|
-
|
|
110
|
-
export const ModalDescription = forwardRef<
|
|
111
|
-
ElementRef<typeof DialogPrimitive.Description>,
|
|
112
|
-
ModalDescriptionProps
|
|
113
|
-
>(({ className, ...props }, ref) => (
|
|
114
|
-
<DialogPrimitive.Description
|
|
115
|
-
ref={ref}
|
|
116
|
-
className={cn("text-caption-2 text-display-on-light-secondary", className)}
|
|
117
|
-
{...props}
|
|
118
|
-
/>
|
|
119
|
-
));
|
|
120
|
-
ModalDescription.displayName = DialogPrimitive.Description.displayName;
|
|
121
|
-
|
|
122
|
-
export type ModalHeaderSlotProps = {
|
|
123
|
-
children: ReactNode;
|
|
124
|
-
className?: string;
|
|
125
|
-
};
|
|
126
|
-
|
|
127
|
-
export const ModalHeaderSlot = ({
|
|
128
|
-
children,
|
|
129
|
-
className,
|
|
130
|
-
}: ModalHeaderSlotProps) => (
|
|
131
|
-
<div className={cn("shrink-0", className)}>{children}</div>
|
|
132
|
-
);
|
|
133
|
-
|
|
134
|
-
export type ModalBodyProps = {
|
|
135
|
-
children: ReactNode;
|
|
136
|
-
className?: string;
|
|
137
|
-
};
|
|
138
|
-
|
|
139
|
-
export const ModalBody = ({ children, className }: ModalBodyProps) => (
|
|
140
|
-
<div className={cn("min-h-0 overflow-auto", className)}>{children}</div>
|
|
141
|
-
);
|
|
1
|
+
import * as DialogPrimitive from "@radix-ui/react-dialog";
|
|
2
|
+
import {
|
|
3
|
+
forwardRef,
|
|
4
|
+
type ComponentPropsWithoutRef,
|
|
5
|
+
type ElementRef,
|
|
6
|
+
type ReactNode,
|
|
7
|
+
} from "react";
|
|
8
|
+
import { cn } from "../utils/cn";
|
|
9
|
+
|
|
10
|
+
export type ModalProps = ComponentPropsWithoutRef<typeof DialogPrimitive.Root>;
|
|
11
|
+
|
|
12
|
+
/** Generic dialog shell — portal, backdrop, focus trap, Escape. */
|
|
13
|
+
export const Modal = DialogPrimitive.Root;
|
|
14
|
+
|
|
15
|
+
export const ModalTrigger = DialogPrimitive.Trigger;
|
|
16
|
+
|
|
17
|
+
export const ModalClose = DialogPrimitive.Close;
|
|
18
|
+
|
|
19
|
+
export type ModalPortalProps = ComponentPropsWithoutRef<
|
|
20
|
+
typeof DialogPrimitive.Portal
|
|
21
|
+
>;
|
|
22
|
+
|
|
23
|
+
export const ModalPortal = DialogPrimitive.Portal;
|
|
24
|
+
|
|
25
|
+
export type ModalOverlayProps = ComponentPropsWithoutRef<
|
|
26
|
+
typeof DialogPrimitive.Overlay
|
|
27
|
+
>;
|
|
28
|
+
|
|
29
|
+
/** Dark gray backdrop (app convention; Figma search frames omit overlay). */
|
|
30
|
+
export const ModalOverlay = forwardRef<
|
|
31
|
+
ElementRef<typeof DialogPrimitive.Overlay>,
|
|
32
|
+
ModalOverlayProps
|
|
33
|
+
>(({ className, ...props }, ref) => (
|
|
34
|
+
<DialogPrimitive.Overlay
|
|
35
|
+
ref={ref}
|
|
36
|
+
className={cn(
|
|
37
|
+
"fixed inset-0 z-[999] bg-grey-900/65 transition-opacity",
|
|
38
|
+
"data-[state=open]:animate-in data-[state=open]:fade-in",
|
|
39
|
+
"data-[state=closed]:animate-out data-[state=closed]:fade-out",
|
|
40
|
+
className,
|
|
41
|
+
)}
|
|
42
|
+
{...props}
|
|
43
|
+
/>
|
|
44
|
+
));
|
|
45
|
+
ModalOverlay.displayName = DialogPrimitive.Overlay.displayName;
|
|
46
|
+
|
|
47
|
+
export type ModalContentProps = ComponentPropsWithoutRef<
|
|
48
|
+
typeof DialogPrimitive.Content
|
|
49
|
+
> & {
|
|
50
|
+
/** When false, backdrop is omitted. Defaults to true. */
|
|
51
|
+
showOverlay?: boolean;
|
|
52
|
+
overlayClassName?: string;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export const ModalContent = forwardRef<
|
|
56
|
+
ElementRef<typeof DialogPrimitive.Content>,
|
|
57
|
+
ModalContentProps
|
|
58
|
+
>(
|
|
59
|
+
(
|
|
60
|
+
{
|
|
61
|
+
className,
|
|
62
|
+
children,
|
|
63
|
+
showOverlay = true,
|
|
64
|
+
overlayClassName,
|
|
65
|
+
...props
|
|
66
|
+
},
|
|
67
|
+
ref,
|
|
68
|
+
) => (
|
|
69
|
+
<ModalPortal>
|
|
70
|
+
{showOverlay && <ModalOverlay className={overlayClassName} />}
|
|
71
|
+
<DialogPrimitive.Content
|
|
72
|
+
ref={ref}
|
|
73
|
+
className={cn(
|
|
74
|
+
"fixed left-1/2 top-1/2 z-[999] w-full max-w-xl -translate-x-1/2 -translate-y-1/2",
|
|
75
|
+
"overflow-hidden rounded-xl border border-divider-primary bg-background-primary shadow-raise3",
|
|
76
|
+
"outline-none",
|
|
77
|
+
"data-[state=open]:animate-in data-[state=open]:fade-in-90",
|
|
78
|
+
"data-[state=closed]:animate-out data-[state=closed]:fade-out",
|
|
79
|
+
className,
|
|
80
|
+
)}
|
|
81
|
+
{...props}
|
|
82
|
+
>
|
|
83
|
+
{children}
|
|
84
|
+
</DialogPrimitive.Content>
|
|
85
|
+
</ModalPortal>
|
|
86
|
+
),
|
|
87
|
+
);
|
|
88
|
+
ModalContent.displayName = DialogPrimitive.Content.displayName;
|
|
89
|
+
|
|
90
|
+
export type ModalTitleProps = ComponentPropsWithoutRef<
|
|
91
|
+
typeof DialogPrimitive.Title
|
|
92
|
+
>;
|
|
93
|
+
|
|
94
|
+
export const ModalTitle = forwardRef<
|
|
95
|
+
ElementRef<typeof DialogPrimitive.Title>,
|
|
96
|
+
ModalTitleProps
|
|
97
|
+
>(({ className, ...props }, ref) => (
|
|
98
|
+
<DialogPrimitive.Title
|
|
99
|
+
ref={ref}
|
|
100
|
+
className={cn("text-subheader-em text-display-on-light-primary", className)}
|
|
101
|
+
{...props}
|
|
102
|
+
/>
|
|
103
|
+
));
|
|
104
|
+
ModalTitle.displayName = DialogPrimitive.Title.displayName;
|
|
105
|
+
|
|
106
|
+
export type ModalDescriptionProps = ComponentPropsWithoutRef<
|
|
107
|
+
typeof DialogPrimitive.Description
|
|
108
|
+
>;
|
|
109
|
+
|
|
110
|
+
export const ModalDescription = forwardRef<
|
|
111
|
+
ElementRef<typeof DialogPrimitive.Description>,
|
|
112
|
+
ModalDescriptionProps
|
|
113
|
+
>(({ className, ...props }, ref) => (
|
|
114
|
+
<DialogPrimitive.Description
|
|
115
|
+
ref={ref}
|
|
116
|
+
className={cn("text-caption-2 text-display-on-light-secondary", className)}
|
|
117
|
+
{...props}
|
|
118
|
+
/>
|
|
119
|
+
));
|
|
120
|
+
ModalDescription.displayName = DialogPrimitive.Description.displayName;
|
|
121
|
+
|
|
122
|
+
export type ModalHeaderSlotProps = {
|
|
123
|
+
children: ReactNode;
|
|
124
|
+
className?: string;
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
export const ModalHeaderSlot = ({
|
|
128
|
+
children,
|
|
129
|
+
className,
|
|
130
|
+
}: ModalHeaderSlotProps) => (
|
|
131
|
+
<div className={cn("shrink-0", className)}>{children}</div>
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
export type ModalBodyProps = {
|
|
135
|
+
children: ReactNode;
|
|
136
|
+
className?: string;
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
export const ModalBody = ({ children, className }: ModalBodyProps) => (
|
|
140
|
+
<div className={cn("min-h-0 overflow-auto", className)}>{children}</div>
|
|
141
|
+
);
|
|
@@ -1,132 +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
|
-
});
|
|
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
|
+
});
|