@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.
- package/dist/bundle/index.html +1 -0
- package/dist/bundle/main.7cdd1723.bundle.js +1 -0
- package/dist/bundle/runtime.fc8958b2.bundle.js +2 -0
- package/dist/bundle/runtime.fc8958b2.bundle.js.map +1 -0
- package/package.json +38 -0
- package/src/components/feature-modal.tsx +8 -0
- package/src/components/feature-popover.tsx +33 -0
- package/src/components/feature-toast.tsx +17 -0
- package/src/components/tour-popover.tsx +68 -0
- package/src/feature-spotlight.module.css +74 -0
- package/src/feature-spotlight.module.css.d.ts +9 -0
- package/src/feature-spotlight.stories.tsx +378 -0
- package/src/index.ts +3 -0
|
@@ -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