@lateralus-ai/shipping-ui 2.0.0-dev.4 → 2.0.0-dev.6

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.
@@ -1,13 +1,19 @@
1
1
  import { ReactNode } from 'react';
2
- export type ResultRowVariant = "report" | "chat" | "issue";
3
- export type ResultRowState = "idle" | "active";
2
+ import { EntryProps, EntryState, EntryVariant } from '../../components/Entry';
3
+ /** @deprecated Prefer `EntryVariant`. */
4
+ export type ResultRowVariant = EntryVariant;
5
+ /** @deprecated Prefer `EntryState`. */
6
+ export type ResultRowState = EntryState;
4
7
  export type ResultRowProps = {
5
- variant?: ResultRowVariant;
6
- state?: ResultRowState;
8
+ variant?: EntryVariant;
9
+ state?: EntryState;
7
10
  title?: ReactNode;
8
- /** Secondary line — excerpt, metadata, or highlighted query match. */
9
11
  subtitle?: ReactNode;
10
12
  onClick?: () => void;
11
13
  className?: string;
12
14
  };
15
+ /**
16
+ * @deprecated Prefer `Entry` — ResultRow is a thin alias for search lists.
17
+ */
13
18
  export declare const ResultRow: ({ variant, state, title, subtitle, onClick, className, }: ResultRowProps) => import("react/jsx-runtime").JSX.Element;
19
+ export type { EntryProps };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lateralus-ai/shipping-ui",
3
- "version": "2.0.0-dev.4",
3
+ "version": "2.0.0-dev.6",
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",
@@ -1,64 +1,118 @@
1
1
  import { type ReactNode } from "react";
2
- import { ChatIcon, IssuesIcon } from "../icons";
2
+ import { ChatIcon, IssuesIcon, ReportIcon, StatusIcon } from "../icons";
3
3
  import { cn } from "../utils/cn";
4
4
 
5
- export type EntryType = "chat" | "issue";
6
- export type EntryState = "idle" | "selected";
5
+ export type EntryVariant = "chat" | "issue" | "report";
6
+ /** @deprecated Prefer `EntryVariant`. */
7
+ export type EntryType = EntryVariant;
8
+ export type EntryState = "idle" | "active";
7
9
 
8
10
  export type EntryProps = {
9
- type: EntryType;
11
+ /** Visual type — drives the default leading icon. */
12
+ variant?: EntryVariant;
13
+ /** @deprecated Use `variant`. */
14
+ type?: EntryVariant;
10
15
  state?: EntryState;
11
- title: string;
12
- subtitle?: string;
13
- meta?: ReactNode;
16
+ title?: ReactNode;
17
+ /** Secondary line — excerpt, metadata, or highlighted query match. */
18
+ subtitle?: ReactNode;
19
+ /** Unread count badge in the title row (Figma red pill). */
20
+ count?: number;
21
+ /** Trailing actions (e.g. ellipsis menu). Shown on hover / when forced. */
22
+ trailing?: ReactNode;
23
+ /** Override the default variant icon. */
24
+ icon?: ReactNode;
14
25
  onClick?: () => void;
15
26
  className?: string;
27
+ /** @deprecated Use `count` or `trailing`. */
28
+ meta?: ReactNode;
16
29
  };
17
30
 
