@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.
Files changed (98) hide show
  1. package/dist/components/Entry.d.ts +27 -7
  2. package/dist/components/PageHeader.d.ts +41 -0
  3. package/dist/components/ScrollableList.d.ts +22 -0
  4. package/dist/components/Tabs.d.ts +5 -5
  5. package/dist/components/index.d.ts +4 -2
  6. package/dist/domain/Filters/FilterDropdown.d.ts +119 -0
  7. package/dist/domain/Filters/FilterPill.d.ts +22 -5
  8. package/dist/domain/Filters/FilterPills.d.ts +18 -0
  9. package/dist/domain/Filters/FilteredPill.d.ts +15 -5
  10. package/dist/domain/Filters/formatActiveFilterChipLabel.d.ts +11 -0
  11. package/dist/domain/Filters/index.d.ts +8 -3
  12. package/dist/index.cjs +33 -33
  13. package/dist/index.d.ts +2 -2
  14. package/dist/index.esm.js +6383 -4792
  15. package/dist/patterns/Search/ResultRow.d.ts +11 -5
  16. package/dist/patterns/Sidebar/sidebar-styles.d.ts +2 -1
  17. package/dist/primitives/Badge.d.ts +7 -4
  18. package/dist/primitives/Button.d.ts +3 -1
  19. package/dist/primitives/Switch.d.ts +5 -0
  20. package/dist/primitives/index.d.ts +1 -1
  21. package/dist/tailwind-theme.d.ts +8 -4
  22. package/dist/theme-entry-Dwr2mV7_.js +1 -0
  23. package/dist/{theme-entry-CxDa1D0_.mjs → theme-entry-tLBc6zGT.mjs} +12 -8
  24. package/dist/theme.cjs +1 -1
  25. package/dist/theme.esm.js +1 -1
  26. package/package.json +3 -2
  27. package/src/components/EmptyState.tsx +44 -44
  28. package/src/components/Entry.tsx +119 -45
  29. package/src/components/Modal.tsx +141 -141
  30. package/src/components/PageHeader.tsx +132 -0
  31. package/src/components/ScrollableList.tsx +61 -0
  32. package/src/components/Tabs.tsx +33 -26
  33. package/src/components/index.ts +15 -1
  34. package/src/domain/Filters/FilterDropdown.tsx +540 -0
  35. package/src/domain/Filters/FilterPill.tsx +74 -25
  36. package/src/domain/Filters/FilterPills.tsx +34 -0
  37. package/src/domain/Filters/FilteredPill.tsx +59 -39
  38. package/src/domain/Filters/FiltersBar.tsx +72 -76
  39. package/src/domain/Filters/formatActiveFilterChipLabel.ts +23 -0
  40. package/src/domain/Filters/index.ts +37 -3
  41. package/src/icons/arrow-paths.ts +8 -8
  42. package/src/icons/chevron-paths.ts +17 -17
  43. package/src/icons/icons-data.ts +656 -656
  44. package/src/index.ts +11 -0
  45. package/src/patterns/Search/ResultRow.tsx +24 -38
  46. package/src/patterns/Search/SearchModal.tsx +26 -8
  47. package/src/patterns/Sidebar/CollapsibleNavGroup.tsx +1 -1
  48. package/src/patterns/Sidebar/SidebarAction.tsx +26 -17
  49. package/src/patterns/Sidebar/SidebarEntry.tsx +54 -39
  50. package/src/patterns/Sidebar/SidebarLink.tsx +30 -20
  51. package/src/patterns/Sidebar/assets/logo-compliance-mark.png +5 -5
  52. package/src/patterns/Sidebar/assets/logo-compliance-mask.png +13 -13
  53. package/src/patterns/Sidebar/assets/logo-compliance.svg +17 -17
  54. package/src/patterns/Sidebar/assets/logo-technical-avatar.svg +17 -17
  55. package/src/patterns/Sidebar/assets/logo-technical-mark.png +5 -5
  56. package/src/patterns/Sidebar/assets/logo-technical.svg +16 -16
  57. package/src/patterns/Sidebar/sidebar-styles.ts +2 -1
  58. package/src/primitives/Badge.tsx +20 -10
  59. package/src/primitives/Button.tsx +21 -11
  60. package/src/primitives/Logo.tsx +20 -8
  61. package/src/primitives/Switch.tsx +27 -7
  62. package/src/primitives/button-styles.ts +92 -92
  63. package/src/primitives/index.ts +1 -1
  64. package/src/stories/canvases/ButtonsCanvas.tsx +107 -107
  65. package/src/stories/canvases/ButtonsMatrixCanvas.tsx +65 -65
  66. package/src/stories/canvases/ContentCanvas.tsx +253 -14
  67. package/src/stories/canvases/FiltersCanvas.tsx +171 -41
  68. package/src/stories/canvases/figma-buttons-layout.ts +83 -83
  69. package/src/stories/canvases/figma-widths.ts +28 -28
  70. package/src/stories/canvases/helpers.tsx +146 -146
  71. package/src/stories/components/Buttons.stories.tsx +11 -11
  72. package/src/stories/components/Chat.stories.tsx +11 -11
  73. package/src/stories/components/Content.stories.tsx +11 -11
  74. package/src/stories/components/Core.stories.tsx +11 -11
  75. package/src/stories/components/DomainForms.stories.tsx +11 -11
  76. package/src/stories/components/Filters.stories.tsx +11 -11
  77. package/src/stories/components/Forms.stories.tsx +11 -11
  78. package/src/stories/components/Icons.stories.tsx +11 -11
  79. package/src/stories/components/Illustrations.stories.tsx +11 -11
  80. package/src/stories/components/Library.stories.tsx +11 -11
  81. package/src/stories/components/Modals.stories.tsx +11 -11
  82. package/src/stories/components/Report.stories.tsx +11 -11
  83. package/src/stories/components/ReportLayout.stories.tsx +11 -11
  84. package/src/stories/components/Search.stories.tsx +11 -11
  85. package/src/stories/components/Settings.stories.tsx +11 -11
  86. package/src/stories/components/Ships.stories.tsx +11 -11
  87. package/src/stories/components/SidebarLayouts.stories.tsx +11 -11
  88. package/src/stories/components/Skeletons.stories.tsx +11 -11
  89. package/src/stories/components/Workflows.stories.tsx +11 -11
  90. package/src/stories/style-guide/Buttons.stories.tsx +11 -11
  91. package/src/stories/style-guide/ColorTokens.stories.tsx +11 -11
  92. package/src/stories/style-guide/Colors.stories.tsx +11 -11
  93. package/src/stories/style-guide/RaiseLevels.stories.tsx +11 -11
  94. package/src/stories/style-guide/Typography.stories.tsx +11 -11
  95. package/src/tailwind-theme.ts +8 -4
  96. package/src/theme-entry.ts +2 -2
  97. package/src/utils/cn.ts +28 -1
  98. package/dist/theme-entry-D2X3Ptjf.js +0 -1
