init project
Some checks failed
No response / noResponse (push) Has been cancelled
CI / Continuous releases (push) Has been cancelled
CI / test-dev (macos-latest) (push) Has been cancelled
CI / test-dev (ubuntu-latest) (push) Has been cancelled
CI / test-dev (windows-latest) (push) Has been cancelled
Maintenance / main (push) Has been cancelled
Scorecards supply-chain security / Scorecards analysis (push) Has been cancelled
CodeQL / Analyze (push) Has been cancelled

This commit is contained in:
how2ice
2025-12-12 14:26:25 +09:00
commit 005cf56baf
43188 changed files with 1079531 additions and 0 deletions

View File

@@ -0,0 +1,5 @@
import * as React from 'react';
const NotificationsContext = React.createContext(null);
export default NotificationsContext;

View File

@@ -0,0 +1,9 @@
import * as React from 'react';
import { ShowNotification, CloseNotification } from './useNotifications';
const NotificationsContext = React.createContext<{
show: ShowNotification;
close: CloseNotification;
} | null>(null);
export default NotificationsContext;

View File

@@ -0,0 +1,180 @@
import * as React from 'react';
import PropTypes from 'prop-types';
import Alert from '@mui/material/Alert';
import Badge from '@mui/material/Badge';
import Button from '@mui/material/Button';
import IconButton from '@mui/material/IconButton';
import Snackbar from '@mui/material/Snackbar';
import SnackbarContent from '@mui/material/SnackbarContent';
import CloseIcon from '@mui/icons-material/Close';
import useSlotProps from '@mui/utils/useSlotProps';
import NotificationsContext from './NotificationsContext';
const RootPropsContext = React.createContext(null);
function Notification({ notificationKey, open, message, options, badge }) {
const notificationsContext = React.useContext(NotificationsContext);
if (!notificationsContext) {
throw new Error('Notifications context was used without a provider.');
}
const { close } = notificationsContext;
const { severity, actionText, onAction, autoHideDuration } = options;
const handleClose = React.useCallback(
(event, reason) => {
if (reason === 'clickaway') {
return;
}
close(notificationKey);
},
[notificationKey, close],
);
const action = (
<React.Fragment>
{onAction ? (
<Button color="inherit" size="small" onClick={onAction}>
{actionText ?? 'Action'}
</Button>
) : null}
<IconButton
size="small"
aria-label="Close"
title="Close"
color="inherit"
onClick={handleClose}
>
<CloseIcon fontSize="small" />
</IconButton>
</React.Fragment>
);
const props = React.useContext(RootPropsContext);
const snackbarSlotProps = useSlotProps({
elementType: Snackbar,
ownerState: props,
externalSlotProps: {},
additionalProps: {
open,
autoHideDuration,
onClose: handleClose,
action,
},
});
return (
<Snackbar key={notificationKey} {...snackbarSlotProps}>
<Badge badgeContent={badge} color="primary" sx={{ width: '100%' }}>
{severity ? (
<Alert severity={severity} sx={{ width: '100%' }} action={action}>
{message}
</Alert>
) : (
<SnackbarContent message={message} action={action} />
)}
</Badge>
</Snackbar>
);
}
Notification.propTypes = {
badge: PropTypes.string,
message: PropTypes.node,
notificationKey: PropTypes.string.isRequired,
open: PropTypes.bool.isRequired,
options: PropTypes.shape({
actionText: PropTypes.node,
autoHideDuration: PropTypes.number,
key: PropTypes.string,
onAction: PropTypes.func,
severity: PropTypes.oneOf(['error', 'info', 'success', 'warning']),
}).isRequired,
};
function Notifications({ state }) {
const currentNotification = state.queue[0] ?? null;
return currentNotification ? (
<Notification
{...currentNotification}
badge={state.queue.length > 1 ? String(state.queue.length) : null}
/>
) : null;
}
Notifications.propTypes = {
state: PropTypes.shape({
queue: PropTypes.arrayOf(
PropTypes.shape({
message: PropTypes.node,
notificationKey: PropTypes.string.isRequired,
open: PropTypes.bool.isRequired,
options: PropTypes.shape({
actionText: PropTypes.node,
autoHideDuration: PropTypes.number,
key: PropTypes.string,
onAction: PropTypes.func,
severity: PropTypes.oneOf(['error', 'info', 'success', 'warning']),
}).isRequired,
}),
).isRequired,
}).isRequired,
};
let nextId = 0;
const generateId = () => {
const id = nextId;
nextId += 1;
return id;
};
/**
* Provider for Notifications. The subtree of this component can use the `useNotifications` hook to
* access the notifications API. The notifications are shown in the same order they are requested.
*/
function NotificationsProvider(props) {
const { children } = props;
const [state, setState] = React.useState({ queue: [] });
const show = React.useCallback((message, options = {}) => {
const notificationKey =
options.key ?? `::toolpad-internal::notification::${generateId()}`;
setState((prev) => {
if (prev.queue.some((n) => n.notificationKey === notificationKey)) {
// deduplicate by key
return prev;
}
return {
...prev,
queue: [...prev.queue, { message, options, notificationKey, open: true }],
};
});
return notificationKey;
}, []);
const close = React.useCallback((key) => {
setState((prev) => ({
...prev,
queue: prev.queue.filter((n) => n.notificationKey !== key),
}));
}, []);
const contextValue = React.useMemo(() => ({ show, close }), [show, close]);
return (
<RootPropsContext.Provider value={props}>
<NotificationsContext.Provider value={contextValue}>
{children}
<Notifications state={state} />
</NotificationsContext.Provider>
</RootPropsContext.Provider>
);
}
NotificationsProvider.propTypes = {
children: PropTypes.node,
};
export default NotificationsProvider;

