@meridian-ui/meridian 1.0.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.
Files changed (61) hide show
  1. package/README.md +63 -0
  2. package/package.json +52 -0
  3. package/postcss.config.mjs +5 -0
  4. package/rollup.config.js +51 -0
  5. package/src/assets/add-tab.svg +4 -0
  6. package/src/assets/chevron-right.svg +4 -0
  7. package/src/assets/delete-tab.svg +3 -0
  8. package/src/assets/dummy-data/skeleton.json +42 -0
  9. package/src/assets/dummy-data/skeleton.ts +28 -0
  10. package/src/assets/meridian-toggle.svg +4 -0
  11. package/src/components/attributes/attribute-price.tsx +17 -0
  12. package/src/components/detail-views/detail-basic.tsx +121 -0
  13. package/src/components/detail-views/detail-view.scss +187 -0
  14. package/src/components/item-views/item-compact.tsx +72 -0
  15. package/src/components/item-views/item-pin.tsx +131 -0
  16. package/src/components/item-views/item-profile.tsx +140 -0
  17. package/src/components/item-views/item-vertical.tsx +145 -0
  18. package/src/components/item-views/item-view.scss +277 -0
  19. package/src/components/malleability/console/console-setting.tsx +184 -0
  20. package/src/components/malleability/console/console-view.tsx +47 -0
  21. package/src/components/malleability/console/detail-view-component.tsx +262 -0
  22. package/src/components/malleability/console/malleability-component.tsx +104 -0
  23. package/src/components/malleability/console/malleability-console.scss +285 -0
  24. package/src/components/malleability/console/overview-component.tsx +174 -0
  25. package/src/components/malleability/malleability-content-toggle.tsx +32 -0
  26. package/src/components/malleability/malleability-overview-tabs.tsx +212 -0
  27. package/src/components/malleability/malleability-toolbar.tsx +15 -0
  28. package/src/components/malleability/malleability.scss +199 -0
  29. package/src/components/overviews/overivew-basic-table.tsx +127 -0
  30. package/src/components/overviews/overview-basic-grid.tsx +27 -0
  31. package/src/components/overviews/overview-basic-list.tsx +61 -0
  32. package/src/components/overviews/overview-basic-map.tsx +358 -0
  33. package/src/components/overviews/overview-basic.scss +88 -0
  34. package/src/components/ui/dropdown-menu.tsx +61 -0
  35. package/src/helpers/attribute-set.helper.ts +4 -0
  36. package/src/helpers/attribute.helper.ts +334 -0
  37. package/src/helpers/spec.helper.ts +92 -0
  38. package/src/helpers/utils.helper.ts +22 -0
  39. package/src/helpers/view.helper.ts +184 -0
  40. package/src/index.css +149 -0
  41. package/src/index.ts +1 -0
  42. package/src/renderer/attribute.scss +59 -0
  43. package/src/renderer/attribute.tsx +305 -0
  44. package/src/renderer/renderer.data-bind.ts +573 -0
  45. package/src/renderer/renderer.defaults.ts +194 -0
  46. package/src/renderer/renderer.denormalize.ts +273 -0
  47. package/src/renderer/renderer.filter.ts +211 -0
  48. package/src/renderer/renderer.props.ts +21 -0
  49. package/src/renderer/renderer.scss +72 -0
  50. package/src/renderer/renderer.tsx +450 -0
  51. package/src/renderer/wrapper.tsx +225 -0
  52. package/src/spec/spec.internal.ts +76 -0
  53. package/src/spec/spec.ts +195 -0
  54. package/src/store/odi-malleability.store.ts +337 -0
  55. package/src/store/odi-navigation.store.ts +44 -0
  56. package/src/store/odi.store.ts +210 -0
  57. package/tailwind.config.js +31 -0
  58. package/tsconfig.json +24 -0
  59. package/types/svg.d.ts +6 -0
  60. package/vercel.json +5 -0
  61. package/webpack.config.js +18 -0