18
- const typeIcons: Record<EntryType, ReactNode> = {
19
- chat: <ChatIcon size="small" />,
20
- issue: <IssuesIcon size="small" />,
31
+ const variantIcons: Record<EntryVariant, ReactNode> = {
32
+ chat: <ChatIcon className="size-4" />,
33
+ issue: <StatusIcon className="size-4" />,
34
+ report: <ReportIcon className="size-4" />,
21
35
  };
22
36
 
37
+ /**
38
+ * List entry row — Figma `Entry` (389:10001).
39
+ * Shared by search results and activity lists.
40
+ */
23
41
  export const Entry = ({
42
+ variant,
24
43
  type,
25
44
  state = "idle",
26
- title,
45
+ title = "Title",
27
46
  subtitle,
28
- meta,
47
+ count,
48
+ trailing,
49
+ icon,
29
50
  onClick,
30
51
  className,
31
- }: EntryProps) => (
32
- <button
33
- type="button"
34
- data-type={type}
35
- data-state={state}
36
- onClick={onClick}
37
- className={cn(
38
- "flex w-full items-start gap-3 rounded-control px-3 py-2.5 text-left transition-colors",
39
- "hover:bg-background-secondary",
40
- "data-[state=selected]:bg-background-selected data-[state=selected]:text-display-on-dark-primary",
41
- "data-[state=selected]:[&_.entry-subtitle]:text-display-on-dark-secondary",
42
- "data-[state=selected]:[&_.entry-meta]:text-display-on-dark-tertiary",
43
- "data-[state=selected]:[&_.entry-icon]:text-display-on-dark-secondary",
44
- className,
45
- )}
46
- >
47
- <span className="entry-icon mt-0.5 shrink-0 text-display-on-light-secondary">
48
- {typeIcons[type]}
49
- </span>
50
- <span className="min-w-0 flex-1">
51
- <span className="block truncate text-caption-2-em">{title}</span>
52
- {subtitle && (
53
- <span className="entry-subtitle mt-0.5 block truncate text-footnote text-display-on-light-tertiary">
54
- {subtitle}
55
- </span>
52
+ meta,
53
+ }: EntryProps) => {
54
+ const resolvedVariant: EntryVariant = variant ?? type ?? "chat";
55
+ const isInteractive = typeof onClick === "function";
56
+ const Comp = isInteractive ? "button" : "div";
57
+
58
+ return (
59
+ <Comp
60
+ {...(isInteractive ? { type: "button" as const } : {})}
61
+ data-variant={resolvedVariant}
62
+ data-state={state}
63
+ onClick={onClick}
64
+ className={cn(
65
+ "group flex w-full items-center gap-4 rounded-control p-2 text-left transition-colors",
66
+ "hover:bg-[rgba(38,36,32,0.04)]",
67
+ state === "active" && "bg-[rgba(38,36,32,0.04)]",
68
+ className,
56
69
  )}
57
- </span>
58
- {meta && (
59
- <span className="entry-meta shrink-0 text-footnote text-display-on-light-quaternary">
60
- {meta}
70
+ >
71
+ <span className="shrink-0 text-display-on-light-secondary">
72
+ {icon ?? variantIcons[resolvedVariant] ?? (
73
+ <IssuesIcon className="size-4" />
74
+ )}
61
75
  </span>
62
- )}
63
- </button>
64
- );
76
+
77
+ <span className="flex min-w-0 flex-1 flex-col">
78
+ <span className="flex min-h-6 w-full items-center gap-4">
79
+ <span className="min-w-0 flex-1 truncate text-caption-1-em text-display-on-light-primary">
80
+ {title}
81
+ </span>
82
+ {typeof count === "number" && count > 0 && (
83
+ <span
84
+ className="inline-flex min-h-5 min-w-5 shrink-0 items-center justify-center rounded-full bg-red-600 px-0.5 text-footnote-em text-white"
85
+ aria-label={`${count} unread`}
86
+ >
87
+ {count > 99 ? "99+" : count}
88
+ </span>
89
+ )}
90
+ {meta != null && typeof count !== "number" && (
91
+ <span className="shrink-0 text-footnote text-display-on-light-quaternary">
92
+ {meta}
93
+ </span>
94
+ )}
95
+ </span>
96
+
97
+ {(subtitle != null && subtitle !== false) || trailing ? (
98
+ <span className="flex min-h-6 w-full items-center gap-4">
99
+ {subtitle != null && subtitle !== false && (
100
+ <span className="min-w-0 flex-1 truncate text-caption-2 text-display-on-light-secondary">
101
+ {subtitle}
102
+ </span>
103
+ )}
104
+ {trailing != null && (
105
+ <span
106
+ className="shrink-0"
107
+ onClick={(event) => event.stopPropagation()}
108
+ onKeyDown={(event) => event.stopPropagation()}
109
+ >
110
+ {trailing}
111
+ </span>
112
+ )}
113
+ </span>
114
+ ) : null}
115
+ </span>
116
+ </Comp>
117
+ );
118
+ };
@@ -51,7 +51,7 @@ export const TabsList = forwardRef<
51
51
  ref={ref}
52
52
  className={cn(
53
53
  "flex items-center",
54
- type === "tabs" && "gap-1 border-b border-divider-primary",
54
+ type === "tabs" && "gap-6 border-b border-divider-primary",
55
55
  type === "pills" && "gap-2",
56
56
  className,
57
57
  )}
@@ -82,7 +82,7 @@ export const TabsTrigger = forwardRef<
82
82
  "inline-flex items-center justify-center transition-colors outline-none",
83
83
  "disabled:pointer-events-none disabled:opacity-50",
84
84
  type === "tabs" && [
85
- "relative px-3 py-2 text-caption-2 text-display-on-light-secondary",
85
+ "relative h-12 gap-2 px-0 py-0 text-caption-2 text-display-on-light-secondary",
86
86
  "hover:text-display-on-light-primary",
87
87
  "data-[state=active]:text-caption-2-em data-[state=active]:text-display-on-light-primary",
88
88
  "data-[state=active]:after:absolute data-[state=active]:after:inset-x-0 data-[state=active]:after:-bottom-px",
@@ -99,18 +99,26 @@ export const TabsTrigger = forwardRef<
99
99
  {...props}
100
100
  >
101
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
- )}
102
+ {typeof count === "number" &&
103
+ (type === "pills" ? (
104
+ <span
105
+ className={cn(
106
+ "inline-flex size-6 shrink-0 items-center justify-center rounded-full text-footnote-em",
107
+ "bg-accent-bg-light text-display-on-light-secondary",
108
+ "group-data-[state=active]:text-display-on-light-tertiary",
109
+ )}
110
+ aria-label={`Count: ${count}`}
111
+ >
112
+ {count > 99 ? "99+" : count}
113
+ </span>
114
+ ) : (
115
+ <span
116
+ className="px-1 text-caption-2 text-display-on-light-secondary"
117
+ aria-label={`Count: ${count}`}
118
+ >
119
+ {count > 99 ? "99+" : count}
120
+ </span>
121
+ ))}
114
122
  </TabsPrimitive.Trigger>