View File

@@ -0,0 +1,181 @@
import * as React from 'react';
import Alert from '@mui/material/Alert';
import Badge from '@mui/material/Badge';
import Button from '@mui/material/Button';
import IconButton from '@mui/material/IconButton';
import Snackbar from '@mui/material/Snackbar';
import SnackbarContent from '@mui/material/SnackbarContent';
import type { SnackbarCloseReason } from '@mui/material/Snackbar';
import type { CloseReason } from '@mui/material/SpeedDial';
import CloseIcon from '@mui/icons-material/Close';
import useSlotProps from '@mui/utils/useSlotProps';
import NotificationsContext from './NotificationsContext';
import type {
CloseNotification,
ShowNotification,
ShowNotificationOptions,
} from './useNotifications';
const RootPropsContext = React.createContext<NotificationsProviderProps | null>(
null,
);
interface NotificationProps {
notificationKey: string;
badge: string | null;
open: boolean;
message: React.ReactNode;
options: ShowNotificationOptions;
}
function Notification({
notificationKey,
open,
message,
options,
badge,
}: NotificationProps) {
const notificationsContext = React.useContext(NotificationsContext);
if (!notificationsContext) {
throw new Error('Notifications context was used without a provider.');
}
const { close } = notificationsContext;
const { severity, actionText, onAction, autoHideDuration } = options;
const handleClose = React.useCallback(
(event: unknown, reason?: CloseReason | SnackbarCloseReason) => {
if (reason === 'clickaway') {
return;
}
close(notificationKey);
},
[notificationKey, close],
);
const action = (
<React.Fragment>
{onAction ? (
<Button color="inherit" size="small" onClick={onAction}>
{actionText ?? 'Action'}
</Button>
) : null}
<IconButton
size="small"
aria-label="Close"
title="Close"
color="inherit"
onClick={handleClose}
>
<CloseIcon fontSize="small" />
</IconButton>
</React.Fragment>
);
const props = React.useContext(RootPropsContext);
const snackbarSlotProps = useSlotProps({
elementType: Snackbar,
ownerState: props,
externalSlotProps: {},
additionalProps: {
open,
autoHideDuration,
onClose: handleClose,
action,
},
});
return (
<Snackbar key={notificationKey} {...snackbarSlotProps}>
<Badge badgeContent={badge} color="primary" sx={{ width: '100%' }}>
{severity ? (
<Alert severity={severity} sx={{ width: '100%' }} action={action}>
{message}
</Alert>
) : (
<SnackbarContent message={message} action={action} />
)}
</Badge>
</Snackbar>
);
}
interface NotificationQueueEntry {
notificationKey: string;
options: ShowNotificationOptions;
open: boolean;
message: React.ReactNode;
}
interface NotificationsState {
queue: NotificationQueueEntry[];
}
interface NotificationsProps {
state: NotificationsState;
}
function Notifications({ state }: NotificationsProps) {
const currentNotification = state.queue[0] ?? null;
return currentNotification ? (
<Notification
{...currentNotification}
badge={state.queue.length > 1 ? String(state.queue.length) : null}
/>
) : null;
}
export interface NotificationsProviderProps {
children?: React.ReactNode;
}
let nextId = 0;
const generateId = () => {
const id = nextId;
nextId += 1;
return id;
};
/**
* Provider for Notifications. The subtree of this component can use the `useNotifications` hook to
* access the notifications API. The notifications are shown in the same order they are requested.
*/
export default function NotificationsProvider(props: NotificationsProviderProps) {
const { children } = props;
const [state, setState] = React.useState<NotificationsState>({ queue: [] });
const show = React.useCallback<ShowNotification>((message, options = {}) => {
const notificationKey =
options.key ?? `::toolpad-internal::notification::${generateId()}`;
setState((prev) => {
if (prev.queue.some((n) => n.notificationKey === notificationKey)) {
// deduplicate by key
return prev;
}
return {
...prev,
queue: [...prev.queue, { message, options, notificationKey, open: true }],
};
});
return notificationKey;
}, []);
const close = React.useCallback<CloseNotification>((key) => {
setState((prev) => ({
...prev,
queue: prev.queue.filter((n) => n.notificationKey !== key),
}));
}, []);
const contextValue = React.useMemo(() => ({ show, close }), [show, close]);
return (
<RootPropsContext.Provider value={props}>
<NotificationsContext.Provider value={contextValue}>
{children}
<Notifications state={state} />
</NotificationsContext.Provider>
</RootPropsContext.Provider>
);
}