@@ -1,65 +1,65 @@
1
- import { AddIcon } from "../../icons";
2
- import { Button } from "../../primitives";
3
- import {
4
- FigmaContent,
5
- FigmaPage,
6
- FigmaRow,
7
- FigmaSection,
8
- } from "../_layout";
9
- import { ButtonSnapshot } from "./helpers";
10
- import { FIGMA_WIDTHS } from "./figma-widths";
11
-
12
- const hierarchies = [
13
- "primary",
14
- "secondary",
15
- "tertiary",
16
- "quaternary",
17
- "destructive",
18
- ] as const;
19
- const columnLabels = ["Primary", "Secondary", "Tertiary", "Quaternary", "Destructive"];
20
- const rowLabels = ["Idle", "Hover", "Disabled"];
21
-
22
- export const ButtonsMatrixCanvas = () => (
23
- <FigmaPage title="Buttons" width={FIGMA_WIDTHS.buttonsMatrix}>
24
- <FigmaContent>
25
- <FigmaSection>
26
- <div className="mb-4 grid grid-cols-[104px_repeat(5,1fr)] gap-4">
27
- <div />
28
- {columnLabels.map((label) => (
29
- <p key={label} className="text-center text-caption-1 text-display-on-light-secondary">
30
- {label}
31
- </p>
32
- ))}
33
- </div>
34
-
35
- {rowLabels.map((rowLabel) => (
36
- <FigmaRow key={rowLabel} label={rowLabel} gap={24}>
37
- <div className="w-24" />
38
- {hierarchies.map((hierarchy) => (
39
- <ButtonSnapshot
40
- key={hierarchy}
41
- hierarchy={hierarchy}
42
- visualState={rowLabel.toLowerCase() as "idle" | "hover" | "disabled"}
43
- />
44
- ))}
45
- </FigmaRow>
46
- ))}
47
-
48
- <FigmaRow label="Idle" gap={24}>
49
- <div className="w-24" />
50
- <Button icon={<AddIcon size="small" />} className="min-w-[304px]">
51
- New chat
52
- </Button>
53
- </FigmaRow>
54
-
55
- <FigmaRow label="Hover" gap={24}>
56
- <div className="w-24" />
57
- <span className="inline-flex min-h-10 min-w-[304px] items-center justify-center gap-2 rounded-control bg-action-primary-hover px-2 text-caption-1 text-action-primary-on-hover">
58
- <AddIcon />
59
- New chat
60
- </span>
61
- </FigmaRow>
62
- </FigmaSection>
63
- </FigmaContent>
64
- </FigmaPage>
65
- );
1
+ import { AddIcon } from "../../icons";
2
+ import { Button } from "../../primitives";
3
+ import {
4
+ FigmaContent,
5
+ FigmaPage,
6
+ FigmaRow,
7
+ FigmaSection,
8
+ } from "../_layout";
9
+ import { ButtonSnapshot } from "./helpers";
10
+ import { FIGMA_WIDTHS } from "./figma-widths";
11
+
12
+ const hierarchies = [
13
+ "primary",
14
+ "secondary",
15
+ "tertiary",
16
+ "quaternary",
17
+ "destructive",
18
+ ] as const;
19
+ const columnLabels = ["Primary", "Secondary", "Tertiary", "Quaternary", "Destructive"];
20
+ const rowLabels = ["Idle", "Hover", "Disabled"];
21
+
22
+ export const ButtonsMatrixCanvas = () => (
23
+ <FigmaPage title="Buttons" width={FIGMA_WIDTHS.buttonsMatrix}>
24
+ <FigmaContent>
25
+ <FigmaSection>
26
+ <div className="mb-4 grid grid-cols-[104px_repeat(5,1fr)] gap-4">
27
+ <div />
28
+ {columnLabels.map((label) => (
29
+ <p key={label} className="text-center text-caption-1 text-display-on-light-secondary">
30
+ {label}
31
+ </p>
32
+ ))}
33
+ </div>
34
+
35
+ {rowLabels.map((rowLabel) => (
36
+ <FigmaRow key={rowLabel} label={rowLabel} gap={24}>
37
+ <div className="w-24" />
38
+ {hierarchies.map((hierarchy) => (
39
+ <ButtonSnapshot
40
+ key={hierarchy}
41
+ hierarchy={hierarchy}
42
+ visualState={rowLabel.toLowerCase() as "idle" | "hover" | "disabled"}
43
+ />
44
+ ))}
45
+ </FigmaRow>
46
+ ))}
47
+
48
+ <FigmaRow label="Idle" gap={24}>
49
+ <div className="w-24" />
50
+ <Button icon={<AddIcon size="small" />} className="min-w-[304px]">
51
+ New chat
52
+ </Button>
53
+ </FigmaRow>
54
+
55
+ <FigmaRow label="Hover" gap={24}>
56
+ <div className="w-24" />
57
+ <span className="inline-flex min-h-10 min-w-[304px] items-center justify-center gap-2 rounded-control bg-action-primary-hover px-2 text-caption-1 text-action-primary-on-hover">
58
+ <AddIcon />
59
+ New chat
60
+ </span>
61
+ </FigmaRow>
62
+ </FigmaSection>
63
+ </FigmaContent>
64
+ </FigmaPage>
65
+ );
@@ -1,14 +1,17 @@
1
- import { Button } from "../../primitives";
1
+ import { Badge, Button, IconButton, Avatar } from "../../primitives";
2
2
  import {
3
3
  EmptyState,
4
4
  Entry,
5
5
  Header,
6
+ PageHeader,
7
+ ScrollableList,
6
8
  Tab,
7
9
  Tabs,
8
10
  TabsContent,
9
11
  TabsList,
10
12
  TabsTrigger,
11
13
  } from "../../components";
