Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] chore(storage-browser): add type definitions #6218

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,17 @@
};

const { StorageBrowser, useAction, useView } = createStorageBrowser({
actions: { custom: { generateLink } },
actions: {
custom: {
generateLink,
madeUp: (input) => {
const { data } = input;
const { madeUp } = data;

Check notice

Code scanning / CodeQL

Unused variable, import, function or class Note

Unused variable madeUp.

return { result: Promise.resolve({ status: 'CANCELED' }) };
},
},
},
config: managedAuthAdapter,
});

Expand All @@ -69,6 +79,7 @@
);

const [{ tasks }, handleCreate] = useAction('generateLink', { items });
const [{ task: ___task }, ___handleCreate] = useAction('generateLink');

return (
<Flex direction="column">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,30 @@ import {
UploadHandler,
} from '../handlers';

/**
* Utility type repressnting the Function signature of `StoageBrowser` action handlers. First
* positional generic adds additional properties to `TaskHandlerInput` `data` parameter,
* second positional generic specifies optional return value provided to `TaskHandlerInput`
* `options.onSuccess` callback and `Task.value`.
*
* @example
* ```ts
* interface MyData {
* user: {
* name: string
* id: string
* };
* }
*
* interface MyReturnValue {
* link: string;
* }
*
* type CreateDownloadLink = ActionHandler<MyData, MyReturnValue>;
*
* const createDonwloadLink: CreateDownloadLink = ()
* ```
*/
export type ActionHandler<TData = any, RValue = any> = TaskHandler<
TaskHandlerInput<TData & TaskData>,
TaskHandlerOutput<RValue>
Expand Down Expand Up @@ -112,6 +136,10 @@ export interface ExtendedDefaultActionConfigs
listLocations: ListLocations;
}

