@servicetitan/feature-spotlight 1.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.
@@ -0,0 +1 @@
1
+ <!doctype html><html><head><meta charset="utf-8"><title>ServiceTitan</title><meta name="viewport" content="width=device-width,initial-scale=1"><script defer="defer" src="runtime.fc8958b2.bundle.js?bd39b39acdeeee4ffef3"></script><script defer="defer" src="main.7cdd1723.bundle.js?bd39b39acdeeee4ffef3"></script></head><body></body></html>
@@ -0,0 +1 @@
1
+ "use strict";(self.webpackChunk_servicetitan_feature_spotlight=self.webpackChunk_servicetitan_feature_spotlight||[]).push([[0],[()=>{}],e=>{e(e.s=0)}]);
@@ -0,0 +1,2 @@
1
+ (()=>{"use strict";var r,e={},t={};function o(r){var i=t[r];if(void 0!==i)return i.exports;var n=t[r]={exports:{}};return e[r](n,n.exports,o),n.exports}o.m=e,r=[],o.O=(e,t,i,n)=>{if(!t){var a=1/0;for(v=0;v<r.length;v++){for(var[t,i,n]=r[v],s=!0,f=0;f<t.length;f++)(!1&n||a>=n)&&Object.keys(o.O).every((r=>o.O[r](t[f])))?t.splice(f--,1):(s=!1,n<a&&(a=n));if(s){r.splice(v--,1);var l=i();void 0!==l&&(e=l)}}return e}n=n||0;for(var v=r.length;v>0&&r[v-1][2]>n;v--)r[v]=r[v-1];r[v]=[t,i,n]},o.o=(r,e)=>Object.prototype.hasOwnProperty.call(r,e),(()=>{var r={1:0};o.O.j=e=>0===r[e];var e=(e,t)=>{var i,n,[a,s,f]=t,l=0;if(a.some((e=>0!==r[e]))){for(i in s)o.o(s,i)&&(o.m[i]=s[i]);if(f)var v=f(o)}for(e&&e(t);l<a.length;l++)n=a[l],o.o(r,n)&&r[n]&&r[n][0](),r[n]=0;return o.O(v)},t=self.webpackChunk_servicetitan_feature_spotlight=self.webpackChunk_servicetitan_feature_spotlight||[];t.forEach(e.bind(null,0)),t.push=e.bind(null,t.push.bind(t))})()})();
2
+ //# sourceMappingURL=runtime.fc8958b2.bundle.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime.fc8958b2.bundle.js","mappings":"uBAAIA,E,KCCAC,EAA2B,CAAC,EAGhC,SAASC,EAAoBC,GAE5B,IAAIC,EAAeH,EAAyBE,GAC5C,QAAqBE,IAAjBD,EACH,OAAOA,EAAaE,QAGrB,IAAIC,EAASN,EAAyBE,GAAY,CAGjDG,QAAS,CAAC,GAOX,OAHAE,EAAoBL,GAAUI,EAAQA,EAAOD,QAASJ,GAG/CK,EAAOD,OACf,CAGAJ,EAAoBO,EAAID,EDzBpBR,EAAW,GACfE,EAAoBQ,EAAI,CAACC,EAAQC,EAAUC,EAAIC,KAC9C,IAAGF,EAAH,CAMA,IAAIG,EAAeC,IACnB,IAASC,EAAI,EAAGA,EAAIjB,EAASkB,OAAQD,IAAK,CAGzC,IAFA,IAAKL,EAAUC,EAAIC,GAAYd,EAASiB,GACpCE,GAAY,EACPC,EAAI,EAAGA,EAAIR,EAASM,OAAQE,MACpB,EAAXN,GAAsBC,GAAgBD,IAAaO,OAAOC,KAAKpB,EAAoBQ,GAAGa,OAAOC,GAAStB,EAAoBQ,EAAEc,GAAKZ,EAASQ,MAC9IR,EAASa,OAAOL,IAAK,IAErBD,GAAY,EACTL,EAAWC,IAAcA,EAAeD,IAG7C,GAAGK,EAAW,CACbnB,EAASyB,OAAOR,IAAK,GACrB,IAAIS,EAAIb,SACER,IAANqB,IAAiBf,EAASe,EAC/B,CACD,CACA,OAAOf,CAnBP,CAJCG,EAAWA,GAAY,EACvB,IAAI,IAAIG,EAAIjB,EAASkB,OAAQD,EAAI,GAAKjB,EAASiB,EAAI,GAAG,GAAKH,EAAUG,IAAKjB,EAASiB,GAAKjB,EAASiB,EAAI,GACrGjB,EAASiB,GAAK,CAACL,EAAUC,EAAIC,EAqBjB,EE1BdZ,EAAoByB,EAAI,CAACC,EAAKC,IAAUR,OAAOS,UAAUC,eAAeC,KAAKJ,EAAKC,G,MCKlF,IAAII,EAAkB,CACrB,EAAG,GAaJ/B,EAAoBQ,EAAEU,EAAKc,GAA0C,IAA7BD,EAAgBC,GAGxD,IAAIC,EAAuB,CAACC,EAA4BC,KACvD,IAGIlC,EAAU+B,GAHTtB,EAAU0B,EAAaC,GAAWF,EAGhBpB,EAAI,EAC3B,GAAGL,EAAS4B,MAAMC,GAAgC,IAAxBR,EAAgBQ,KAAa,CACtD,IAAItC,KAAYmC,EACZpC,EAAoByB,EAAEW,EAAanC,KACrCD,EAAoBO,EAAEN,GAAYmC,EAAYnC,IAGhD,GAAGoC,EAAS,IAAI5B,EAAS4B,EAAQrC,EAClC,CAEA,IADGkC,GAA4BA,EAA2BC,GACrDpB,EAAIL,EAASM,OAAQD,IACzBiB,EAAUtB,EAASK,GAChBf,EAAoByB,EAAEM,EAAiBC,IAAYD,EAAgBC,IACrED,EAAgBC,GAAS,KAE1BD,EAAgBC,GAAW,EAE5B,OAAOhC,EAAoBQ,EAAEC,EAAO,EAGjC+B,EAAqBC,KAAkD,4CAAIA,KAAkD,6CAAK,GACtID,EAAmBE,QAAQT,EAAqBU,KAAK,KAAM,IAC3DH,EAAmBI,KAAOX,EAAqBU,KAAK,KAAMH,EAAmBI,KAAKD,KAAKH,G","sources":["webpack://@servicetitan/feature-spotlight/webpack/runtime/chunk loaded","webpack://@servicetitan/feature-spotlight/webpack/bootstrap","webpack://@servicetitan/feature-spotlight/webpack/runtime/hasOwnProperty shorthand","webpack://@servicetitan/feature-spotlight/webpack/runtime/jsonp chunk loading"],"sourcesContent":["var deferred = [];\n__webpack_require__.O = (result, chunkIds, fn, priority) => {\n\tif(chunkIds) {\n\t\tpriority = priority || 0;\n\t\tfor(var i = deferred.length; i > 0 && deferred[i - 1][2] > priority; i--) deferred[i] = deferred[i - 1];\n\t\tdeferred[i] = [chunkIds, fn, priority];\n\t\treturn;\n\t}\n\tvar notFulfilled = Infinity;\n\tfor (var i = 0; i < deferred.length; i++) {\n\t\tvar [chunkIds, fn, priority] = deferred[i];\n\t\tvar fulfilled = true;\n\t\tfor (var j = 0; j < chunkIds.length; j++) {\n\t\t\tif ((priority & 1 === 0 || notFulfilled >= priority) && Object.keys(__webpack_require__.O).every((key) => (__webpack_require__.O[key](chunkIds[j])))) {\n\t\t\t\tchunkIds.splice(j--, 1);\n\t\t\t} else {\n\t\t\t\tfulfilled = false;\n\t\t\t\tif(priority < notFulfilled) notFulfilled = priority;\n\t\t\t}\n\t\t}\n\t\tif(fulfilled) {\n\t\t\tdeferred.splice(i--, 1)\n\t\t\tvar r = fn();\n\t\t\tif (r !== undefined) result = r;\n\t\t}\n\t}\n\treturn result;\n};","// The module cache\nvar __webpack_module_cache__ = {};\n\n// The require function\nfunction __webpack_require__(moduleId) {\n\t// Check if module is in cache\n\tvar cachedModule = __webpack_module_cache__[moduleId];\n\tif (cachedModule !== undefined) {\n\t\treturn cachedModule.exports;\n\t}\n\t// Create a new module (and put it into the cache)\n\tvar module = __webpack_module_cache__[moduleId] = {\n\t\t// no module.id needed\n\t\t// no module.loaded needed\n\t\texports: {}\n\t};\n\n\t// Execute the module function\n\t__webpack_modules__[moduleId](module, module.exports, __webpack_require__);\n\n\t// Return the exports of the module\n\treturn module.exports;\n}\n\n// expose the modules object (__webpack_modules__)\n__webpack_require__.m = __webpack_modules__;\n\n","__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))","// no baseURI\n\n// object to store loaded and loading chunks\n// undefined = chunk not loaded, null = chunk preloaded/prefetched\n// [resolve, reject, Promise] = chunk loading, 0 = chunk loaded\nvar installedChunks = {\n\t1: 0\n};\n\n// no chunk on demand loading\n\n// no prefetching\n\n// no preloaded\n\n// no HMR\n\n// no HMR manifest\n\n__webpack_require__.O.j = (chunkId) => (installedChunks[chunkId] === 0);\n\n// install a JSONP callback for chunk loading\nvar webpackJsonpCallback = (parentChunkLoadingFunction, data) => {\n\tvar [chunkIds, moreModules, runtime] = data;\n\t// add \"moreModules\" to the modules object,\n\t// then flag all \"chunkIds\" as loaded and fire callback\n\tvar moduleId, chunkId, i = 0;\n\tif(chunkIds.some((id) => (installedChunks[id] !== 0))) {\n\t\tfor(moduleId in moreModules) {\n\t\t\tif(__webpack_require__.o(moreModules, moduleId)) {\n\t\t\t\t__webpack_require__.m[moduleId] = moreModules[moduleId];\n\t\t\t}\n\t\t}\n\t\tif(runtime) var result = runtime(__webpack_require__);\n\t}\n\tif(parentChunkLoadingFunction) parentChunkLoadingFunction(data);\n\tfor(;i < chunkIds.length; i++) {\n\t\tchunkId = chunkIds[i];\n\t\tif(__webpack_require__.o(installedChunks, chunkId) && installedChunks[chunkId]) {\n\t\t\tinstalledChunks[chunkId][0]();\n\t\t}\n\t\tinstalledChunks[chunkId] = 0;\n\t}\n\treturn __webpack_require__.O(result);\n}\n\nvar chunkLoadingGlobal = self[\"webpackChunk_servicetitan_feature_spotlight\"] = self[\"webpackChunk_servicetitan_feature_spotlight\"] || [];\nchunkLoadingGlobal.forEach(webpackJsonpCallback.bind(null, 0));\nchunkLoadingGlobal.push = webpackJsonpCallback.bind(null, chunkLoadingGlobal.push.bind(chunkLoadingGlobal));"],"names":["deferred","__webpack_module_cache__","__webpack_require__","moduleId","cachedModule","undefined","exports","module","__webpack_modules__","m","O","result","chunkIds","fn","priority","notFulfilled","Infinity","i","length","fulfilled","j","Object","keys","every","key","splice","r","o","obj","prop","prototype","hasOwnProperty","call","installedChunks","chunkId","webpackJsonpCallback","parentChunkLoadingFunction","data","moreModules","runtime","some","id","chunkLoadingGlobal","self","forEach","bind","push"],"sourceRoot":""}
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "@servicetitan/feature-spotlight",
3
+ "version": "1.1.0",
4
+ "description": "Provides specific styles to Anvil components to highlight a feature",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "https://github.com/servicetitan/anvil-uikit-contrib",
8
+ "directory": "packages/marketing-ui"
9
+ },
10
+ "main": "./dist/index.js",
11
+ "typings": "./dist/index.d.ts",
12
+ "sideEffects": false,
13
+ "files": [
14
+ "dist",
15
+ "src"
16
+ ],
17
+ "devDependencies": {
18
+ "@servicetitan/design-system": ">=12.4.1",
19
+ "@types/history": "~4.7.10",
20
+ "@types/react": "~17.0.38",
21
+ "@types/react-router": "~5.1.17",
22
+ "react": "~17.0.2",
23
+ "react-router-dom": "~5.3.0"
24
+ },
25
+ "peerDependencies": {
26
+ "@servicetitan/design-system": ">=12.4.1",
27
+ "react": "~17.0.2",
28
+ "react-router-dom": "~5.3.0"
29
+ },
30
+ "dependencies": {
31
+ "@reactour/tour": "^3.1.4",
32
+ "classnames": "~2.3.1"
33
+ },
34
+ "publishConfig": {
35
+ "access": "public"
36
+ },
37
+ "gitHead": "1acc525c3516017ff8dc26cfe8a3988ec76678d4"
38
+ }
@@ -0,0 +1,8 @@
1
+ import { FC } from 'react';
2
+ import classNames from 'classnames';
3
+ import { Modal, ModalProps } from '@servicetitan/design-system';
4
+ import * as Styles from '../feature-spotlight.module.css';
5
+
6
+ export const FeatureModal: FC<ModalProps> = ({ ...rest }) => {
7
+ return <Modal className={classNames(Styles.featureModalHeader)} {...rest} />;
8
+ };
@@ -0,0 +1,33 @@
1
+ import { FC } from 'react';
2
+ import classNames from 'classnames';
3
+ import { Popover, PopoverProps } from '@servicetitan/design-system';
4
+ import * as Styles from '../feature-spotlight.module.css';
5
+
6
+ export interface FeaturePopoverProps extends PopoverProps {
7
+ footer?: PopoverProps['footer'];
8
+ header?: PopoverProps['header'];
9
+ children?: PopoverProps['children'];
10
+ }
11
+
12
+ export const FeaturePopover: FC<FeaturePopoverProps> = ({ children, footer, header, ...rest }) => {
13
+ return (
14
+ <Popover
15
+ className={classNames(Styles.featurePopover)}
16
+ caret={{ show: true, className: Styles.featurePopoverCaret }}
17
+ padding="m"
18
+ {...rest}
19
+ >
20
+ {header && (
21
+ <Popover.Header className={classNames(Styles.featurePopoverHeader)}>
22
+ {header}
23
+ </Popover.Header>
24
+ )}
25
+ {children}
26
+ {footer && (
27
+ <Popover.Footer className={classNames(Styles.featurePopoverFooter)} align="right">
28
+ {footer}
29
+ </Popover.Footer>
30
+ )}
31
+ </Popover>
32
+ );
33
+ };
@@ -0,0 +1,17 @@
1
+ import { FC } from 'react';
2
+ import classNames from 'classnames';
3
+ import { Toast, ToastProps } from '@servicetitan/design-system';
4
+ import * as Styles from '../feature-spotlight.module.css';
5
+
6
+ export interface FeatureToastProps extends ToastProps {
7
+ center?: boolean;
8
+ }
9
+
10
+ export const FeatureToast: FC<FeatureToastProps> = ({ center, ...rest }) => {
11
+ return (
12
+ <Toast
13
+ className={classNames(Styles.featureToast, center && Styles.featureToastCenter)}
14
+ {...rest}
15
+ />
16
+ );
17
+ };
@@ -0,0 +1,68 @@
1
+ import { FC, SyntheticEvent, ReactNode } from 'react';
2
+ import classNames from 'classnames';
3
+ import {
4
+ PopoverProps,
5
+ Stack,
6
+ Headline,
7
+ BodyText,
8
+ Button,
9
+ ButtonGroup,
10
+ } from '@servicetitan/design-system';
11
+ import * as Styles from '../feature-spotlight.module.css';
12
+
13
+ export interface TourPopoverProps extends PopoverProps {
14
+ children?: ReactNode;
15
+ currentStep?: number;
16
+ endText?: string;
17
+ headerText?: string;
18
+ nextText?: string;
19
+ onClose?: (e?: SyntheticEvent<HTMLElement>) => void;
20
+ onNext?: (e?: SyntheticEvent<HTMLElement>) => void;
21
+ totalStepCount?: number;
22
+ }
23
+
24
+ export const TourPopover: FC<TourPopoverProps> = ({
25
+ children,
26
+ currentStep = 1,
27
+ endText = 'Close',
28
+ headerText,
29
+ nextText = 'Next',
30
+ onClose,
31
+ onNext,
32
+ totalStepCount = 1,
33
+ }) => {
34
+ return (
35
+ <Stack
36
+ direction="column"
37
+ className={classNames(
38
+ Styles.featurePopover,
39
+ 'Popper__body Popover__body Popover__body--open Popover--w-s p-x-3 p-y-2'
40
+ )}
41
+ >
42
+ <Stack.Item className="m-t-half">
43
+ {headerText && <Headline size="small">{headerText}</Headline>}
44
+ {children}
45
+ <Stack
46
+ direction="row"
47
+ alignItems="center"
48
+ justifyContent="space-between"
49
+ className="m-t-2 m-b-half"
50
+ >
51
+ <BodyText subdued>
52
+ {currentStep + 1}/{totalStepCount}
53
+ </BodyText>
54
+ <ButtonGroup>
55
+ {currentStep !== totalStepCount - 1 && (
56
+ <Button fill="subtle" size="small" onClick={onClose}>
57
+ Skip
58
+ </Button>
59
+ )}
60
+ <Button size="small" onClick={onNext} primary>
61
+ {currentStep !== totalStepCount - 1 ? nextText : endText}
62
+ </Button>
63
+ </ButtonGroup>
64
+ </Stack>
65
+ </Stack.Item>
66
+ </Stack>
67
+ );
68
+ };
@@ -0,0 +1,74 @@
1
+ /* Popover-specific */
2
+
3
+ .feature-popover {
4
+ border-color: var(--colorsSurfacePrimarySubdued, var(--color-blue));
5
+ }
6
+
7
+ .feature-popover .featurePopoverHeader {
8
+ border: none;
9
+ padding: var(--spacing-half) 0 var(--spacing-1);
10
+ }
11
+
12
+ .feature-popover .featurePopoverFooter {
13
+ border: none;
14
+ padding: var(--spacing-2) 0 var(--spacing-half);
15
+ }
16
+
17
+ .feature-popover-caret::before {
18
+ background: var(--colorsSurfaceDefault, var(--color-white));
19
+ box-shadow: -1px 1px var(--colorsSurfacePrimarySubdued, var(--color-blue));
20
+ }
21
+
22
+ /* Creates the Popover caret's border, which changes based on the position of the caret */
23
+
24
+ [data-popper-placement^='top'] .feature-popover-caret::before {
25
+ box-shadow: 1px 1px var(--colorsSurfacePrimarySubdued, var(--color-blue));
26
+ }
27
+
28
+ [data-popper-placement^='bottom'] .feature-popover-caret::before {
29
+ box-shadow: -1px -1px var(--colorsSurfacePrimarySubdued, var(--color-blue));
30
+ }
31
+
32
+ [data-popper-placement^='right'] .feature-popover-caret::before {
33
+ box-shadow: -1px 1px var(--colorsSurfacePrimarySubdued, var(--color-blue));
34
+ }
35
+
36
+ [data-popper-placement^='left'] .feature-popover-caret::before {
37
+ box-shadow: 1px -1px var(--colorsSurfacePrimarySubdued, var(--color-blue));
38
+ }
39
+
40
+ /* Modal-specific */
41
+ .feature-modal-header :global(.Modal__header) {
42
+ background: var(--colorsSurfacePrimarySubdued, var(--color-blue));
43
+ color: white;
44
+ fill: white;
45
+ }
46
+ .feature-modal-header :global(.Modal__header > *) {
47
+ color: var(--colorsTextInverted, var(--color-white));
48
+ fill: var(--colorsTextInverted, var(--color-white));
49
+ }
50
+
51
+ /* Toast-specific */
52
+ .feature-toast {
53
+ border-color: var(--colorsSurfacePrimarySubdued, var(--color-blue));
54
+ }
55
+ .feature-toast :global(.Toast__icon) {
56
+ display: none;
57
+ }
58
+ .feature-toast :global(.Toast__text) {
59
+ min-width: auto;
60
+ }
61
+ .feature-toast :global(.Toast__title) {
62
+ color: var(--colorsText, var(--color-black));
63
+ }
64
+
65
+ .feature-toast-center {
66
+ text-align: center;
67
+ }
68
+ .feature-toast-center :global(.Toast__action) {
69
+ text-align: center;
70
+ }
71
+
72
+ .feature-toast-center :global(.Toast__title) {
73
+ padding-left: var(--spacing-5);
74
+ }
@@ -0,0 +1,9 @@
1
+ export const __esModule: true;
2
+ export const featurePopover: string;
3
+ export const featurePopoverHeader: string;
4
+ export const featurePopoverFooter: string;
5
+ export const featurePopoverCaret: string;
6
+ export const featureModalHeader: string;
7
+ export const featureToast: string;
8
+ export const featureToastCenter: string;
9
+
@@ -0,0 +1,378 @@
1
+ import React, { FC } from 'react';
2
+ import {
3
+ BodyText,
4
+ Stack,
5
+ Button,
6
+ ButtonGroup,
7
+ Link,
8
+ Checkbox,
9
+ Toast,
10
+ Frame,
11
+ Headline,
12
+ Page,
13
+ Layout,
14
+ Card,
15
+ } from '@servicetitan/design-system';
16
+ import { FeaturePopover } from './components/feature-popover';
17
+ import { FeatureModal } from './components/feature-modal';
18
+ import { FeatureToast } from './components/feature-toast';
19
+ import { TourPopover } from './components/tour-popover';
20
+ import geofence from '../../../storybook-static/geofence.png';
21
+ import dashboard from '../../../storybook-static/dashboard.png';
22
+
23
+ import { TourProvider, StepType, useTour } from '@reactour/tour';
24
+
25
+ const image = {
26
+ src: geofence,
27
+ alt: 'alt text description',
28
+ };
29
+
30
+ const imageDashboard = {
31
+ src: dashboard,
32
+ alt: 'alt text description',
33
+ };
34
+
35
+ export default {
36
+ title: 'Feature Spotlight/Demos',
37
+ };
38
+
39
+ export const Popover = () => (
40
+ <React.Fragment>
41
+ <Stack
42
+ direction="column"
43
+ spacing={5}
44
+ alignItems="center"
45
+ justifyContent="center"
46
+ className="p-4"
47
+ style={{ height: '800px' }}
48
+ >
49
+ <FeaturePopover
50
+ trigger={<Button inactive>Top</Button>}
51
+ open
52
+ direction="t"
53
+ header="👋 Bills has moved"
54
+ footer={
55
+ <ButtonGroup>
56
+ <Button small primary>
57
+ Take me there
58
+ </Button>
59
+ </ButtonGroup>
60
+ }
61
+ footerAlign="right"
62
+ offset={{ distance: 200 }}
63
+ >
64
+ <Stack spacing={1} direction="column">
65
+ <BodyText size="small">
66
+ Bills has a new home in Accounting. It is still the same feature you know
67
+ and love except now something goes here.
68
+ <Link primary>Learn More</Link>
69
+ </BodyText>
70
+ <Checkbox size="small" label="Don’t show me again" />
71
+ </Stack>
72
+ </FeaturePopover>
73
+
74
+ <FeaturePopover
75
+ trigger={<Button inactive>Right</Button>}
76
+ open
77
+ direction="r"
78
+ header="👋 Bills has moved"
79
+ footer={
80
+ <ButtonGroup>
81
+ <Button small primary>
82
+ Take me there
83
+ </Button>
84
+ </ButtonGroup>
85
+ }
86
+ footerAlign="right"
87
+ offset={{ distance: 200 }}
88
+ >
89
+ <Stack spacing={1} direction="column">
90
+ <BodyText size="small">
91
+ Bills has a new home in Accounting. It is still the same feature you know
92
+ and love except now something goes here.
93
+ <Link primary>Learn More</Link>
94
+ </BodyText>
95
+ <Checkbox size="small" label="Don’t show me again" />
96
+ </Stack>
97
+ </FeaturePopover>
98
+
99
+ <FeaturePopover
100
+ trigger={<Button inactive>Left</Button>}
101
+ open
102
+ direction="l"
103
+ header="👋 Bills has moved"
104
+ footer={
105
+ <ButtonGroup>
106
+ <Button small primary>
107
+ Take me there
108
+ </Button>
109
+ </ButtonGroup>
110
+ }
111
+ footerAlign="right"
112
+ offset={{ distance: 200 }}
113
+ >
114
+ <Stack spacing={1} direction="column">
115
+ <BodyText size="small">
116
+ Bills has a new home in Accounting. It is still the same feature you know
117
+ and love except now something goes here.
118
+ <Link primary>Learn More</Link>
119
+ </BodyText>
120
+ <Checkbox size="small" label="Don’t show me again" />
121
+ </Stack>
122
+ </FeaturePopover>
123
+
124
+ <FeaturePopover
125
+ trigger={<Button inactive>Bottom</Button>}
126
+ open
127
+ direction="b"
128
+ header="👋 Bills has moved"
129
+ footer={
130
+ <ButtonGroup>
131
+ <Button small primary>
132
+ Take me there
133
+ </Button>
134
+ </ButtonGroup>
135
+ }
136
+ footerAlign="right"
137
+ offset={{ distance: 200 }}
138
+ >
139
+ <Stack spacing={1} direction="column">
140
+ <BodyText size="small">
141
+ Bills has a new home in Accounting. It is still the same feature you know
142
+ and love except now something goes here.
143
+ <Link primary>Learn More</Link>
144
+ </BodyText>
145
+ <Checkbox size="small" label="Don’t show me again" />
146
+ </Stack>
147
+ </FeaturePopover>
148
+ </Stack>
149
+ </React.Fragment>
150
+ );
151
+
152
+ const closeMethod = () => alert('closing');
153
+
154
+ export const modal = (args: any) => <FeatureModal {...args} />;
155
+ modal.args = {
156
+ open: true,
157
+ title: 'FleetPro Has Launched!',
158
+ closable: true,
159
+ onClose: closeMethod,
160
+ scrollable: true,
161
+ children: (
162
+ <Stack direction="column" alignItems="center" spacing={4}>
163
+ <img src={image.src} alt={image.alt} className="p-t-4" />
164
+ <BodyText size="large" className="ta-center">
165
+ We’re excited to announce that FleetPro is now live in ServiceTitan! With FleetPro,
166
+ you can manage and track your company’s vehicles all in one place. Add FleetPro to
167
+ your existing tenant now for $100 /mo or view the{' '}
168
+ <Link primary>Knowledge Base</Link> to learn more.
169
+ </BodyText>
170
+ </Stack>
171
+ ),
172
+ footer: (
173
+ <ButtonGroup>
174
+ <Button onClick={closeMethod}>No, Thank You</Button>
175
+ <Button primary onClick={closeMethod}>
176
+ Add FleetPro Now
177
+ </Button>
178
+ </ButtonGroup>
179
+ ),
180
+ };
181
+
182
+ export const toast = () => {
183
+ return (
184
+ <Frame>
185
+ <Toast.Group>
186
+ <FeatureToast
187
+ title="✨ This page has a new look and feel"
188
+ onPrimaryActionClick={() => {}}
189
+ primaryActionName="Got it"
190
+ onSecondaryActionClick={() => {}}
191
+ secondaryActionName="Revert to Old View"
192
+ onClose={closeMethod}
193
+ portal={false}
194
+ >
195
+ <BodyText>
196
+ We’ve updated this page with some cool stuff...but if you don’t like it you
197
+ can go back to the old one for now. This can be changed later in settings.
198
+ </BodyText>
199
+ </FeatureToast>
200
+ <Toast status="info" title="Ordinary toast" portal={false}>
201
+ In progress
202
+ </Toast>
203
+ <FeatureToast
204
+ center
205
+ title="Dashboards"
206
+ onPrimaryActionClick={() => {}}
207
+ primaryActionName="Learn more"
208
+ onClose={closeMethod}
209
+ portal={false}
210
+ >
211
+ <Stack direction="column" spacing={2} alignItems="center">
212
+ <img
213
+ src={imageDashboard.src}
214
+ alt={image.alt}
215
+ width="166px"
216
+ className="p-t-1"
217
+ />
218
+ <BodyText>
219
+ We noticed you don’t really use this thing, but did you know it could
220
+ increase your profit margins by 10%?
221
+ </BodyText>
222
+ </Stack>
223
+ </FeatureToast>
224
+ <FeatureToast
225
+ center
226
+ title="Slightly longer line that wraps to a second"
227
+ onPrimaryActionClick={() => {}}
228
+ primaryActionName="Learn More"
229
+ onClose={closeMethod}
230
+ portal={false}
231
+ >
232
+ <Stack direction="column" spacing={2} alignItems="center">
233
+ <img
234
+ src={imageDashboard.src}
235
+ alt={image.alt}
236
+ width="166px"
237
+ className="p-t-1"
238
+ />
239
+ <BodyText>
240
+ We noticed you don’t really use this thing, but did you know it could
241
+ increase your profit margins by 10%?
242
+ </BodyText>
243
+ </Stack>
244
+ </FeatureToast>
245
+ </Toast.Group>
246
+ </Frame>
247
+ );
248
+ };
249
+
250
+ interface InternalTourProps {
251
+ children?: React.ReactNode;
252
+ headerText?: string;
253
+ }
254
+
255
+ const InternalTourControls: FC<InternalTourProps> = React.memo(({ children, headerText }) => {
256
+ const { setCurrentStep, currentStep, steps, setIsOpen } = useTour();
257
+
258
+ const handleClose = React.useCallback(() => {
259
+ setIsOpen(false);
260
+ }, [setIsOpen]);
261
+
262
+ const handleNext = React.useCallback(() => {
263
+ if (currentStep === steps.length - 1) {
264
+ handleClose();
265
+ } else {
266
+ setCurrentStep(currentStep + 1);
267
+ }
268
+ }, [currentStep, handleClose, setCurrentStep, steps.length]);
269
+
270
+ return (
271
+ <TourPopover
272
+ currentStep={currentStep}
273
+ onClose={handleClose}
274
+ onNext={handleNext}
275
+ totalStepCount={steps.length}
276
+ headerText={headerText}
277
+ >
278
+ {children}
279
+ </TourPopover>
280
+ );
281
+ });
282
+
283
+ const ReactTour: FC = ({ children }) => {
284
+ const steps: StepType[] = [
285
+ {
286
+ selector: '.step-1',
287
+ content: (
288
+ <InternalTourControls headerText="⚠️ This is now a required field">
289
+ <BodyText size="small" className="m-b-2">
290
+ You must now select a date range beforehand because of reasons.
291
+ </BodyText>
292
+ <BodyText size="small">
293
+ <Link primary>Learn More</Link>
294
+ </BodyText>
295
+ </InternalTourControls>
296
+ ),
297
+ },
298
+ {
299
+ selector: '.step-2',
300
+ content: (
301
+ <InternalTourControls headerText="Give it up for step 2"></InternalTourControls>
302
+ ),
303
+ },
304
+ {
305
+ selector: '.step-3',
306
+ content: (
307
+ <InternalTourControls>
308
+ This is the final step with just body text.
309
+ </InternalTourControls>
310
+ ),
311
+ },
312
+ ];
313
+
314
+ const styles = {
315
+ popover: (base: any) => ({
316
+ ...base,
317
+ borderRadius: '6px',
318
+ padding: 'inherit',
319
+ margin: 'inherit',
320
+ maxWidth: 'inherit',
321
+ }),
322
+ };
323
+
324
+ return (
325
+ <TourProvider
326
+ defaultOpen
327
+ steps={steps}
328
+ showBadge={false}
329
+ showPrevNextButtons={false}
330
+ showCloseButton={false}
331
+ showNavigation={false}
332
+ showDots={false}
333
+ styles={styles}
334
+ >
335
+ {children}
336
+ </TourProvider>
337
+ );
338
+ };
339
+
340
+ const StartTourButton = () => {
341
+ const { setIsOpen, setCurrentStep } = useTour();
342
+
343
+ const handleTourClick = React.useCallback(() => {
344
+ setCurrentStep(0);
345
+ setIsOpen(true);
346
+ }, [setCurrentStep, setIsOpen]);
347
+
348
+ return <Button onClick={handleTourClick}>Start Tour</Button>;
349
+ };
350
+
351
+ export const tour = () => {
352
+ return (
353
+ <Frame>
354
+ <ReactTour>
355
+ <Page
356
+ header={
357
+ <Stack justifyContent="space-between" alignItems="center">
358
+ <Headline size="large" className="step-3">Page Title</Headline>
359
+ <StartTourButton />
360
+ </Stack>
361
+ }
362
+ >
363
+ <Layout type="3Col">
364
+ <Layout.Section>
365
+ <Card className="step-1">Card 1</Card>
366
+ </Layout.Section>
367
+ <Layout.Section>
368
+ <Card className="step-2">Card 2</Card>
369
+ </Layout.Section>
370
+ <Layout.Section>
371
+ <Card>Card 3</Card>
372
+ </Layout.Section>
373
+ </Layout>
374
+ </Page>
375
+ </ReactTour>
376
+ </Frame>
377
+ );
378
+ };
package/src/index.ts ADDED
@@ -0,0 +1,3 @@
1
+ export * from './components/feature-popover';
2
+ export * from './components/feature-modal';
3
+ export * from './components/feature-toast';