View File

@@ -0,0 +1,10 @@
import * as React from 'react';
import NotificationsContext from './NotificationsContext';
export default function useNotifications() {
const notificationsContext = React.useContext(NotificationsContext);
if (!notificationsContext) {
throw new Error('Notifications context was used without a provider.');
}
return notificationsContext;
}

View File

@@ -0,0 +1,60 @@
import * as React from 'react';
import NotificationsContext from './NotificationsContext';
export interface ShowNotificationOptions {
/**
* The key to use for deduping notifications. If not provided, a unique key will be generated.
*/
key?: string;
/**
* The severity of the notification. When provided, the snackbar will show an alert with the
* specified severity.
*/
severity?: 'info' | 'warning' | 'error' | 'success';
/**
* The duration in milliseconds after which the notification will automatically close.
*/
autoHideDuration?: number;
/**
* The text to display on the action button.
*/
actionText?: React.ReactNode;
/**
* The callback to call when the action button is clicked.
*/
onAction?: () => void;
}
export interface ShowNotification {
/**
* Show a snackbar in the application.
*
* @param message The message to display in the snackbar.
* @param options Options for the snackbar.
* @returns The key that represents the notification. Useful for programmatically
* closing it.
*/
(message: React.ReactNode, options?: ShowNotificationOptions): string;
}
export interface CloseNotification {
/**
* Close a snackbar in the application.
*
* @param key The key of the notification to close.
*/
(key: string): void;
}
interface UseNotifications {
show: ShowNotification;
close: CloseNotification;
}
export default function useNotifications(): UseNotifications {
const notificationsContext = React.useContext(NotificationsContext);
if (!notificationsContext) {
throw new Error('Notifications context was used without a provider.');
}
return notificationsContext;
}