@lateralus-ai/shipping-ui 2.0.0-dev.3 → 2.0.0-dev.30
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/Entry.d.ts +27 -7
- package/dist/components/PageHeader.d.ts +41 -0
- package/dist/components/ScrollableList.d.ts +22 -0
- package/dist/components/Tabs.d.ts +5 -5
- package/dist/components/index.d.ts +4 -2
- package/dist/domain/Filters/FilterDropdown.d.ts +119 -0
- package/dist/domain/Filters/FilterPill.d.ts +22 -5
- package/dist/domain/Filters/FilterPills.d.ts +18 -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.d.ts +2 -2
- package/dist/index.esm.js +6383 -4792
- package/dist/patterns/Search/ResultRow.d.ts +11 -5
- package/dist/patterns/Sidebar/sidebar-styles.d.ts +2 -1
- package/dist/primitives/Badge.d.ts +7 -4
- package/dist/primitives/Button.d.ts +3 -1
- package/dist/primitives/Switch.d.ts +5 -0
- package/dist/primitives/index.d.ts +1 -1
- package/dist/tailwind-theme.d.ts +8 -4
- package/dist/theme-entry-Dwr2mV7_.js +1 -0
- package/dist/{theme-entry-CxDa1D0_.mjs → theme-entry-tLBc6zGT.mjs} +12 -8
- package/dist/theme.cjs +1 -1
- package/dist/theme.esm.js +1 -1
- package/package.json +3 -2
- package/src/components/EmptyState.tsx +44 -44
- package/src/components/Entry.tsx +119 -45
- package/src/components/Modal.tsx +141 -141
- package/src/components/PageHeader.tsx +132 -0
- package/src/components/ScrollableList.tsx +61 -0
- package/src/components/Tabs.tsx +33 -26
- package/src/components/index.ts +15 -1
- package/src/domain/Filters/FilterDropdown.tsx +540 -0
- package/src/domain/Filters/FilterPill.tsx +74 -25
- package/src/domain/Filters/FilterPills.tsx +34 -0
- package/src/domain/Filters/FilteredPill.tsx +59 -39
- package/src/domain/Filters/FiltersBar.tsx +72 -76
- package/src/domain/Filters/formatActiveFilterChipLabel.ts +23 -0
- package/src/domain/Filters/index.ts +37 -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 +11 -0
- package/src/patterns/Search/ResultRow.tsx +24 -38
- package/src/patterns/Search/SearchModal.tsx +26 -8
- package/src/patterns/Sidebar/CollapsibleNavGroup.tsx +1 -1
- package/src/patterns/Sidebar/SidebarAction.tsx +26 -17
- package/src/patterns/Sidebar/SidebarEntry.tsx +54 -39
- package/src/patterns/Sidebar/SidebarLink.tsx +30 -20
- 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/Sidebar/sidebar-styles.ts +2 -1
- package/src/primitives/Badge.tsx +20 -10
- package/src/primitives/Button.tsx +21 -11
- package/src/primitives/Logo.tsx +20 -8
- package/src/primitives/Switch.tsx +27 -7
- package/src/primitives/button-styles.ts +92 -92
- package/src/primitives/index.ts +1 -1
- package/src/stories/canvases/ButtonsCanvas.tsx +107 -107
- package/src/stories/canvases/ButtonsMatrixCanvas.tsx +65 -65
- package/src/stories/canvases/ContentCanvas.tsx +253 -14
- package/src/stories/canvases/FiltersCanvas.tsx +171 -41
- 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 +8 -4
- package/src/theme-entry.ts +2 -2
- package/src/utils/cn.ts +28 -1
- package/dist/theme-entry-D2X3Ptjf.js +0 -1
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
|
+
);
|
|
@@ -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
|
+
});
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { type ComponentPropsWithoutRef } from "react";
|
|
2
|
+
import { cn } from "../utils/cn";
|
|
3
|
+
|
|
4
|
+
export type ScrollableListProps = ComponentPropsWithoutRef<"div">;
|
|
5
|
+
export type ScrollableListHeaderProps = ComponentPropsWithoutRef<"div">;
|
|
6
|
+
export type ScrollableListBodyProps = ComponentPropsWithoutRef<"div">;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Fill-height list chrome — Figma `Table` (6154:152285).
|
|
10
|
+
* Outer grey-100 frame + header slot + internally scrolling grey-50 body.
|
|
11
|
+
* Column layout and rows are caller-owned children.
|
|
12
|
+
*/
|
|
13
|
+
function ScrollableListRoot({ className, ...props }: ScrollableListProps) {
|
|
14
|
+
return (
|
|
15
|
+
<div
|
|
16
|
+
className={cn(
|
|
17
|
+
"flex min-h-0 flex-1 flex-col gap-2 overflow-hidden rounded-lg bg-grey-100 p-1",
|
|
18
|
+
className,
|
|
19
|
+
)}
|
|
20
|
+
{...props}
|
|
21
|
+
/>
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Column title bar. Default left padding aligns with a 24px leading icon +
|
|
27
|
+
* 8px gap + 8px row padding (Figma). Override `className` when rows have no icon.
|
|
28
|
+
*/
|
|
29
|
+
function ScrollableListHeader({
|
|
30
|
+
className,
|
|
31
|
+
...props
|
|
32
|
+
}: ScrollableListHeaderProps) {
|
|
33
|
+
return (
|
|
34
|
+
<div
|
|
35
|
+
className={cn(
|
|
36
|
+
"flex shrink-0 items-start gap-8 pl-10 pr-2 py-1",
|
|
37
|
+
"text-caption-2-em text-display-on-light-secondary",
|
|
38
|
+
className,
|
|
39
|
+
)}
|
|
40
|
+
{...props}
|
|
41
|
+
/>
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Internally scrolling row region. */
|
|
46
|
+
function ScrollableListBody({ className, ...props }: ScrollableListBodyProps) {
|
|
47
|
+
return (
|
|
48
|
+
<div
|
|
49
|
+
className={cn(
|
|
50
|
+
"flex min-h-0 flex-1 flex-col gap-1 overflow-y-auto rounded bg-grey-50 p-1",
|
|
51
|
+
className,
|
|
52
|
+
)}
|
|
53
|
+
{...props}
|
|
54
|
+
/>
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export const ScrollableList = Object.assign(ScrollableListRoot, {
|
|
59
|
+
Header: ScrollableListHeader,
|
|
60
|
+
Body: ScrollableListBody,
|
|
61
|
+
});
|
package/src/components/Tabs.tsx
CHANGED
|
@@ -11,26 +11,37 @@ import { cn } from "../utils/cn";
|
|
|
11
11
|
|
|
12
12
|
export type TabsType = "tabs" | "pills";
|
|
13
13
|
|
|
14
|
+
/** Soft = search filters (filled idle, light accent active). Solid = list filters (grey-100 idle, blue-600 active). */
|
|
15
|
+
export type TabsAppearance = "soft" | "solid";
|
|
16
|
+
|
|
14
17
|
type TabsContextValue = {
|
|
15
18
|
type: TabsType;
|
|
19
|
+
appearance: TabsAppearance;
|
|
16
20
|
};
|
|
17
21
|
|
|
18
|
-
const TabsContext = createContext<TabsContextValue>({
|
|
22
|
+
const TabsContext = createContext<TabsContextValue>({
|
|
23
|
+
type: "tabs",
|
|
24
|
+
appearance: "soft",
|
|
25
|
+
});
|
|
19
26
|
|
|
20
27
|
export type TabsProps = ComponentPropsWithoutRef<typeof TabsPrimitive.Root> & {
|
|
21
28
|
type?: TabsType;
|
|
29
|
+
/** Only applies when `type="pills"`. Defaults to `soft`. */
|
|
30
|
+
appearance?: TabsAppearance;
|
|
22
31
|
};
|
|
23
32
|
|
|
24
33
|
export const Tabs = ({
|
|
25
34
|
type = "tabs",
|
|
35
|
+
appearance = "soft",
|
|
26
36
|
className,
|
|
27
37
|
children,
|
|
28
38
|
...props
|
|
29
39
|
}: TabsProps) => (
|
|
30
|
-
<TabsContext.Provider value={{ type }}>
|
|
40
|
+
<TabsContext.Provider value={{ type, appearance }}>
|
|
31
41
|
<TabsPrimitive.Root
|
|
32
42
|
className={cn("flex flex-col", className)}
|
|
33
43
|
data-type={type}
|
|
44
|
+
data-appearance={type === "pills" ? appearance : undefined}
|
|
34
45
|
{...props}
|
|
35
46
|
>
|
|
36
47
|
{children}
|
|
@@ -51,7 +62,7 @@ export const TabsList = forwardRef<
|
|
|
51
62
|
ref={ref}
|
|
52
63
|
className={cn(
|
|
53
64
|
"flex items-center",
|
|
54
|
-
type === "tabs" && "gap-
|
|
65
|
+
type === "tabs" && "gap-6 border-b border-divider-primary",
|
|
55
66
|
type === "pills" && "gap-2",
|
|
56
67
|
className,
|
|
57
68
|
)}
|
|
@@ -64,52 +75,48 @@ TabsList.displayName = TabsPrimitive.List.displayName;
|
|
|
64
75
|
export type TabsTriggerProps = ComponentPropsWithoutRef<
|
|
65
76
|
typeof TabsPrimitive.Trigger
|
|
66
77
|
> & {
|
|
67
|
-
/** Optional count badge (pill variant / search filters). */
|
|
68
|
-
count?: number;
|
|
69
78
|
children: ReactNode;
|
|
70
79
|
};
|
|
71
80
|
|
|
72
81
|
export const TabsTrigger = forwardRef<
|
|
73
82
|
ElementRef<typeof TabsPrimitive.Trigger>,
|
|
74
83
|
TabsTriggerProps
|
|
75
|
-
>(({ className,
|
|
76
|
-
const { type } = useContext(TabsContext);
|
|
84
|
+
>(({ className, children, ...props }, ref) => {
|
|
85
|
+
const { type, appearance } = useContext(TabsContext);
|
|
77
86
|
|
|
78
87
|
return (
|
|
79
88
|
<TabsPrimitive.Trigger
|
|
80
89
|
ref={ref}
|
|
81
90
|
className={cn(
|
|
82
|
-
"inline-flex items-center justify-center transition-colors outline-none",
|
|
91
|
+
"inline-flex items-center justify-center gap-2.5 transition-colors outline-none",
|
|
83
92
|
"disabled:pointer-events-none disabled:opacity-50",
|
|
84
93
|
type === "tabs" && [
|
|
85
|
-
"relative px-
|
|
94
|
+
"relative h-12 gap-2 px-0 py-0 text-caption-2 text-display-on-light-secondary",
|
|
86
95
|
"hover:text-display-on-light-primary",
|
|
87
96
|
"data-[state=active]:text-caption-2-em data-[state=active]:text-display-on-light-primary",
|
|
88
97
|
"data-[state=active]:after:absolute data-[state=active]:after:inset-x-0 data-[state=active]:after:-bottom-px",
|
|
89
98
|
"data-[state=active]:after:h-0.5 data-[state=active]:after:rounded-full data-[state=active]:after:bg-grey-900",
|
|
90
99
|
],
|
|
91
|
-
type === "pills" &&
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
100
|
+
type === "pills" &&
|
|
101
|
+
appearance === "soft" && [
|
|
102
|
+
"min-h-9 rounded-full px-3 py-1 text-caption-2-em",
|
|
103
|
+
"bg-grey-100 text-display-on-light-secondary",
|
|
104
|
+
"hover:bg-grey-200",
|
|
105
|
+
"data-[state=active]:bg-accent-bg-light data-[state=active]:text-display-on-light-primary",
|
|
106
|
+
],
|
|
107
|
+
type === "pills" &&
|
|
108
|
+
appearance === "solid" && [
|
|
109
|
+
"min-h-9 rounded-full px-3 py-1 text-caption-2-em",
|
|
110
|
+
"bg-grey-100 text-display-on-light-tertiary",
|
|
111
|
+
"hover:bg-grey-200 hover:text-display-on-light-primary",
|
|
112
|
+
"data-[state=active]:bg-blue-600 data-[state=active]:text-white",
|
|
113
|
+
"data-[state=active]:hover:bg-blue-600",
|
|
114
|
+
],
|
|
97
115
|
className,
|
|
98
116
|
)}
|
|
99
117
|
{...props}
|
|
100
118
|
>
|
|
101
119
|
{children}
|
|
102
|
-
{typeof count === "number" && (
|
|
103
|
-
<span
|
|
104
|
-
className={cn(
|
|
105
|
-
"inline-flex size-6 shrink-0 items-center justify-center rounded-full text-footnote-em",
|
|
106
|
-
"bg-accent-bg-light text-display-on-light-tertiary",
|
|
107
|
-
)}
|
|
108
|
-
aria-label={`Count: ${count}`}
|
|
109
|
-
>
|
|
110
|
-
{count > 99 ? "99+" : count}
|
|
111
|
-
</span>
|
|
112
|
-
)}
|
|
113
120
|
</TabsPrimitive.Trigger>
|
|
114
121
|
);
|
|
115
122
|
});
|
package/src/components/index.ts
CHANGED
|
@@ -15,9 +15,23 @@ export {
|
|
|
15
15
|
type TabsContentProps,
|
|
16
16
|
type TabsItem,
|
|
17
17
|
type TabsType,
|
|
18
|
+
type TabsAppearance,
|
|
18
19
|
} from "./Tabs";
|
|
19
20
|
export { Header, type HeaderProps, type HeaderVariant } from "./Header";
|
|
20
|
-
export {
|
|
21
|
+
export {
|
|
22
|
+
PageHeader,
|
|
23
|
+
type PageHeaderProps,
|
|
24
|
+
type PageHeaderCrumb,
|
|
25
|
+
type PageHeaderShellProps,
|
|
26
|
+
type PageHeaderBodyProps,
|
|
27
|
+
} from "./PageHeader";
|
|
28
|
+
export {
|
|
29
|
+
ScrollableList,
|
|
30
|
+
type ScrollableListProps,
|
|
31
|
+
type ScrollableListHeaderProps,
|
|
32
|
+
type ScrollableListBodyProps,
|
|
33
|
+
} from "./ScrollableList";
|
|
34
|
+
export { Entry, type EntryProps, type EntryType, type EntryState, type EntryVariant } from "./Entry";
|
|
21
35
|
export { EmptyState, type EmptyStateProps } from "./EmptyState";
|
|
22
36
|
export {
|
|
23
37
|
Modal,
|