@superdispatch/ui-lab 0.50.5 → 0.50.6

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 (122) hide show
  1. package/LICENSE +21 -0
  2. package/package.json +14 -39
  3. package/.babelrc.js +0 -5
  4. package/pkg/README.md +0 -10
  5. package/pkg/package.json +0 -41
  6. package/playroom.ts +0 -24
  7. package/src/alert/Alert.stories.tsx +0 -162
  8. package/src/alert/Alert.tsx +0 -135
  9. package/src/banner/Banner.stories.tsx +0 -64
  10. package/src/banner/Banner.tsx +0 -120
  11. package/src/box/Box.stories.tsx +0 -20
  12. package/src/box/Box.tsx +0 -257
  13. package/src/button/Button.stories.tsx +0 -739
  14. package/src/button/Button.tsx +0 -498
  15. package/src/button-area/ButtonArea.stories.tsx +0 -65
  16. package/src/button-area/ButtonArea.tsx +0 -90
  17. package/src/chat/Chat.stories.tsx +0 -130
  18. package/src/chat/Chat.tsx +0 -57
  19. package/src/chat/ChatMessage.tsx +0 -45
  20. package/src/chat/README.MD +0 -7
  21. package/src/chat/__tests__/Chat.spec.tsx +0 -29
  22. package/src/chat/__tests__/ChatMessage.spec.tsx +0 -39
  23. package/src/container/Container.tsx +0 -48
  24. package/src/description-item/DescriptionItem.stories.tsx +0 -163
  25. package/src/description-item/DescriptionItem.tsx +0 -104
  26. package/src/description-line-item/DescriptionLineItem.stories.tsx +0 -23
  27. package/src/description-line-item/DescriptionLineItem.tsx +0 -29
  28. package/src/email-autocomplate/CloseIcon.tsx +0 -20
  29. package/src/email-autocomplate/EmailAutocomplete.stories.tsx +0 -47
  30. package/src/email-autocomplate/EmailAutocomplete.tsx +0 -138
  31. package/src/file-drop-zone/FileDropZone.stories.tsx +0 -44
  32. package/src/file-drop-zone/FileDropZone.tsx +0 -170
  33. package/src/file-list-item/FileListItem.stories.tsx +0 -37
  34. package/src/file-list-item/FileListItem.tsx +0 -148
  35. package/src/file-list-item/__tests__/FileListItem.spec.tsx +0 -339
  36. package/src/flag-list/FlagList.stories.tsx +0 -72
  37. package/src/flag-list/FlagList.tsx +0 -54
  38. package/src/flag-list/FlagListItem.tsx +0 -126
  39. package/src/index.spec.ts +0 -53
  40. package/src/index.ts +0 -36
  41. package/src/linked-text/LinkeText.stories.tsx +0 -42
  42. package/src/linked-text/LinkedText.tsx +0 -47
  43. package/src/multiline-text/MultilineText.stories.tsx +0 -30
  44. package/src/multiline-text/MultilineText.ts +0 -16
  45. package/src/navbar/Navbar.stories.tsx +0 -137
  46. package/src/navbar/Navbar.tsx +0 -132
  47. package/src/navbar/NavbarAccordion.tsx +0 -195
  48. package/src/navbar/NavbarAvatar.tsx +0 -51
  49. package/src/navbar/NavbarBottomBar.tsx +0 -135
  50. package/src/navbar/NavbarContext.tsx +0 -22
  51. package/src/navbar/NavbarItem.tsx +0 -125
  52. package/src/navbar/NavbarList.tsx +0 -247
  53. package/src/navbar/NavbarMenu.tsx +0 -102
  54. package/src/passwordStepper/PasswordStrength.stories.tsx +0 -95
  55. package/src/passwordStepper/PasswordStrength.tsx +0 -107
  56. package/src/passwordStepper/PasswordUtils.tsx +0 -42
  57. package/src/passwordStepper/PasswordValidationComponents.tsx +0 -95
  58. package/src/sidebar/Sidebar.stories.tsx +0 -376
  59. package/src/sidebar/Sidebar.tsx +0 -75
  60. package/src/sidebar/SidebarBackButton.tsx +0 -33
  61. package/src/sidebar/SidebarContainer.tsx +0 -114
  62. package/src/sidebar/SidebarContent.tsx +0 -119
  63. package/src/sidebar/SidebarDivider.tsx +0 -15
  64. package/src/sidebar/SidebarMenuItem.tsx +0 -196
  65. package/src/sidebar/SidebarMenuItemAction.tsx +0 -27
  66. package/src/sidebar/SidebarMenuItemAvatar.tsx +0 -59
  67. package/src/sidebar/SidebarMenuItemContext.tsx +0 -33
  68. package/src/sidebar/SidebarSubheader.tsx +0 -38
  69. package/src/styled.d.ts +0 -12
  70. package/src/text-box/TextBox.stories.tsx +0 -114
  71. package/src/text-box/TextBox.tsx +0 -238
  72. package/src/utils/RuleNormalizer.ts +0 -24
  73. package/src/utils/mergeStyles.ts +0 -28
  74. package/tsconfig.json +0 -19
  75. /package/{pkg/dist-node → dist-node}/index.js +0 -0
  76. /package/{pkg/dist-node → dist-node}/index.js.map +0 -0
  77. /package/{pkg/dist-src → dist-src}/alert/Alert.js +0 -0
  78. /package/{pkg/dist-src → dist-src}/banner/Banner.js +0 -0
  79. /package/{pkg/dist-src → dist-src}/box/Box.js +0 -0
  80. /package/{pkg/dist-src → dist-src}/button/Button.js +0 -0
  81. /package/{pkg/dist-src → dist-src}/button-area/ButtonArea.js +0 -0
  82. /package/{pkg/dist-src → dist-src}/chat/Chat.js +0 -0
  83. /package/{pkg/dist-src → dist-src}/chat/ChatMessage.js +0 -0
  84. /package/{pkg/dist-src → dist-src}/container/Container.js +0 -0
  85. /package/{pkg/dist-src → dist-src}/description-item/DescriptionItem.js +0 -0
  86. /package/{pkg/dist-src → dist-src}/description-line-item/DescriptionLineItem.js +0 -0
  87. /package/{pkg/dist-src → dist-src}/email-autocomplate/CloseIcon.js +0 -0
  88. /package/{pkg/dist-src → dist-src}/email-autocomplate/EmailAutocomplete.js +0 -0
  89. /package/{pkg/dist-src → dist-src}/file-drop-zone/FileDropZone.js +0 -0
  90. /package/{pkg/dist-src → dist-src}/file-list-item/FileListItem.js +0 -0
  91. /package/{pkg/dist-src → dist-src}/flag-list/FlagList.js +0 -0
  92. /package/{pkg/dist-src → dist-src}/flag-list/FlagListItem.js +0 -0
  93. /package/{pkg/dist-src → dist-src}/index.js +0 -0
  94. /package/{pkg/dist-src → dist-src}/linked-text/LinkedText.js +0 -0
  95. /package/{pkg/dist-src → dist-src}/multiline-text/MultilineText.js +0 -0
  96. /package/{pkg/dist-src → dist-src}/navbar/Navbar.js +0 -0
  97. /package/{pkg/dist-src → dist-src}/navbar/NavbarAccordion.js +0 -0
  98. /package/{pkg/dist-src → dist-src}/navbar/NavbarAvatar.js +0 -0
  99. /package/{pkg/dist-src → dist-src}/navbar/NavbarBottomBar.js +0 -0
  100. /package/{pkg/dist-src → dist-src}/navbar/NavbarContext.js +0 -0
  101. /package/{pkg/dist-src → dist-src}/navbar/NavbarItem.js +0 -0
  102. /package/{pkg/dist-src → dist-src}/navbar/NavbarList.js +0 -0
  103. /package/{pkg/dist-src → dist-src}/navbar/NavbarMenu.js +0 -0
  104. /package/{pkg/dist-src → dist-src}/passwordStepper/PasswordStrength.js +0 -0
  105. /package/{pkg/dist-src → dist-src}/passwordStepper/PasswordUtils.js +0 -0
  106. /package/{pkg/dist-src → dist-src}/passwordStepper/PasswordValidationComponents.js +0 -0
  107. /package/{pkg/dist-src → dist-src}/sidebar/Sidebar.js +0 -0
  108. /package/{pkg/dist-src → dist-src}/sidebar/SidebarBackButton.js +0 -0
  109. /package/{pkg/dist-src → dist-src}/sidebar/SidebarContainer.js +0 -0
  110. /package/{pkg/dist-src → dist-src}/sidebar/SidebarContent.js +0 -0
  111. /package/{pkg/dist-src → dist-src}/sidebar/SidebarDivider.js +0 -0
  112. /package/{pkg/dist-src → dist-src}/sidebar/SidebarMenuItem.js +0 -0
  113. /package/{pkg/dist-src → dist-src}/sidebar/SidebarMenuItemAction.js +0 -0
  114. /package/{pkg/dist-src → dist-src}/sidebar/SidebarMenuItemAvatar.js +0 -0
  115. /package/{pkg/dist-src → dist-src}/sidebar/SidebarMenuItemContext.js +0 -0
  116. /package/{pkg/dist-src → dist-src}/sidebar/SidebarSubheader.js +0 -0
  117. /package/{pkg/dist-src → dist-src}/text-box/TextBox.js +0 -0
  118. /package/{pkg/dist-src → dist-src}/utils/RuleNormalizer.js +0 -0
  119. /package/{pkg/dist-src → dist-src}/utils/mergeStyles.js +0 -0
  120. /package/{pkg/dist-types → dist-types}/index.d.ts +0 -0
  121. /package/{pkg/dist-web → dist-web}/index.js +0 -0
  122. /package/{pkg/dist-web → dist-web}/index.js.map +0 -0
