gristlabs_grist-core/app/client/ui/ExampleCard.ts

178 lines
4.8 KiB
TypeScript
Raw Normal View History

import {IExampleInfo} from 'app/client/ui/ExampleInfo';
import {prepareForTransition, TransitionWatcher} from 'app/client/ui/transitions';
import {colors, mediaXSmall, testId, vars} from 'app/client/ui2018/cssVars';
import {icon} from 'app/client/ui2018/icons';
import {cssLink} from 'app/client/ui2018/links';
import {dom, styled} from 'grainjs';
import {AutomaticHelpToolInfo} from "app/client/ui/Tools";
let prevCardClose: (() => void)|null = null;
// Open a popup with a card introducing this example, if the user hasn't dismissed it in the past.
export function showExampleCard(
example: IExampleInfo, toolInfo: AutomaticHelpToolInfo
) {
const {elem: btnElem, markAsSeen, reopen} = toolInfo;
// Close the example card.
function close() {
prevCardClose = null;
collapseAndRemoveCard(cardElem, btnElem.getBoundingClientRect());
markAsSeen();
}
const card = example.welcomeCard;
if (!card) { return null; }
const cardElem = cssCard(
cssImage({src: example.imgUrl}),
cssBody(
cssTitle(card.title),
cssInfo(card.text),
cssButtons(
cssLinkBtn(cssLinkIcon('Page'), card.tutorialName,
{href: example.tutorialUrl, target: '_blank'},
),
// TODO: Add a link to the overview video (as popup or to a support page that shows the
// video). Also include a 'Video' icon.
// cssLinkBtn(cssLinkIcon('Video'), 'Grist Video Tour'),
)
),
cssCloseButton(cssBigIcon('CrossBig'),
dom.on('click', close),
testId('example-card-close'),
),
testId('example-card'),
);
document.body.appendChild(cardElem);
// When reopening, open the card smoothly, for a nicer-looking effect.
if (reopen) {
expandCard(cardElem, btnElem.getBoundingClientRect());
}
prevCardClose?.();
prevCardClose = () => disposeCard(cardElem);
}
function disposeCard(cardElem: HTMLElement) {
dom.domDispose(cardElem);
cardElem.remove();
}
// When closing the card, collapse it visually into the button that can open it again, to hint to
// the user where to find that button. Remove the card after the animation.
function collapseAndRemoveCard(card: HTMLElement, collapsedRect: DOMRect) {
const watcher = new TransitionWatcher(card);
watcher.onDispose(() => disposeCard(card));
collapseCard(card, collapsedRect);
}
// Implements the collapsing animation by simply setting a scale transform with a suitable origin.
function collapseCard(card: HTMLElement, collapsedRect: DOMRect) {
const rect = card.getBoundingClientRect();
const originX = (collapsedRect.left + collapsedRect.width / 2) - rect.left;
const originY = (collapsedRect.top + collapsedRect.height / 2) - rect.top;
Object.assign(card.style, {
transform: `scale(${collapsedRect.width / rect.width}, ${collapsedRect.height / rect.height})`,
transformOrigin: `${originX}px ${originY}px`,
opacity: '0',
});
}
// To expand the card visually, we reverse the process by collapsing it first with transitions
// disabled, then resetting properties to their defaults with transitions enabled again.
function expandCard(card: HTMLElement, collapsedRect: DOMRect) {
prepareForTransition(card, () => collapseCard(card, collapsedRect));
Object.assign(card.style, {
transform: '',
opacity: '',
visibility: 'visible',
});
}
const cssCard = styled('div', `
position: absolute;
left: 24px;
bottom: 24px;
margin-right: 24px;
max-width: 624px;
padding: 32px 56px 32px 32px;
background-color: white;
box-shadow: 0 2px 18px 0 rgba(31,37,50,0.31), 0 0 1px 0 rgba(76,86,103,0.24);
display: flex;
overflow: hidden;
transition-property: opacity, transform;
transition-duration: 0.5s;
transition-timing-func: ease-in;
--title-font-size: ${vars.headerControlFontSize};
@media ${mediaXSmall} {
& {
flex-direction: column;
padding: 32px;
--title-font-size: 18px;
}
}
`);
const cssImage = styled('img', `
flex: none;
width: 180px;
height: 140px;
margin: 0 16px 0 -8px;
@media ${mediaXSmall} {
& {
margin: auto;
}
}
`);
const cssBody = styled('div', `
min-width: 0px;
`);
const cssTitle = styled('div', `
font-size: var(--title-font-size);
font-weight: ${vars.headerControlTextWeight};
margin-bottom: 16px;
`);
const cssInfo = styled('div', `
margin: 16px 0 24px 0;
line-height: 1.6;
`);
export const cssButtons = styled('div', `
display: flex;
`);
export const cssLinkBtn = styled(cssLink, `
&:not(:last-child) {
margin-right: 32px;
}
`);
export const cssLinkIcon = styled(icon, `
margin-right: 8px;
margin-top: -2px;
`);
const cssCloseButton = styled('div', `
position: absolute;
top: 8px;
right: 8px;
padding: 4px;
border-radius: 4px;
cursor: pointer;
--icon-color: ${colors.slate};
&:hover {
background-color: ${colors.mediumGreyOpaque};
}
`);
const cssBigIcon = styled(icon, `
padding: 12px;
`);