14
+ import { FormsIcon, MoreIcon, ShipIcon, TickIcon } from "../../icons";
12
15
  import { WorkflowsIllustration } from "../../illustrations";
13
16
  import {
14
17
  FigmaContent,
@@ -19,6 +22,125 @@ import {
19
22
  } from "../_layout";
20
23
  import { FIGMA_WIDTHS } from "./figma-widths";
21
24
 
25
+ const demoActions = (
26
+ <>
27
+ <Button hierarchy="secondary">Button</Button>
28
+ <IconButton hierarchy="tertiary" size="small" aria-label="More">
29
+ <MoreIcon size="small" />
30
+ </IconButton>
31
+ </>
32
+ );
33
+
34
+ type DemoRow = {
35
+ title: string;
36
+ assignee?: string;
37
+ unassigned?: boolean;
38
+ signatures?: number[];
39
+ date: string;
40
+ vessel: string;
41
+ initials: string;
42
+ avatarClassName: string;
43
+ };
44
+
45
+ const DEMO_ROWS: DemoRow[] = [
46
+ {
47
+ title: "Cargo Tank Cleaning for Grade Change",
48
+ assignee: "Dimitris Konstantinou",
49
+ signatures: [1, 2, 3],
50
+ date: "23/04/2026",
51
+ vessel: "The Coral Explorer",
52
+ initials: "MS",
53
+ avatarClassName: "bg-purple-500 text-white",
54
+ },
55
+ {
56
+ title: "Hot Work in Engine Room",
57
+ assignee: "Dimitris Konstantinou",
58
+ signatures: [1, 2, 3],
59
+ date: "23/04/2026",
60
+ vessel: "Ocean Voyager",
61
+ initials: "PO",
62
+ avatarClassName: "bg-blue-700 text-white",
63
+ },
64
+ {
65
+ title: "Enclosed Space Entry in Ballast Tank",
66
+ assignee: "Yiannis Papadopoulos",
67
+ signatures: [1],
68
+ date: "23/04/2026",
69
+ vessel: "Ocean Voyager",
70
+ initials: "PO",
71
+ avatarClassName: "bg-blue-700 text-white",
72
+ },
73
+ {
74
+ title: "Working at Height on Deck Crane",
75
+ assignee: "Yiannis Papadopoulos",
76
+ date: "23/04/2026",
77
+ vessel: "Albatross Wind",
78
+ initials: "LD",
79
+ avatarClassName: "bg-grey-700 text-white",
80
+ },
81
+ {
82
+ title: "Electrical Isolation for Switchboard Maintenance",
83
+ assignee: "Dimitris Konstantinou",
84
+ signatures: [1, 2, 3],
85
+ date: "23/04/2026",
86
+ vessel: "The Coral Explorer",
87
+ initials: "MS",
88
+ avatarClassName: "bg-purple-500 text-white",
89
+ },
90
+ {
91
+ title: "Pilot Ladder Rigging in High Swell",
92
+ unassigned: true,
93
+ date: "23/04/2026",
94
+ vessel: "Albatross Wind",
95
+ initials: "",
96
+ avatarClassName: "",
97
+ },
98
+ ];
99
+
100
+ function DemoListRow({ row }: { row: DemoRow }) {
101
+ return (
102
+ <div className="flex h-12 w-full shrink-0 items-center gap-2 rounded-lg p-2">
103
+ <FormsIcon size="large" className="shrink-0 text-display-on-light-primary" />
104
+ <div className="flex min-w-0 flex-1 items-center gap-8">
105
+ <div className="flex min-w-0 flex-1 items-center gap-2">
106
+ <p className="shrink-0 truncate text-body text-display-on-light-primary">
107
+ {row.title}
108
+ </p>
109
+ <div className="flex shrink-0 items-start gap-1">
110
+ {row.unassigned ? (
111
+ <Badge color="red">Unassigned</Badge>
112
+ ) : row.assignee ? (
113
+ <Badge color="grey">{row.assignee}</Badge>
114
+ ) : null}
115
+ {row.signatures?.map((n) => (
116
+ <Badge key={n} color="orange" type="icon">
117
+ {n}
118
+ </Badge>
119
+ ))}
120
+ </div>
121
+ </div>
122
+ <div className="w-[90px] shrink-0 text-caption-2 text-display-on-light-secondary">
123
+ {row.date}
124
+ </div>
125
+ <div className="w-[150px] shrink-0 truncate text-caption-2 text-display-on-light-secondary">
126
+ {row.vessel}
127
+ </div>
128
+ {row.initials ? (
129
+ <Avatar
130
+ chief="initials"
131
+ size={24}
132
+ initials={row.initials}
133
+ className={row.avatarClassName}
134
+ />
135
+ ) : (
136
+ <span className="size-6 shrink-0" aria-hidden />
137
+ )}
138
+ </div>
139
+ </div>
140
+ );
141
+ }
142
+
143
+
22
144
  export const ContentCanvas = () => (
23
145
  <FigmaPage title="Content" width={FIGMA_WIDTHS.content}>
24
146
  <FigmaContent>
@@ -39,37 +161,130 @@ export const ContentCanvas = () => (
39
161
  <TabsTrigger value="reports">Reports</TabsTrigger>
40
162
  <TabsTrigger value="issues">Issues</TabsTrigger>
41
163
  </TabsList>
42
- <TabsContent value="all" className="pt-3 text-caption-2 text-display-on-light-secondary">
164
+ <TabsContent
165
+ value="all"
166
+ className="pt-3 text-caption-2 text-display-on-light-secondary"
167
+ >
43
168
  All content
44
169
  </TabsContent>
45
- <TabsContent value="reports" className="pt-3 text-caption-2 text-display-on-light-secondary">
170
+ <TabsContent
171
+ value="reports"
172
+ className="pt-3 text-caption-2 text-display-on-light-secondary"
173
+ >
46
174
  Reports content
47
175
  </TabsContent>
48
- <TabsContent value="issues" className="pt-3 text-caption-2 text-display-on-light-secondary">
176
+ <TabsContent
177
+ value="issues"
178
+ className="pt-3 text-caption-2 text-display-on-light-secondary"
179
+ >
49
180
  Issues content
50
181
  </TabsContent>
51
182
  </Tabs>
52
183
  </FigmaVariant>
53
- <FigmaVariant label="Pills">
54
- <Tabs defaultValue="active" type="pills">
184
+ <FigmaVariant label="Pills · soft">
185
+ <Tabs defaultValue="active" type="pills" appearance="soft">
55
186
  <TabsList>
56
187
  <TabsTrigger value="active">Active</TabsTrigger>
57
- <TabsTrigger value="completed" count={3}>
188
+ <TabsTrigger value="completed">
58
189
  Completed
190
+ <Badge color="blue">3</Badge>
59
191
  </TabsTrigger>
60
192
  </TabsList>
61
- <TabsContent value="active" className="pt-3 text-caption-2 text-display-on-light-secondary">
193
+ <TabsContent
194
+ value="active"
195
+ className="pt-3 text-caption-2 text-display-on-light-secondary"
196
+ >
62
197
  Active content
63
198
  </TabsContent>
64
- <TabsContent value="completed" className="pt-3 text-caption-2 text-display-on-light-secondary">
199
+ <TabsContent
200
+ value="completed"
201
+ className="pt-3 text-caption-2 text-display-on-light-secondary"
202
+ >
65
203
  Completed content
66
204
  </TabsContent>
67
205
  </Tabs>
68
206
  </FigmaVariant>
207
+ <FigmaVariant label="Pills · solid">
208
+ <Tabs defaultValue="chat" type="pills" appearance="solid">
209
+ <TabsList>
210
+ <TabsTrigger value="chat">Chat</TabsTrigger>
211
+ <TabsTrigger value="questions">
212
+ Questions
213
+ <Badge color="grey">208</Badge>
214
+ </TabsTrigger>
215
+ <TabsTrigger value="review">
216
+ Review
217
+ <Badge color="green" type="icon">
218
+ <TickIcon size="xs" />
219
+ </Badge>
220
+ </TabsTrigger>
221
+ <TabsTrigger value="gaps">
222
+ Gaps
223
+ <Badge color="grey">47</Badge>
224
+ </TabsTrigger>
225
+ </TabsList>
226
+ </Tabs>
227
+ </FigmaVariant>
69
228
  </div>
70
229
  </FigmaSection>
71
230
 
72
- <FigmaSection label="Header">
231
+ <FigmaSection label="PageHeader">
232
+ <div className="max-w-[1120px] space-y-8">
233
+ <FigmaVariant label="Standard">
234
+ <PageHeader
235
+ icon={<ShipIcon size="large" />}
236
+ title="Title"
237
+ actions={demoActions}
238
+ />
239
+ </FigmaVariant>
240
+ <FigmaVariant label="Nested">
241
+ <PageHeader
242
+ icon={<ShipIcon size="large" />}
243
+ title="Title"
244
+ crumbs={[{ label: "Parent", href: "#parent" }]}
245
+ actions={demoActions}
246
+ />
247
+ </FigmaVariant>
248
+ <FigmaVariant label="Shell (pinned header)">
249
+ <div className="h-64 overflow-hidden rounded-control border border-divider-primary">
250
+ <PageHeader.Shell className="bg-background-primary p-4">
251
+ <PageHeader
252
+ icon={<ShipIcon size="large" />}
253
+ title="Fleet"
254
+ crumbs={[{ label: "Ships", href: "#ships" }]}
255
+ actions={demoActions}
256
+ />
257
+ <PageHeader.Body className="mt-4 space-y-2 text-caption-2 text-display-on-light-secondary">
258
+ {Array.from({ length: 20 }, (_, i) => (
259
+ <p key={i}>Scrollable body line {i + 1}</p>
260
+ ))}
261
+ </PageHeader.Body>
262
+ </PageHeader.Shell>
263
+ </div>
264
+ </FigmaVariant>
265
+ </div>
266
+ </FigmaSection>
267
+
268
+ <FigmaSection label="ScrollableList (Table)">
269
+ <FigmaVariant label="Column header + scrolling body">
270
+ <div className="flex h-[420px] w-full max-w-[960px] flex-col">
271
+ <ScrollableList>
272
+ <ScrollableList.Header>
273
+ <span className="min-w-0 flex-1">Title</span>
274
+ <span className="w-[90px] shrink-0">Date</span>
275
+ <span className="w-[210px] shrink-0">Vessel</span>
276
+ </ScrollableList.Header>
277
+ <ScrollableList.Body>
278
+ {DEMO_ROWS.map((row) => (
279
+ <DemoListRow key={row.title} row={row} />
280
+ ))}
281
+ </ScrollableList.Body>
282
+ </ScrollableList>
283
+ </div>
284
+ </FigmaVariant>
285
+ </FigmaSection>
286
+
287
+ <FigmaSection label="Header (legacy)">
73
288
  <div className="max-w-xl space-y-4">
74
289
  <Header title="Fleet overview" />
75
290
  <Header
@@ -82,9 +297,31 @@ export const ContentCanvas = () => (
82
297
 
83
298
  <FigmaSection label="Entry">
84
299
  <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" />
300
+ <Entry
301
+ variant="chat"
302
+ title="Hull inspection report"
303
+ subtitle="We’ve got a persistent oil leak from the fuel pump."
304
+ />
305
+ <Entry
306
+ variant="chat"
307
+ state="active"
308
+ title="Ballast Pump Not Starting"
309
+ subtitle="The fuel pump won’t start consistently."
310
+ trailing={
311
+ <span className="text-display-on-light-secondary">···</span>
312
+ }
313
+ />
314
+ <Entry
315
+ variant="issue"
316
+ title="Excessive Rust on Fuel Pump"
317
+ subtitle="Created by Jake Silva on March 28"
318
+ count={3}
319
+ />
320
+ <Entry
321
+ variant="report"
322
+ title="Engine Room Oil Leak"
323
+ subtitle="We’ve spotted a steady oil leak near the fuel pump."
324
+ />
88
325
  </div>
89
326
  </FigmaSection>
90
327
 
@@ -102,7 +339,9 @@ export const ContentCanvas = () => (
102
339
  <div className="w-80">
103
340
  <EmptyState
104
341
  title="No reports"
105
- illustration={<WorkflowsIllustration className="mx-auto h-24 w-32" />}
342
+ illustration={
343
+ <WorkflowsIllustration className="mx-auto h-24 w-32" />
344
+ }
106
345
  action={{ children: "Create report", hierarchy: "primary" }}
107
346
  />
108
347
  </div>
@@ -1,41 +1,171 @@
1
- import {
2
- FiltersBar,
3
- FilteredPill,
4
- FilterPill,
5
- } from "../../domain/Filters";
6
- import {
7
- FigmaContent,
8
- FigmaGrid,
9
- FigmaPage,
10
- FigmaSection,
11
- } from "../_layout";
12
- import { FIGMA_WIDTHS } from "./figma-widths";
13
-
14
- export const FiltersCanvas = () => (
15
- <FigmaPage title="Filters" width={FIGMA_WIDTHS.filters}>
16
- <FigmaContent>
17
- <FigmaSection label="Filter Pill">
18
- <FigmaGrid gap={16}>
19
- <FilterPill />
20
- <FilterPill indicator="on" />
21
- </FigmaGrid>
22
- </FigmaSection>
23
-
24
- <FigmaSection label="Filtered Pill">
25
- <FigmaGrid gap={16}>
26
- <FilteredPill />
27
- <FilteredPill label="Status: Open" count={5} />
28
- <FilteredPill label="Priority: High" count={2} state="hover" />
29
- </FigmaGrid>
30
- </FigmaSection>
31
-
32
- <FigmaSection label="Filters Bar">
33
- <div className="space-y-6">
34
- <FiltersBar />
35
- <FiltersBar filters="on" activeTab="Reports" />
36
- <FiltersBar tabs="off" searchPlaceholder="Search workflows..." />
37
- </div>
38
- </FigmaSection>
39
- </FigmaContent>
40
- </FigmaPage>
41
- );
1
+ import { useMemo, useState } from "react";
2
+ import {
3
+ FilterDropdown,
4
+ FilterPill,
5
+ FilterPills,
6
+ FilteredPill,
7
+ FiltersBar,
8
+ formatActiveFilterChipLabel,
9
+ type FilterCategoryRow,
10
+ } from "../../domain/Filters";
11
+ import {
12
+ FigmaContent,
13
+ FigmaGrid,
14
+ FigmaPage,
15
+ FigmaSection,
16
+ } from "../_layout";
17
+ import { FIGMA_WIDTHS } from "./figma-widths";
18
+
19
+ const demoOptions = {
20
+ status: [
21
+ { value: "approved", label: "Approved" },
22
+ { value: "reviewed", label: "Reviewed" },
23
+ { value: "pending", label: "Pending" },
24
+ ],
25
+ vessel: [
26
+ { value: "aurora", label: "Aurora" },
27
+ { value: "north", label: "North Star" },
28
+ ],
29
+ };
30
+
31
+ export const FiltersCanvas = () => {
32
+ const [status, setStatus] = useState<string[]>([]);
33
+ const [vessel, setVessel] = useState<string[]>([]);
34
+ const [groupByVessel, setGroupByVessel] = useState(false);
35
+
36
+ const selectedValuesBySelectionKey = useMemo(
37
+ () => ({ status, vessel }),
38
+ [status, vessel],
39
+ );
40
+
41
+ const categoryRows: FilterCategoryRow[] = useMemo(
42
+ () => [
43
+ {
44
+ id: "status",
45
+ label: "Status",
46
+ content: {
47
+ type: "options",
48
+ selectionKey: "status",
49
+ selectionMode: "multi",
50
+ options: demoOptions.status,
51
+ },
52
+ },
53
+ {
54
+ id: "vessel",
55
+ label: "Vessel",
56
+ content: {
57
+ type: "options",
58
+ selectionKey: "vessel",
59
+ selectionMode: "multi",
60
+ options: demoOptions.vessel,
61
+ },
62
+ },
63
+ {
64
+ kind: "toggle",
65
+ id: "group",
66
+ label: "Group by Vessel",
67
+ checked: groupByVessel,
68
+ onCheckedChange: setGroupByVessel,
69
+ },
70
+ ],
71
+ [groupByVessel],
72
+ );
73
+
74
+ const chips = useMemo(() => {
75
+ const next: {
76
+ key: string;
77
+ label: string;
78
+ onRemove: () => void;
79
+ }[] = [];
80
+ if (status.length > 0) {
81
+ const labels = status.map(
82
+ (v) => demoOptions.status.find((o) => o.value === v)?.label ?? v,
83
+ );
84
+ next.push({
85
+ key: "status",
86
+ label: formatActiveFilterChipLabel("Status", labels),
87
+ onRemove: () => setStatus([]),
88
+ });
89
+ }
90
+ if (vessel.length > 0) {
91
+ const labels = vessel.map(
92
+ (v) => demoOptions.vessel.find((o) => o.value === v)?.label ?? v,
93
+ );
94
+ next.push({
95
+ key: "vessel",
96
+ label: formatActiveFilterChipLabel("Vessel", labels),
97
+ onRemove: () => setVessel([]),
98
+ });
99
+ }
100
+ return next;
101
+ }, [status, vessel]);
102
+
103
+ const activeFilterCount = chips.length;
104
+
105
+ return (
106
+ <FigmaPage title="Filters" width={FIGMA_WIDTHS.filters}>
107
+ <FigmaContent>
108
+ <FigmaSection label="Filter Pill (trigger)">
109
+ <FigmaGrid gap={16}>
110
+ <FilterPill />
111
+ <FilterPill activeFilterCount={2} />
112
+ <FilterPill appearance="ghost" />
113
+ <FilterPill appearance="ghost" activeFilterCount={2} />
114
+ </FigmaGrid>
115
+ </FigmaSection>
116
+
117
+ <FigmaSection label="Filtered Pill">
118
+ <FigmaGrid gap={16}>
119
+ <FilteredPill label="Approved" onRemove={() => undefined} />
120
+ <FilteredPill label="Reviewed" onRemove={() => undefined} />
121
+ </FigmaGrid>
122
+ </FigmaSection>
123
+
124
+ <FigmaSection label="FilterDropdown + FilterPills (interactive)">
125
+ <div className="flex w-full items-center justify-between gap-4">
126
+ <FilterPills chips={chips} />
127
+ <div className="flex shrink-0 items-center gap-4">
128
+ <FilterDropdown
129
+ align="end"
130
+ submenuSide="left"
131
+ triggerAppearance="ghost"
132
+ categoryRows={categoryRows}
133
+ selectedValuesBySelectionKey={selectedValuesBySelectionKey}
134
+ activeFilterCount={activeFilterCount}
135
+ onSelectOption={(key, value) => {
136
+ if (key === "status") {
137
+ setStatus((prev) =>
138
+ prev.includes(value)
139
+ ? prev.filter((v) => v !== value)
140
+ : [...prev, value],
141
+ );
142
+ }
143
+ if (key === "vessel") {
144
+ setVessel((prev) =>
145
+ prev.includes(value)
146
+ ? prev.filter((v) => v !== value)
147
+ : [...prev, value],
148
+ );
149
+ }
150
+ }}
151
+ onResetAll={() => {
152
+ setStatus([]);
153
+ setVessel([]);
154
+ setGroupByVessel(false);
155
+ }}
156
+ />
157
+ </div>
158
+ </div>
159
+ </FigmaSection>
160
+
161
+ <FigmaSection label="Filters Bar (layout stub)">
162
+ <div className="space-y-6">
163
+ <FiltersBar />
164
+ <FiltersBar filters="on" activeTab="Reports" />
165
+ <FiltersBar tabs="off" searchPlaceholder="Search workflows..." />
166
+ </div>
167
+ </FigmaSection>
168
+ </FigmaContent>
169
+ </FigmaPage>
170
+ );
171
+ };