@@ -1,20 +0,0 @@
1
- import { SvgIcon, SvgIconProps } from '@material-ui/core';
2
- import { Color } from '@superdispatch/ui';
3
- import { ReactElement } from 'react';
4
-
5
- export function CloseIcon(props: SvgIconProps): ReactElement {
6
- return (
7
- <SvgIcon
8
- {...props}
9
- viewBox="0 0 6 6"
10
- style={{ width: '6px', height: '6px' }}
11
- >
12
- <path
13
- fillRule="evenodd"
14
- clipRule="evenodd"
15
- d="M3.72278 2.99999L5.91671 0.806074L5.19396 0.0833282L3.00004 2.27725L0.80612 0.0833282L0.083374 0.806076L2.27729 2.99999L0.083374 5.19391L0.80612 5.91666L3.00004 3.72274L5.19396 5.91666L5.91671 5.19392L3.72278 2.99999Z"
16
- fill={Color.Dark100}
17
- />
18
- </SvgIcon>
19
- );
20
- }
@@ -1,47 +0,0 @@
1
- import { Meta } from '@storybook/react';
2
- import { UseState } from '@superdispatch/ui-docs';
3
- import { Box } from '@superdispatch/ui-lab';
4
- import { EmailAutocomplete } from './EmailAutocomplete';
5
-
6
- export default {
7
- title: 'Inputs/EmailAutocomplete',
8
- component: EmailAutocomplete,
9
- decorators: [
10
- (Story) => (
11
- <Box maxWidth="400px">
12
- <Story />
13
- </Box>
14
- ),
15
- ],
16
- } as Meta;
17
-
18
- const options = [
19
- 'test@example.com',
20
- 'user@example.com',
21
- 'admin@example.com',
22
- 'test@mail.com,test2@mail.com',
23
- ];
24
-
25
- export const basic = () => (
26
- <UseState initialState={[]}>
27
- {(state, setState) => (
28
- <EmailAutocomplete options={options} value={state} onChange={setState} />
29
- )}
30
- </UseState>
31
- );
32
-
33
- export const textarea = () => (
34
- <UseState initialState={[]}>
35
- {(state, setState) => (
36
- <EmailAutocomplete
37
- options={options}
38
- value={state}
39
- onChange={setState}
40
- TextFieldProps={{
41
- multiline: true,
42
- placeholder: 'Enter email addresses',
43
- }}
44
- />
45
- )}
46
- </UseState>
47
- );
@@ -1,138 +0,0 @@
1
- import {
2
- IconButton,
3
- TextField,
4
- TextFieldProps as TextFieldPropsType,
5
- } from '@material-ui/core';
6
- import {
7
- Autocomplete,
8
- AutocompleteChangeReason,
9
- AutocompleteProps,
10
- } from '@material-ui/lab';
11
- import { Inline, Tag } from '@superdispatch/ui';
12
- import { forwardRef } from 'react';
13
- import styled from 'styled-components';
14
- import { TextBox } from '../text-box/TextBox';
15
- import { CloseIcon } from './CloseIcon';
16
-
17
- const MultipleFieldText = styled(TextField)`
18
- /* Inline input next to tags rather than next line on focus */
19
- & {
20
- width: 100%;
21
- margin-top: 8px;
22
- overflow: hidden;
23
- }
24
-
25
- && .MuiAutocomplete-inputRoot {
26
- flex-direction: column;
27
- align-items: flex-start;
28
- width: 100%;
29
- gap: 8px;
30
-
31
- & .MuiAutocomplete-input {
32
- width: 100%;
33
- }
34
- }
35
- `;
36
-
37
- export interface EmailAutocompleteProps
38
- extends Omit<
39
- AutocompleteProps<string, true, true, true>,
40
- 'onChange' | 'renderInput' | 'renderTags'
41
- > {
42
- options: string[];
43
- TextFieldProps?: Omit<TextFieldPropsType, 'onChange' | 'onBlur'>;
44
-
45
- onAdd?: (value: string) => void;
46
- onRemove?: (value: string) => void;
47
- onChange?: (
48
- value: string[] | undefined,
49
- reason: AutocompleteChangeReason,
50
- ) => void;
51
- }
52
-
53
- export const EmailAutocomplete = forwardRef<
54
- HTMLDivElement,
55
- Omit<EmailAutocompleteProps, 'ref'>
56
- >(({ value, onChange, TextFieldProps, ...props }, ref) => {
57
- function handleDelete(index: number): void {
58
- const filteredOrders = value?.filter(
59
- (_item, fieldIndex) => fieldIndex !== index,
60
- );
61
- void onChange?.(filteredOrders, 'remove-option');
62
- }
63
-
64
- return (
65
- <Autocomplete
66
- {...props}
67
- ref={ref}
68
- multiple={true}
69
- freeSolo={true}
70
- value={value}
71
- disableClearable={true}
72
- filterSelectedOptions={true}
73
- filterOptions={(filterOptions) => {
74
- return filterOptions.filter((option) => option !== '');
75
- }}
76
- onChange={(_event, selectedValue, reason) => {
77
- const emails = selectedValue
78
- .flatMap((item) => item.split(','))
79
- .map((item) => item.trim());
80
-
81
- onChange?.(emails, reason);
82
- }}
83
- renderTags={(items) => {
84
- return (
85
- <Inline space="xxsmall">
86
- {items.map((option, index) => {
87
- return (
88
- <Tag key={index} color="grey" variant="subtle">
89
- <Inline space="xxsmall">
90
- <TextBox wrapOverflow={true} color="primary">
91
- {option}
92
- </TextBox>
93
-
94
- <IconButton
95
- size="small"
96
- onClick={() => {
97
- handleDelete(index);
98
- }}
99
- >
100
- <CloseIcon />
101
- </IconButton>
102
- </Inline>
103
- </Tag>
104
- );
105
- })}
106
- </Inline>
107
- );
108
- }}
109
- renderInput={(params) => (
110
- <MultipleFieldText
111
- {...params}
112
- {...TextFieldProps}
113
- InputProps={{
114
- ...TextFieldProps?.InputProps,
115
- ...params.InputProps,
116
- startAdornment: params.InputProps.startAdornment,
117
- }}
118
- onChange={(event) => {
119
- const text = event.target.value.replace(/,/g, '');
120
- const hasCommaOrSpace = /,|\s/.test(event.target.value);
121
-
122
- if (hasCommaOrSpace && text.trim() !== '') {
123
- onChange?.([...(value || []), text.trim()], 'select-option');
124
- }
125
- }}
126
- onBlur={(event) => {
127
- const text = event.target.value;
128
- if (text.trim() !== '') {
129
- onChange?.([...(value || []), text.trim()], 'select-option');
130
- }
131
- }}
132
- />
133
- )}
134
- />
135
- );
136
- });
137
-
138
- EmailAutocomplete.displayName = 'EmailAutocomplete';
@@ -1,44 +0,0 @@
1
- import { Meta } from '@storybook/react';
2
- import { FileDropZone, toBytes } from './FileDropZone';
3
-
4
- export default { title: 'Lab/FileDropZone', component: FileDropZone } as Meta;
5
-
6
- export const basic = () => (
7
- <FileDropZone
8
- onDropAccepted={(files) => {
9
- alert(`Accepted files: ${files.map((file) => file.name).join(', ')}`);
10
- }}
11
- />
12
- );
13
-
14
- export const accept = () => (
15
- <FileDropZone
16
- accept={['.jpeg', '.jpg', '.png', '.gif']}
17
- hintText="or Drag & Drop .jpeg .jpg .png .gif files"
18
- onDropAccepted={(files) => {
19
- alert(`Accepted files: ${files.map((file) => file.name).join(', ')}`);
20
- }}
21
- />
22
- );
23
-
24
- export const maxSize = () => (
25
- <FileDropZone
26
- maxSize={toBytes(20, 'mb')}
27
- hintText="or Drag & Drop files less than 20 MB"
28
- onDropAccepted={(files) => {
29
- alert(`Accepted files: ${files.map((file) => file.name).join(', ')}`);
30
- }}
31
- />
32
- );
33
-
34
- export const maxFiles = () => (
35
- <FileDropZone
36
- maxFiles={1}
37
- hintText="or Drag & Drop file"
38
- onDropAccepted={(files) => {
39
- alert(`Accepted files: ${files.map((file) => file.name).join(', ')}`);
40
- }}
41
- >
42
- Upload attachment
43
- </FileDropZone>
44
- );
@@ -1,170 +0,0 @@
1
- import { CircularProgress, SvgIcon } from '@material-ui/core';
2
- import { Error } from '@material-ui/icons';
3
- import { mdiUpload } from '@mdi/js';
4
- import { CardButton, ColorDynamic, Column, Columns } from '@superdispatch/ui';
5
- import { forwardRef, ReactElement, ReactNode, Suspense } from 'react';
6
- import Dropzone, { FileRejection } from 'react-dropzone';
7
- import styled from 'styled-components';
8
-
9
- export function toBytes(input: number, unit: 'kb' | 'mb' | 'gb'): number {
10
- if (unit === 'gb') return input * (1 << 30);
11
- if (unit === 'mb') return input * (1 << 20);
12
- return input * (1 << 10);
13
- }
14
-
15
- const KILOBYTE = 1024;
16
- const BYTE_UNITS = [
17
- 'B',
18
- 'KB',
19
- 'MB',
20
- 'GB',
21
- 'TB',
22
- 'PB',
23
- 'EB',
24
- 'ZB',
25
- 'YB',
26
- ] as const;
27
- export function formatBytes(bytes: number): string {
28
- if (bytes === 0) return '0 Bytes';
29
- const unitIndex = Math.floor(Math.log(bytes) / Math.log(KILOBYTE));
30
- const unit = BYTE_UNITS[unitIndex] as string;
31
- return `${(bytes / Math.pow(KILOBYTE, unitIndex)).toFixed(2)} ${unit}`;
32
- }
33
-
34
- const StyledCardButton = styled(CardButton)<{
35
- status?: 'idle' | 'active' | 'error';
36
- }>(({ status }) => ({
37
- backgroundColor: status === 'active' ? ColorDynamic.Blue50 : undefined,
38
- }));
39
-
40
- interface UploadRejectionProps {
41
- maxSize?: number;
42
- maxFiles?: number;
43
- accept?: string | string[];
44
- rejection: FileRejection;
45
- }
46
-
47
- function UploadRejection({
48
- maxSize,
49
- rejection,
50
- }: UploadRejectionProps): null | ReactElement {
51
- const [error] = rejection.errors;
52
- if (!error) return null;
53
-
54
- return (
55
- <Columns align="center">
56
- <Column width="content">
57
- <Error />
58
- </Column>
59
-
60
- <Column>
61
- {error.code === 'file-too-large'
62
- ? maxSize == null
63
- ? 'Attachment size is too large'
64
- : `Attachment size should be less than ${formatBytes(maxSize)}`
65
- : error.message}
66
- </Column>
67
- </Columns>
68
- );
69
- }
70
-
71
- export interface FileDropZoneProps {
72
- children?: ReactNode;
73
- startIcon?: ReactNode;
74
- hintText?: ReactNode;
75
- fallback?: ReactNode;
76
- disabled?: boolean;
77
-
78
- maxSize?: number;
79
- maxFiles?: number;
80
- accept?: string | string[];
81
- onDropAccepted?: (files: File[]) => void;
82
- onDropRejected?: (fileRejections: FileRejection[]) => void;
83
- }
84
-
85
- export const FileDropZone = forwardRef<HTMLButtonElement, FileDropZoneProps>(
86
- (props, ref) => {
87
- const {
88
- // CardButton
89
- disabled = false,
90
- children = 'Upload Attachments',
91
- hintText = 'or Drag & Drop files',
92
- startIcon = (
93
- <SvgIcon>
94
- <path d={mdiUpload} />
95
- </SvgIcon>
96
- ),
97
- fallback = (
98
- <CardButton
99
- ref={ref}
100
- disabled={true}
101
- startIcon={<CircularProgress size="1em" color="inherit" />}
102
- >
103
- Loading dependencies…
104
- </CardButton>
105
- ),
106
-
107
- // Dropzone
108
- accept,
109
- maxSize = Infinity,
110
- maxFiles = Infinity,
111
- onDropRejected,
112
- onDropAccepted,
113
- ...dropzoneProps
114
- } = props;
115
-
116
- return (
117
- <Suspense fallback={fallback}>
118
- <Dropzone
119
- {...dropzoneProps}
120
- accept={accept}
121
- maxSize={maxSize}
122
- maxFiles={maxFiles}
123
- disabled={disabled}
124
- onDropAccepted={(files) => {
125
- onDropAccepted?.(files);
126
- }}
127
- onDropRejected={(fileRejections) => {
128
- onDropRejected?.(fileRejections);
129
- }}
130
- >
131
- {({
132
- isDragActive,
133
- isDragReject,
134
- getRootProps,
135
- getInputProps,
136
- fileRejections: [fileRejection],
137
- }) => {
138
- return (
139
- <>
140
- <input {...getInputProps()} />
141
-
142
- <StyledCardButton
143
- {...getRootProps()}
144
- ref={ref}
145
- hint={hintText}
146
- disabled={disabled}
147
- startIcon={startIcon}
148
- status={
149
- isDragActive ? 'active' : isDragReject ? 'error' : 'idle'
150
- }
151
- error={
152
- !!fileRejection && (
153
- <UploadRejection
154
- accept={accept}
155
- maxSize={maxSize}
156
- rejection={fileRejection}
157
- />
158
- )
159
- }
160
- >
161
- {children}
162
- </StyledCardButton>
163
- </>
164
- );
165
- }}
166
- </Dropzone>
167
- </Suspense>
168
- );
169
- },
170
- );
@@ -1,37 +0,0 @@
1
- import { Meta } from '@storybook/react';
2
- import { Stack } from '@superdispatch/ui';
3
- import { Box } from '../box/Box';
4
- import { FileListItem } from './FileListItem';
5
-
6
- export default {
7
- title: 'Lab/FileListItem',
8
- component: FileListItem,
9
- decorators: [
10
- (Story) => (
11
- <Box maxWidth="200px">
12
- <Story />
13
- </Box>
14
- ),
15
- ],
16
- } as Meta;
17
-
18
- export const basic = () => (
19
- <Stack>
20
- <FileListItem name="Read this document.txt" />
21
- <FileListItem name="TST1208 Dispatcher Info.pdf" />
22
- <FileListItem name="TST1208 Dispatcher Info.pdf" canDelete={false} />
23
- <FileListItem
24
- name="attachment.jpg"
25
- url="https://picsum.photos/seed/picsum/1024/768"
26
- />
27
- </Stack>
28
- );
29
-
30
- export const status = () => (
31
- <Stack>
32
- <FileListItem name="TST1208 Dispatcher Info.pdf" status="idle" />
33
- <FileListItem name="TST1208 Dispatcher Info.pdf" status="loading" />
34
- <FileListItem name="TST1208 Dispatcher Info.pdf" status="success" />
35
- <FileListItem name="TST1208 Dispatcher Info.pdf" status="error" />
36
- </Stack>
37
- );
@@ -1,148 +0,0 @@
1
- import {
2
- CircularProgress,
3
- IconButton,
4
- Link,
5
- SvgIcon,
6
- SvgIconProps,
7
- Tooltip,
8
- } from '@material-ui/core';
9
- import { CheckCircle, Delete, Error, Image, Refresh } from '@material-ui/icons';
10
- import { mdiFilePdfBox, mdiTextBox } from '@mdi/js';
11
- import {
12
- ColorDynamic,
13
- Column,
14
- Columns,
15
- useResponsiveValue,
16
- useUID,
17
- } from '@superdispatch/ui';
18
- import { forwardRef, memo, ReactNode, useState } from 'react';
19
- import styled from 'styled-components';
20
-
21
- const FileListItemName = styled.div`
22
- overflow: hidden;
23
- line-height: 22px;
24
- white-space: nowrap;
25
- text-overflow: ellipsis;
26
- `;
27
-
28
- const FileListItemProgress = styled(CircularProgress)`
29
- font-size: 24px;
30
-
31
- ${({ theme }) => theme.breakpoints.up('sm')} {
32
- font-size: 14px;
33
- }
34
- `;
35
-
36
- const PdfIcon = memo((props: SvgIconProps) => (
37
- <SvgIcon {...props}>
38
- <path d={mdiFilePdfBox} />
39
- </SvgIcon>
40
- ));
41
- const TextBoxIcon = memo((props: SvgIconProps) => (
42
- <SvgIcon {...props}>
43
- <path d={mdiTextBox} />
44
- </SvgIcon>
45
- ));
46
-
47
- type FileType = 'pdf' | 'image' | 'unknown';
48
- const PDF_FILE_PATTERN = /\.pdf$/;
49
- const IMAGE_FILE_PATTERN = /\.(gif|png|jpg|jpeg)$/;
50
-
51
- function getFileType(name: string): FileType {
52
- if (PDF_FILE_PATTERN.exec(name)) return 'pdf';
53
- if (IMAGE_FILE_PATTERN.exec(name)) return 'image';
54
- return 'unknown';
55
- }
56
-
57
- export interface FileListItemProps {
58
- url?: string;
59
- name: string;
60
- onRetry?: () => void;
61
- onDelete?: () => void;
62
- canDelete?: boolean;
63
- helperText?: ReactNode;
64
- status?: 'idle' | 'loading' | 'error' | 'success';
65
- }
66
- export const FileListItem = forwardRef<HTMLDivElement, FileListItemProps>(
67
- ({ url, name, status, canDelete = true, onRetry, onDelete }, ref) => {
68
- const uid = useUID();
69
- const fileType = getFileType(name);
70
- const [isHoveredState, setIsHovered] = useState(false);
71
- const isHovered = useResponsiveValue(true, isHoveredState);
72
-
73
- return (
74
- <div
75
- ref={ref}
76
- onMouseOver={() => {
77
- setIsHovered(true);
78
- }}
79
- onMouseLeave={() => {
80
- setIsHovered(false);
81
- }}
82
- aria-label="file-list-item"
83
- >
84
- <Columns align="center" space="xsmall">
85
- <Column width="content">
86
- {status === 'error' ? (
87
- <Error color="error" fontSize="small" />
88
- ) : fileType === 'pdf' ? (
89
- <PdfIcon color="action" fontSize="small" />
90
- ) : fileType === 'image' ? (
91
- <Image color="action" fontSize="small" />
92
- ) : (
93
- <TextBoxIcon color="action" fontSize="small" />
94
- )}
95
- </Column>
96
-
97
- <Column width="fluid">
98
- <FileListItemName id={uid}>
99
- {!url ? (
100
- name
101
- ) : (
102
- <Link
103
- href={url}
104
- noWrap={true}
105
- target="_blank"
106
- rel="noopener noreferrer"
107
- >
108
- {name}
109
- </Link>
110
- )}
111
- </FileListItemName>
112
- </Column>
113
-
114
- {status === 'error' && (
115
- <Column width="content">
116
- <Tooltip title="Retry">
117
- <IconButton size="small" onClick={onRetry}>
118
- <Refresh fontSize="small" />
119
- </IconButton>
120
- </Tooltip>
121
- </Column>
122
- )}
123
-
124
- <Column width="content">
125
- {status === 'loading' ? (
126
- <IconButton size="small" disabled={true}>
127
- <FileListItemProgress size="1em" />
128
- </IconButton>
129
- ) : !isHovered && status === 'success' ? (
130
- <IconButton size="small">
131
- <CheckCircle
132
- fontSize="small"
133
- htmlColor={ColorDynamic.Green300}
134
- />
135
- </IconButton>
136
- ) : canDelete ? (
137
- <Tooltip title="Delete">
138
- <IconButton size="small" onClick={onDelete}>
139
- <Delete fontSize="small" />
140
- </IconButton>
141
- </Tooltip>
142
- ) : null}
143
- </Column>
144
- </Columns>
145
- </div>
146
- );
147
- },
148
- );