@axinom/mosaic-ui 0.42.0-rc.3 → 0.42.0-rc.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.
Files changed (43) hide show
  1. package/dist/components/Tabs/Tab/CustomTab.d.ts +3 -0
  2. package/dist/components/Tabs/Tab/CustomTab.d.ts.map +1 -0
  3. package/dist/components/Tabs/Tab/index.d.ts +2 -0
  4. package/dist/components/Tabs/Tab/index.d.ts.map +1 -0
  5. package/dist/components/Tabs/TabList/CustomTabList.d.ts +3 -0
  6. package/dist/components/Tabs/TabList/CustomTabList.d.ts.map +1 -0
  7. package/dist/components/Tabs/TabList/ScrollContainer/ScrollContainer.d.ts +3 -0
  8. package/dist/components/Tabs/TabList/ScrollContainer/ScrollContainer.d.ts.map +1 -0
  9. package/dist/components/Tabs/TabList/ScrollContainer/index.d.ts +2 -0
  10. package/dist/components/Tabs/TabList/ScrollContainer/index.d.ts.map +1 -0
  11. package/dist/components/Tabs/TabList/ScrollContainer/useScroll.d.ts +10 -0
  12. package/dist/components/Tabs/TabList/ScrollContainer/useScroll.d.ts.map +1 -0
  13. package/dist/components/Tabs/TabList/index.d.ts +2 -0
  14. package/dist/components/Tabs/TabList/index.d.ts.map +1 -0
  15. package/dist/components/Tabs/TabPanel/CustomTabPanel.d.ts +3 -0
  16. package/dist/components/Tabs/TabPanel/CustomTabPanel.d.ts.map +1 -0
  17. package/dist/components/Tabs/TabPanel/index.d.ts +2 -0
  18. package/dist/components/Tabs/TabPanel/index.d.ts.map +1 -0
  19. package/dist/components/Tabs/index.d.ts +5 -0
  20. package/dist/components/Tabs/index.d.ts.map +1 -0
  21. package/dist/index.es.js +4 -4
  22. package/dist/index.es.js.map +1 -1
  23. package/dist/index.js +4 -4
  24. package/dist/index.js.map +1 -1
  25. package/package.json +4 -3
  26. package/src/components/FormElements/CustomTags/CustomTags.scss +12 -11
  27. package/src/components/FormStation/FormStation.stories.tsx +172 -0
  28. package/src/components/Tabs/Tab/CustomTab.scss +42 -0
  29. package/src/components/Tabs/Tab/CustomTab.tsx +34 -0
  30. package/src/components/Tabs/Tab/index.ts +1 -0
  31. package/src/components/Tabs/TabList/CustomTabList.scss +7 -0
  32. package/src/components/Tabs/TabList/CustomTabList.tsx +15 -0
  33. package/src/components/Tabs/TabList/ScrollContainer/ScrollContainer.scss +34 -0
  34. package/src/components/Tabs/TabList/ScrollContainer/ScrollContainer.tsx +39 -0
  35. package/src/components/Tabs/TabList/ScrollContainer/index.ts +1 -0
  36. package/src/components/Tabs/TabList/ScrollContainer/useScroll.ts +114 -0
  37. package/src/components/Tabs/TabList/index.ts +1 -0
  38. package/src/components/Tabs/TabPanel/CustomTabPanel.scss +10 -0
  39. package/src/components/Tabs/TabPanel/CustomTabPanel.tsx +26 -0
  40. package/src/components/Tabs/TabPanel/index.ts +1 -0
  41. package/src/components/Tabs/Tabs.stories.tsx +108 -0
  42. package/src/components/Tabs/index.ts +4 -0
  43. package/src/styles/variables.scss +3 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@axinom/mosaic-ui",
3
- "version": "0.42.0-rc.3",
3
+ "version": "0.42.0-rc.5",
4
4
  "description": "UI components for building Axinom Mosaic applications",
5
5
  "author": "Axinom",
6
6
  "license": "PROPRIETARY",
@@ -32,7 +32,7 @@
32
32
  "build-storybook": "storybook build"
33
33
  },