/**
* Accepts either an ActionViewConfig for creation of a action list item an corresponding view
* slot or an action handler for standalone action usage
*/
export type CustomActionConfigs = Record<
ActionName,
ActionViewConfig | ActionHandler
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ import {
CreateStorageBrowserOutput,
StorageBrowserProviderProps,
StorageBrowserType,
DerivedCustomViews,
DerivedActionViews,
DerivedActionViewType,
} from './types';
import {
Expand Down Expand Up @@ -127,7 +127,7 @@ export function createStorageBrowser<

const StorageBrowser: StorageBrowserType<
DerivedActionViewType<RInput>,
DerivedCustomViews<RInput>
DerivedActionViews<RInput>
> = ({ views, displayText }) => (
<ErrorBoundary>
<Provider displayText={displayText} views={views}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,8 @@ export {
CreateManagedAuthAdapterInput,
StorageBrowserAuthAdapter,
} from './adapters';
export { CreateStorageBrowserInput, StorageBrowserType } from './types';
export {
CreateStorageBrowserInput,
StorageBrowserProps,
StorageBrowserType,
} from './types';
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,17 @@ export type ActionTypeStateContext = [
];

export interface ActionTypeProviderProps {
/**
* Sets initial `actionType`. Provide to initialize the `StorageBrowser` with an initial
* `actionType`.
*
* @example
* ```tsx
* <StorageBrowser.Provider actionType="upload">
* <StorageBrowser />
* </StorageBrowser.Provider>
* ```
*/
actionType?: string;
children?: React.ReactNode;
}
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,14 @@ export type LocationStateContext = [

export interface LocationProviderProps {
children?: React.ReactNode;
/**
* Sets initial `location` data.
*/
location?: LocationData;

/**
* Sets initial `location` subpath to establish initial navigation state.
*/
path?: string;
}

Expand Down
129 changes: 116 additions & 13 deletions packages/react-storage/src/components/StorageBrowser/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,13 @@ import {
DefaultActionConfigs,
ExtendedActionConfigs,
ListLocations,
LocationData,
} from './actions';
import { GetLocationCredentials } from './credentials/types';

import { UseView } from './views/useView';

import { Components } from './ComponentsProvider';
import { StorageBrowserComponents } from './ComponentsProvider';

import { RegisterAuthListener, StoreProviderProps } from './providers';

Expand All @@ -22,14 +23,14 @@ import {
LocationActionViewType,
LocationDetailViewType,
LocationsViewType,
Views,
StorageBrowserViews,
} from './views';

import { StorageBrowserDisplayText } from './displayText';

import { DerivedActionHandlers, UseAction } from './useAction';

export interface Config {
export interface StorageBrowserConfig {
accountId?: string;
customEndpoint?: string;
getLocationCredentials: GetLocationCredentials;
Expand All @@ -44,46 +45,148 @@ export interface StorageBrowserActions {
}

export interface CreateStorageBrowserInput {
/**
* Override and default `StorageBrowser` actions and action view configs.
*/
actions?: StorageBrowserActions;
config: Config;
components?: Components;

/**
* `StorageBrowser` configuration properties.
*/
config: StorageBrowserConfig;

/**
* Overrides default `components` used within `StorageBrowser`
*/
components?: StorageBrowserComponents;
}

export interface StorageBrowserProps<K = string, V = {}> {
/**
* Overrides default display string values. Supports resolving of static and dynamic text
* values and error messages.
*
* @example
* ```tsx
* const myDisplayText = {
* CreateFolderView: {
* // static text
* // default: "Create folder"
* title: 'Add subfolder",
*
* // dynamic text
* // default: () => 'Folder name cannot contain "/", nor end or start with "."'
* getValidationMessage?: (foldeerName) =>
* `Folder name ${folderName} cannot contain "/", nor end or start with "."`
* }
* }
*
* <StorageBrowser displayText={{ displayText: myDisplayText }} />
* ```
*/
displayText?: StorageBrowserDisplayText;
views?: Views<K, V>;

/**
* Overrides default top level views and custom views defined by the `actions` parameter of
* `createStorageBrowser`.
*/
views?: StorageBrowserViews<K, V>;
}

export interface StorageBrowserProviderProps<V = {}>
extends StoreProviderProps {
/**
* See @typedef {StorageBrowserProps['displayText']}
*/
displayText?: StorageBrowserDisplayText;
// `views` intentionally scoped to custom slots to prevent conflicts with composability

views?: V;

/**
* Sets initial `location` data. used to fetch Provide to initialize the `StorageBrowser` with an initial `actionType`
*
* @example
* ```tsx
* <StorageBroeser.Provider actionType="upload">
* <StorageBroeser />
* </StorageBroeser.Provider>
* ```
*/
location?: LocationData;
}

/**
* StorageBrowser` component, provider and view components.
*/
export interface StorageBrowserType<K = string, V = {}> {
(props: StorageBrowserProps<K, V>): React.JSX.Element;
displayName: string;
/**
* `StorageBrowser` React.Context provider. Composed `StorageBrowser` components
* must be a descendant of a `Provider` element.
*
* @example
* ```tsx
* <StorageBrowser.Provider>
* <Modal content={<StorageBrowser.UploadView />}>
* <StorageBrowser.Provider>
* ```
*/
Provider: (props: StorageBrowserProviderProps<V>) => React.JSX.Element;

/**
* Utility view aggregating all action views. Can be used to render a standalone
* action view.
*
* @example
* ```tsx
* <StorageBrowser.LocationActionView type="copy" />
* ```
*/
LocationActionView: LocationActionViewType<K>;

/**
* Displays data related to the selected or provided `location` and action
* selection.
*/
LocationDetailView: LocationDetailViewType;

/**
* Entry point view displaying end user allowed locations.
*/
LocationsView: LocationsViewType;

/**
* Standalone composable default action view components.
*/
CopyView: CopyViewType;
CreateFolderView: CreateFolderViewType;
DeleteView: DeleteViewType;
UploadView: UploadViewType;
LocationActionView: LocationActionViewType<K>;
LocationDetailView: LocationDetailViewType;
LocationsView: LocationsViewType;
}

type DefaultActionType<T = string> = Exclude<T, keyof DefaultActionConfigs>;

export type DerivedCustomViews<T extends StorageBrowserActions> = {
/**
* Utility type resolving available custom action view component slots
*/
export type DerivedActionViews<T extends StorageBrowserActions> = {
[K in keyof T['custom'] as K extends DefaultActionType<K>
? T['custom'][K] extends { viewName: `${string}View` }
? T['custom'][K]['viewName']
: never
: never]?: () => React.JSX.Element | null;
};

/**
* Utility type representing default actions that do not have a corresponding action view
* and require exclusionn from aggregated action views.
*/
type DefaultActionWithoutViewType = 'download';

/**
* Utility type resolving available location action view types
*/
export type DerivedActionViewType<T extends StorageBrowserActions> =
| keyof {
[K in keyof T['custom'] as K extends DefaultActionType<K>
Expand All @@ -92,14 +195,14 @@ export type DerivedActionViewType<T extends StorageBrowserActions> =
: never
: never]?: any;
}
| Exclude<keyof DefaultActionConfigs, 'download'>;
| Exclude<keyof DefaultActionConfigs, DefaultActionWithoutViewType>;

export interface CreateStorageBrowserOutput<
C extends ExtendedActionConfigs = ExtendedActionConfigs,
> {
StorageBrowser: StorageBrowserType<
DerivedActionViewType<C>,
DerivedCustomViews<C>
DerivedActionViews<C>
>;
useAction: UseAction<DerivedActionHandlers<C>>;
useView: UseView;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,9 @@ export type DerivedActionHandlers<
[K in keyof D]: ResolveHandlerType<D[K]>;
};

export interface HandleTasksOptions<U extends TaskData = TaskData> {
export interface HandleTasksOptions<U extends TaskData = TaskData, R = any> {
items: U[];
onTaskSuccess?: (task: Task<U>) => void;
onTaskSuccess?: (task: Task<U, R>) => void;
}

interface HandleTasksInput {
Expand Down Expand Up @@ -108,16 +108,30 @@ export type UseHandlerState<
U = undefined,
> = U extends undefined ? UseTaskState<T, R> : UseTasksState<T, R>;

export interface UseActionOptions<TD extends TaskData = TaskData, R = any>
extends HandleTasksOptions<TD, R> {}

export type UseActionState<
TD extends TaskData = TaskData,
R = {},
U = undefined,
> = UseHandlerState<TD, R, U>;

/**
* `StorageBrowser` React hook utility used to call default and custom actions
* from within a parent `StorageBrowser.Provider`.
*
* `useAction` provides the called action with `location` state and credentials
* values, as well as any parameters provided as `data` at the `useAction` call
* site.
*/
export type UseAction<V extends Record<keyof V, ActionHandler>> = <
K extends keyof V,
TData extends V[K] extends ActionHandler<infer D> ? D & TaskData : never,
TOptions extends HandleTasksOptions<TData>,
RValue extends V[K] extends ActionHandler<any, infer R> ? R : never,
TOptions extends UseActionOptions<TData, RValue>,
U extends TOptions | undefined = undefined,
>(
key: K,
options?: U
) => UseHandlerState<
TData,
V[K] extends ActionHandler<any, infer R> ? R : never,
U
>;
) => UseActionState<TData, RValue, U>;
Loading
Loading