@aws-amplify/ui-react-core 3.0.29 → 3.1.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 (38) hide show
  1. package/dist/elements.js +116 -3
  2. package/dist/esm/elements/ControlsContext.mjs +71 -0
  3. package/dist/esm/elements/ElementsContext.mjs +3 -2
  4. package/dist/esm/elements/defineBaseElement.mjs +30 -1
  5. package/dist/esm/elements/utils.mjs +11 -0
  6. package/dist/esm/elements/withBaseElementProps.mjs +1 -1
  7. package/dist/esm/elements.mjs +3 -1
  8. package/dist/esm/hooks/useDataState.mjs +10 -4
  9. package/dist/esm/hooks/useDropZone.mjs +13 -4
  10. package/dist/esm/utils/createContextUtilities.mjs +6 -3
  11. package/dist/esm/utils/processDroppedItems.mjs +66 -0
  12. package/dist/index.js +92 -11
  13. package/dist/types/elements/ControlsContext.d.ts +63 -0
  14. package/dist/types/elements/ElementsContext.d.ts +3 -2
  15. package/dist/types/elements/defineBaseElement.d.ts +19 -2
  16. package/dist/types/elements/index.d.ts +4 -1
  17. package/dist/types/elements/types.d.ts +88 -6
  18. package/dist/types/elements/utils.d.ts +3 -0
  19. package/dist/types/elements/withBaseElementProps.d.ts +3 -3
  20. package/dist/types/hooks/index.d.ts +1 -1
  21. package/dist/types/hooks/useDataState.d.ts +6 -1
  22. package/dist/types/index.d.ts +1 -1
  23. package/dist/types/utils/createContextUtilities.d.ts +1 -1
  24. package/dist/types/utils/processDroppedItems.d.ts +1 -0
  25. package/package.json +3 -3
  26. package/src/elements/ControlsContext.tsx +89 -0
  27. package/src/elements/ElementsContext.tsx +3 -2
  28. package/src/elements/defineBaseElement.tsx +50 -2
  29. package/src/elements/index.ts +7 -1
  30. package/src/elements/types.ts +114 -6
  31. package/src/elements/utils.ts +20 -0
  32. package/src/elements/withBaseElementProps.tsx +3 -3
  33. package/src/hooks/index.ts +6 -1
  34. package/src/hooks/useDataState.ts +25 -7
  35. package/src/hooks/useDropZone.ts +18 -7
  36. package/src/index.ts +2 -0
  37. package/src/utils/createContextUtilities.tsx +9 -6
  38. package/src/utils/processDroppedItems.ts +79 -0
@@ -13,16 +13,31 @@ import React from 'react';
13
13
  * are always optional at the interface level, allowing for additional `props`
14
14
  * to be added to existing `BaseElement` interfaces as needed.
15
15
  */
16
- export type BaseElement<T = {}, K = {}> = React.ForwardRefExoticComponent<
16
+ export type BaseElement<T = {}> = (props: T) => React.JSX.Element;
17
+
18
+ /**
19
+ * @internal @unstable
20
+ *
21
+ * see @type {BaseElement}
22
+ *
23
+ * `BaseElement` with a `ref` corresponding to the `element` type
24
+ */
25
+ export type BaseElementWithRef<
26
+ T = {},
27
+ K = {},
28
+ > = React.ForwardRefExoticComponent<
17
29
  React.PropsWithoutRef<T> & React.RefAttributes<K>
18
30
  >;
19
31
 
20
32
  type ListElementSubType = 'Ordered' | 'Unordered';
21
- type ListElementDisplayName = 'List' | `${ListElementSubType}List`;
33
+ type ListElementDisplayName = `${ListElementSubType}List`;
22
34
 
23
- type TableElementSubType = 'Body' | 'Data' | 'Row' | 'Head' | 'Header';
35
+ type TableElementSubType = 'Body' | 'DataCell' | 'Row' | 'Head' | 'Header';
24
36
  type TableElementDisplayName = 'Table' | `Table${TableElementSubType}`;
25
37
 
38
+ type DescriptionElementSubType = 'Details' | 'List' | 'Term';
39
+ type DescriptionElementDisplayName = `Description${DescriptionElementSubType}`;
40
+
26
41
  /**
27
42
  * @internal @unstable
28
43
  *
@@ -30,7 +45,6 @@ type TableElementDisplayName = 'Table' | `Table${TableElementSubType}`;
30
45
  */
