@scality/core-ui 0.126.0 → 0.128.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 (27) hide show
  1. package/dist/components/inputv2/inputv2.d.ts.map +1 -1
  2. package/dist/components/inputv2/inputv2.js +8 -0
  3. package/dist/components/searchinput/SearchInput.component.d.ts +1 -0
  4. package/dist/components/searchinput/SearchInput.component.d.ts.map +1 -1
  5. package/dist/components/searchinput/SearchInput.component.js +2 -2
  6. package/dist/components/tabsv2/StyledTabs.d.ts +1 -0
  7. package/dist/components/tabsv2/StyledTabs.d.ts.map +1 -1
  8. package/dist/components/tabsv2/StyledTabs.js +8 -4
  9. package/dist/components/tabsv2/Tab.d.ts +1 -0
  10. package/dist/components/tabsv2/Tab.d.ts.map +1 -1
  11. package/dist/components/tabsv2/Tabsv2.component.d.ts +3 -3
  12. package/dist/components/tabsv2/Tabsv2.component.d.ts.map +1 -1
  13. package/dist/components/tabsv2/Tabsv2.component.js +6 -7
  14. package/dist/components/tabsv2/useScrollingTabs.d.ts +1 -1
  15. package/dist/components/tabsv2/useScrollingTabs.d.ts.map +1 -1
  16. package/dist/organisms/attachments/AttachmentTable.d.ts.map +1 -1
  17. package/dist/organisms/attachments/AttachmentTable.js +115 -121
  18. package/package.json +1 -1
  19. package/src/lib/components/inputv2/inputv2.tsx +8 -0
  20. package/src/lib/components/searchinput/SearchInput.component.tsx +3 -0
  21. package/src/lib/components/tabsv2/StyledTabs.ts +12 -6
  22. package/src/lib/components/tabsv2/Tab.ts +1 -0
  23. package/src/lib/components/tabsv2/Tabsv2.component.tsx +45 -36
  24. package/src/lib/components/tabsv2/useScrollingTabs.ts +1 -1
  25. package/src/lib/organisms/attachments/AttachmentTable.tsx +203 -211
  26. package/stories/attachment.stories.tsx +5 -1
  27. package/stories/tabsv2.stories.tsx +56 -40