@@ -0,0 +1,72 @@
1
+ .odi-wrapper {
2
+ flex: 1;
3
+ position: inherit;
4
+ height: inherit;
5
+
6
+ &.popup-active {
7
+ width: 100vw;
8
+ height: 100vh;
9
+ overflow: hidden;
10
+ position: relative;
11
+ }
12
+ }
13
+
14
+ .odi-wrapper-container {
15
+ display: flex;
16
+ }
17
+
18
+ .malleability-tooltip {
19
+ position: absolute;
20
+ display: flex;
21
+ flex-direction: column;
22
+ gap: 2px;
23
+ background-color: white;
24
+ padding: 2px;
25
+ border-radius: 4px;
26
+ box-shadow: 0 0 10px 0 rgba(0, 0, 0, 0.1);
27
+ transition: all 0.3s ease;
28
+ z-index: 50;
29
+ }
30
+
31
+ .pop-up {
32
+ position: absolute;
33
+ top: 0;
34
+ left: 0;
35
+ width: 100%;
36
+ height: 100%;
37
+ display: flex;
38
+ justify-content: center;
39
+ align-items: center;
40
+ z-index: 40;
41
+ overflow: hidden;
42
+
43
+ .overlay {
44
+ position: absolute;
45
+ top: 0;
46
+ left: 0;
47
+ width: 100%;
48
+ height: 100%;
49
+ background-color: black;
50
+ opacity: 0.3;
51
+ }
52
+ .detail-view-wrapper {
53
+ width: 80%;
54
+ max-width: 1000px;
55
+ // width: fit-content;
56
+ max-height: 90%;
57
+ height: fit-content;
58
+ background-color: white;
59
+ box-shadow: 0 0 10px 0 rgba(0, 0, 0, 0.1);
60
+ border-radius: 4px;
61
+ z-index: 10;
62
+ overflow: auto;
63
+ position: relative;
64
+ }
65
+ }
66
+
67
+ body.no-scroll {
68
+ overflow: hidden;
69
+ position: fixed;
70
+ width: 100%;
71
+ height: 100%;
72
+ }
@@ -0,0 +1,450 @@
1
+ 'use client';
2
+
3
+ import {
4
+ AttributeType,
5
+ Overview,
6
+ DetailView,
7
+ ODI,
8
+ OverviewType,
9
+ ItemView,
10
+ ItemViewConfig,
11
+ } from '../spec/spec';
12
+ import { useODI } from '../store/odi.store';
13
+ import { OverviewBasicList } from '../components/overviews/overview-basic-list';
14
+ import '../index.css';
15
+ import { OverviewBasicGrid } from '../components/overviews/overview-basic-grid';
16
+ import { DetailBasic } from '../components/detail-views/detail-basic';
17
+ import { JSX, ReactNode, useEffect } from 'react';
18
+ import { denormalizeODI } from './renderer.denormalize';
19
+ import {
20
+ getFirstDetail,
21
+ getFirstOverview,
22
+ filterItemAttributes,
23
+ mapRecursiveAttributes,
24
+ } from './renderer.filter';
25
+ import React from 'react';
26
+ import { MalleabilityAttributesToggle } from '../components/malleability/malleability-content-toggle';
27
+ import { ItemProfile } from '../components/item-views/item-profile';
28
+ import { attributeInScope, rolesToIds } from '../helpers/attribute.helper';
29
+ import { MalleabilityOvervewTabs } from '../components/malleability/malleability-overview-tabs';
30
+ import { OverviewBasicMap } from '../components/overviews/overview-basic-map';
31
+ import {
32
+ addDetailViewType,
33
+ addItemViewType,
34
+ addOverviewType,
35
+ CustomDetailViewType,
36
+ CustomItemViewType,
37
+ CustomOverviewType,
38
+ DetailViewTypeConfig,
39
+ detailViewTypesMap,
40
+ getOverviewTypesMap,
41
+ itemViewTypesMap,
42
+ ItemViewTypeConfig,
43
+ OverviewTypeConfig,
44
+ overviewTypesMap,
45
+ } from './renderer.defaults';
46
+ import {
47
+ getBottomCenter,
48
+ checkDataLists,
49
+ getDataBindingById,
50
+ findItemDetailViewToOpen,
51
+ findOverviewById,
52
+ getAttributeDataBindingById,
53
+ } from '../helpers/view.helper';
54
+ import {
55
+ mapDataToFetchedItems,
56
+ getFetchedODIFromData,
57
+ } from './renderer.data-bind';
58
+ import {
59
+ FetchedItemType,
60
+ FetchedODI,
61
+ ViewOptions,
62
+ FetchedAttributeType,
63
+ FetchedAttributeGroupType,
64
+ } from '../spec/spec.internal';
65
+ import skeletonData from '../assets/dummy-data/skeleton.json';
66
+ import { skeletonODI } from '../assets/dummy-data/skeleton';
67
+
68
+ // const ODIRenderer: React.FC<{ odi: BookingODI }> = ({ odi }) => {
69
+ // return (
70
+ // <div>
71
+ // {odi.overviews.map((overview, index) => {
72
+ // switch (overview.type) {
73
+ // case 'basic-list':
74
+ // return <BasicListComponent key={index} {...overview} />;
75
+ // case 'basic-map':
76
+ // return <BasicMapComponent key={index} {...overview} />;
77
+ // default:
78
+ // return null;
79
+ // }
80
+ // })}
81
+ // </div>
82
+ // );
83
+ // };
84
+
85
+ export const MeridianOverview = ({
86
+ overviewIdToShow,
87
+ attribute,
88
+ }: {
89
+ overviewIdToShow?: string;
90
+ attribute?: FetchedAttributeGroupType;
91
+ }) => {
92
+ const {
93
+ selectedItemEntity,
94
+ odi,
95
+ data: dataLists,
96
+ getSelectedAttributeSet,
97
+ onOpenDetailNewPage,
98
+ onOpenOverviewNewPage,
99
+ enabledMalleableContent,
100
+ malleableCompositionSetting,
101
+ activeOverview,
102
+ } = useODI();
103
+
104
+ if (!odi) return <></>;
105
+ if (overviewIdToShow) {
106
+ const overview = findOverviewById(odi, overviewIdToShow);
107
+
108
+ if (overview) {
109
+ const OverviewComponent =
110
+ overviewTypesMap[overview.type]?.view ?? OverviewBasicList;
111
+ const OverviewWrapper = React.Fragment;
112
+
113
+ let items: FetchedItemType[] = [];
114
+
115
+ if (attribute) {
116
+ const attributeItems = (getAttributeDataBindingById(
117
+ odi,
118
+ overview.bindingId,
119
+ attribute
120
+ )?.attributes ?? []) as unknown as FetchedItemType[];
121
+
122
+ const parentItems = getDataBindingById(odi, overview.bindingId).items;
123
+
124
+ const mappedAttributeItems = mapRecursiveAttributes(
125
+ attributeItems,
126
+ parentItems,
127
+ overview.id ?? ''
128
+ );
129
+
130
+ // console.log('1', attributeItems, mappedAttributeItems);
131
+
132
+ items = filterItemAttributes(
133
+ mappedAttributeItems,
134
+ rolesToIds(
135
+ [...parentItems, ...attributeItems],
136
+ overview.shownAttributes ?? []
137
+ ),
138
+ rolesToIds(attributeItems, overview.hiddenAttributes ?? []),
139
+ ''
140
+ ).map((item) => ({
141
+ ...item,
142
+ id: item.itemId,
143
+ }));
144
+ } else {
145
+ items = filterItemAttributes(
146
+ getDataBindingById(odi, overview.bindingId).items,
147
+ overview.shownAttributes,
148
+ overview.hiddenAttributes,
149
+ overview?.id ?? ''
150
+ );
151
+ }
152
+
153
+ // console.log('2', getDataBindingById(odi, overview.bindingId).items);
154
+
155
+ // console.log('overview', overview, overview.shownAttributes);
156
+ return (
157
+ <OverviewWrapper key={`${overview.id}-${0}`}>
158
+ {!attribute && enabledMalleableContent() && (
159
+ <MalleabilityAttributesToggle />
160
+ )}
161
+ <OverviewComponent
162
+ overview={overview}
163
+ items={items}
164
+ viewType={'overview'}
165
+ onOpenDetailNewPage={onOpenDetailNewPage ?? (() => {})}
166
+ onOpenOverviewNewPage={onOpenOverviewNewPage ?? (() => {})}
167
+ />
168
+ </OverviewWrapper>
169
+ );
170
+ }
171
+ }
172
+
173
+ console.log('bbbb', odi);
174
+
175
+ return (
176
+ <div>
177
+ <div className="flex flex-col">
178
+ {malleableCompositionSetting().includes('tabs') && (
179
+ <MalleabilityOvervewTabs />
180
+ )}
181
+ {enabledMalleableContent() && <MalleabilityAttributesToggle />}
182
+ </div>
183
+ {odi.overviews
184
+ .filter(
185
+ (overview) =>
186
+ !malleableCompositionSetting().includes('tabs') ||
187
+ activeOverview === overview.id
188
+ )
189
+ .map((overview, index) => {
190
+ const OverviewComponent =
191
+ overviewTypesMap[overview.type]?.view ?? OverviewBasicList;
192
+ const OverviewWrapper = React.Fragment;
193
+ const items = filterItemAttributes(
194
+ getDataBindingById(odi, overview.bindingId).items,
195
+ overview.shownAttributes,
196
+ overview.hiddenAttributes,
197
+ '6734625345' // It's empty because I don't need to filter by viewId. This basically removes attributes with the same id as the overview.
198
+ );
199
+
200
+ return (
201
+ <OverviewWrapper key={`${overview.id}-${index}`}>
202
+ <OverviewComponent
203
+ overview={overview}
204
+ items={items}
205
+ viewType={'overview'}
206
+ onOpenDetailNewPage={onOpenDetailNewPage ?? (() => {})}
207
+ onOpenOverviewNewPage={onOpenOverviewNewPage ?? (() => {})}
208
+ />
209
+ </OverviewWrapper>
210
+ );
211
+ })}
212
+ {
213
+ // ---- SIDE BY SIDE VIEW ----
214
+ selectedItemEntity?.detail.openIn === 'side-by-side' && (
215
+ <div className="">
216
+ <MeridianDetail
217
+ dataLists={dataLists}
218
+ odi={odi}
219
+ itemId={selectedItemEntity?.itemId}
220
+ />
221
+ </div>
222
+ )
223
+ }
224
+ </div>
225
+ );
226
+ };
227
+
228
+ export const MeridianItem = ({
229
+ options,
230
+ item,
231
+ index,
232
+ itemView,
233
+ style,
234
+ className,
235
+ }: {
236
+ options: ViewOptions;
237
+ item: FetchedItemType;
238
+ index: number;
239
+ itemView?: ItemView;
240
+ style?: React.CSSProperties;
241
+ className?: string;
242
+ }) => {
243
+ const {
244
+ odi,
245
+ selectedItemEntity,
246
+ setSelectedItemEntity,
247
+ getSelectedAttributeSet,
248
+ highlightAttributes,
249
+ onOpenDetailNewPage,
250
+ onOpenOverviewNewPage,
251
+ } = useODI();
252
+
253
+ const itemViewType =
254
+ itemView?.type ??
255
+ overviewTypesMap[options.overview.type]?.defaultSpec.itemView?.type ??
256
+ 'profile';
257
+
258
+ const ItemComponent = itemViewTypesMap[itemViewType]?.view ?? ItemProfile;
259
+
260
+ // console.log('detailToOpen', options, odi);
261
+ // Find the detail view that should open from clicking this attribute
262
+ const detailToOpen = findItemDetailViewToOpen(options, odi);
263
+
264
+ return (
265
+ <div
266
+ // className="w-fit relative"
267
+ className={`${className}`}
268
+ style={{
269
+ cursor: detailToOpen && !highlightAttributes ? 'pointer' : 'auto',
270
+ ...style,
271
+ }}
272
+ onClick={(e) => {
273
+ if (detailToOpen && !highlightAttributes) {
274
+ setSelectedItemEntity(
275
+ detailToOpen,
276
+ item.overviewIndex ?? 0,
277
+ item.itemId,
278
+ {
279
+ ...options,
280
+ viewType: 'detail',
281
+ overview: {
282
+ ...options.overview,
283
+ detailViews: options.overview.detailViews,
284
+ },
285
+ // details: detailToOpen.details,
286
+ },
287
+ { x: e.clientX, y: e.clientY }
288
+ );
289
+ }
290
+ if (detailToOpen?.openIn === 'new-page') {
291
+ // If open in new page, run callback function to route.
292
+ options.onOpenDetailNewPage(options.items[index]);
293
+ }
294
+ }}
295
+ >
296
+ {odi &&
297
+ selectedItemEntity?.itemId === item.itemId &&
298
+ selectedItemEntity?.detail.openIn === 'tooltip' && (
299
+ <div
300
+ className="absolute w-[400px] h-fit max-h-[500px] bg-white shadow-2xl h-[96%] rounded-md z-10 overflow-scroll"
301
+ style={{
302
+ left: 'calc(50% - 200px)',
303
+ top: -520,
304
+ }}
305
+ >
306
+ <MeridianDetail odi={odi} itemId={item.itemId} />
307
+ </div>
308
+ )}
309
+ <ItemComponent
310
+ options={options}
311
+ item={item}
312
+ index={index}
313
+ className={className + (detailToOpen ? ' item-hover' : '')}
314
+ style={style}
315
+ />
316
+ </div>
317
+ );
318
+ };
319
+
320
+ // * Another application idea, that's not really ODI toolkit, but more specification+demo:
321
+ // * 1. Specification --> UI --> Screenshot can then provide more info about the spec from the UI
322
+ // * 2. A coding framework for ODIs through the spec??
323
+ export const MeridianDetail = ({
324
+ odi: fetchedODI,
325
+ dataLists,
326
+ itemId,
327
+ detailId,
328
+ onAction,
329
+ }: {
330
+ odi?: FetchedODI; // Accept either FetchedODI or ODI
331
+ dataLists?: any[][];
332
+ itemId: string | undefined;
333
+ // item: FetchedItemType | undefined;
334
+ detailId?: string;
335
+ onAction?: React.MouseEventHandler<HTMLButtonElement>;
336
+ }) => {
337
+ const {
338
+ odi,
339
+ setODI,
340
+ setOriginalODI,
341
+ selectedItemEntity,
342
+ setSelectedItemEntity,
343
+ getSelectedAttributeSet,
344
+ setOnOpenNewPage,
345
+ } = useODI();
346
+
347
+ // Only set the ODI from the imported spec variable if DNE in the store.
348
+ useEffect(() => {
349
+ if (fetchedODI && !odi && dataLists) {
350
+ if (fetchedODI) {
351
+ setODI(denormalizeODI(fetchedODI));
352
+ setOriginalODI(denormalizeODI(fetchedODI));
353
+ }
354
+ }
355
+ }, [fetchedODI]);
356
+
357
+ // Get default overview and detail view if selected detail content isn't found in the store.
358
+ useEffect(() => {
359
+ if (!selectedItemEntity && odi) {
360
+ if (detailId) {
361
+ const allDetailViews = odi.overviews
362
+ .flatMap((overview) =>
363
+ overview.detailViews
364
+ ?.map((d) => {
365
+ // Convert string references to actual detail view objects
366
+ const detailView =
367
+ typeof d === 'string'
368
+ ? odi.detailViews?.find((dv) => dv.id === d)
369
+ : d;
370
+ return detailView ? { detailView, overview } : null;
371
+ })
372
+ .filter(Boolean)
373
+ )
374
+ .concat(
375
+ odi.detailViews?.map((d) => ({
376
+ detailView: d,
377
+ overview: odi.overviews[0],
378
+ })) || []
379
+ );
380
+ const overviewDetailObject = allDetailViews.find(
381
+ (detail) => detail?.detailView.id === detailId
382
+ );
383
+
384
+ if (overviewDetailObject && itemId) {
385
+ setSelectedItemEntity(
386
+ overviewDetailObject.detailView,
387
+ odi.overviews.findIndex(
388
+ (overview) => overview.id === overviewDetailObject.overview.id
389
+ ),
390
+ itemId,
391
+ {
392
+ overview: overviewDetailObject.overview,
393
+ items:
394
+ getDataBindingById(
395
+ odi,
396
+ overviewDetailObject?.overview?.bindingId ??
397
+ overviewDetailObject?.detailView?.bindingId
398
+ )?.items || [],
399
+ viewType: 'detail',
400
+ onOpenDetailNewPage: () => {},
401
+ onOpenOverviewNewPage: () => {},
402
+ },
403
+ { x: 0, y: 0 }
404
+ );
405
+ }
406
+ } else {
407
+ const firstOverview = getFirstOverview(odi);
408
+ const firstDetail = getFirstDetail(odi);
409
+ if (itemId) {
410
+ setSelectedItemEntity(
411
+ firstDetail,
412
+ 0,
413
+ itemId,
414
+ {
415
+ overview: firstOverview,
416
+ items:
417
+ getDataBindingById(
418
+ odi,
419
+ firstOverview?.bindingId ?? firstDetail?.bindingId
420
+ )?.items || [],
421
+ viewType: 'detail',
422
+ onOpenDetailNewPage: () => {},
423
+ onOpenOverviewNewPage: () => {},
424
+ },
425
+ { x: 0, y: 0 }
426
+ );
427
+ }
428
+ }
429
+ }
430
+ }, [itemId, odi]);
431
+
432
+ const selectedItem = getSelectedAttributeSet();
433
+ if (selectedItem && odi) {
434
+ const DetailComponent =
435
+ detailViewTypesMap[selectedItemEntity?.detail.type ?? 'basic']?.view ??
436
+ DetailBasic;
437
+
438
+ return (
439
+ <div className="">
440
+ {(odi?.malleability?.disabled === false ||
441
+ odi?.malleability?.disabled === undefined) && (
442
+ <MalleabilityAttributesToggle />
443
+ )}
444
+ <DetailComponent item={selectedItem} />
445
+ </div>
446
+ );
447
+ } else {
448
+ return <div></div>;
449
+ }
450
+ };