31
46
  export type ElementDisplayName =
32
47
  | 'Button'
33
- | 'Divider'
34
48
  | 'Heading' // h1, h2, etc
35
49
  | 'Icon'
36
50
  | 'Image'
@@ -44,6 +58,7 @@ export type ElementDisplayName =
44
58
  | 'TextArea'
45
59
  | 'Title'
46
60
  | 'View'
61
+ | DescriptionElementDisplayName
47
62
  | ListElementDisplayName
48
63
  | TableElementDisplayName;
49
64
 
@@ -68,7 +83,7 @@ export type ReactElementType = keyof React.JSX.IntrinsicElements;
68
83
  * @internal @unstable
69
84
  */
70
85
  export type ReactElementProps<T extends ReactElementType> =
71
- React.JSX.IntrinsicElements[T];
86
+ React.ComponentProps<T>;
72
87
 
73
88
  /**
74
89
  * @internal @unstable
@@ -85,5 +100,98 @@ export type BaseElementProps<
85
100
  V = string,
86
101
  K extends Record<ElementPropKey<keyof K>, any> = Record<string, any>,
87
102
  > = React.AriaAttributes &
88
- React.RefAttributes<ElementRefType<K>> &
103
+ React.Attributes &
89
104
  Pick<K, ElementPropKey<T>> & { testId?: string; variant?: V };
105
+
106
+ /**
107
+ * @internal @unstable
108
+ */
109
+ export type BaseElementWithRefProps<
110
+ T extends keyof K,
111
+ V = string,
112
+ K extends Record<ElementPropKey<keyof K>, any> = Record<string, any>,
113
+ > = BaseElementProps<T, V, K> & React.RefAttributes<ElementRefType<K>>;
114
+
115
+ /**
116
+ * @internal @unstable
117
+ */
118
+ export type ElementWithAndWithoutRef<
119
+ T extends ReactElementType,
120
+ K extends React.ComponentType<React.ComponentProps<T>> = React.ComponentType<
121
+ React.ComponentProps<T>
122
+ >,
123
+ > = K extends React.ComponentType<infer U>
124
+ ? React.ForwardRefExoticComponent<U>
125
+ : never;
126
+
127
+ /**
128
+ * @internal @unstable
129
+ *
130
+ * Merge `BaseElement` defintions with `elements` types provided by
131
+ * consumers, for use with top level connected component function
132
+ * signatures.
133
+ *
134
+ * Example:
135
+ *
136
+ * ```tsx
137
+ * export function createStorageBrowser<
138
+ * T extends Partial<StorageBrowserElements>,
139
+ * >({ elements }: CreateStorageBrowserInput<T> = {}): {
140
+ * StorageBrowser: StorageBrowser<MergeElements<StorageBrowserElements, T>>
141
+ * } {
142
+ * // ...do create stuff
143
+ * };
144
+ * ```
145
+ */
146
+ export type MergeBaseElements<T, K extends Partial<T>> = {
147
+ [U in keyof T]: K[U] extends T[U] ? K[U] : T[U];
148
+ };
149
+
150
+ /**
151
+ * @internal @unstable
152
+ *
153
+ * Extend the defintion of a `BaseElement` with additional `props`.
154
+ *
155
+ * Use cases are restricted to scenarios where additional `props`
156
+ * are required for a `ControlElement` interface, for example:
157
+ *
158
+ * @example
159
+ * ```tsx
160
+ * const FieldInput = defineBaseElementWithRef({
161
+ * type: 'input',
162
+ * displayName: 'Input'
163
+ * });
164
+ *
165
+ * type InputWithSearchCallback =
166
+ * ExtendBaseElement<
167
+ * typeof FieldInput,
168
+ * { onSearch?: (event: { value: string }) => void }
169
+ * >
170
+ *
171
+ * const SearchInput = React.forwardRef((
172
+ * { onSearch, ...props }
173
+ * ref
174
+ * ) => {
175
+ * // ...do something with onSearch
176
+ *
177
+ * return <FieldInput {...props} ref={ref} />;
178
+ * });
179
+ * ```
180
+ *
181
+ * Caveats:
182
+ * - additional `props` should not be passed directly to
183
+ * `BaseElement` components, the outputted interface should be
184
+ * applied to a wrapping element that handles the additional `props`
185
+ *
186
+ * - additional `props` that share a key with existing `props`
187
+ * are omitted from the outputted interface to adhere to `BaseElement`
188
+ * type contracts
189
+ *
190
+ */
191
+ export type ExtendBaseElement<
192
+ // `BaseElement` to extend
193
+ T extends React.ComponentType,
194
+ // additional `props`
195
+ K = {},
196
+ U extends React.ComponentPropsWithRef<T> = React.ComponentPropsWithRef<T>,
197
+ > = BaseElementWithRef<U & Omit<K, keyof U>, U>;
@@ -0,0 +1,20 @@
1
+ import React from 'react';
2
+
3
+ export function isComponent<T>(
4
+ component?: React.ComponentType<T> | React.ForwardRefExoticComponent<T>
5
+ ): component is React.ComponentType<T> {
6
+ return typeof component === 'function';
7
+ }
8
+
9
+ export function isForwardRefExoticComponent<T>(
10
+ component: React.ComponentType<T> | React.ForwardRefExoticComponent<T>
11
+ ): component is React.ForwardRefExoticComponent<T> {
12
+ return (
13
+ typeof component === 'object' &&
14
+ typeof (component as React.ForwardRefExoticComponent<T>).$$typeof ===
15
+ 'symbol' &&
16
+ ['react.memo', 'react.forward_ref'].includes(
17
+ (component as React.ForwardRefExoticComponent<T>).$$typeof.description!
18
+ )
19
+ );
20
+ }
@@ -1,5 +1,5 @@
1
1
  import React from 'react';
