@lateralus-ai/shipping-ui 2.0.0-dev.5 → 2.0.0-dev.7

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.5",
3
+ "version": "2.0.0-dev.7",
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,138 @@
1
- import { type ReactNode } from "react";
2
- import { ChatIcon, IssuesIcon } from "../icons";
1
+ import {
2
+ type KeyboardEvent,
3
+ type ReactNode,
4
+ } from "react";
5
+ import { ChatIcon, IssuesIcon, ReportIcon, StatusIcon } from "../icons";
3
6
  import { cn } from "../utils/cn";
4
7
 
5
- export type EntryType = "chat" | "issue";
6
- export type EntryState = "idle" | "selected";
8
+ export type EntryVariant = "chat" | "issue" | "report";
9
+ /** @deprecated Prefer `EntryVariant`. */
10
+ export type EntryType = EntryVariant;
11
+ export type EntryState = "idle" | "active";
7
12
 
8
13
  export type EntryProps = {
9
- type: EntryType;
14
+ /** Visual type — drives the default leading icon. */
15
+ variant?: EntryVariant;
16
+ /** @deprecated Use `variant`. */
17
+ type?: EntryVariant;
10
18
  state?: EntryState;
11
- title: string;
12
- subtitle?: string;
13
- meta?: ReactNode;
19
+ title?: ReactNode;
20
+ /** Secondary line — excerpt, metadata, or highlighted query match. */
21
+ subtitle?: ReactNode;
22
+ /** Unread count badge in the title row (Figma red pill). */
23
+ count?: number;
24
+ /** Trailing actions (e.g. ellipsis menu). Shown on hover / when forced. */
25
+ trailing?: ReactNode;
26
+ /** Override the default variant icon. */
27
+ icon?: ReactNode;
14
28
  onClick?: () => void;
15
29
  className?: string;
30
+ /** @deprecated Use `count` or `trailing`. */
31
+ meta?: ReactNode;
16
32
  };
17
33
 
18
- const typeIcons: Record<EntryType, ReactNode> = {
19
- chat: <ChatIcon size="small" />,
20
- issue: <IssuesIcon size="small" />,
34
+ const variantIcons: Record<EntryVariant, ReactNode> = {
35
+ chat: <ChatIcon className="size-4" />,
36
+ issue: <StatusIcon className="size-4" />,
37
+ report: <ReportIcon className="size-4" />,
21
38
  };
22
39
 
40
+ /**
41
+ * List entry row — Figma `Entry` (389:10001).
42
+ * Shared by search results and activity lists.
43
+ *
44
+ * Renders a `div` (not `<button>`) so trailing controls can be real buttons
45
+ * without nested-button issues. Whole-row click uses `onClick` + keyboard.
46
+ */
23
47
  export const Entry = ({
48
+ variant,
24
49
  type,
25
50
  state = "idle",
26
- title,
51
+ title = "Title",
27
52
  subtitle,
28
- meta,
53
+ count,
54
+ trailing,
55
+ icon,
29
56
  onClick,
30
57
  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>
58
+ meta,
59
+ }: EntryProps) => {
60
+ const resolvedVariant: EntryVariant = variant ?? type ?? "chat";
61
+ const isInteractive = typeof onClick === "function";
62
+
63
+ const handleKeyDown = (event: KeyboardEvent<HTMLDivElement>) => {
64
+ if (!onClick) {
65
+ return;
66
+ }
67
+ if (event.key === "Enter" || event.key === " ") {
68
+ event.preventDefault();
69
+ onClick();
70
+ }
71
+ };
72
+
73
+ return (
74
+ <div
75
+ role={isInteractive ? "button" : undefined}
76
+ tabIndex={isInteractive ? 0 : undefined}
77
+ data-variant={resolvedVariant}
78
+ data-state={state}
79
+ onClick={onClick}
80
+ onKeyDown={handleKeyDown}
81
+ className={cn(
82
+ "group flex w-full items-center gap-4 rounded-control p-2 text-left transition-colors",
83
+ "hover:bg-[rgba(38,36,32,0.04)]",
84
+ state === "active" && "bg-[rgba(38,36,32,0.04)]",
85
+ isInteractive && "cursor-pointer",
86
+ className,
56
87
  )}
57
- </span>
58
- {meta && (
59
- <span className="entry-meta shrink-0 text-footnote text-display-on-light-quaternary">
60
- {meta}
88
+ >
89
+ <span className="shrink-0 text-display-on-light-secondary">
90
+ {icon ?? variantIcons[resolvedVariant] ?? (
91
+ <IssuesIcon className="size-4" />
92
+ )}
93
+ </span>
94
+
95
+ <span className="flex min-w-0 flex-1 flex-col">
96
+ <span className="flex min-h-6 w-full items-center gap-4">
97
+ <span className="min-w-0 flex-1 truncate text-caption-1-em text-display-on-light-primary">
98
+ {title}
99
+ </span>
100
+ {typeof count === "number" && count > 0 && (
101
+ <span
102
+ 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"
103
+ aria-label={`${count} unread`}
104
+ >
105
+ {count > 99 ? "99+" : count}
106
+ </span>
107
+ )}
108
+ {meta != null && typeof count !== "number" && (
109
+ <span className="shrink-0 text-footnote text-display-on-light-quaternary">
110
+ {meta}
111
+ </span>
112
+ )}
113
+ </span>
114
+
115
+ {(subtitle != null && subtitle !== false) || trailing ? (
116
+ <span className="flex min-h-6 w-full items-center gap-4">
117
+ {subtitle != null && subtitle !== false ? (
118
+ <span className="min-w-0 flex-1 truncate text-caption-2 text-display-on-light-secondary">
119
+ {subtitle}
120
+ </span>
121
+ ) : (
122
+ <span className="min-w-0 flex-1" />
123
+ )}
124
+ {trailing != null && (
125
+ <span
126
+ className="inline-flex size-5 shrink-0 items-center justify-center"
127
+ onClick={(event) => event.stopPropagation()}
128
+ onKeyDown={(event) => event.stopPropagation()}
129
+ >
130
+ {trailing}
131
+ </span>
132
+ )}
133
+ </span>
134
+ ) : null}
61
135
  </span>
62
- )}
63
- </button>
64
- );
136
+ </div>
137
+ );
138
+ };
@@ -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