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

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,292 +1,304 @@
1
- import {
2
- type ChangeEvent,
3
- type InputHTMLAttributes,
4
- type ReactNode,
5
- } from "react";
6
- import {
7
- Modal,
8
- ModalBody,
9
- ModalContent,
10
- ModalDescription,
11
- ModalTitle,
12
- type ModalContentProps,
13
- type ModalProps,
14
- } from "../../components/Modal";
15
- import { Tabs, TabsContent, TabsList, TabsTrigger } from "../../components/Tabs";
16
- import { cn } from "../../utils/cn";
17
- import { ResultRow, type ResultRowProps } from "./ResultRow";
18
- import { SectionHeader } from "./SectionHeader";
19
- import { Skeleton } from "../Skeleton";
20
-
21
- export type SearchModalState = "idle" | "loading" | "results";
22
-
23
- export type SearchFilterTab = {
24
- value: string;
25
- label: string;
26
- count?: number;
27
- };
28
-
29
- export type SearchModalProps = ModalProps & {
30
- /** Panel state when using the built-in layout helpers. */
31
- state?: SearchModalState;
32
- className?: string;
33
- contentClassName?: string;
34
- showOverlay?: boolean;
35
- overlayClassName?: string;
36
- children?: ReactNode;
37
- };
38
-
39
- const DEFAULT_FILTERS: SearchFilterTab[] = [
40
- { value: "all", label: "All", count: 0 },
41
- { value: "reports", label: "Reports", count: 0 },
42
- { value: "chats", label: "Chats", count: 0 },
43
- { value: "issues", label: "Issues", count: 0 },
44
- ];
45
-
46
- /**
47
- * Search dialog composed on Modal.
48
- * Use compound parts (Input / Body / Filters / Results) for app wiring,
49
- * or pass `state` + demo children for Storybook.
50
- */
51
- export const SearchModal = ({
52
- state,
53
- className,
54
- contentClassName,
55
- showOverlay = true,
56
- overlayClassName,
57
- children,
58
- ...modalProps
59
- }: SearchModalProps) => (
60
- <Modal {...modalProps}>
61
- <ModalContent
62
- showOverlay={showOverlay}
63
- overlayClassName={overlayClassName}
64
- className={cn(
65
- "flex max-h-[min(80vh,640px)] w-[min(100vw-2rem,700px)] max-w-[768px] flex-col p-0",
66
- contentClassName,
67
- )}
68
- aria-label="Search"
69
- data-state={state}
70
- >
71
- <ModalTitle className="sr-only">Search</ModalTitle>
72
- <ModalDescription className="sr-only">
73
- Search reports, chats, or issues
74
- </ModalDescription>
75
- <div className={cn("flex min-h-0 flex-1 flex-col", className)}>
76
- {children}
77
- </div>
78
- </ModalContent>
79
- </Modal>
80
- );
81
-
82
- export type SearchModalInputProps = Omit<
83
- InputHTMLAttributes<HTMLInputElement>,
84
- "type"
85
- > & {
86
- className?: string;
87
- wrapperClassName?: string;
88
- };
89
-
90
- export const SearchModalInput = ({
91
- className,
92
- wrapperClassName,
93
- placeholder = "Search reports, chats, or issues…",
94
- ...props
95
- }: SearchModalInputProps) => (
96
- <div
97
- className={cn(
98
- "shrink-0 p-3",
99
- wrapperClassName,
100
- )}
101
- >
102
- <div className="flex min-h-10 items-center px-3">
103
- <input
104
- type="search"
105
- placeholder={placeholder}
106
- className={cn(
107
- "w-full bg-transparent text-caption-1 text-display-on-light-primary",
108
- "placeholder:text-display-on-light-tertiary focus:outline-none",
109
- className,
110
- )}
111
- aria-label="Search query"
112
- {...props}
113
- />
114
- </div>
115
- </div>
116
- );
117
-
118
- export const SearchModalDivider = ({ className }: { className?: string }) => (
119
- <div
120
- className={cn("h-px w-full shrink-0 bg-divider-primary", className)}
121
- aria-hidden
122
- />
123
- );
124
-
125
- export type SearchModalBodyProps = {
126
- children: ReactNode;
127
- className?: string;
128
- };
129
-
130
- export const SearchModalBody = ({
131
- children,
132
- className,
133
- }: SearchModalBodyProps) => (
134
- <ModalBody className={cn("min-w-0 flex-1 p-2", className)}>
135
- {children}
136
- </ModalBody>
137
- );
138
-
139
- export type SearchModalFiltersProps = {
140
- tabs?: SearchFilterTab[];
141
- value: string;
142
- onValueChange: (value: string) => void;
143
- children?: ReactNode;
144
- className?: string;
145
- listClassName?: string;
146
- };
147
-
148
- /**
149
- * Pill-style filter tabs for the results state.
150
- * Children render below the tab list (typically a single filtered results list).
151
- * For per-tab panels, nest `SearchModalFilterPanel` (TabsContent) as children.
152
- */
153
- export const SearchModalFilters = ({
154
- tabs = DEFAULT_FILTERS,
155
- value,
156
- onValueChange,
157
- children,
158
- className,
159
- listClassName,
160
- }: SearchModalFiltersProps) => (
161
- <Tabs
162
- type="pills"
163
- value={value}
164
- onValueChange={onValueChange}
165
- className={cn("gap-0", className)}
166
- >
167
- <TabsList className={cn("w-full p-2", listClassName)}>
168
- {tabs.map((tab) => (
169
- <TabsTrigger key={tab.value} value={tab.value} count={tab.count}>
170
- {tab.label}
171
- </TabsTrigger>
172
- ))}
173
- </TabsList>
174
- {children}
175
- </Tabs>
176
- );
177
-
178
- export type SearchModalFilterPanelProps = {
179
- value: string;
180
- children: ReactNode;
181
- className?: string;
182
- };
183
-
184
- export const SearchModalFilterPanel = ({
185
- value,
186
- children,
187
- className,
188
- }: SearchModalFilterPanelProps) => (
189
- <TabsContent value={value} className={className}>
190
- {children}
191
- </TabsContent>
192
- );
193
-
194
- export type SearchModalResultsProps = {
195
- items?: ResultRowProps[];
196
- children?: ReactNode;
197
- className?: string;
198
- };
199
-
200
- export const SearchModalResults = ({
201
- items,
202
- children,
203
- className,
204
- }: SearchModalResultsProps) => (
205
- <div className={cn("flex flex-col", className)}>
206
- {items?.map((item, index) => (
207
- <ResultRow key={index} {...item} />
208
- ))}
209
- {children}
210
- </div>
211
- );
212
-
213
- export type SearchModalIdleProps = {
214
- label?: string;
215
- items?: ResultRowProps[];
216
- children?: ReactNode;
217
- className?: string;
218
- };
219
-
220
- export const SearchModalIdle = ({
221
- label = "Recently viewed",
222
- items,
223
- children,
224
- className,
225
- }: SearchModalIdleProps) => (
226
- <div className={cn("flex flex-col p-1", className)}>
227
- <SectionHeader
228
- label={label}
229
- className="px-3 py-1 text-caption-2 font-normal normal-case tracking-[0.01em] text-display-on-light-secondary"
230
- />
231
- <SearchModalResults items={items}>{children}</SearchModalResults>
232
- </div>
233
- );
234
-
235
- export type SearchModalLoadingProps = {
236
- className?: string;
237
- };
238
-
239
- export const SearchModalLoading = ({ className }: SearchModalLoadingProps) => (
240
- <Skeleton variant="search" className={cn("rounded-none", className)} />
241
- );
242
-
243
- /** Convenience controlled panel for Storybook / simple embeds. */
244
- export type SearchModalPanelProps = {
245
- state?: SearchModalState;
246
- query?: string;
247
- onQueryChange?: (query: string) => void;
248
- filter?: string;
249
- onFilterChange?: (filter: string) => void;
250
- filters?: SearchFilterTab[];
251
- recentItems?: ResultRowProps[];
252
- resultItems?: ResultRowProps[];
253
- className?: string;
254
- };
255
-
256
- export const SearchModalPanel = ({
257
- state = "idle",
258
- query = "",
259
- onQueryChange,
260
- filter = "all",
261
- onFilterChange,
262
- filters = DEFAULT_FILTERS,
263
- recentItems,
264
- resultItems,
265
- className,
266
- }: SearchModalPanelProps) => {
267
- const handleChange = (event: ChangeEvent<HTMLInputElement>) => {
268
- onQueryChange?.(event.target.value);
269
- };
270
-
271
- return (
272
- <div className={cn("flex min-h-0 flex-1 flex-col", className)} data-state={state}>
273
- <SearchModalInput value={query} onChange={handleChange} />
274
- <SearchModalDivider />
275
- <SearchModalBody className={state === "loading" ? "p-0" : undefined}>
276
- {state === "idle" && <SearchModalIdle items={recentItems} />}
277
- {state === "loading" && <SearchModalLoading />}
278
- {state === "results" && (
279
- <SearchModalFilters
280
- tabs={filters}
281
- value={filter}
282
- onValueChange={(next) => onFilterChange?.(next)}
283
- >
284
- <SearchModalResults items={resultItems} />
285
- </SearchModalFilters>
286
- )}
287
- </SearchModalBody>
288
- </div>
289
- );
290
- };
291
-
292
- export type { ModalContentProps };
1
+ import {
2
+ type ChangeEvent,
3
+ type InputHTMLAttributes,
4
+ type ReactNode,
5
+ } from "react";
6
+ import {
7
+ Modal,
8
+ ModalBody,
9
+ ModalContent,
10
+ ModalDescription,
11
+ ModalTitle,
12
+ type ModalContentProps,
13
+ type ModalProps,
14
+ } from "../../components/Modal";
15
+ import { Tabs, TabsContent, TabsList, TabsTrigger } from "../../components/Tabs";
16
+ import { cn } from "../../utils/cn";
17
+ import { ResultRow, type ResultRowProps } from "./ResultRow";
18
+ import { SectionHeader } from "./SectionHeader";
19
+ import { Skeleton } from "../Skeleton";
20
+
21
+ export type SearchModalState = "idle" | "loading" | "results";
22
+
23
+ export type SearchFilterTab = {
24
+ value: string;
25
+ label: string;
26
+ count?: number;
27
+ };
28
+
29
+ export type SearchModalProps = ModalProps & {
30
+ /** Panel state when using the built-in layout helpers. */
31
+ state?: SearchModalState;
32
+ className?: string;
33
+ contentClassName?: string;
34
+ showOverlay?: boolean;
35
+ overlayClassName?: string;
36
+ children?: ReactNode;
37
+ };
38
+
39
+ const DEFAULT_FILTERS: SearchFilterTab[] = [
40
+ { value: "all", label: "All", count: 0 },
41
+ { value: "reports", label: "Reports", count: 0 },
42
+ { value: "chats", label: "Chats", count: 0 },
43
+ { value: "issues", label: "Issues", count: 0 },
44
+ ];
45
+
46
+ /**
47
+ * Search dialog composed on Modal.
48
+ * Use compound parts (Input / Body / Filters / Results) for app wiring,
49
+ * or pass `state` + demo children for Storybook.
50
+ */
51
+ export const SearchModal = ({
52
+ state,
53
+ className,
54
+ contentClassName,
55
+ showOverlay = true,
56
+ overlayClassName,
57
+ children,
58
+ ...modalProps
59
+ }: SearchModalProps) => (
60
+ <Modal {...modalProps}>
61
+ <ModalContent
62
+ showOverlay={showOverlay}
63
+ overlayClassName={overlayClassName}
64
+ className={cn(
65
+ "flex max-h-[min(80vh,640px)] w-[min(100vw-2rem,700px)] max-w-[768px] flex-col p-0",
66
+ contentClassName,
67
+ )}
68
+ aria-label="Search"
69
+ data-state={state}
70
+ >
71
+ <ModalTitle className="sr-only">Search</ModalTitle>
72
+ <ModalDescription className="sr-only">
73
+ Search reports, chats, or issues
74
+ </ModalDescription>
75
+ <div className={cn("flex min-h-0 flex-1 flex-col", className)}>
76
+ {children}
77
+ </div>
78
+ </ModalContent>
79
+ </Modal>
80
+ );
81
+
82
+ export type SearchModalInputProps = Omit<
83
+ InputHTMLAttributes<HTMLInputElement>,
84
+ "type"
85
+ > & {
86
+ className?: string;
87
+ wrapperClassName?: string;
88
+ };
89
+
90
+ export const SearchModalInput = ({
91
+ className,
92
+ wrapperClassName,
93
+ placeholder = "Search reports, chats, or issues…",
94
+ ...props
95
+ }: SearchModalInputProps) => (
96
+ <div
97
+ className={cn(
98
+ "shrink-0 p-3",
99
+ wrapperClassName,
100
+ )}
101
+ >
102
+ <div className="flex min-h-10 items-center px-3">
103
+ <input
104
+ type="search"
105
+ placeholder={placeholder}
106
+ className={cn(
107
+ "w-full bg-transparent text-caption-1 text-display-on-light-primary",
108
+ "placeholder:text-display-on-light-tertiary focus:outline-none",
109
+ className,
110
+ )}
111
+ aria-label="Search query"
112
+ {...props}
113
+ />
114
+ </div>
115
+ </div>
116
+ );
117
+
118
+ export const SearchModalDivider = ({ className }: { className?: string }) => (
119
+ <div
120
+ className={cn("h-px w-full shrink-0 bg-divider-primary", className)}
121
+ aria-hidden
122
+ />
123
+ );
124
+
125
+ export type SearchModalBodyProps = {
126
+ children: ReactNode;
127
+ className?: string;
128
+ };
129
+
130
+ export const SearchModalBody = ({
131
+ children,
132
+ className,
133
+ }: SearchModalBodyProps) => (
134
+ <ModalBody
135
+ className={cn(
136
+ "flex min-h-0 min-w-0 flex-1 flex-col overflow-hidden p-2",
137
+ className,
138
+ )}
139
+ >
140
+ {children}
141
+ </ModalBody>
142
+ );
143
+
144
+ export type SearchModalFiltersProps = {
145
+ tabs?: SearchFilterTab[];
146
+ value: string;
147
+ onValueChange: (value: string) => void;
148
+ children?: ReactNode;
149
+ className?: string;
150
+ listClassName?: string;
151
+ };
152
+
153
+ /**
154
+ * Pill-style filter tabs for the results state.
155
+ * Children render below the tab list (typically a single filtered results list).
156
+ * For per-tab panels, nest `SearchModalFilterPanel` (TabsContent) as children.
157
+ */
158
+ export const SearchModalFilters = ({
159
+ tabs = DEFAULT_FILTERS,
160
+ value,
161
+ onValueChange,
162
+ children,
163
+ className,
164
+ listClassName,
165
+ }: SearchModalFiltersProps) => (
166
+ <Tabs
167
+ type="pills"
168
+ value={value}
169
+ onValueChange={onValueChange}
170
+ className={cn("min-h-0 flex-1 gap-0", className)}
171
+ >
172
+ <TabsList
173
+ className={cn(
174
+ "w-full shrink-0 bg-background-primary p-2",
175
+ listClassName,
176
+ )}
177
+ >
178
+ {tabs.map((tab) => (
179
+ <TabsTrigger key={tab.value} value={tab.value} count={tab.count}>
180
+ {tab.label}
181
+ </TabsTrigger>
182
+ ))}
183
+ </TabsList>
184
+ <div className="min-h-0 flex-1 overflow-y-auto">{children}</div>
185
+ </Tabs>
186
+ );
187
+
188
+ export type SearchModalFilterPanelProps = {
189
+ value: string;
190
+ children: ReactNode;
191
+ className?: string;
192
+ };
193
+
194
+ export const SearchModalFilterPanel = ({
195
+ value,
196
+ children,
197
+ className,
198
+ }: SearchModalFilterPanelProps) => (
199
+ <TabsContent value={value} className={className}>
200
+ {children}
201
+ </TabsContent>
202
+ );
203
+
204
+ export type SearchModalResultsProps = {
205
+ items?: ResultRowProps[];
206
+ children?: ReactNode;
207
+ className?: string;
208
+ };
209
+
210
+ export const SearchModalResults = ({
211
+ items,
212
+ children,
213
+ className,
214
+ }: SearchModalResultsProps) => (
215
+ <div className={cn("flex flex-col", className)}>
216
+ {items?.map((item, index) => (
217
+ <ResultRow key={index} {...item} />
218
+ ))}
219
+ {children}
220
+ </div>
221
+ );
222
+
223
+ export type SearchModalIdleProps = {
224
+ label?: string;
225
+ items?: ResultRowProps[];
226
+ children?: ReactNode;
227
+ className?: string;
228
+ };
229
+
230
+ export const SearchModalIdle = ({
231
+ label = "Recently viewed",
232
+ items,
233
+ children,
234
+ className,
235
+ }: SearchModalIdleProps) => (
236
+ <div className={cn("flex min-h-0 flex-1 flex-col p-1", className)}>
237
+ <SectionHeader
238
+ label={label}
239
+ className="shrink-0 px-3 py-1 text-caption-2 font-normal normal-case tracking-[0.01em] text-display-on-light-secondary"
240
+ />
241
+ <div className="min-h-0 flex-1 overflow-y-auto">
242
+ <SearchModalResults items={items}>{children}</SearchModalResults>
243
+ </div>
244
+ </div>
245
+ );
246
+
247
+ export type SearchModalLoadingProps = {
248
+ className?: string;
249
+ };
250
+
251
+ export const SearchModalLoading = ({ className }: SearchModalLoadingProps) => (
252
+ <Skeleton variant="search" className={cn("rounded-none", className)} />
253
+ );
254
+
255
+ /** Convenience controlled panel for Storybook / simple embeds. */
256
+ export type SearchModalPanelProps = {
257
+ state?: SearchModalState;
258
+ query?: string;
259
+ onQueryChange?: (query: string) => void;
260
+ filter?: string;
261
+ onFilterChange?: (filter: string) => void;
262
+ filters?: SearchFilterTab[];
263
+ recentItems?: ResultRowProps[];
264
+ resultItems?: ResultRowProps[];
265
+ className?: string;
266
+ };
267
+
268
+ export const SearchModalPanel = ({
269
+ state = "idle",
270
+ query = "",
271
+ onQueryChange,
272
+ filter = "all",
273
+ onFilterChange,
274
+ filters = DEFAULT_FILTERS,
275
+ recentItems,
276
+ resultItems,
277
+ className,
278
+ }: SearchModalPanelProps) => {
279
+ const handleChange = (event: ChangeEvent<HTMLInputElement>) => {
280
+ onQueryChange?.(event.target.value);
281
+ };
282
+
283
+ return (
284
+ <div className={cn("flex min-h-0 flex-1 flex-col", className)} data-state={state}>
285
+ <SearchModalInput value={query} onChange={handleChange} />
286
+ <SearchModalDivider />
287
+ <SearchModalBody className={state === "loading" ? "p-0" : undefined}>
288
+ {state === "idle" && <SearchModalIdle items={recentItems} />}
289
+ {state === "loading" && <SearchModalLoading />}
290
+ {state === "results" && (
291
+ <SearchModalFilters
292
+ tabs={filters}
293
+ value={filter}
294
+ onValueChange={(next) => onFilterChange?.(next)}
295
+ >
296
+ <SearchModalResults items={resultItems} />
297
+ </SearchModalFilters>
298
+ )}
299
+ </SearchModalBody>
300
+ </div>
301
+ );
302
+ };
303
+
304
+ export type { ModalContentProps };
@@ -1,31 +1,31 @@
1
- export { Pill, type PillProps, type PillSize, type PillState } from "./Pill";
2
- export { PillInfo, type PillInfoProps, type PillInfoType } from "./PillInfo";
3
- export {
4
- ResultRow,
5
- type ResultRowProps,
6
- type ResultRowState,
7
- type ResultRowVariant,
8
- } from "./ResultRow";
9
- export {
10
- SearchModal,
11
- SearchModalBody,
12
- SearchModalDivider,
13
- SearchModalFilterPanel,
14
- SearchModalFilters,
15
- SearchModalIdle,
16
- SearchModalInput,
17
- SearchModalLoading,
18
- SearchModalPanel,
19
- SearchModalResults,
20
- type SearchFilterTab,
21
- type SearchModalBodyProps,
22
- type SearchModalFiltersProps,
23
- type SearchModalIdleProps,
24
- type SearchModalInputProps,
25
- type SearchModalLoadingProps,
26
- type SearchModalPanelProps,
27
- type SearchModalProps,
28
- type SearchModalResultsProps,
29
- type SearchModalState,
30
- } from "./SearchModal";
31
- export { SectionHeader, type SectionHeaderProps } from "./SectionHeader";
1
+ export { Pill, type PillProps, type PillSize, type PillState } from "./Pill";
2
+ export { PillInfo, type PillInfoProps, type PillInfoType } from "./PillInfo";
3
+ export {
4
+ ResultRow,
5
+ type ResultRowProps,
6
+ type ResultRowState,
7
+ type ResultRowVariant,
8
+ } from "./ResultRow";
9
+ export {
10
+ SearchModal,
11
+ SearchModalBody,
12
+ SearchModalDivider,
13
+ SearchModalFilterPanel,
14
+ SearchModalFilters,
15
+ SearchModalIdle,
16
+ SearchModalInput,
17
+ SearchModalLoading,
18
+ SearchModalPanel,
19
+ SearchModalResults,
20
+ type SearchFilterTab,
21
+ type SearchModalBodyProps,
22
+ type SearchModalFiltersProps,
23
+ type SearchModalIdleProps,
24
+ type SearchModalInputProps,
25
+ type SearchModalLoadingProps,
26
+ type SearchModalPanelProps,
27
+ type SearchModalProps,
28
+ type SearchModalResultsProps,
29
+ type SearchModalState,
30
+ } from "./SearchModal";
31
+ export { SectionHeader, type SectionHeaderProps } from "./SectionHeader";