34
34
  "dependencies": {
35
- "@axinom/mosaic-core": "^0.4.15-rc.3",
35
+ "@axinom/mosaic-core": "^0.4.15-rc.5",
36
36
  "@faker-js/faker": "^7.4.0",
37
37
  "@popperjs/core": "^2.11.8",
38
38
  "clsx": "^1.1.0",
@@ -43,6 +43,7 @@
43
43
  "react-content-loader": "^6.0.3",
44
44
  "react-imask": "^6.4.3",
45
45
  "react-popper": "^2.2.5",
46
+ "react-tabs": "4.3.0",
46
47
  "react-transition-group": "^4.3.0",
47
48
  "yup": "^0.32.11"
48
49
  },
@@ -104,5 +105,5 @@
104
105
  "publishConfig": {
105
106
  "access": "public"
106
107
  },
107
- "gitHead": "cb9bba0b7ae9543a415ea8eefbcb3444e76ac6f2"
108
+ "gitHead": "0a4a124c07abc12604ad50a1892c8ae6b4e49a15"
108
109
  }
@@ -85,14 +85,13 @@
85
85
 
86
86
  input,
87
87
  .autoComplete > li {
88
- padding: 0 60px 0 12px;
88
+ padding: 15px;
89
89
  color: var(--input-color, $input-color);
90
90
  }
91
91
 
92
92
  .autoComplete {
93
93
  display: grid;
94
94
  grid-template-columns: 1fr;
95
- grid-auto-rows: 25px;
96
95
  align-items: center;
97
96
 
98
97
  box-sizing: border-box;
@@ -101,25 +100,27 @@
101
100
  position: absolute;
102
101
  top: 36px;
103
102
  left: 0;
104
- right: -25px;
103
+ right: 0;
105
104
 
106
105
  list-style-type: none;
107
106
  z-index: 10;
108
107
 
109
- border: 1px solid gray;
108
+ border: 1px solid
109
+ var(--live-suggest-border-color, $live-suggest-border-color);
110
110
  background-color: #ffffff;
111
111
 
112
+ li {
113
+ color: var(--live-suggest-text-color, $live-suggest-text-color);
114
+ }
115
+
112
116
  li:hover {
113
- color: #ffffff;
114
- background-color: #1e90ff;
117
+ background-color: var(
118
+ --live-suggest-background-selected-color,
119
+ $live-suggest-background-selected-color
120
+ );
115
121
 
116
122
  cursor: pointer;
117
123
  }
118
-
119
- li.selected {
120
- color: #ffffff;
121
- background-color: #1e90ff;
122
- }
123
124
  }
124
125
 
125
126
  .plusButton {
@@ -24,6 +24,7 @@ import { CheckboxField } from '../FormElements/Checkbox/CheckboxField';
24
24
  import { DynamicDataListField } from '../FormElements/DynamicDataListControl/DynamicDataListField';
25
25
  import { MaskedSingleLineTextField } from '../FormElements/MaskedSingleLineText/MaskedSingleLineTextField';
26
26
  import { InfoPanel, Paragraph, Section } from '../InfoPanel';
27
+ import { Tab, TabList, TabPanel, Tabs } from '../Tabs';
27
28
  import { ErrorType } from '../models';
28
29
  import { Details, DetailsProps } from './Details/Details';
29
30
  import { ObjectSchemaDefinition } from './FormStation';
@@ -294,6 +295,177 @@ export const Extended: StoryObj<typeof Details> = (() => {
294
295
  };
295
296
  })();
296
297
 
