@lateralus-ai/shipping-ui 1.1.2 → 1.3.0
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/DocumentEditor/DocumentEditor.d.ts +4 -0
- package/dist/components/DocumentEditor/index.d.ts +1 -0
- package/dist/components/ModalPanel.d.ts +4 -0
- package/dist/components/PdfViewer/ImageViewer.d.ts +1 -1
- package/dist/components/PdfViewer/PdfViewer.d.ts +1 -1
- package/dist/components/PdfViewer/usePageManagement.d.ts +5 -1
- package/dist/components/SearchModal.d.ts +24 -0
- package/dist/components/Tabs.d.ts +13 -0
- package/dist/components/icons/SettingsIcon.d.ts +3 -0
- package/dist/components/index.d.ts +2 -0
- package/dist/defect-report.pdf +0 -0
- package/dist/example.pdf +0 -0
- package/dist/index.cjs +272 -10
- package/dist/index.esm.js +35960 -1124
- package/dist/material-theme.d.ts +26 -0
- package/dist/sample-document.docx +0 -0
- package/dist/stories/SearchModal.d.ts +1 -0
- package/dist/style.css +1 -0
- package/dist/tailwind-theme.d.ts +12 -0
- package/dist/types/documentEditor.d.ts +47 -0
- package/dist/utils/checkboxModule.d.ts +54 -0
- package/package.json +5 -1
- package/src/components/DocumentEditor/DocumentEditor.tsx +872 -0
- package/src/components/DocumentEditor/index.ts +1 -0
- package/src/components/ModalPanel.tsx +13 -0
- package/src/components/PdfViewer/ImageViewer.tsx +8 -3
- package/src/components/PdfViewer/PdfViewer.tsx +138 -17
- package/src/components/PdfViewer/usePageManagement.ts +22 -7
- package/src/components/SearchModal.tsx +320 -0
- package/src/components/Tabs.tsx +43 -0
- package/src/components/icons/SettingsIcon.tsx +33 -0
- package/src/components/index.ts +2 -0
- package/src/material-theme.ts +30 -0
- package/src/stories/DocumentEditor.stories.tsx +287 -0
- package/src/stories/ModalHeader.stories.tsx +36 -0
- package/src/stories/PDFViewer.stories.tsx +1 -1
- package/src/stories/SearchModal.stories.tsx +132 -0
- package/src/stories/SearchModal.tsx +82 -0
- package/src/stories/Tabs.stories.tsx +51 -0
- package/src/styles/tabs.css +55 -0
- package/src/tailwind-theme.ts +12 -0
- package/src/types/documentEditor.ts +56 -0
- package/src/utils/checkboxModule.ts +242 -0
package/src/material-theme.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { tabPanel } from "@material-tailwind/react";
|
|
2
|
+
|
|
1
3
|
export const materialTheme = {
|
|
2
4
|
button: {
|
|
3
5
|
defaultProps: {
|
|
@@ -444,4 +446,32 @@ export const materialTheme = {
|
|
|
444
446
|
},
|
|
445
447
|
},
|
|
446
448
|
},
|
|
449
|
+
tabsHeader: {
|
|
450
|
+
defaultProps: {
|
|
451
|
+
className:
|
|
452
|
+
"grow gap-6 rounded-none bg-transparent !p-0 border-b border-b-gray-100 flex items-end px-0 w-full mb-6",
|
|
453
|
+
},
|
|
454
|
+
},
|
|
455
|
+
tab: {
|
|
456
|
+
defaultProps: {
|
|
457
|
+
className:
|
|
458
|
+
"min-w-fit max-w-fit justify-start font-medium text-gray-600 pl-0 pr-2 h-12",
|
|
459
|
+
},
|
|
460
|
+
styles: {
|
|
461
|
+
base: {
|
|
462
|
+
indicator: {
|
|
463
|
+
bg: "bg-transparent border-b-2 border-gray-900 !transform-none",
|
|
464
|
+
borderRadius: "radius-none",
|
|
465
|
+
boxShadow: "shadow-none",
|
|
466
|
+
},
|
|
467
|
+
},
|
|
468
|
+
},
|
|
469
|
+
},
|
|
470
|
+
tabPanel: {
|
|
471
|
+
styles: {
|
|
472
|
+
base: {
|
|
473
|
+
p: "px-0",
|
|
474
|
+
},
|
|
475
|
+
},
|
|
476
|
+
},
|
|
447
477
|
};
|
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
2
|
+
import { DocumentEditor } from "../components/DocumentEditor";
|
|
3
|
+
import type { Schema } from "../types/documentEditor";
|
|
4
|
+
|
|
5
|
+
// Sample schema based on the defect report form
|
|
6
|
+
const sampleSchema: Schema = {
|
|
7
|
+
type: "object",
|
|
8
|
+
properties: {
|
|
9
|
+
shipName: {
|
|
10
|
+
type: "string",
|
|
11
|
+
description: "Name of the ship",
|
|
12
|
+
},
|
|
13
|
+
reportDate: {
|
|
14
|
+
type: "string",
|
|
15
|
+
format: "date",
|
|
16
|
+
description: "Date of the report",
|
|
17
|
+
},
|
|
18
|
+
reportNo: {
|
|
19
|
+
type: "string",
|
|
20
|
+
description: "Report number",
|
|
21
|
+
},
|
|
22
|
+
malfunctionDateTime: {
|
|
23
|
+
type: "string",
|
|
24
|
+
format: "date",
|
|
25
|
+
description: "Date the malfunction occurred",
|
|
26
|
+
},
|
|
27
|
+
malfunctionDescription: {
|
|
28
|
+
type: "string",
|
|
29
|
+
description: "Description of the malfunction or defect",
|
|
30
|
+
},
|
|
31
|
+
isCriticalEquipment: {
|
|
32
|
+
type: "boolean",
|
|
33
|
+
description: "Whether the equipment referenced is critical",
|
|
34
|
+
docx_mapping: {
|
|
35
|
+
type: "checkbox",
|
|
36
|
+
true_name: "Check1",
|
|
37
|
+
false_name: "Check2",
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
equipmentManufacturer: {
|
|
41
|
+
type: "string",
|
|
42
|
+
description: "The malfunctioning equipment manufacturer",
|
|
43
|
+
},
|
|
44
|
+
equipmentModel: {
|
|
45
|
+
type: "string",
|
|
46
|
+
description: "The malfunctioning equipment model",
|
|
47
|
+
},
|
|
48
|
+
equipmentSerial: {
|
|
49
|
+
type: "string",
|
|
50
|
+
description: "The malfunctioning equipment serial number",
|
|
51
|
+
},
|
|
52
|
+
equipmentManufacturerModelSerial: {
|
|
53
|
+
type: "string",
|
|
54
|
+
description: "Manufacturer, model, type, and serial number",
|
|
55
|
+
},
|
|
56
|
+
consequences: {
|
|
57
|
+
type: "string",
|
|
58
|
+
description: "Consequences of the malfunction",
|
|
59
|
+
},
|
|
60
|
+
rootCause: {
|
|
61
|
+
type: "string",
|
|
62
|
+
description: "Root cause of the defect (if known)",
|
|
63
|
+
},
|
|
64
|
+
actionsTaken: {
|
|
65
|
+
type: "string",
|
|
66
|
+
description: "Actions taken or suggestions for corrective actions",
|
|
67
|
+
},
|
|
68
|
+
isShoreAssistanceRequired: {
|
|
69
|
+
type: "boolean",
|
|
70
|
+
description: "Whether shore assistance is required",
|
|
71
|
+
docx_mapping: {
|
|
72
|
+
type: "checkbox",
|
|
73
|
+
true_name: "Check3",
|
|
74
|
+
false_name: "Check4",
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
shoreAssistanceRequisition: {
|
|
78
|
+
type: "string",
|
|
79
|
+
description: "Requisition details for shore assistance",
|
|
80
|
+
},
|
|
81
|
+
isEquipmentLandedAshore: {
|
|
82
|
+
type: "boolean",
|
|
83
|
+
description: "Whether equipment/machinery was landed ashore",
|
|
84
|
+
docx_mapping: {
|
|
85
|
+
type: "checkbox",
|
|
86
|
+
true_name: "Check5",
|
|
87
|
+
false_name: "Check6",
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
equipmentLandedRequisition: {
|
|
91
|
+
type: "string",
|
|
92
|
+
description: "Requisition details for equipment landed ashore",
|
|
93
|
+
},
|
|
94
|
+
areSparesAvailableOnboard: {
|
|
95
|
+
type: "boolean",
|
|
96
|
+
description: "Whether required spare parts are available onboard",
|
|
97
|
+
docx_mapping: {
|
|
98
|
+
type: "checkbox",
|
|
99
|
+
true_name: "Check7",
|
|
100
|
+
false_name: "Check8",
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
sparesAvailableRequisition: {
|
|
104
|
+
type: "string",
|
|
105
|
+
description: "Requisition details if spares are available onboard",
|
|
106
|
+
},
|
|
107
|
+
isNextDryDock: {
|
|
108
|
+
type: "boolean",
|
|
109
|
+
description: "If it's going to close out in next dry dock",
|
|
110
|
+
docx_mapping: {
|
|
111
|
+
type: "checkbox",
|
|
112
|
+
true_name: "Check9",
|
|
113
|
+
false_name: "Check10",
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
proposedCompletionDate: {
|
|
117
|
+
type: "string",
|
|
118
|
+
format: "date",
|
|
119
|
+
description: "Proposed date for completion",
|
|
120
|
+
},
|
|
121
|
+
chiefEngineerName: {
|
|
122
|
+
type: "string",
|
|
123
|
+
description: "Name of the Chief Engineer",
|
|
124
|
+
},
|
|
125
|
+
chiefEngineerSignature: {
|
|
126
|
+
type: "string",
|
|
127
|
+
description: "Signature of the Chief Engineer (surname only)",
|
|
128
|
+
},
|
|
129
|
+
chiefEngineerDate: {
|
|
130
|
+
type: "string",
|
|
131
|
+
format: "date",
|
|
132
|
+
description: "Date signed by the Chief Engineer",
|
|
133
|
+
},
|
|
134
|
+
masterName: {
|
|
135
|
+
type: "string",
|
|
136
|
+
description: "Name of the Master",
|
|
137
|
+
},
|
|
138
|
+
masterSignature: {
|
|
139
|
+
type: "string",
|
|
140
|
+
description: "Signature of the Master (surname only)",
|
|
141
|
+
},
|
|
142
|
+
masterDate: {
|
|
143
|
+
type: "string",
|
|
144
|
+
format: "date",
|
|
145
|
+
description: "Date signed by the Master",
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
required: [],
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
// Sample initial data for demonstration
|
|
152
|
+
const sampleInitialData = {
|
|
153
|
+
shipName: "MV Excellence",
|
|
154
|
+
reportNo: "DR-2024-001",
|
|
155
|
+
reportDate: "2024-03-15",
|
|
156
|
+
malfunctionDateTime: "2024-03-14",
|
|
157
|
+
malfunctionDescription:
|
|
158
|
+
"Main engine turbocharger failure during normal operation",
|
|
159
|
+
isCriticalEquipment: true,
|
|
160
|
+
equipmentManufacturer: "ABB",
|
|
161
|
+
equipmentModel: "TPL 85-B",
|
|
162
|
+
equipmentSerial: "TC-12345-2020",
|
|
163
|
+
consequences: "Reduced engine power, speed limited to 10 knots",
|
|
164
|
+
rootCause: "Bearing failure due to inadequate lubrication",
|
|
165
|
+
actionsTaken:
|
|
166
|
+
"Turbocharger isolated, vessel proceeding at reduced speed to nearest port",
|
|
167
|
+
isShoreAssistanceRequired: true,
|
|
168
|
+
shoreAssistanceRequisition:
|
|
169
|
+
"Service engineer required for turbocharger overhaul",
|
|
170
|
+
isEquipmentLandedAshore: false,
|
|
171
|
+
areSparesAvailableOnboard: false,
|
|
172
|
+
isNextDryDock: false,
|
|
173
|
+
proposedCompletionDate: "2024-03-20",
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
const DocumentEditorWrapper = () => {
|
|
177
|
+
const handleDataChange = (data: Record<string, any>) => {
|
|
178
|
+
console.log("Data changed:", data);
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
return (
|
|
182
|
+
<div className="w-full max-w-6xl mx-auto p-4">
|
|
183
|
+
<DocumentEditor
|
|
184
|
+
docxUrl="/sample-document.docx"
|
|
185
|
+
schema={sampleSchema}
|
|
186
|
+
onDataChange={handleDataChange}
|
|
187
|
+
initialData={sampleInitialData}
|
|
188
|
+
readonly={false}
|
|
189
|
+
/>
|
|
190
|
+
</div>
|
|
191
|
+
);
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
const meta = {
|
|
195
|
+
title: "Design System/DocumentEditor",
|
|
196
|
+
component: DocumentEditorWrapper,
|
|
197
|
+
parameters: {
|
|
198
|
+
layout: "fullscreen",
|
|
199
|
+
docs: {
|
|
200
|
+
description: {
|
|
201
|
+
component: `
|
|
202
|
+
The DocumentEditor component provides an interactive Word document editor with inline field editing capabilities.
|
|
203
|
+
|
|
204
|
+
## Features
|
|
205
|
+
- Renders Word documents using docx-preview
|
|
206
|
+
- Supports inline editing with various field types (text, boolean, date, dropdown)
|
|
207
|
+
- Tracks field changes with visual color coding
|
|
208
|
+
- Handles checkbox patterns (single, dual Yes/No, radio buttons)
|
|
209
|
+
- Preserves document formatting and layout
|
|
210
|
+
|
|
211
|
+
## Usage
|
|
212
|
+
\`\`\`tsx
|
|
213
|
+
import { DocumentEditor } from '@lateralus-ai/shipping-ui';
|
|
214
|
+
|
|
215
|
+
<DocumentEditor
|
|
216
|
+
docxUrl="/path/to/document.docx"
|
|
217
|
+
schema={documentSchema}
|
|
218
|
+
onDataChange={(data) => console.log(data)}
|
|
219
|
+
initialData={initialValues}
|
|
220
|
+
readonly={false}
|
|
221
|
+
/>
|
|
222
|
+
\`\`\`
|
|
223
|
+
`,
|
|
224
|
+
},
|
|
225
|
+
},
|
|
226
|
+
},
|
|
227
|
+
tags: ["autodocs"],
|
|
228
|
+
argTypes: {
|
|
229
|
+
docxUrl: {
|
|
230
|
+
control: "text",
|
|
231
|
+
description: "URL to the DOCX document template",
|
|
232
|
+
},
|
|
233
|
+
readonly: {
|
|
234
|
+
control: "boolean",
|
|
235
|
+
description: "Whether the document is in read-only mode",
|
|
236
|
+
},
|
|
237
|
+
},
|
|
238
|
+
} satisfies Meta<typeof DocumentEditorWrapper>;
|
|
239
|
+
|
|
240
|
+
export default meta;
|
|
241
|
+
type Story = StoryObj<typeof meta>;
|
|
242
|
+
|
|
243
|
+
export const Default: Story = {
|
|
244
|
+
render: () => {
|
|
245
|
+
return (
|
|
246
|
+
<div className="w-full max-w-6xl mx-auto p-4">
|
|
247
|
+
<DocumentEditor
|
|
248
|
+
docxUrl="/sample-document.docx"
|
|
249
|
+
schema={sampleSchema}
|
|
250
|
+
initialData={sampleInitialData}
|
|
251
|
+
onDataChange={(data) => console.log(data)}
|
|
252
|
+
/>
|
|
253
|
+
</div>
|
|
254
|
+
);
|
|
255
|
+
},
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
export const ReadOnly: Story = {
|
|
259
|
+
render: () => {
|
|
260
|
+
return (
|
|
261
|
+
<div className="w-full max-w-6xl mx-auto p-4">
|
|
262
|
+
<DocumentEditor
|
|
263
|
+
docxUrl="/sample-document.docx"
|
|
264
|
+
schema={sampleSchema}
|
|
265
|
+
initialData={sampleInitialData}
|
|
266
|
+
readonly={true}
|
|
267
|
+
/>
|
|
268
|
+
</div>
|
|
269
|
+
);
|
|
270
|
+
},
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
export const Empty: Story = {
|
|
274
|
+
render: () => {
|
|
275
|
+
return (
|
|
276
|
+
<div className="w-full max-w-6xl mx-auto p-4">
|
|
277
|
+
<DocumentEditor
|
|
278
|
+
docxUrl="/sample-document.docx"
|
|
279
|
+
schema={sampleSchema}
|
|
280
|
+
initialData={{}}
|
|
281
|
+
readonly={false}
|
|
282
|
+
onDataChange={(data) => console.log(data)}
|
|
283
|
+
/>
|
|
284
|
+
</div>
|
|
285
|
+
);
|
|
286
|
+
},
|
|
287
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
2
|
+
import { ModalPanel } from "../components/ModalPanel";
|
|
3
|
+
|
|
4
|
+
const meta: Meta<typeof ModalPanel.Header> = {
|
|
5
|
+
title: "Design System/ModalPanel",
|
|
6
|
+
component: ModalPanel.Header,
|
|
7
|
+
parameters: {
|
|
8
|
+
layout: "centered",
|
|
9
|
+
},
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export default meta;
|
|
13
|
+
type Story = StoryObj<typeof ModalPanel.Header>;
|
|
14
|
+
|
|
15
|
+
export const Default: Story = {
|
|
16
|
+
args: {
|
|
17
|
+
onClose: () => console.log("Close button clicked"),
|
|
18
|
+
children: "Modal Panel Example",
|
|
19
|
+
},
|
|
20
|
+
decorators: [
|
|
21
|
+
(Story) => (
|
|
22
|
+
<div className="w-[500px] border border-gray-200 rounded-xl">
|
|
23
|
+
<Story />
|
|
24
|
+
<ModalPanel.Body className="h-64 p-4">
|
|
25
|
+
<p className="text-body">
|
|
26
|
+
This is the modal body content. The height is controlled by the h-64 class,
|
|
27
|
+
and the width fills the container automatically.
|
|
28
|
+
</p>
|
|
29
|
+
<p className="text-body mt-4">
|
|
30
|
+
You can add any content here and control the height through the className prop.
|
|
31
|
+
</p>
|
|
32
|
+
</ModalPanel.Body>
|
|
33
|
+
</div>
|
|
34
|
+
),
|
|
35
|
+
],
|
|
36
|
+
};
|
|
@@ -17,7 +17,7 @@ const StoryPage = () => {
|
|
|
17
17
|
className="w-[600px] h-[800px] p-8 flex flex-col"
|
|
18
18
|
title="Document Viewer"
|
|
19
19
|
>
|
|
20
|
-
<PdfViewer src="
|
|
20
|
+
<PdfViewer src="/defect-report.pdf" />
|
|
21
21
|
</div>
|
|
22
22
|
</div>
|
|
23
23
|
);
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
2
|
+
import { useState } from "react";
|
|
3
|
+
import SearchModal from "../components/SearchModal";
|
|
4
|
+
import type { SearchItem } from "../components/SearchModal";
|
|
5
|
+
import { Icon } from "@iconify/react";
|
|
6
|
+
|
|
7
|
+
const meta: Meta<typeof SearchModal> = {
|
|
8
|
+
title: "Design System/SearchModal",
|
|
9
|
+
component: SearchModal,
|
|
10
|
+
parameters: {
|
|
11
|
+
layout: "centered",
|
|
12
|
+
},
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export default meta;
|
|
16
|
+
type Story = StoryObj<typeof SearchModal>;
|
|
17
|
+
|
|
18
|
+
const mockItems: SearchItem[] = [
|
|
19
|
+
{
|
|
20
|
+
id: "1",
|
|
21
|
+
title: "Engine maintenance report",
|
|
22
|
+
subtitle: "Created by John Doe on January 15, 2025",
|
|
23
|
+
icon: <Icon icon="lucide:users" />,
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
id: "2",
|
|
27
|
+
title: "Navigation system update",
|
|
28
|
+
subtitle: "Updated yesterday at 3:45 PM",
|
|
29
|
+
icon: <Icon icon="lucide:message-circle" />,
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
id: "3",
|
|
33
|
+
title: "Crew training schedule",
|
|
34
|
+
subtitle: "Due date: February 1, 2025",
|
|
35
|
+
icon: <Icon icon="lucide:file" />,
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
id: "4",
|
|
39
|
+
title: "Fuel consumption analysis",
|
|
40
|
+
subtitle: "Last reviewed 3 days ago",
|
|
41
|
+
icon: <Icon icon="lucide:file-lock" />,
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
id: "5",
|
|
45
|
+
title: "Weather routing optimization",
|
|
46
|
+
subtitle: "Active route to Singapore",
|
|
47
|
+
icon: <Icon icon="lucide:file-lock" />,
|
|
48
|
+
},
|
|
49
|
+
];
|
|
50
|
+
|
|
51
|
+
const SearchModalWrapper = (args: any) => {
|
|
52
|
+
const [open, setOpen] = useState(true);
|
|
53
|
+
const [items, setItems] = useState(mockItems);
|
|
54
|
+
const [loading, setLoading] = useState(false);
|
|
55
|
+
const [selectedFilter, setSelectedFilter] = useState("All");
|
|
56
|
+
|
|
57
|
+
const handleSearch = (query: string) => {
|
|
58
|
+
setLoading(true);
|
|
59
|
+
setTimeout(() => {
|
|
60
|
+
const filtered = mockItems.filter(
|
|
61
|
+
(item) =>
|
|
62
|
+
item.title.toLowerCase().includes(query.toLowerCase()) ||
|
|
63
|
+
item.subtitle?.toLowerCase().includes(query.toLowerCase()),
|
|
64
|
+
);
|
|
65
|
+
setItems(filtered);
|
|
66
|
+
setLoading(false);
|
|
67
|
+
}, 300);
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const handleSelect = (item: SearchItem) => {
|
|
71
|
+
console.log("Selected item:", item);
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
return (
|
|
75
|
+
<>
|
|
76
|
+
<button
|
|
77
|
+
onClick={() => setOpen(true)}
|
|
78
|
+
className="px-4 py-2 bg-blue-500 text-white rounded-lg"
|
|
79
|
+
>
|
|
80
|
+
Open Search
|
|
81
|
+
</button>
|
|
82
|
+
<SearchModal
|
|
83
|
+
{...args}
|
|
84
|
+
open={open}
|
|
85
|
+
onClose={() => setOpen(false)}
|
|
86
|
+
items={items}
|
|
87
|
+
loading={loading}
|
|
88
|
+
onSearch={handleSearch}
|
|
89
|
+
onSelect={handleSelect}
|
|
90
|
+
selectedFilter={selectedFilter}
|
|
91
|
+
onFilterChange={setSelectedFilter}
|
|
92
|
+
/>
|
|
93
|
+
</>
|
|
94
|
+
);
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
export const Default: Story = {
|
|
98
|
+
render: (args) => <SearchModalWrapper {...args} />,
|
|
99
|
+
args: {
|
|
100
|
+
placeholder: "Search reports, chats, or issues…",
|
|
101
|
+
recentlyViewedLabel: "Recently viewed",
|
|
102
|
+
noResultsText: "No results found.",
|
|
103
|
+
},
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
export const WithFilters: Story = {
|
|
107
|
+
render: (args) => <SearchModalWrapper {...args} />,
|
|
108
|
+
args: {
|
|
109
|
+
placeholder: "Search reports, chats, or issues…",
|
|
110
|
+
filterOptions: ["All", "Chats", "Issues", "Resolved"],
|
|
111
|
+
recentlyViewedLabel: "Recently viewed",
|
|
112
|
+
noResultsText: "No results found.",
|
|
113
|
+
},
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
export const Loading: Story = {
|
|
117
|
+
args: {
|
|
118
|
+
open: true,
|
|
119
|
+
loading: true,
|
|
120
|
+
onClose: () => console.log("Closed"),
|
|
121
|
+
},
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
export const Empty: Story = {
|
|
125
|
+
args: {
|
|
126
|
+
open: true,
|
|
127
|
+
items: [],
|
|
128
|
+
onClose: () => console.log("Closed"),
|
|
129
|
+
placeholder: "Search for something...",
|
|
130
|
+
noResultsText: "No items found. Try a different search.",
|
|
131
|
+
},
|
|
132
|
+
};
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { useState } from "react";
|
|
2
|
+
import { SearchModal } from "../components/SearchModal";
|
|
3
|
+
|
|
4
|
+
const mockResults = [
|
|
5
|
+
{
|
|
6
|
+
id: "1",
|
|
7
|
+
title: "Create React App",
|
|
8
|
+
description: "Set up a modern web app by running one command",
|
|
9
|
+
category: "Documentation",
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
id: "2",
|
|
13
|
+
title: "React Router",
|
|
14
|
+
description: "Declarative routing for React apps at any scale",
|
|
15
|
+
category: "Library",
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
id: "3",
|
|
19
|
+
title: "Redux Toolkit",
|
|
20
|
+
description:
|
|
21
|
+
"The official, opinionated, batteries-included toolset for efficient Redux development",
|
|
22
|
+
category: "State Management",
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
id: "4",
|
|
26
|
+
title: "Material Tailwind",
|
|
27
|
+
description:
|
|
28
|
+
"React components library that implements Google's Material Design",
|
|
29
|
+
category: "UI Library",
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
id: "5",
|
|
33
|
+
title: "Tailwind CSS",
|
|
34
|
+
description: "A utility-first CSS framework for rapid UI development",
|
|
35
|
+
category: "Styling",
|
|
36
|
+
},
|
|
37
|
+
];
|
|
38
|
+
|
|
39
|
+
export const SearchModals = () => {
|
|
40
|
+
const [open, setOpen] = useState(false);
|
|
41
|
+
const [searchQuery, setSearchQuery] = useState("");
|
|
42
|
+
const [activeFilter, setActiveFilter] = useState<string>("");
|
|
43
|
+
|
|
44
|
+
const filteredResults = mockResults.filter((result) => {
|
|
45
|
+
const matchesQuery = result.title
|
|
46
|
+
.toLowerCase()
|
|
47
|
+
.includes(searchQuery.toLowerCase());
|
|
48
|
+
const matchesFilter = !activeFilter || result.category === activeFilter;
|
|
49
|
+
return matchesQuery && matchesFilter;
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
return (
|
|
53
|
+
<div className="p-8">
|
|
54
|
+
<button
|
|
55
|
+
onClick={() => setOpen(true)}
|
|
56
|
+
className="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600"
|
|
57
|
+
>
|
|
58
|
+
Open Search Modal
|
|
59
|
+
</button>
|
|
60
|
+
|
|
61
|
+
<SearchModal
|
|
62
|
+
open={open}
|
|
63
|
+
onClose={() => setOpen(false)}
|
|
64
|
+
results={searchQuery ? filteredResults : []}
|
|
65
|
+
onSearch={setSearchQuery}
|
|
66
|
+
onResultClick={(result) => {
|
|
67
|
+
console.log("Selected:", result);
|
|
68
|
+
alert(`Selected: ${result.title}`);
|
|
69
|
+
}}
|
|
70
|
+
filters={[
|
|
71
|
+
"Documentation",
|
|
72
|
+
"Library",
|
|
73
|
+
"State Management",
|
|
74
|
+
"UI Library",
|
|
75
|
+
"Styling",
|
|
76
|
+
]}
|
|
77
|
+
activeFilter={activeFilter}
|
|
78
|
+
onFilterChange={setActiveFilter}
|
|
79
|
+
/>
|
|
80
|
+
</div>
|
|
81
|
+
);
|
|
82
|
+
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
2
|
+
import {
|
|
3
|
+
Tabs,
|
|
4
|
+
TabsHeader,
|
|
5
|
+
Tab,
|
|
6
|
+
TabsBody,
|
|
7
|
+
TabPanel,
|
|
8
|
+
} from "@material-tailwind/react";
|
|
9
|
+
import { SettingsIcon } from "../components/icons/SettingsIcon";
|
|
10
|
+
|
|
11
|
+
const TabsComponent = () => (
|
|
12
|
+
<Tabs value="chats" className="w-[800px]">
|
|
13
|
+
<TabsHeader>
|
|
14
|
+
<Tab value="chats">Chats</Tab>
|
|
15
|
+
<Tab value="issues">Issues</Tab>
|
|
16
|
+
|
|
17
|
+
<button className="cursor-pointer mb-2 ml-auto rounded p-1 hover:bg-gray-100">
|
|
18
|
+
<SettingsIcon className="size-4" />
|
|
19
|
+
</button>
|
|
20
|
+
</TabsHeader>
|
|
21
|
+
|
|
22
|
+
<TabsBody>
|
|
23
|
+
<TabPanel value="chats">
|
|
24
|
+
Chats panel orem ipsum dolor sit, amet consectetur adipisicing elit.
|
|
25
|
+
Saepe deserunt officiis tempora accusamus sunt illo quae placeat velit
|
|
26
|
+
eius maiores, iste ipsa alias quos similique dolores odio? Magnam, quia
|
|
27
|
+
deleniti?
|
|
28
|
+
</TabPanel>
|
|
29
|
+
<TabPanel value="issues">
|
|
30
|
+
Issues panel orem ipsum dolor sit, amet consectetur adipisicing elit.
|
|
31
|
+
Saepe deserunt officiis tempora accusamus sunt illo quae placeat velit
|
|
32
|
+
eius maiores, iste ipsa alias quos similique dolores odio? Magnam, quia
|
|
33
|
+
deleniti?
|
|
34
|
+
</TabPanel>
|
|
35
|
+
</TabsBody>
|
|
36
|
+
</Tabs>
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
const meta = {
|
|
40
|
+
title: "Design System/Tabs",
|
|
41
|
+
component: TabsComponent,
|
|
42
|
+
parameters: {
|
|
43
|
+
layout: "centered",
|
|
44
|
+
},
|
|
45
|
+
tags: ["autodocs"],
|
|
46
|
+
} satisfies Meta<typeof TabsComponent>;
|
|
47
|
+
|
|
48
|
+
export default meta;
|
|
49
|
+
type Story = StoryObj<typeof meta>;
|
|
50
|
+
|
|
51
|
+
export const Default: Story = {};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
.tabs-container {
|
|
2
|
+
width: 100%;
|
|
3
|
+
min-width: 400px;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
.tabs-header {
|
|
7
|
+
display: flex;
|
|
8
|
+
border-bottom: 2px solid #e5e7eb;
|
|
9
|
+
margin-bottom: 16px;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.tab-button {
|
|
13
|
+
padding: 12px 24px;
|
|
14
|
+
background: none;
|
|
15
|
+
border: none;
|
|
16
|
+
cursor: pointer;
|
|
17
|
+
font-size: 14px;
|
|
18
|
+
font-weight: 500;
|
|
19
|
+
color: #6b7280;
|
|
20
|
+
position: relative;
|
|
21
|
+
transition: color 0.2s;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.tab-button:hover {
|
|
25
|
+
color: #374151;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.tab-button.active {
|
|
29
|
+
color: #3b82f6;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.tab-button.active::after {
|
|
33
|
+
content: "";
|
|
34
|
+
position: absolute;
|
|
35
|
+
bottom: -2px;
|
|
36
|
+
left: 0;
|
|
37
|
+
right: 0;
|
|
38
|
+
height: 2px;
|
|
39
|
+
background-color: #3b82f6;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.tab-content {
|
|
43
|
+
animation: fadeIn 0.3s ease-in-out;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
@keyframes fadeIn {
|
|
47
|
+
from {
|
|
48
|
+
opacity: 0;
|
|
49
|
+
transform: translateY(4px);
|
|
50
|
+
}
|
|
51
|
+
to {
|
|
52
|
+
opacity: 1;
|
|
53
|
+
transform: translateY(0);
|
|
54
|
+
}
|
|
55
|
+
}
|