@lateralus-ai/shipping-ui 2.0.0-dev.20 → 2.0.0-dev.4
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 +7 -27
- package/dist/components/Tabs.d.ts +5 -5
- package/dist/components/index.d.ts +2 -4
- package/dist/index.cjs +25 -25
- package/dist/index.d.ts +2 -2
- package/dist/index.esm.js +3374 -3584
- package/dist/patterns/Search/ResultRow.d.ts +5 -11
- package/dist/patterns/Sidebar/sidebar-styles.d.ts +1 -2
- package/dist/primitives/Badge.d.ts +4 -7
- package/dist/primitives/index.d.ts +1 -1
- package/dist/tailwind-theme.d.ts +0 -4
- package/dist/{theme-entry-tLBc6zGT.mjs → theme-entry-BUK3MJUJ.mjs} +4 -8
- package/dist/theme-entry-ygTpGaNC.js +1 -0
- package/dist/theme.cjs +1 -1
- package/dist/theme.esm.js +1 -1
- package/package.json +128 -128
- package/src/components/Entry.tsx +45 -119
- package/src/components/Tabs.tsx +27 -33
- package/src/components/index.ts +1 -15
- package/src/index.ts +0 -11
- package/src/patterns/Search/ResultRow.tsx +38 -24
- package/src/patterns/Search/SearchModal.tsx +1 -7
- package/src/patterns/Sidebar/CollapsibleNavGroup.tsx +1 -1
- package/src/patterns/Sidebar/SidebarAction.tsx +17 -26
- package/src/patterns/Sidebar/SidebarEntry.tsx +39 -54
- package/src/patterns/Sidebar/SidebarLink.tsx +20 -30
- package/src/patterns/Sidebar/sidebar-styles.ts +1 -2
- package/src/primitives/Badge.tsx +10 -20
- package/src/primitives/Button.tsx +11 -16
- package/src/primitives/index.ts +1 -1
- package/src/stories/canvases/ContentCanvas.tsx +14 -253
- package/src/tailwind-theme.ts +182 -186
- package/src/utils/cn.ts +1 -28
- package/dist/components/PageHeader.d.ts +0 -41
- package/dist/components/ScrollableList.d.ts +0 -22
- package/dist/theme-entry-Dwr2mV7_.js +0 -1
- package/src/components/PageHeader.tsx +0 -132
- package/src/components/ScrollableList.tsx +0 -61
package/src/components/Tabs.tsx
CHANGED
|
@@ -11,37 +11,26 @@ 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
|
-
|
|
17
14
|
type TabsContextValue = {
|
|
18
15
|
type: TabsType;
|
|
19
|
-
appearance: TabsAppearance;
|
|
20
16
|
};
|
|
21
17
|
|
|
22
|
-
const TabsContext = createContext<TabsContextValue>({
|
|
23
|
-
type: "tabs",
|
|
24
|
-
appearance: "soft",
|
|
25
|
-
});
|
|
18
|
+
const TabsContext = createContext<TabsContextValue>({ type: "tabs" });
|
|
26
19
|
|
|
27
20
|
export type TabsProps = ComponentPropsWithoutRef<typeof TabsPrimitive.Root> & {
|
|
28
21
|
type?: TabsType;
|
|
29
|
-
/** Only applies when `type="pills"`. Defaults to `soft`. */
|
|
30
|
-
appearance?: TabsAppearance;
|
|
31
22
|
};
|
|
32
23
|
|
|
33
24
|
export const Tabs = ({
|
|
34
25
|
type = "tabs",
|
|
35
|
-
appearance = "soft",
|
|
36
26
|
className,
|
|
37
27
|
children,
|
|
38
28
|
...props
|
|
39
29
|
}: TabsProps) => (
|
|
40
|
-
<TabsContext.Provider value={{ type
|
|
30
|
+
<TabsContext.Provider value={{ type }}>
|
|
41
31
|
<TabsPrimitive.Root
|
|
42
32
|
className={cn("flex flex-col", className)}
|
|
43
33
|
data-type={type}
|
|
44
|
-
data-appearance={type === "pills" ? appearance : undefined}
|
|
45
34
|
{...props}
|
|
46
35
|
>
|
|
47
36
|
{children}
|
|
@@ -62,7 +51,7 @@ export const TabsList = forwardRef<
|
|
|
62
51
|
ref={ref}
|
|
63
52
|
className={cn(
|
|
64
53
|
"flex items-center",
|
|
65
|
-
type === "tabs" && "gap-
|
|
54
|
+
type === "tabs" && "gap-1 border-b border-divider-primary",
|
|
66
55
|
type === "pills" && "gap-2",
|
|
67
56
|
className,
|
|
68
57
|
)}
|
|
@@ -75,48 +64,53 @@ TabsList.displayName = TabsPrimitive.List.displayName;
|
|
|
75
64
|
export type TabsTriggerProps = ComponentPropsWithoutRef<
|
|
76
65
|
typeof TabsPrimitive.Trigger
|
|
77
66
|
> & {
|
|
67
|
+
/** Optional count badge (pill variant / search filters). */
|
|
68
|
+
count?: number;
|
|
78
69
|
children: ReactNode;
|
|
79
70
|
};
|
|
80
71
|
|
|
81
72
|
export const TabsTrigger = forwardRef<
|
|
82
73
|
ElementRef<typeof TabsPrimitive.Trigger>,
|
|
83
74
|
TabsTriggerProps
|
|
84
|
-
>(({ className, children, ...props }, ref) => {
|
|
85
|
-
const { type
|
|
75
|
+
>(({ className, count, children, ...props }, ref) => {
|
|
76
|
+
const { type } = useContext(TabsContext);
|
|
86
77
|
|
|
87
78
|
return (
|
|
88
79
|
<TabsPrimitive.Trigger
|
|
89
80
|
ref={ref}
|
|
90
81
|
className={cn(
|
|
91
|
-
"inline-flex items-center justify-center
|
|
82
|
+
"inline-flex items-center justify-center transition-colors outline-none",
|
|
92
83
|
"disabled:pointer-events-none disabled:opacity-50",
|
|
93
84
|
type === "tabs" && [
|
|
94
|
-
"relative
|
|
85
|
+
"relative px-3 py-2 text-caption-2 text-display-on-light-secondary",
|
|
95
86
|
"hover:text-display-on-light-primary",
|
|
96
87
|
"data-[state=active]:text-caption-2-em data-[state=active]:text-display-on-light-primary",
|
|
97
88
|
"data-[state=active]:after:absolute data-[state=active]:after:inset-x-0 data-[state=active]:after:-bottom-px",
|
|
98
89
|
"data-[state=active]:after:h-0.5 data-[state=active]:after:rounded-full data-[state=active]:after:bg-grey-900",
|
|
99
90
|
],
|
|
100
|
-
type === "pills" &&
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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
|
-
],
|
|
91
|
+
type === "pills" && [
|
|
92
|
+
"group min-h-9 gap-2.5 rounded-full px-3 py-1 text-caption-2-em",
|
|
93
|
+
"bg-grey-100 text-display-on-light-secondary",
|
|
94
|
+
"hover:bg-grey-200",
|
|
95
|
+
"data-[state=active]:bg-accent-bg-light data-[state=active]:text-display-on-light-primary",
|
|
96
|
+
],
|
|
115
97
|
className,
|
|
116
98
|
)}
|
|
117
99
|
{...props}
|
|
118
100
|
>
|
|
119
101
|
{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-secondary",
|
|
107
|
+
"group-data-[state=active]:text-display-on-light-tertiary",
|
|
108
|
+
)}
|
|
109
|
+
aria-label={`Count: ${count}`}
|
|
110
|
+
>
|
|
111
|
+
{count > 99 ? "99+" : count}
|
|
112
|
+
</span>
|
|
113
|
+
)}
|
|
120
114
|
</TabsPrimitive.Trigger>
|
|
121
115
|
);
|
|
122
116
|
});
|
package/src/components/index.ts
CHANGED
|
@@ -15,23 +15,9 @@ export {
|
|
|
15
15
|
type TabsContentProps,
|
|
16
16
|
type TabsItem,
|
|
17
17
|
type TabsType,
|
|
18
|
-
type TabsAppearance,
|
|
19
18
|
} from "./Tabs";
|
|
20
19
|
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";
|
|
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";
|
|
20
|
+
export { Entry, type EntryProps, type EntryType, type EntryState } from "./Entry";
|
|
35
21
|
export { EmptyState, type EmptyStateProps } from "./EmptyState";
|
|
36
22
|
export {
|
|
37
23
|
Modal,
|
package/src/index.ts
CHANGED
|
@@ -19,7 +19,6 @@ export {
|
|
|
19
19
|
TabsTrigger,
|
|
20
20
|
TabsContent,
|
|
21
21
|
Header,
|
|
22
|
-
PageHeader,
|
|
23
22
|
Entry,
|
|
24
23
|
EmptyState,
|
|
25
24
|
Modal,
|
|
@@ -36,7 +35,6 @@ export {
|
|
|
36
35
|
PdfViewer,
|
|
37
36
|
ImageViewer,
|
|
38
37
|
DocumentEditor,
|
|
39
|
-
ScrollableList,
|
|
40
38
|
} from "./components";
|
|
41
39
|
|
|
42
40
|
export type {
|
|
@@ -58,20 +56,11 @@ export type {
|
|
|
58
56
|
TabsContentProps,
|
|
59
57
|
TabsItem,
|
|
60
58
|
TabsType,
|
|
61
|
-
TabsAppearance,
|
|
62
59
|
HeaderProps,
|
|
63
60
|
HeaderVariant,
|
|
64
|
-
PageHeaderProps,
|
|
65
|
-
PageHeaderCrumb,
|
|
66
|
-
PageHeaderShellProps,
|
|
67
|
-
PageHeaderBodyProps,
|
|
68
|
-
ScrollableListProps,
|
|
69
|
-
ScrollableListHeaderProps,
|
|
70
|
-
ScrollableListBodyProps,
|
|
71
61
|
EntryProps,
|
|
72
62
|
EntryType,
|
|
73
63
|
EntryState,
|
|
74
|
-
EntryVariant,
|
|
75
64
|
EmptyStateProps,
|
|
76
65
|
ModalProps,
|
|
77
66
|
ModalOverlayProps,
|
|
@@ -1,28 +1,26 @@
|
|
|
1
1
|
import { type ReactNode } from "react";
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
type EntryProps,
|
|
5
|
-
type EntryState,
|
|
6
|
-
type EntryVariant,
|
|
7
|
-
} from "../../components/Entry";
|
|
2
|
+
import { ChatIcon, IssuesIcon, ReportIcon } from "../../icons";
|
|
3
|
+
import { cn } from "../../utils/cn";
|
|
8
4
|
|
|
9
|
-
|
|
10
|
-
export type
|
|
11
|
-
/** @deprecated Prefer `EntryState`. */
|
|
12
|
-
export type ResultRowState = EntryState;
|
|
5
|
+
export type ResultRowVariant = "report" | "chat" | "issue";
|
|
6
|
+
export type ResultRowState = "idle" | "active";
|
|
13
7
|
|
|
14
8
|
export type ResultRowProps = {
|
|
15
|
-
variant?:
|
|
16
|
-
state?:
|
|
9
|
+
variant?: ResultRowVariant;
|
|
10
|
+
state?: ResultRowState;
|
|
17
11
|
title?: ReactNode;
|
|
12
|
+
/** Secondary line — excerpt, metadata, or highlighted query match. */
|
|
18
13
|
subtitle?: ReactNode;
|
|
19
14
|
onClick?: () => void;
|
|
20
15
|
className?: string;
|
|
21
16
|
};
|
|
22
17
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
18
|
+
const variantIcons: Record<ResultRowVariant, ReactNode> = {
|
|
19
|
+
report: <ReportIcon className="size-4" />,
|
|
20
|
+
chat: <ChatIcon className="size-4" />,
|
|
21
|
+
issue: <IssuesIcon className="size-4" />,
|
|
22
|
+
};
|
|
23
|
+
|
|
26
24
|
export const ResultRow = ({
|
|
27
25
|
variant = "report",
|
|
28
26
|
state = "idle",
|
|
@@ -31,14 +29,30 @@ export const ResultRow = ({
|
|
|
31
29
|
onClick,
|
|
32
30
|
className,
|
|
33
31
|
}: ResultRowProps) => (
|
|
34
|
-
<
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
32
|
+
<button
|
|
33
|
+
type="button"
|
|
34
|
+
className={cn(
|
|
35
|
+
"flex w-full items-center gap-4 rounded-control px-3 py-2.5 text-left transition-colors",
|
|
36
|
+
"hover:bg-[rgba(38,36,32,0.04)]",
|
|
37
|
+
state === "active" && "bg-[rgba(38,36,32,0.04)]",
|
|
38
|
+
className,
|
|
39
|
+
)}
|
|
40
|
+
data-variant={variant}
|
|
41
|
+
data-state={state}
|
|
39
42
|
onClick={onClick}
|
|
40
|
-
|
|
41
|
-
|
|
43
|
+
>
|
|
44
|
+
<span className="shrink-0 text-display-on-light-secondary">
|
|
45
|
+
{variantIcons[variant]}
|
|
46
|
+
</span>
|
|
47
|
+
<span className="min-w-0 flex-1">
|
|
48
|
+
<span className="block truncate text-caption-1-em text-display-on-light-primary">
|
|
49
|
+
{title}
|
|
50
|
+
</span>
|
|
51
|
+
{subtitle != null && subtitle !== false && (
|
|
52
|
+
<span className="mt-0.5 block truncate text-caption-2 text-display-on-light-secondary">
|
|
53
|
+
{subtitle}
|
|
54
|
+
</span>
|
|
55
|
+
)}
|
|
56
|
+
</span>
|
|
57
|
+
</button>
|
|
42
58
|
);
|
|
43
|
-
|
|
44
|
-
export type { EntryProps };
|
|
@@ -13,7 +13,6 @@ import {
|
|
|
13
13
|
type ModalProps,
|
|
14
14
|
} from "../../components/Modal";
|
|
15
15
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "../../components/Tabs";
|
|
16
|
-
import { Badge } from "../../primitives/Badge";
|
|
17
16
|
import { cn } from "../../utils/cn";
|
|
18
17
|
import { ResultRow, type ResultRowProps } from "./ResultRow";
|
|
19
18
|
import { SectionHeader } from "./SectionHeader";
|
|
@@ -177,13 +176,8 @@ export const SearchModalFilters = ({
|
|
|
177
176
|
)}
|
|
178
177
|
>
|
|
179
178
|
{tabs.map((tab) => (
|
|
180
|
-
<TabsTrigger key={tab.value} value={tab.value}>
|
|
179
|
+
<TabsTrigger key={tab.value} value={tab.value} count={tab.count}>
|
|
181
180
|
{tab.label}
|
|
182
|
-
{typeof tab.count === "number" && (
|
|
183
|
-
<Badge color="blue">
|
|
184
|
-
{tab.count > 99 ? "99+" : tab.count}
|
|
185
|
-
</Badge>
|
|
186
|
-
)}
|
|
187
181
|
</TabsTrigger>
|
|
188
182
|
))}
|
|
189
183
|
</TabsList>
|
|
@@ -45,6 +45,7 @@ export const CollapsibleNavGroup = ({
|
|
|
45
45
|
href={href}
|
|
46
46
|
label={label}
|
|
47
47
|
icon={icon}
|
|
48
|
+
tall
|
|
48
49
|
onNavigate={onNavigate}
|
|
49
50
|
trailing={
|
|
50
51
|
<IconButton
|
|
@@ -92,7 +93,6 @@ export const CollapsibleNavGroup = ({
|
|
|
92
93
|
) : undefined
|
|
93
94
|
}
|
|
94
95
|
menuItems={entryMenuItems}
|
|
95
|
-
onNavigate={onNavigate}
|
|
96
96
|
/>
|
|
97
97
|
))}
|
|
98
98
|
</div>
|
|
@@ -1,14 +1,7 @@
|
|
|
1
1
|
import { type ReactNode } from "react";
|
|
2
2
|
import { Tooltip } from "../../primitives";
|
|
3
3
|
import { cn } from "../../utils/cn";
|
|
4
|
-
import {
|
|
5
|
-
sidebarLinkActive,
|
|
6
|
-
sidebarLinkIdle,
|
|
7
|
-
sidebarRowInteractiveHover,
|
|
8
|
-
sidebarSectionIconClass,
|
|
9
|
-
sidebarSectionShell,
|
|
10
|
-
sidebarSectionShellCollapsed,
|
|
11
|
-
} from "./sidebar-styles";
|
|
4
|
+
import { sidebarLinkActive, sidebarLinkIdle, sidebarRowClass, sidebarRowInteractiveHover, sidebarSectionAnchorClass } from "./sidebar-styles";
|
|
12
5
|
|
|
13
6
|
export type SidebarActionProps = {
|
|
14
7
|
label: string;
|
|
@@ -30,28 +23,26 @@ export const SidebarAction = ({
|
|
|
30
23
|
className,
|
|
31
24
|
}: SidebarActionProps) => {
|
|
32
25
|
const row = (
|
|
33
|
-
<
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
active ? sidebarLinkActive : sidebarLinkIdle,
|
|
41
|
-
!active && sidebarRowInteractiveHover,
|
|
42
|
-
!collapsed && "gap-2",
|
|
43
|
-
className,
|
|
26
|
+
<div
|
|
27
|
+
className={sidebarRowClass(
|
|
28
|
+
cn(
|
|
29
|
+
active ? sidebarLinkActive : sidebarLinkIdle,
|
|
30
|
+
!active && sidebarRowInteractiveHover,
|
|
31
|
+
className,
|
|
32
|
+
),
|
|
44
33
|
)}
|
|
45
34
|
data-active={active}
|
|
46
35
|
data-collapsed={collapsed}
|
|
47
36
|
>
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
37
|
+
<button
|
|
38
|
+
type="button"
|
|
39
|
+
className={sidebarSectionAnchorClass(collapsed ? "justify-center" : undefined)}
|
|
40
|
+
onClick={onClick}
|
|
41
|
+
>
|
|
42
|
+
{icon && <span className="shrink-0 [&>svg]:size-5">{icon}</span>}
|
|
43
|
+
{!collapsed && <span className="truncate">{label}</span>}
|
|
44
|
+
</button>
|
|
45
|
+
</div>
|
|
55
46
|
);
|
|
56
47
|
|
|
57
48
|
if (!collapsed || !tooltip) return row;
|
|
@@ -3,6 +3,7 @@ import { DropdownMenu, DropdownMenuItem, IconButton } from "../../primitives";
|
|
|
3
3
|
import { MoreIcon } from "../../icons";
|
|
4
4
|
import { cn } from "../../utils/cn";
|
|
5
5
|
import {
|
|
6
|
+
sidebarAnchorClass,
|
|
6
7
|
sidebarEntryHover,
|
|
7
8
|
sidebarEntryIdle,
|
|
8
9
|
sidebarEntrySelected,
|
|
@@ -65,74 +66,58 @@ export const SidebarEntry = ({
|
|
|
65
66
|
)}
|
|
66
67
|
data-state={state}
|
|
67
68
|
>
|
|
68
|
-
{/* Stretch hit target over shell padding (hover was on the shell, link was content-only). */}
|
|
69
69
|
<a
|
|
70
70
|
href={href}
|
|
71
|
-
className="
|
|
72
|
-
aria-label={label}
|
|
71
|
+
className={sidebarAnchorClass("gap-1")}
|
|
73
72
|
aria-current={state === "selected" ? "true" : undefined}
|
|
74
73
|
onClick={onNavigate}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
<div className="pointer-events-none relative z-10 flex min-w-0 flex-1 items-center gap-2 text-caption-2">
|
|
78
|
-
{icon && <span className="shrink-0 [&>svg]:size-4">{icon}</span>}
|
|
74
|
+
>
|
|
75
|
+
{icon && <span className="shrink-0 px-1 py-0.5 [&>svg]:size-4">{icon}</span>}
|
|
79
76
|
<span className="truncate">{label}</span>
|
|
80
|
-
</
|
|
77
|
+
</a>
|
|
81
78
|
|
|
82
79
|
{badge && !(showMenu && menuVisible) && (
|
|
83
80
|
<span
|
|
84
|
-
className={cn(
|
|
85
|
-
"pointer-events-none relative z-10 shrink-0",
|
|
86
|
-
showMenu && !forceMenuVisible && "group-hover/row:hidden",
|
|
87
|
-
)}
|
|
81
|
+
className={cn("shrink-0", showMenu && !forceMenuVisible && "group-hover/row:hidden")}
|
|
88
82
|
>
|
|
89
83
|
{badge}
|
|
90
84
|
</span>
|
|
91
85
|
)}
|
|
92
86
|
|
|
93
87
|
{showMenu && (
|
|
94
|
-
<
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
>
|
|
128
|
-
{item.icon && (
|
|
129
|
-
<span className="shrink-0 [&>svg]:size-4">{item.icon}</span>
|
|
130
|
-
)}
|
|
131
|
-
{item.label}
|
|
132
|
-
</DropdownMenuItem>
|
|
133
|
-
))}
|
|
134
|
-
</DropdownMenu>
|
|
135
|
-
</div>
|
|
88
|
+
<DropdownMenu
|
|
89
|
+
open={menuOpen}
|
|
90
|
+
onOpenChange={setMenuOpen}
|
|
91
|
+
align="end"
|
|
92
|
+
trigger={
|
|
93
|
+
<IconButton
|
|
94
|
+
hierarchy="quaternary"
|
|
95
|
+
size="small"
|
|
96
|
+
aria-label={`Actions for ${label}`}
|
|
97
|
+
aria-haspopup="menu"
|
|
98
|
+
aria-expanded={menuOpen}
|
|
99
|
+
className={cn(
|
|
100
|
+
"size-6 min-h-6 shrink-0 p-1",
|
|
101
|
+
sidebarQuaternaryIconButton,
|
|
102
|
+
!forceMenuVisible && !menuOpen && "hidden group-hover/row:inline-flex",
|
|
103
|
+
(forceMenuVisible || menuOpen) && "inline-flex",
|
|
104
|
+
)}
|
|
105
|
+
>
|
|
106
|
+
<MoreIcon size="small" />
|
|
107
|
+
</IconButton>
|
|
108
|
+
}
|
|
109
|
+
>
|
|
110
|
+
{menuItems?.map((item) => (
|
|
111
|
+
<DropdownMenuItem
|
|
112
|
+
key={item.id}
|
|
113
|
+
className={cn(item.destructive && "text-red-500 hover:bg-red-50 hover:text-red-700")}
|
|
114
|
+
onSelect={() => item.onSelect()}
|
|
115
|
+
>
|
|
116
|
+
{item.icon && <span className="shrink-0 [&>svg]:size-4">{item.icon}</span>}
|
|
117
|
+
{item.label}
|
|
118
|
+
</DropdownMenuItem>
|
|
119
|
+
))}
|
|
120
|
+
</DropdownMenu>
|
|
136
121
|
)}
|
|
137
122
|
</div>
|
|
138
123
|
);
|
|
@@ -58,7 +58,6 @@ export const SidebarLink = ({
|
|
|
58
58
|
const row = collapsed ? (
|
|
59
59
|
<div
|
|
60
60
|
className={cn(
|
|
61
|
-
"relative",
|
|
62
61
|
sidebarSectionShellCollapsed,
|
|
63
62
|
stateStyles[state],
|
|
64
63
|
state === "idle" && sidebarRowInteractiveHover,
|
|
@@ -69,24 +68,24 @@ export const SidebarLink = ({
|
|
|
69
68
|
>
|
|
70
69
|
<a
|
|
71
70
|
href={href}
|
|
72
|
-
className="
|
|
71
|
+
className="flex w-full items-center justify-center"
|
|
73
72
|
aria-current={state === "active" ? "page" : undefined}
|
|
74
73
|
aria-label={label}
|
|
75
74
|
onClick={onNavigate}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
75
|
+
>
|
|
76
|
+
<span className="relative inline-flex shrink-0 items-center">
|
|
77
|
+
{icon && <span className={sidebarSectionIconClass}>{icon}</span>}
|
|
78
|
+
{unread && (
|
|
79
|
+
<span aria-hidden className={sidebarUnreadOverlay}>
|
|
80
|
+
<span className={sidebarUnreadDot} />
|
|
81
|
+
</span>
|
|
82
|
+
)}
|
|
83
|
+
</span>
|
|
84
|
+
</a>
|
|
85
85
|
</div>
|
|
86
86
|
) : (
|
|
87
87
|
<div
|
|
88
88
|
className={cn(
|
|
89
|
-
"relative",
|
|
90
89
|
tall ? sidebarSectionShellTall : sidebarSectionShell,
|
|
91
90
|
stateStyles[state],
|
|
92
91
|
state === "idle" && sidebarRowInteractiveHover,
|
|
@@ -95,27 +94,18 @@ export const SidebarLink = ({
|
|
|
95
94
|
data-state={state}
|
|
96
95
|
data-collapsed={collapsed}
|
|
97
96
|
>
|
|
98
|
-
<
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
<div
|
|
106
|
-
className={cn(
|
|
107
|
-
sidebarSectionContent,
|
|
108
|
-
"pointer-events-none relative z-10",
|
|
109
|
-
)}
|
|
110
|
-
>
|
|
111
|
-
<span className="flex min-w-0 flex-1 items-center gap-2 truncate text-left">
|
|
97
|
+
<div className={sidebarSectionContent}>
|
|
98
|
+
<a
|
|
99
|
+
href={href}
|
|
100
|
+
className="flex min-w-0 flex-1 items-center gap-2 truncate text-left"
|
|
101
|
+
aria-current={state === "active" ? "page" : undefined}
|
|
102
|
+
onClick={onNavigate}
|
|
103
|
+
>
|
|
112
104
|
{icon && <span className={sidebarSectionIconClass}>{icon}</span>}
|
|
113
105
|
<span className={sidebarSectionLabelClass}>{label}</span>
|
|
114
106
|
{badge}
|
|
115
|
-
</
|
|
116
|
-
{trailing
|
|
117
|
-
<span className="pointer-events-auto shrink-0">{trailing}</span>
|
|
118
|
-
)}
|
|
107
|
+
</a>
|
|
108
|
+
{trailing}
|
|
119
109
|
</div>
|
|
120
110
|
</div>
|
|
121
111
|
);
|
|
@@ -30,8 +30,7 @@ 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
|
-
|
|
34
|
-
export const sidebarSectionContent = "flex w-full min-w-0 items-center gap-2";
|
|
33
|
+
export const sidebarSectionContent = "flex w-full min-w-0 items-center gap-2 pl-1";
|
|
35
34
|
|
|
36
35
|
export const sidebarSectionIconClass = "shrink-0 [&>svg]:size-4";
|
|
37
36
|
|
package/src/primitives/Badge.tsx
CHANGED
|
@@ -1,40 +1,30 @@
|
|
|
1
1
|
import { type ReactNode } from "react";
|
|
2
2
|
import { cn } from "../utils/cn";
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
type BadgeColor = "blue" | "green" | "red" | "orange" | "purple" | "grey";
|
|
5
|
+
type BadgeType = "label" | "icon";
|
|
6
6
|
|
|
7
7
|
export type BadgeProps = {
|
|
8
8
|
color: BadgeColor;
|
|
9
|
-
/** `label` grows with content (pill). `icon` is a compact circle for a glyph/digit. */
|
|
10
9
|
type?: BadgeType;
|
|
11
10
|
children: ReactNode;
|
|
12
11
|
className?: string;
|
|
13
12
|
};
|
|
14
13
|
|
|
15
14
|
const colorStyles: Record<BadgeColor, string> = {
|
|
16
|
-
blue: "bg-meta-blue text-meta-
|
|
17
|
-
green: "bg-meta-green text-meta-
|
|
18
|
-
red: "bg-meta-red text-meta-
|
|
19
|
-
orange: "bg-meta-orange text-meta-
|
|
20
|
-
purple: "bg-meta-purple text-meta-
|
|
15
|
+
blue: "bg-meta-blue text-meta-on-blue",
|
|
16
|
+
green: "bg-meta-green text-meta-on-green",
|
|
17
|
+
red: "bg-meta-red text-meta-on-red",
|
|
18
|
+
orange: "bg-meta-orange text-meta-on-orange",
|
|
19
|
+
purple: "bg-meta-purple text-meta-on-purple",
|
|
21
20
|
grey: "bg-grey-100 text-display-on-light-secondary",
|
|
22
21
|
};
|
|
23
22
|
|
|
24
|
-
|
|
25
|
-
* Figma Badge (4365:74363) — padding + rounded-full; no fixed width on labels.
|
|
26
|
-
*/
|
|
27
|
-
export const Badge = ({
|
|
28
|
-
color,
|
|
29
|
-
type = "label",
|
|
30
|
-
children,
|
|
31
|
-
className,
|
|
32
|
-
}: BadgeProps) => (
|
|
23
|
+
export const Badge = ({ color, type = "label", children, className }: BadgeProps) => (
|
|
33
24
|
<span
|
|
34
25
|
className={cn(
|
|
35
|
-
"inline-flex shrink-0 items-center justify-center text-footnote-em
|
|
36
|
-
type === "label"
|
|
37
|
-
type === "icon" && "size-5 rounded-full [&>svg]:size-3",
|
|
26
|
+
"inline-flex shrink-0 items-center justify-center text-footnote-em",
|
|
27
|
+
type === "label" ? "rounded-control px-2 py-0.5" : "size-6 rounded-full",
|
|
38
28
|
colorStyles[color],
|
|
39
29
|
className,
|
|
40
30
|
)}
|