115
123
  );
116
124
  });
@@ -17,7 +17,7 @@ export {
17
17
  type TabsType,
18
18
  } from "./Tabs";
19
19
  export { Header, type HeaderProps, type HeaderVariant } from "./Header";
20
- export { Entry, type EntryProps, type EntryType, type EntryState } from "./Entry";
20
+ export { Entry, type EntryProps, type EntryType, type EntryState, type EntryVariant } from "./Entry";
21
21
  export { EmptyState, type EmptyStateProps } from "./EmptyState";
22
22
  export {
23
23
  Modal,
package/src/index.ts CHANGED
@@ -61,6 +61,7 @@ export type {
61
61
  EntryProps,
62
62
  EntryType,
63
63
  EntryState,
64
+ EntryVariant,
64
65
  EmptyStateProps,
65
66
  ModalProps,
66
67
  ModalOverlayProps,
@@ -1,26 +1,28 @@
1
1
  import { type ReactNode } from "react";
2
- import { ChatIcon, IssuesIcon, ReportIcon } from "../../icons";
3
- import { cn } from "../../utils/cn";
2
+ import {
3
+ Entry,
4
+ type EntryProps,
5
+ type EntryState,
6
+ type EntryVariant,
7
+ } from "../../components/Entry";
4
8
 
5
- export type ResultRowVariant = "report" | "chat" | "issue";
6
- export type ResultRowState = "idle" | "active";
9
+ /** @deprecated Prefer `EntryVariant`. */
10
+ export type ResultRowVariant = EntryVariant;
11
+ /** @deprecated Prefer `EntryState`. */
12
+ export type ResultRowState = EntryState;
7
13
 
8
14
  export type ResultRowProps = {
9
- variant?: ResultRowVariant;
10
- state?: ResultRowState;
15
+ variant?: EntryVariant;
16
+ state?: EntryState;
11
17
  title?: ReactNode;
12
- /** Secondary line — excerpt, metadata, or highlighted query match. */
13
18
  subtitle?: ReactNode;
14
19
  onClick?: () => void;
15
20
  className?: string;
16
21
  };
17
22
 
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
-
23
+ /**
24
+ * @deprecated Prefer `Entry` — ResultRow is a thin alias for search lists.
25
+ */
24
26
  export const ResultRow = ({
25
27
  variant = "report",
26
28
  state = "idle",
@@ -29,30 +31,14 @@ export const ResultRow = ({
29
31
  onClick,
30
32
  className,
31
33
  }: ResultRowProps) => (
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}
34
+ <Entry
35
+ variant={variant}
36
+ state={state}
37
+ title={title}
38
+ subtitle={subtitle}
42
39
  onClick={onClick}
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>
40
+ className={className}
41
+ />
58
42
  );
43
+
44
+ export type { EntryProps };
@@ -82,9 +82,29 @@ export const ContentCanvas = () => (
82
82
 
83
83
  <FigmaSection label="Entry">
84
84
  <div className="max-w-xl space-y-2">
85
- <Entry type="chat" title="Hull inspection report" subtitle="2 hours ago" meta="17" />
86
- <Entry type="issue" title="Critical valve malfunction" state="selected" meta="High" />
87
- <Entry type="chat" title="Port state control checklist" subtitle="Yesterday" className="bg-background-hover" />
85
+ <Entry
86
+ variant="chat"
87
+ title="Hull inspection report"
88
+ subtitle="We’ve got a persistent oil leak from the fuel pump."
89
+ />
90
+ <Entry
91
+ variant="chat"
92
+ state="active"
93
+ title="Ballast Pump Not Starting"
94
+ subtitle="The fuel pump won’t start consistently."
95
+ trailing={<span className="text-display-on-light-secondary">···</span>}
96
+ />
97
+ <Entry
98
+ variant="issue"
99
+ title="Excessive Rust on Fuel Pump"
100
+ subtitle="Created by Jake Silva on March 28"
101
+ count={3}
102
+ />
103
+ <Entry
104
+ variant="report"
105
+ title="Engine Room Oil Leak"
106
+ subtitle="We’ve spotted a steady oil leak near the fuel pump."
107
+ />
88
108
  </div>
89
109
  </FigmaSection>
90
110