@@ -1,6 +1,10 @@
1
- // @ts-nocheck
2
- import React, { createContext, useEffect, useState, useCallback } from 'react';
3
- import { Element, ChildrenArray } from 'react';
1
+ import React, {
2
+ createContext,
3
+ useEffect,
4
+ useState,
5
+ useCallback,
6
+ ReactElement,
7
+ } from 'react';
4
8
  import {
5
9
  TabBar,
6
10
  ScrollableContainer,
@@ -15,6 +19,7 @@ import {
15
19
  useRouteMatch,
16
20
  matchPath,
17
21
  Route,
22
+ Switch,
18
23
  } from 'react-router-dom';
19
24
  import { SecondaryText, BasicText, EmphaseText } from '../text/Text.component';
20
25
  import { ScrollButton } from './ScrollButton';
@@ -32,7 +37,7 @@ type TabsProps = {
32
37
  tabContentColor?: string;
33
38
  separatorColor?: string;
34
39
  tabHoverColor?: string;
35
- children: ChildrenArray<Element<typeof Tab>>;
40
+ children: ReactElement<TabProps>[];
36
41
  className?: string;
37
42
  };
38
43
  export const TabsContext = createContext<boolean>(false);
@@ -60,9 +65,11 @@ function Tabs({
60
65
  number | null | undefined
61
66
  >(null);
62
67
  const queryURL = new URLSearchParams(location.search);
63
- const filteredTabsChildren = React.Children.toArray(children).filter(
68
+ const filteredTabsChildren: ReactElement<TabProps>[] = React.Children.toArray(
69
+ children,
70
+ ).filter(
64
71
  (child) => React.isValidElement(child) && child.type === Tab,
65
- );
72
+ ) as ReactElement<TabProps>[];
66
73
 
67
74
  const matchQuery = useCallback(
68
75
  (query: Query): boolean => {
@@ -168,19 +175,19 @@ function Tabs({
168
175
  >
169
176
  {icon && <TabIcon label={label}>{icon}</TabIcon>}
170
177
  {isSelected ? (
171
- <BasicText className="sc-tabs-item-title">{label}</BasicText>
178
+ <BasicText>{label}</BasicText>
172
179
  ) : (
173
- <SecondaryText className="sc-tabs-item-title">{label}</SecondaryText>
174
- )}
175
- {textBadge && (
176
- <EmphaseText className="sc-tabs-item-icon">{textBadge}</EmphaseText>
180
+ <SecondaryText>{label}</SecondaryText>
177
181
  )}
182
+ {textBadge && <EmphaseText>{textBadge}</EmphaseText>}
178
183
  </TabItem>
179
184
  );
180
185
  });
181
186
  return (
182
187
  <TabsContext.Provider value={true}>
188
+ {/*@ts-expect-error containerType is not yet a valid prop for react */}
183
189
  <TabsContainer
190
+ style={{ containerType: 'size' }}
184
191
  className={['sc-tabs', className].join(' ')}
185
192
  tabLineColor={tabLineColor}
186
193
  separatorColor={separatorColor}
@@ -212,31 +219,33 @@ function Tabs({
212
219
  />
213
220
  )}
214
221
  </ScrollableContainer>
215
- <TabContent
216
- className="sc-tabs-item-content"
217
- tabContentColor={tabContentColor}
218
- >
219
- {filteredTabsChildren.map((tab: Element<typeof Tab>, index) => (
220
- <Route
221
- exact={tab.props.exact}
222
- sensitive={tab.props.sensitive}
223
- strict={tab.props.strict}
224
- key={index}
225
- path={
226
- tab.props.path.startsWith('/')
227
- ? tab.props.path
228
- : url + '/' + tab.props.path
229
- }
230
- >
231
- {!tab.props.query ||
232
- (tab.props.query && matchQuery(tab.props.query)) ? (
233
- tab.props.children
234
- ) : (
235
- <></>
236
- )}
237
- </Route>
238
- ))}
239
- </TabContent>
222
+
223
+ {filteredTabsChildren.map((tab, index) => (
224
+ <Route
225
+ exact={tab.props.exact}
226
+ sensitive={tab.props.sensitive}
227
+ strict={tab.props.strict}
228
+ path={
229
+ tab.props.path.startsWith('/')
230
+ ? tab.props.path
231
+ : url + '/' + tab.props.path
232
+ }
233
+ key={index}
234
+ >
235
+ {!tab.props.query ||
236
+ (tab.props.query && matchQuery(tab.props.query)) ? (
237
+ <TabContent
238
+ className="sc-tabs-item-content"
239
+ tabContentColor={tabContentColor}
240
+ withoutPadding={tab.props.withoutPadding}
241
+ >
242
+ {tab.props.children}
243
+ </TabContent>
244
+ ) : (
245
+ <></>
246
+ )}
247
+ </Route>
248
+ ))}
240
249
  </TabsContainer>
241
250
  </TabsContext.Provider>
242
251
  );
@@ -283,7 +283,7 @@ const useScrollingTabs = (selectedTabIndex: number | null | undefined) => {
283
283
  scrollSelectedIntoView();
284
284
  }, [scrollSelectedIntoView, selectedTabIndex]);
285
285
 
286
- const handleKeyDown = (event: KeyboardEvent) => {
286
+ const handleKeyDown = (event: React.KeyboardEvent<HTMLDivElement>) => {
287
287
  const list = tabsListRef.current;
288
288
  const ownerDocument = (list && list.ownerDocument) || document;
289
289
  const currentFocus = ownerDocument.activeElement;
@@ -77,10 +77,7 @@ const MenuContainer = styled.ul<{
77
77
  position: absolute;
78
78
  width: ${(props) => props.width};
79
79
  z-index: 1;
80
- margin-top: -1.7rem;
81
- margin-left: 0;
82
- margin-bottom: 0;
83
- margin-right: 0;
80
+ margin: 0;
84
81
  ${(props) =>
85
82
  props.isOpen
86
83
  ? `
@@ -105,17 +102,18 @@ const MenuContainer = styled.ul<{
105
102
  `;
106
103
 
107
104
  const SearchBoxContainer = styled.div`
105
+ position: relative;
108
106
  padding: ${spacing.r16};
109
107
  `;
110
108
 
111
- const StyledSearchInput = styled(SearchInput)`
109
+ const StyledSearchInput = styled(SearchInput)<{ searchInputIsFocused }>`
112
110
  flex-grow: 1;
113
- .sc-input-type:focus {
114
- border-bottom: 0;
115
- border-top-left-radius: 4px;
116
- border-top-right-radius: 4px;
117
- border-bottom-right-radius: 0;
111
+
112
+ & > div:focus-within {
113
+ border-color: ${(props) => props.theme.selectedActive};
118
114
  border-bottom-left-radius: 0;
115
+ border-bottom-right-radius: 0;
116
+ border-bottom: 0;
119
117
  }
120
118
  `;
121
119
 
@@ -123,11 +121,6 @@ const AttachmentTableContainer = styled.div`
123
121
  height: 100%;
124
122
  `;
125
123
 
126
- const StyledTable = styled.div`
127
- background: ${(props) => props.theme.backgroundLevel4};
128
- height: 100%;
129
- `;
130
-
131
124
  const CenterredSecondaryText = styled(SecondaryText)`
132
125
  display: block;
133
126
  text-align: center;
@@ -471,7 +464,101 @@ export const AttachmentTable = <ENTITY_TYPE,>({
471
464
  const [searchInputIsFocused, setSearchInputIsFocused] = useState(false);
472
465
 
473
466
  return (
474
- <AttachmentTableContainer>
467
+ <Table
468
+ columns={[
469
+ {
470
+ Header: 'Name',
471
+ accessor: 'name',
472
+ cellStyle: {
473
+ flex: 1.5,
474
+ marginRight: '1.5rem',
475
+ },
476
+ Cell: ({
477
+ value,
478
+ row: { original: entity },
479
+ }: {
480
+ value: string;
481
+ row: { original: AttachableEntity<ENTITY_TYPE> };
482
+ }) => {
483
+ const { data: asyncName, status } = useQuery({
484
+ ...(getNameQuery
485
+ ? getNameQuery(entity)
486
+ : { queryKey: ['fakeQuery'], queryFn: () => value }),
487
+ enabled: !value,
488
+ });
489
+
490
+ if (value) {
491
+ return <ConstrainedText text={value} lineClamp={2} />;
492
+ }
493
+ if (status === 'error') {
494
+ return (
495
+ <>An error occured while loading {entityName.singular} name</>
496
+ );
497
+ }
498
+ if (status === 'loading' || status === 'idle') {
499
+ return <>Loading...</>;
500
+ }
501
+ if (status === 'success') {
502
+ if (!asyncName) {
503
+ return <EmptyCell />;
504
+ }
505
+ return <ConstrainedText text={asyncName} lineClamp={2} />;
506
+ }
507
+
508
+ return <EmptyCell />;
509
+ },
510
+ },
511
+ {
512
+ Header: 'Attachment',
513
+ accessor: 'isPending',
514
+ cellStyle: {
515
+ flex: 0.5,
516
+ },
517
+ Cell: ({ value }: { value?: boolean }) => {
518
+ return value ? <>Pending</> : <>Attached</>;
519
+ },
520
+ },
521
+ {
522
+ Header: <Box flex={0.5} />,
523
+ accessor: 'action',
524
+ cellStyle: {
525
+ textAlign: 'right',
526
+ flex: 0.5,
527
+ marginLeft: 'auto',
528
+ marginRight: '0.5rem',
529
+ },
530
+ Cell: ({
531
+ row: { original: entity },
532
+ }: {
533
+ row: { original: AttachableEntity<ENTITY_TYPE> };
534
+ }) => (
535
+ <Button
536
+ size="inline"
537
+ onClick={() => {
538
+ dispatch({
539
+ action: AttachmentAction.REMOVE,
540
+ entity: {
541
+ name: entity.name,
542
+ id: entity.id,
543
+ type: entity.type,
544
+ },
545
+ });
546
+ }}
547
+ icon={<Icon name="Close" />}
548
+ label="Remove"
549
+ variant="danger"
550
+ disabled={!!entity.disableDetach}
551
+ />
552
+ ),
553
+ },
554
+ ]}
555
+ data={desiredAttachedEntities.map((entity) => ({
556
+ ...entity,
557
+ isPending: entity.isPending || false,
558
+ action: null,
559
+ }))}
560
+ defaultSortingKey="name"
561
+ >
475
562
  <SearchBoxContainer
476
563
  {...{
477
564
  ref: (element) => {
@@ -493,6 +580,7 @@ export const AttachmentTable = <ENTITY_TYPE,>({
493
580
  >
494
581
  <Stack>
495
582
  <StyledSearchInput
583
+ autoComplete="off"
496
584
  placeholder={searchEntityPlaceholder}
497
585
  {...getInputProps({
498
586
  ref: (element) => {
@@ -514,6 +602,7 @@ export const AttachmentTable = <ENTITY_TYPE,>({
514
602
  </Tooltip>
515
603
  ) : (
516
604
  <StyledSearchInput
605
+ autoComplete="off"
517
606
  placeholder={searchEntityPlaceholder}
518
607
  {...getInputProps({
519
608
  ref: (element) => {
@@ -528,210 +617,113 @@ export const AttachmentTable = <ENTITY_TYPE,>({
528
617
  setSearchInputIsFocused(false);
529
618
  }}
530
619
  disableToggle
620
+ searchInputIsFocused={searchInputIsFocused}
531
621
  />
532
622
  )}
533
- </SearchBoxContainer>
534
- <MenuContainer
535
- {...getMenuProps()}
536
- width={searchWidth}
537
- isOpen={isOpen}
538
- searchInputIsFocused={searchInputIsFocused}
539
- >
540
- {isOpen &&
541
- filteredEntities.status === 'success' &&
542
- filteredEntities.data?.entities.map((item, index) => (
543
- <li key={`${item.id}${index}`} {...getItemProps({ item, index })}>
544
- <Text>{item.name}</Text>
545
- </li>
546
- ))}
547
- {isOpen && filteredEntities.status === 'loading' && (
548
- <li>
549
- <Text>Searching...</Text>
550
- </li>
551
- )}
552
- {isOpen && filteredEntities.status === 'error' && (
553
- <li>
554
- <Text color="statusCritical">An error occured while searching</Text>
555
- </li>
556
- )}
557
- {isOpen &&
558
- filteredEntities.status === 'success' &&
559
- (filteredEntities.data?.number || 0) >
560
- filteredEntities.data?.entities.length && (
623
+ <MenuContainer
624
+ {...getMenuProps()}
625
+ width={searchWidth}
626
+ isOpen={isOpen}
627
+ searchInputIsFocused={searchInputIsFocused}
628
+ >
629
+ {isOpen &&
630
+ filteredEntities.status === 'success' &&
631
+ filteredEntities.data?.entities.map((item, index) => (
632
+ <li key={`${item.id}${index}`} {...getItemProps({ item, index })}>
633
+ <Text>{item.name}</Text>
634
+ </li>
635
+ ))}
636
+ {isOpen && filteredEntities.status === 'loading' && (
561
637
  <li>
562
- <Text
563
- isGentleEmphazed={true}
564
- color="textSecondary"
565
- style={{ textAlign: 'right' }}
566
- >
567
- There{' '}
568
- {(filteredEntities.data?.number || 0) -
569
- filteredEntities.data?.entities.length ===
570
- 1
571
- ? 'is'
572
- : 'are'}{' '}
573
- {(filteredEntities.data?.number || 0) -
574
- filteredEntities.data?.entities.length}{' '}
575
- more{' '}
576
- {(filteredEntities.data?.number || 0) -
577
- filteredEntities.data?.entities.length ===
578
- 1
579
- ? entityName.singular
580
- : entityName.plural}{' '}
581
- matching your search. Suggestion: try more specific search
582
- expression.
583
- </Text>
638
+ <Text>Searching...</Text>
584
639
  </li>
585
640
  )}
586
- {isOpen &&
587
- filteredEntities.status === 'success' &&
588
- filteredEntities.data?.entities.length === 0 && (
641
+ {isOpen && filteredEntities.status === 'error' && (
589
642
  <li>
590
- <Text isGentleEmphazed={true} color="textSecondary">
591
- No {entityName.plural} found matching your search.
643
+ <Text color="statusCritical">
644
+ An error occured while searching
592
645
  </Text>
593
646
  </li>
594
647
  )}
595
- </MenuContainer>
596
- <StyledTable>
597
- <Table
598
- columns={[
599
- {
600
- Header: 'Name',
601
- accessor: 'name',
602
- cellStyle: {
603
- flex: 1.5,
604
- marginRight: '1.5rem',
605
- },
606
- Cell: ({
607
- value,
608
- row: { original: entity },
609
- }: {
610
- value: string;
611
- row: { original: AttachableEntity<ENTITY_TYPE> };
612
- }) => {
613
- const { data: asyncName, status } = useQuery({
614
- ...(getNameQuery
615
- ? getNameQuery(entity)
616
- : { queryKey: ['fakeQuery'], queryFn: () => value }),
617
- enabled: !value,
618
- });
619
-
620
- if (value) {
621
- return <ConstrainedText text={value} lineClamp={2} />;
622
- }
623
- if (status === 'error') {
624
- return (
625
- <>
626
- An error occured while loading {entityName.singular} name
627
- </>
628
- );
629
- }
630
- if (status === 'loading' || status === 'idle') {
631
- return <>Loading...</>;
632
- }
633
- if (status === 'success') {
634
- if (!asyncName) {
635
- return <EmptyCell />;
636
- }
637
- return <ConstrainedText text={asyncName} lineClamp={2} />;
638
- }
639
-
640
- return <EmptyCell />;
641
- },
642
- },
643
- {
644
- Header: 'Attachment',
645
- accessor: 'isPending',
646
- cellStyle: {
647
- flex: 0.5,
648
- },
649
- Cell: ({ value }: { value?: boolean }) => {
650
- return value ? <>Pending</> : <>Attached</>;
651
- },
652
- },
653
- {
654
- Header: <Box flex={0.5} />,
655
- accessor: 'action',
656
- cellStyle: {
657
- textAlign: 'right',
658
- flex: 0.5,
659
- marginLeft: 'auto',
660
- marginRight: '0.5rem',
661
- },
662
- Cell: ({
663
- row: { original: entity },
664
- }: {
665
- row: { original: AttachableEntity<ENTITY_TYPE> };
666
- }) => (
667
- <Button
668
- size="inline"
669
- onClick={() => {
670
- dispatch({
671
- action: AttachmentAction.REMOVE,
672
- entity: {
673
- name: entity.name,
674
- id: entity.id,
675
- type: entity.type,
676
- },
677
- });
678
- }}
679
- icon={<Icon name="Close" />}
680
- label="Remove"
681
- variant="danger"
682
- disabled={!!entity.disableDetach}
683
- />
684
- ),
685
- },
686
- ]}
687
- data={desiredAttachedEntities.map((entity) => ({
688
- ...entity,
689
- isPending: entity.isPending || false,
690
- action: null,
691
- }))}
692
- defaultSortingKey="name"
693
- >
694
- <Table.SingleSelectableContent
695
- rowHeight={rowHeight}
696
- separationLineVariant="backgroundLevel2"
697
- >
698
- {(rows) => (
699
- <>
700
- {initiallyAttachedEntitiesStatus === 'idle' ||
701
- initiallyAttachedEntitiesStatus === 'loading' ? (
702
- <Wrap style={{ height: `${tableRowHeight[rowHeight]}rem` }}>
703
- <p></p>
704
- <Stack>
705
- <Loader />
706
- <Text>Loading {entityName.plural}...</Text>
707
- </Stack>
708
- <p></p>
709
- </Wrap>
710
- ) : initiallyAttachedEntitiesStatus === 'error' ? (
711
- <Stack
712
- style={{
713
- justifyContent: 'center',
714
- height: `${tableRowHeight[rowHeight]}rem`,
715
- }}
716
- >
717
- <Icon name="Exclamation-circle" color="statusWarning" />
718
- <Text color="textSecondary">
719
- Failed to load attached {entityName.plural}.
720
- </Text>
721
- </Stack>
722
- ) : (
723
- desiredAttachedEntities.length === 0 && (
724
- <CenterredSecondaryText>
725
- No {entityName.plural} attached
726
- </CenterredSecondaryText>
727
- )
728
- )}
729
- {desiredAttachedEntities.length > 0 && rows}
730
- </>
648
+ {isOpen &&
649
+ filteredEntities.status === 'success' &&
650
+ (filteredEntities.data?.number || 0) >
651
+ filteredEntities.data?.entities.length && (
652
+ <li>
653
+ <Text
654
+ isGentleEmphazed={true}
655
+ color="textSecondary"
656
+ style={{ textAlign: 'right' }}
657
+ >
658
+ There{' '}
659
+ {(filteredEntities.data?.number || 0) -
660
+ filteredEntities.data?.entities.length ===
661
+ 1
662
+ ? 'is'
663
+ : 'are'}{' '}
664
+ {(filteredEntities.data?.number || 0) -
665
+ filteredEntities.data?.entities.length}{' '}
666
+ more{' '}
667
+ {(filteredEntities.data?.number || 0) -
668
+ filteredEntities.data?.entities.length ===
669
+ 1
670
+ ? entityName.singular
671
+ : entityName.plural}{' '}
672
+ matching your search. Suggestion: try more specific search
673
+ expression.
674
+ </Text>
675
+ </li>
676
+ )}
677
+ {isOpen &&
678
+ filteredEntities.status === 'success' &&
679
+ filteredEntities.data?.entities.length === 0 && (
680
+ <li>
681
+ <Text isGentleEmphazed={true} color="textSecondary">
682
+ No {entityName.plural} found matching your search.
683
+ </Text>
684
+ </li>
731
685
  )}
732
- </Table.SingleSelectableContent>
733
- </Table>
734
- </StyledTable>
735
- </AttachmentTableContainer>
686
+ </MenuContainer>
687
+ </SearchBoxContainer>
688
+ <Table.SingleSelectableContent
689
+ rowHeight={rowHeight}
690
+ separationLineVariant="backgroundLevel2"
691
+ >
692
+ {(rows) => (
693
+ <>
694
+ {initiallyAttachedEntitiesStatus === 'idle' ||
695
+ initiallyAttachedEntitiesStatus === 'loading' ? (
696
+ <Wrap style={{ height: `${tableRowHeight[rowHeight]}rem` }}>
697
+ <p></p>
698
+ <Stack>
699
+ <Loader />
700
+ <Text>Loading {entityName.plural}...</Text>
701
+ </Stack>
702
+ <p></p>
703
+ </Wrap>
704
+ ) : initiallyAttachedEntitiesStatus === 'error' ? (
705
+ <Stack
706
+ style={{
707
+ justifyContent: 'center',
708
+ height: `${tableRowHeight[rowHeight]}rem`,
709
+ }}
710
+ >
711
+ <Icon name="Exclamation-circle" color="statusWarning" />
712
+ <Text color="textSecondary">
713
+ Failed to load attached {entityName.plural}.
714
+ </Text>
715
+ </Stack>
716
+ ) : (
717
+ desiredAttachedEntities.length === 0 && (
718
+ <CenterredSecondaryText>
719
+ No {entityName.plural} attached
720
+ </CenterredSecondaryText>
721
+ )
722
+ )}
723
+ {desiredAttachedEntities.length > 0 && rows}
724
+ </>
725
+ )}
726
+ </Table.SingleSelectableContent>
727
+ </Table>
736
728
  );
737
729
  };
@@ -28,7 +28,11 @@ export const Playground = {
28
28
  status: 'success',
29
29
  data: {
30
30
  number: 1,
31
- entities: [{ name: 'User A', id: 'test', type: 'USER' }],
31
+ entities: [
32
+ { name: 'User A', id: 'test', type: 'USER' },
33
+ { name: 'User B', id: 'test', type: 'USER' },
34
+ { name: 'User C', id: 'test', type: 'USER' },
35
+ ],
32
36
  },
33
37
  }}
34
38
  initialAttachmentOperations={[]}