@furystack/shades-common-components
Version:
Common UI components for FuryStack Shades
145 lines • 6.29 kB
JavaScript
import { createComponent, Shade } from '@furystack/shades';
import { cssVariableTheme } from '../services/css-variable-theme.js';
import { NotyService } from '../services/noty-service.js';
import { ThemeProviderService } from '../services/theme-provider-service.js';
import { promisifyAnimation } from '../utils/promisify-animation.js';
import { close } from './icons/icon-definitions.js';
import { Icon } from './icons/icon.js';
export const getDefaultNotyTimeouts = (type) => {
switch (type) {
case 'error':
return 0;
case 'warning':
return 0;
case 'success':
return 5000;
case 'info':
return 20000;
default:
return 0;
}
};
export const NotyComponent = Shade({
customElementName: 'shade-noty',
css: {
margin: cssVariableTheme.spacing.xs,
overflow: 'hidden',
borderRadius: cssVariableTheme.shape.borderRadius.md,
boxShadow: cssVariableTheme.shadows.sm,
width: '300px',
display: 'flex',
flexDirection: 'column',
height: '0px',
backgroundColor: 'var(--noty-bg)',
color: 'var(--noty-text)',
'& .noty-header': {
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
padding: `${cssVariableTheme.spacing.sm} ${cssVariableTheme.spacing.sm} 0 ${cssVariableTheme.spacing.sm}`,
},
'& .noty-title': {
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
margin: '0',
fontSize: '0.85em',
fontWeight: cssVariableTheme.typography.fontWeight.semibold,
},
'& .dismiss-button': {
margin: '0',
padding: cssVariableTheme.spacing.xs,
fontSize: cssVariableTheme.typography.fontSize.md,
minWidth: 'auto',
background: 'transparent',
border: 'none',
cursor: 'pointer',
opacity: '0.7',
transition: `opacity ${cssVariableTheme.transitions.duration.fast} ease`,
lineHeight: '1',
},
'& .dismiss-button:hover': {
opacity: '1',
},
'& .noty-body': {
padding: `${cssVariableTheme.spacing.xs} ${cssVariableTheme.spacing.sm} ${cssVariableTheme.spacing.sm} ${cssVariableTheme.spacing.sm}`,
fontSize: '0.8em',
lineHeight: '1.4',
},
},
render: ({ props, injector, useDisposable, useHostProps, useRef }) => {
const wrapperRef = useRef('wrapper');
useDisposable('enter-animation', () => {
setTimeout(() => {
const hostEl = wrapperRef.current?.closest('shade-noty');
if (!hostEl)
return;
const height = hostEl.scrollHeight || 60;
void promisifyAnimation(hostEl, [
{ opacity: '0', height: '0px' },
{ opacity: '1', height: `${height}px` },
], {
fill: 'forwards',
duration: 500,
easing: 'cubic-bezier(0.190, 1.000, 0.220, 1.000)',
});
});
return { [Symbol.dispose]: () => { } };
});
const themeProvider = injector.get(ThemeProviderService);
const colors = themeProvider.theme.palette[props.model.type];
const removeSelf = async () => {
const hostEl = wrapperRef.current?.closest('shade-noty');
await promisifyAnimation(hostEl, [
{ opacity: '1', height: `${hostEl?.scrollHeight || 0}px`, margin: '6px 6px' },
{ opacity: '0', height: '0px', margin: '0px 6px' },
], {
fill: 'forwards',
duration: 500,
easing: 'cubic-bezier(0.190, 1.000, 0.220, 1.000)',
});
props.onDismiss();
};
const timeout = props.model.timeout || getDefaultNotyTimeouts(props.model.type);
if (timeout) {
setTimeout(() => void removeSelf(), timeout);
}
useHostProps({
'data-noty-type': props.model.type,
style: { '--noty-bg': colors.main, '--noty-text': colors.mainContrast },
});
return (createComponent("div", { ref: wrapperRef, style: { display: 'contents' } },
createComponent("div", { className: "noty-header" },
createComponent("span", { className: "noty-title", title: props.model.title }, props.model.title),
createComponent("button", { className: "dismiss-button", onclick: removeSelf, title: "Close", style: { color: 'inherit' } },
createComponent(Icon, { icon: close, size: 14 }))),
createComponent("div", { className: "noty-body" }, props.model.body)));
},
});
export const NotyList = Shade({
customElementName: 'shade-noty-list',
css: {
position: 'fixed',
fontFamily: cssVariableTheme.typography.fontFamily,
bottom: cssVariableTheme.spacing.md,
right: cssVariableTheme.spacing.md,
display: 'flex',
flexDirection: 'column',
},
render: ({ useDisposable, injector, useRef }) => {
const notyService = injector.get(NotyService);
const containerRef = useRef('container');
const currentNotys = notyService.getNotyList();
useDisposable('addNoty', () => notyService.subscribe('onNotyAdded', (n) => containerRef.current?.append(createComponent(NotyComponent, { model: n, onDismiss: () => notyService.emit('onNotyRemoved', n) }))));
useDisposable('removeNoty', () => notyService.subscribe('onNotyRemoved', (n) => {
const notys = containerRef.current?.querySelectorAll('shade-noty') || [];
notys.forEach((e) => {
if (e.props.model === n) {
e.remove();
}
});
}));
return (createComponent("div", { ref: containerRef, style: { display: 'contents' } }, currentNotys.map((n) => (createComponent(NotyComponent, { model: n, onDismiss: () => injector.get(NotyService).emit('onNotyRemoved', n) })))));
},
});
//# sourceMappingURL=noty-list.js.map