298
+ export const TabbedContent: StoryObj<typeof Details> = (() => {
299
+ const listData = generateItemArray(4, (index) => ({
300
+ position: index + 1,
301
+ id: index,
302
+ locale: faker.random.locale(),
303
+ country: faker.address.country(),
304
+ }));
305
+ return {
306
+ args: {
307
+ titleProperty: 'title',
308
+ subtitle: 'Movies',
309
+ actions: generateActions(7),
310
+ validationSchema: Yup.object<ObjectSchemaDefinition<DetailsValues>>({
311
+ title: Yup.string().required().max(25).label('Title'),
312
+ genres: Yup.array<DetailsValues>()
313
+ .of(Yup.string())
314
+ .max(2)
315
+ .label('Genres'),
316
+ shortDescription: Yup.string().required().label('Short Description'),
317
+ cast: Yup.array<DetailsValues>().of(Yup.string()).min(1).label('Cast'),
318
+ }),
319
+ initialData: {
320
+ loading: false,
321
+ data: {
322
+ id: 12344567890,
323
+ title: 'My Movie',
324
+ publishState: 'PUBLISHED',
325
+ genres: [],
326
+ licenses: [],
327
+ ratings: 'PG-13',
328
+ shortDescription: 'Some short abstract...',
329
+ longDescription: '',
330
+ cast: ['Jane Doe', 'John Doe'],
331
+ released: '2020-04-03T00:00:00.000+00:00',
332
+ list: listData,
333
+ archived: false,
334
+ timestamp: '00:00:00.001',
335
+ },
336
+ },
337
+ children: (
338
+ <Tabs>
339
+ <TabList>
340
+ <Tab>Tab 1</Tab>
341
+ <Tab>Tab 2</Tab>
342
+ </TabList>
343
+ <TabPanel>
344
+ <div
345
+ style={{
346
+ display: 'grid',
347
+ gridAutoRows: 'max-content',
348
+ rowGap: '20px',
349
+ paddingTop: '20px',
350
+ paddingBottom: '20px',
351
+ }}
352
+ >
353
+ <Field name="id" label="Id" as={ReadOnlyField} />
354
+ <Field name="title" label="Title" as={SingleLineTextField} />
355
+ <Field
356
+ name="publishState"
357
+ label="Publish State"
358
+ as={ReadOnlyField}
359
+ />
360
+ <Field
361
+ name="genres"
362
+ label="Genre(s)"
363
+ tagsOptions={['Crime', 'Drama', 'Thriller']}
364
+ as={TagsField}
365
+ />
366
+
367
+ <Field
368
+ name="ratings"
369
+ label="Age Rating"
370
+ options={[
371
+ { value: 'PG', label: 'Parental Guidance Suggested (PG)' },
372
+ {
373
+ value: 'PG-13',
374
+ label: 'Parents Strongly Cautioned (PG-13)',
375
+ },
376
+ { value: 'R', label: 'Restricted (R)' },
377
+ ]}
378
+ as={SelectField}
379
+ />
380
+
381
+ <Field
382
+ name="shortDescription"
383
+ label="Short Description"
384
+ placeholder="Enter a short description..."
385
+ as={SingleLineTextField}
386
+ />
387
+ <Field
388
+ name="longDescription"
389
+ label="Long Description"
390
+ placeholder="Enter a description..."
391
+ as={SingleLineTextField}
392
+ />
393
+ <Field name="cast" label="Cast" as={CustomTagsField} />
394
+ <Field name="released" label="Released" as={DateTimeTextField} />
395
+ <Field
396
+ name="password"
397
+ label="Password"
398
+ as={SingleLineTextField}
399
+ type="password"
400
+ />
401
+
402
+ <Field
403
+ name="licenses"
404
+ label="License Countries"
405
+ tagsOptions={[
406
+ { value: 'DE', key: 'Germany' },
407
+ { value: 'EE', key: 'Estonia' },
408
+ { value: 'LK', key: 'Sri Lanka' },
409
+ ]}
410
+ as={TagsField}
411
+ displayKey="key"
412
+ valueKey="value"
413
+ />
414
+ <Field
415
+ name="list"
416
+ label="Subtitles"
417
+ columns={[
418
+ {
419
+ propertyName: 'id',
420
+ label: 'Id',
421
+ size: '50px',
422
+ },
423
+ {
424
+ propertyName: 'locale',
425
+ label: 'Locale',
426
+ dataEntryRender: createInputRenderer({
427
+ placeholder: 'Enter Locale',
428
+ }),
429
+ },
430
+ {
431
+ propertyName: 'country',
432
+ label: 'Country',
433
+ dataEntryRender: createSelectRenderer({
434
+ options: [
435
+ { value: 'Country 10', label: 'Country 10' },
436
+ { value: 'Country 11', label: 'Country 11' },
437
+ { value: 'Country 12', label: 'Country 12' },
438
+ ],
439
+ placeholder: 'Enter Country',
440
+ }),
441
+ },
442
+ ]}
443
+ data={listData}
444
+ positionPropertyName={'position'}
445
+ allowReordering={true}
446
+ allowRowDragging={true}
447
+ allowNewData={true}
448
+ as={DynamicDataListField}
449
+ />
450
+ <Field name="archived" label="Set Archived" as={CheckboxField} />
451
+ <Field
452
+ name="timestamp"
453
+ label="Timestamp"
454
+ mask="00:00:00.000"
455
+ as={MaskedSingleLineTextField}
456
+ />
457
+ </div>
458
+ </TabPanel>
459
+ <TabPanel>
460
+ <h2>Tab 2</h2>
461
+ <p>{faker.lorem.paragraph(20)}</p>
462
+ </TabPanel>
463
+ </Tabs>
464
+ ),
465
+ },
466
+ };
467
+ })();
468
+
297
469
  const errorGroups = createGroups({
298
470
  'Storybook (Loading Error)': [
299
471
  'loadingError',
@@ -0,0 +1,42 @@
1
+ @import '../../../styles/common.scss';
2
+
3
+ .tab {
4
+ @include boxSizing;
5
+
6
+ display: grid;
7
+ height: 49px;
8
+ min-width: 180px;
9
+ background-color: $blue;
10
+ color: white;
11
+ font-size: 16px;
12
+ border: 1px solid $blue;
13
+ border-bottom: none;
14
+
15
+ padding: 0 15px;
16
+
17
+ cursor: pointer;
18
+
19
+ &.selected {
20
+ background: white;
21
+ color: $dark-gray;
22
+ border: 1px solid $light-gray;
23
+ border-bottom: none;
24
+ }
25
+
26
+ &.disabled {
27
+ background-color: $dark-gray;
28
+ cursor: default;
29
+ }
30
+
31
+ &:focus {
32
+ outline: none;
33
+ }
34
+ }
35
+
36
+ .content {
37
+ display: grid;
38
+ align-items: center;
39
+ height: 100%;
40
+ width: 100%;
41
+ justify-content: center;
42
+ }
@@ -0,0 +1,34 @@
1
+ import React, { useEffect, useRef } from 'react';
2
+ import { ReactTabsFunctionComponent, Tab, TabProps } from 'react-tabs';
3
+ import classes from './CustomTab.scss';
4
+
5
+ export const CustomTab: ReactTabsFunctionComponent<TabProps> = ({
6
+ children,
7
+ ...otherProps
8
+ }) => {
9
+ const ref = useRef<HTMLDivElement>(null);
10
+
11
+ useEffect(() => {
12
+ if (ref.current && ref.current.parentElement && otherProps.selected) {
13
+ ref.current.parentElement.scrollIntoView({
14
+ behavior: 'smooth',
15
+ block: 'nearest',
16
+ });
17
+ }
18
+ }, [otherProps.selected]);
19
+
20
+ return (
21
+ <Tab
22
+ {...otherProps}
23
+ className={classes.tab}
24
+ selectedClassName={classes.selected}
25
+ data-test-id="tab"
26
+ >
27
+ <div className={classes.content} ref={ref}>
28
+ {children}
29
+ </div>
30
+ </Tab>
31
+ );
32
+ };
33
+
34
+ CustomTab.tabsRole = 'Tab';
@@ -0,0 +1 @@
1
+ export { CustomTab as Tab } from './CustomTab';
@@ -0,0 +1,7 @@
1
+ @import '../../../styles/common.scss';
2
+
3
+ .tablist {
4
+ border: none;
5
+ margin: 0;
6
+ padding: 0;
7
+ }
@@ -0,0 +1,15 @@
1
+ import React from 'react';
2
+ import { ReactTabsFunctionComponent, TabList, TabListProps } from 'react-tabs';
3
+ import classes from './CustomTabList.scss';
4
+ import { ScrollContainer } from './ScrollContainer';
5
+
6
+ export const CustomTabList: ReactTabsFunctionComponent<TabListProps> = ({
7
+ children,
8
+ ...otherProps
9
+ }) => (
10
+ <TabList {...otherProps} className={classes.tablist}>
11
+ <ScrollContainer>{children}</ScrollContainer>
12
+ </TabList>
13
+ );
14
+
15
+ CustomTabList.tabsRole = 'TabList';
@@ -0,0 +1,34 @@
1
+ @import '../../../../styles/common.scss';
2
+
3
+ .tablistWrapper {
4
+ overflow-x: auto;
5
+ overflow-y: hidden;
6
+ display: grid;
7
+ width: 100%;
8
+ white-space: nowrap;
9
+ grid-auto-flow: column;
10
+ grid-auto-columns: minmax(max-content, auto);
11
+ gap: 2px;
12
+
13
+ -ms-overflow-style: none; /* IE and Edge */
14
+ scrollbar-width: none; /* Firefox */
15
+ }
16
+
17
+ /* Hide scrollbar for Chrome, Safari and Opera */
18
+ .tablistWrapper::-webkit-scrollbar {
19
+ display: none;
20
+ }
21
+
22
+ .container {
23
+ display: grid;
24
+ // grid-template-columns: 50px auto 50px;
25
+
26
+ &.scroll {
27
+ grid-template-columns: 50px auto 50px;
28
+ gap: 2px;
29
+ }
30
+ }
31
+
32
+ .hide {
33
+ display: none;
34
+ }
@@ -0,0 +1,39 @@
1
+ import clsx from 'clsx';
2
+ import React from 'react';
3
+ import { Button } from '../../../Buttons';
4
+ import { IconName } from '../../../Icons';
5
+ import classes from './ScrollContainer.scss';
6
+ import { useScroll } from './useScroll';
7
+
8
+ export const ScrollContainer: React.FC = ({ children }) => {
9
+ const {
10
+ scrollRef,
11
+ showScroll,
12
+ scrollLeft,
13
+ scrollRight,
14
+ enableScrollLeft,
15
+ enableScrollRight,
16
+ } = useScroll<HTMLDivElement>();
17
+
18
+ return (
19
+ <div className={clsx(classes.container, { [classes.scroll]: showScroll })}>
20
+ <Button
21
+ icon={IconName.ChevronLeft}
22
+ onButtonClicked={scrollLeft}
23
+ className={clsx({ [classes.hide]: !showScroll })}
24
+ disabled={!enableScrollLeft}
25
+ />
26
+
27
+ <div className={classes.tablistWrapper} ref={scrollRef}>
28
+ {children}
29
+ </div>
30
+
31
+ <Button
32
+ icon={IconName.ChevronRight}
33
+ onButtonClicked={scrollRight}
34
+ className={clsx({ [classes.hide]: !showScroll })}
35
+ disabled={!enableScrollRight}
36
+ />
37
+ </div>
38
+ );
39
+ };
@@ -0,0 +1 @@
1
+ export { ScrollContainer } from './ScrollContainer';
@@ -0,0 +1,114 @@
1
+ import debounce from 'lodash/debounce';
2
+ import React, { useCallback, useEffect, useLayoutEffect, useRef } from 'react';
3
+
4
+ export const useScroll = <T extends HTMLElement>(): {
5
+ scrollLeft: () => void;
6
+ scrollRight: () => void;
7
+ scrollRef: React.RefObject<T>;
8
+ showScroll: boolean;
9
+ enableScrollLeft: boolean;
10
+ enableScrollRight: boolean;
11
+ } => {
12
+ const scrollRef = useRef<T>(null);
13
+
14
+ const scrollLeft = useCallback(() => {
15
+ if (scrollRef.current) {
16
+ scrollRef.current.scrollBy({
17
+ left: -220,
18
+ behavior: 'smooth',
19
+ });
20
+ }
21
+ }, []);
22
+
23
+ const scrollRight = useCallback(() => {
24
+ if (scrollRef.current) {
25
+ scrollRef.current.scrollBy({
26
+ left: 220,
27
+ behavior: 'smooth',
28
+ });
29
+ }
30
+ }, []);
31
+
32
+ const [showScroll, setShowScroll] = React.useState<boolean>(false);
33
+ const [enableScrollLeft, setEnableScrollLeft] =
34
+ React.useState<boolean>(false);
35
+ const [enableScrollRight, setEnableScrollRight] =
36
+ React.useState<boolean>(false);
37
+
38
+ const updateScroll = useCallback(() => {
39
+ if (scrollRef.current) {
40
+ const { scrollWidth, clientWidth } = scrollRef.current;
41
+
42
+ if (showScroll) {
43
+ // take into account the width of the scroll buttons
44
+ setShowScroll(scrollWidth > clientWidth + 100);
45
+ } else {
46
+ setShowScroll(scrollWidth > clientWidth);
47
+ }
48
+ }
49
+ }, [showScroll]);
50
+
51
+ const updateScrollButtons = useCallback(() => {
52
+ if (scrollRef.current) {
53
+ const { scrollLeft, scrollWidth, clientWidth } = scrollRef.current;
54
+
55
+ setEnableScrollLeft(scrollLeft > 0);
56
+ setEnableScrollRight(scrollLeft + clientWidth < scrollWidth);
57
+ }
58
+ }, []);
59
+
60
+ // Omitting the dependency array here since this effect needs to run at every render
61
+ // to handle the case where the components are dynamically added/removed
62
+ useEffect(updateScroll);
63
+ useEffect(updateScrollButtons);
64
+
65
+ useLayoutEffect(() => {
66
+ // debounce is needed to avoid re-rendering everything on every resize event
67
+ const debouncedUpdateScroll = debounce(updateScroll, 50);
68
+
69
+ // add window resize event listener on mount
70
+ window.addEventListener('resize', debouncedUpdateScroll);
71
+
72
+ debouncedUpdateScroll();
73
+
74
+ const scrollContainer = scrollRef.current;
75
+ const debouncedUpdateScrollButtons = debounce(updateScrollButtons, 50);
76
+
77
+ scrollContainer?.addEventListener('scroll', debouncedUpdateScrollButtons);
78
+
79
+ updateScrollButtons();
80
+
81
+ const transformScroll = (event: WheelEvent): void => {
82
+ if (event.deltaY !== 0 && scrollContainer) {
83
+ scrollContainer.scrollLeft += event.deltaY;
84
+ }
85
+ };
86
+
87
+ scrollContainer?.addEventListener('wheel', transformScroll, {
88
+ passive: false, // added for scrolling on Safari
89
+ });
90
+
91
+ // clear all event listeners on unmount
92
+ return () => {
93
+ debouncedUpdateScroll.cancel();
94
+ window.removeEventListener('resize', debouncedUpdateScroll);
95
+
96
+ debouncedUpdateScrollButtons.cancel();
97
+ scrollContainer?.removeEventListener(
98
+ 'scroll',
99
+ debouncedUpdateScrollButtons,
100
+ );
101
+
102
+ scrollContainer?.removeEventListener('wheel', transformScroll);
103
+ };
104
+ }, [showScroll, updateScroll, updateScrollButtons]);
105
+
106
+ return {
107
+ scrollLeft,
108
+ scrollRight,
109
+ scrollRef,
110
+ showScroll,
111
+ enableScrollLeft,
112
+ enableScrollRight,
113
+ };
114
+ };
@@ -0,0 +1 @@
1
+ export { CustomTabList as TabList } from './CustomTabList';
@@ -0,0 +1,10 @@
1
+ @import '../../../styles/common.scss';
2
+
3
+ .tabpanel {
4
+ border-bottom: 1px solid $light-gray;
5
+ display: none;
6
+
7
+ &.selected {
8
+ display: block;
9
+ }
10
+ }
@@ -0,0 +1,26 @@
1
+ import clsx from 'clsx';
2
+ import React from 'react';
3
+ import {
4
+ ReactTabsFunctionComponent,
5
+ TabPanel,
6
+ TabPanelProps,
7
+ } from 'react-tabs';
8
+ import classes from './CustomTabPanel.scss';
9
+
10
+ export const CustomTabPanel: ReactTabsFunctionComponent<TabPanelProps> = ({
11
+ children,
12
+ className,
13
+ selectedClassName,
14
+ ...otherProps
15
+ }) => (
16
+ <TabPanel
17
+ {...otherProps}
18
+ className={clsx(className, classes.tabpanel)}
19
+ selectedClassName={clsx(selectedClassName, classes.selected)}
20
+ data-test-id="tab"
21
+ >
22
+ {children}
23
+ </TabPanel>
24
+ );
25
+
26
+ CustomTabPanel.tabsRole = 'TabPanel';
@@ -0,0 +1 @@
1
+ export { CustomTabPanel as TabPanel } from './CustomTabPanel';