2
- import { BaseElement, ElementRefType } from './types';
2
+ import { BaseElementWithRef, ElementRefType } from './types';
3
3
 
4
4
  /**
5
5
  * @internal @unstable
@@ -16,7 +16,7 @@ import { BaseElement, ElementRefType } from './types';
16
16
  * type InputElementPropKey = 'onChange' | 'type';
17
17
  *
18
18
  * // create `InputElement` base with `type` generic and extended `props` key
19
- * export const InputElement = defineBaseElement<"input", InputElementPropKey>({
19
+ * export const InputElement = defineBaseElementWithRef<"input", InputElementPropKey>({
20
20
  * type: "input",
21
21
  * displayName: "Input",
22
22
  * });
@@ -38,7 +38,7 @@ export default function withBaseElementProps<
38
38
  >(
39
39
  Target: React.ForwardRefExoticComponent<T>,
40
40
  defaultProps: K
41
- ): BaseElement<T, ElementRefType<T>> {
41
+ ): BaseElementWithRef<T, ElementRefType<T>> {
42
42
  const Component = React.forwardRef<ElementRefType<T>, T>((props, ref) => (
43
43
  <Target
44
44
  {...{
@@ -1,4 +1,9 @@
1
- export { default as useDataState, DataState } from './useDataState';
1
+ export {
2
+ default as useDataState,
3
+ AsyncDataAction,
4
+ DataAction,
5
+ DataState,
6
+ } from './useDataState';
2
7
 
3
8
  export {
4
9
  default as useDeprecationWarning,
@@ -1,3 +1,4 @@
1
+ import { isFunction } from '@aws-amplify/ui';
1
2
  import React from 'react';
2
3
 
3
4
  export interface DataState<T> {
@@ -7,6 +8,13 @@ export interface DataState<T> {
7
8
  message: string | undefined;
8
9
  }
9
10
 
11
+ export type DataAction<T = any, K = any> = (prevData: T, input: K) => T;
12
+
13
+ export type AsyncDataAction<T = any, K = any> = (
14
+ prevData: T,
15
+ input: K
16
+ ) => Promise<T>;
17
+
10
18
  // default state
11
19
  const INITIAL_STATE = { hasError: false, isLoading: false, message: undefined };
12
20
  const LOADING_STATE = { hasError: false, isLoading: true, message: undefined };
@@ -20,9 +28,13 @@ const resolveMaybeAsync = async <T>(
20
28
  };
21
29
 
22
30
  export default function useDataState<T, K>(
23
- action: (prevData: T, ...input: K[]) => T | Promise<T>,
24
- initialData: T
25
- ): [state: DataState<T>, handleAction: (...input: K[]) => void] {
31
+ action: DataAction<T, K> | AsyncDataAction<T, K>,
32
+ initialData: T,
33
+ options?: {
34
+ onSuccess?: (data: T) => void;
35
+ onError?: (message: string) => void;
36
+ }
37
+ ): [state: DataState<T>, handleAction: (input: K) => void] {
26
38
  const [dataState, setDataState] = React.useState<DataState<T>>(() => ({
27
39
  ...INITIAL_STATE,
28
40
  data: initialData,
@@ -30,20 +42,26 @@ export default function useDataState<T, K>(
30
42
 
31
43
  const prevData = React.useRef(initialData);
32
44
 
33
- const handleAction: (...input: K[]) => void = React.useCallback(
34
- (...input) => {
45
+ const { onSuccess, onError } = options ?? {};
46
+
47
+ const handleAction: (input: K) => void = React.useCallback(
48
+ (input) => {
35
49
  setDataState(({ data }) => ({ ...LOADING_STATE, data }));
36
50
 
37
- resolveMaybeAsync(action(prevData.current, ...input))
51
+ resolveMaybeAsync(action(prevData.current, input))
38
52
  .then((data: T) => {
53
+ if (isFunction(onSuccess)) onSuccess(data);
54
+
39
55
  prevData.current = data;
40
56
  setDataState({ ...INITIAL_STATE, data });
41
57
  })
42
58
  .catch(({ message }: Error) => {
59
+ if (isFunction(onError)) onError(message);
60
+
43
61
  setDataState(({ data }) => ({ ...ERROR_STATE, data, message }));
44
62
  });
45
63
  },
46
- [action]
64
+ [action, onError, onSuccess]
47
65
  );
48
66
 
49
67
  return [dataState, handleAction];
@@ -1,6 +1,7 @@
1
1
  import { useState } from 'react';
2
2
  import { isFunction } from '@aws-amplify/ui';
3
3
  import { filterAllowedFiles } from '../utils/filterAllowedFiles';
4
+ import { processDroppedItems } from '../utils/processDroppedItems';
4
5
 
5
6
  interface DragEvents {
6
7
  onDragStart: (event: React.DragEvent<HTMLDivElement>) => void;
@@ -85,17 +86,27 @@ export default function useDropZone({
85
86
  event.preventDefault();
86
87
  event.stopPropagation();
87
88
  setDragState('inactive');
88
- const files = Array.from(event.dataTransfer.files);
89
- const { acceptedFiles, rejectedFiles } = filterAllowedFiles<File>(
90
- files,
91
- acceptedFileTypes
92
- );
89
+
90
+ const { files, items } = event.dataTransfer;
93
91
 
94
92
  if (isFunction(_onDrop)) {
95
93
  _onDrop(event);
96
94
  }
97
- if (isFunction(onDropComplete)) {
98
- onDropComplete({ acceptedFiles, rejectedFiles });
95
+
96
+ const completeDrop = (files: File[]) => {
97
+ const { acceptedFiles, rejectedFiles } = filterAllowedFiles<File>(
98
+ files,
99
+ acceptedFileTypes
100
+ );
101
+ if (isFunction(onDropComplete)) {
102
+ onDropComplete({ acceptedFiles, rejectedFiles });
103
+ }
104
+ };
105
+
106
+ if (!items) {
107
+ completeDrop(Array.from(files));
108
+ } else {
109
+ processDroppedItems(Array.from(items)).then(completeDrop);
99
110
  }
100
111
  };
101
112
 
package/src/index.ts CHANGED
@@ -35,6 +35,8 @@ export {
35
35
  } from './components';
36
36
 
37
37
  export {
38
+ AsyncDataAction,
39
+ DataAction,
38
40
  useDeprecationWarning,
39
41
  UseDeprecationWarning,
40
42
  useGetUrl,
@@ -36,7 +36,7 @@ type CreateContextUtilitiesReturn<ContextType, ContextName extends string> = {
36
36
  : Key extends `use${string}`
37
37
  ? (params?: HookParams) => ContextType
38
38
  : Key extends `${string}Context`
39
- ? React.Context<ContextType | undefined>
39
+ ? React.Context<ContextType>
40
40
  : never;
41
41
  };
42
42
 
@@ -89,7 +89,7 @@ type CreateContextUtilitiesReturn<ContextType, ContextName extends string> = {
89
89
  export default function createContextUtilities<
90
90
  ContextType,
91
91
  ContextName extends string = string,
92
- Message extends string | undefined = string | undefined
92
+ Message extends string | undefined = string | undefined,
93
93
  >(
94
94
  options: ContextOptions<ContextType, ContextName, Message>
95
95
  ): CreateContextUtilitiesReturn<ContextType, ContextName> {
@@ -99,7 +99,11 @@ export default function createContextUtilities<
99
99
  throw new Error(INVALID_OPTIONS_MESSAGE);
100
100
  }
101
101
 
102
+ const contextDisplayName = `${contextName}Context`;
103
+ const providerDisplayName = `${contextName}Provider`;
104
+
102
105
  const Context = React.createContext<ContextType | undefined>(defaultValue);
106
+ Context.displayName = contextDisplayName;
103
107
 
104
108
  function Provider(props: React.PropsWithChildren<ContextType>) {
105
109
  const { children, ...context } = props;
@@ -113,8 +117,7 @@ export default function createContextUtilities<
113
117
  return <Context.Provider value={value}>{children}</Context.Provider>;
114
118
  }
115
119
 
116
- Provider.displayName = `${contextName}Provider`;
117
-
120
+ Provider.displayName = providerDisplayName;
118
121
  return {
119
122
  [`use${contextName}`]: function (params?: HookParams) {
120
123
  const context = React.useContext(Context);
@@ -125,7 +128,7 @@ export default function createContextUtilities<
125
128
 
126
129
  return context;
127
130
  },
128
- [`${contextName}Provider`]: Provider,
129
- [`${contextName}Context`]: Context,
131
+ [providerDisplayName]: Provider,
132
+ [contextDisplayName]: Context,
130
133
  } as CreateContextUtilitiesReturn<ContextType, ContextName>;
131
134
  }
@@ -0,0 +1,79 @@
1
+ // Helper function to convert FileSystemFileEntry to File
2
+ const getFileFromEntry = (fileEntry: FileSystemFileEntry): Promise<File> => {
3
+ return new Promise((resolve) => {
4
+ fileEntry.file(resolve);
5
+ });
6
+ };
7
+
8
+ // Helper function to read all entries in a directory
9
+ const readAllDirectoryEntries = async (
10
+ dirReader: FileSystemDirectoryReader
11
+ ): Promise<FileSystemEntry[]> => {
12
+ const entries: FileSystemEntry[] = [];
13
+
14
+ let readBatch: FileSystemEntry[] = [];
15
+ do {
16
+ readBatch = await new Promise<FileSystemEntry[]>((resolve, reject) => {
17
+ try {
18
+ dirReader.readEntries(resolve, reject);
19
+ } catch (error) {
20
+ reject(error);
21
+ }
22
+ });
23
+ entries.push(...readBatch);
24
+ } while (readBatch.length > 0);
25
+
26
+ return entries;
27
+ };
28
+
29
+ // Helper function to process files and folder contents
30
+ export async function processDroppedItems(
31
+ dataTransferItems: DataTransferItem[]
32
+ ): Promise<File[]> {
33
+ const files: File[] = [];
34
+
35
+ const processFileSystemEntry = async (
36
+ entry: FileSystemEntry
37
+ ): Promise<void> => {
38
+ if (entry.isFile) {
39
+ const file = await getFileFromEntry(entry as FileSystemFileEntry);
40
+ // drag and dropped files do not have a webkitRelativePath property,
41
+ // but they do have a fullPath property which has the same information
42
+ // https://github.com/ant-design/ant-design/issues/16426
43
+ if (entry.fullPath && !file.webkitRelativePath) {
44
+ Object.defineProperties(file, {
45
+ webkitRelativePath: {
46
+ writable: true,
47
+ },
48
+ });
49
+
50
+ // intentionally overwriting webkitRelativePath
51
+ // @ts-expect-error
52
+ file.webkitRelativePath = entry.fullPath.replace(/^\//, '');
53
+
54
+ Object.defineProperties(file, {
55
+ webkitRelativePath: {
56
+ writable: false,
57
+ },
58
+ });
59
+ }
60
+ files.push(file);
61
+ } else if (entry.isDirectory) {
62
+ const dirReader = (entry as FileSystemDirectoryEntry).createReader();
63
+ const dirEntries = await readAllDirectoryEntries(dirReader);
64
+ await Promise.all(dirEntries.map(processFileSystemEntry));
65
+ }
66
+ };
67
+
68
+ // Filter out and process files from the data transfer items
69
+ await Promise.all(
70
+ dataTransferItems
71
+ .reduce<FileSystemEntry[]>((acc, item) => {
72
+ const entry = item.webkitGetAsEntry();
73
+ return item.kind === 'file' && entry ? [...acc, entry] : acc;
74
+ }, [])
75
+ .map(processFileSystemEntry)
76
+ );
77
+
78
+ return files;
79
+ }