@automattic/vip-design-system 2.11.1 → 2.12.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/build/system/Snackbar/Snackbar.d.ts +29 -0
- package/build/system/Snackbar/Snackbar.js +166 -0
- package/build/system/Snackbar/Snackbar.stories.d.ts +25 -0
- package/build/system/Snackbar/Snackbar.stories.js +82 -0
- package/build/system/Snackbar/Snackbar.test.d.ts +2 -0
- package/build/system/Snackbar/Snackbar.test.js +116 -0
- package/build/system/Snackbar/index.d.ts +5 -0
- package/build/system/Snackbar/index.js +5 -0
- package/build/system/index.d.ts +2 -1
- package/build/system/index.js +2 -0
- package/build/system/theme/index.js +11 -0
- package/package.json +1 -1
- package/src/system/Snackbar/Snackbar.stories.tsx +86 -0
- package/src/system/Snackbar/Snackbar.test.tsx +90 -0
- package/src/system/Snackbar/Snackbar.tsx +166 -0
- package/src/system/Snackbar/index.ts +6 -0
- package/src/system/index.js +2 -0
- package/src/system/theme/index.js +11 -0
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/** @jsxImportSource theme-ui */
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { ThemeUIStyleObject } from 'theme-ui';
|
|
4
|
+
export type SnackbarProps = React.HTMLAttributes<HTMLDivElement> & {
|
|
5
|
+
children?: React.ReactNode;
|
|
6
|
+
sx?: ThemeUIStyleObject;
|
|
7
|
+
title?: React.ReactNode;
|
|
8
|
+
variant?: ColorVariants;
|
|
9
|
+
loading?: boolean;
|
|
10
|
+
isDismissable?: boolean;
|
|
11
|
+
className?: string;
|
|
12
|
+
ctaOnClick?: () => void;
|
|
13
|
+
ctaText?: string;
|
|
14
|
+
ctaHref?: string;
|
|
15
|
+
};
|
|
16
|
+
type ColorVariants = 'error' | 'info' | 'success' | 'system' | 'warning';
|
|
17
|
+
export declare const Snackbar: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLDivElement> & {
|
|
18
|
+
children?: React.ReactNode;
|
|
19
|
+
sx?: ThemeUIStyleObject | undefined;
|
|
20
|
+
title?: React.ReactNode;
|
|
21
|
+
variant?: ColorVariants | undefined;
|
|
22
|
+
loading?: boolean | undefined;
|
|
23
|
+
isDismissable?: boolean | undefined;
|
|
24
|
+
className?: string | undefined;
|
|
25
|
+
ctaOnClick?: (() => void) | undefined;
|
|
26
|
+
ctaText?: string | undefined;
|
|
27
|
+
ctaHref?: string | undefined;
|
|
28
|
+
} & React.RefAttributes<HTMLDivElement>>;
|
|
29
|
+
export {};
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
var _excluded = ["children", "className", "sx", "title", "variant", "loading", "isDismissable", "ctaOnClick", "ctaText", "ctaHref"];
|
|
2
|
+
function _extends() { _extends = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
|
|
3
|
+
function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }
|
|
4
|
+
/** @jsxImportSource theme-ui */
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* External dependencies
|
|
8
|
+
*/
|
|
9
|
+
import classNames from 'classnames';
|
|
10
|
+
import React from 'react';
|
|
11
|
+
import { MdError, MdWarning, MdInfo, MdCheckCircle, MdLock } from 'react-icons/md';
|
|
12
|
+
import { Grid } from 'theme-ui';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Internal dependencies
|
|
16
|
+
*/
|
|
17
|
+
import { Box, Flex, Heading, Spinner } from '..';
|
|
18
|
+
import { jsx as _jsx } from "theme-ui/jsx-runtime";
|
|
19
|
+
import { jsxs as _jsxs } from "theme-ui/jsx-runtime";
|
|
20
|
+
var SnackbarIcon = function SnackbarIcon(_ref) {
|
|
21
|
+
var color = _ref.color,
|
|
22
|
+
variant = _ref.variant,
|
|
23
|
+
loading = _ref.loading;
|
|
24
|
+
var sx = {
|
|
25
|
+
color: color,
|
|
26
|
+
flex: '0 0 auto'
|
|
27
|
+
};
|
|
28
|
+
var size = 24;
|
|
29
|
+
if (loading) {
|
|
30
|
+
return _jsx(Spinner, {
|
|
31
|
+
strokeWidth: 3,
|
|
32
|
+
sx: sx,
|
|
33
|
+
size: size,
|
|
34
|
+
variant: "loading"
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
var elements = {
|
|
38
|
+
"default": MdWarning,
|
|
39
|
+
error: MdError,
|
|
40
|
+
info: MdInfo,
|
|
41
|
+
success: MdCheckCircle,
|
|
42
|
+
system: MdLock,
|
|
43
|
+
warning: MdWarning
|
|
44
|
+
};
|
|
45
|
+
var Element = elements[variant] || elements["default"];
|
|
46
|
+
return _jsx(Element, {
|
|
47
|
+
sx: sx,
|
|
48
|
+
size: size,
|
|
49
|
+
"aria-hidden": "true"
|
|
50
|
+
});
|
|
51
|
+
};
|
|
52
|
+
export var Snackbar = /*#__PURE__*/React.forwardRef(function (_ref2, forwardRef) {
|
|
53
|
+
var children = _ref2.children,
|
|
54
|
+
_ref2$className = _ref2.className,
|
|
55
|
+
className = _ref2$className === void 0 ? null : _ref2$className,
|
|
56
|
+
_ref2$sx = _ref2.sx,
|
|
57
|
+
sx = _ref2$sx === void 0 ? {} : _ref2$sx,
|
|
58
|
+
title = _ref2.title,
|
|
59
|
+
_ref2$variant = _ref2.variant,
|
|
60
|
+
variant = _ref2$variant === void 0 ? 'warning' : _ref2$variant,
|
|
61
|
+
_ref2$loading = _ref2.loading,
|
|
62
|
+
loading = _ref2$loading === void 0 ? false : _ref2$loading,
|
|
63
|
+
_ref2$isDismissable = _ref2.isDismissable,
|
|
64
|
+
isDismissable = _ref2$isDismissable === void 0 ? false : _ref2$isDismissable,
|
|
65
|
+
ctaOnClick = _ref2.ctaOnClick,
|
|
66
|
+
ctaText = _ref2.ctaText,
|
|
67
|
+
_ref2$ctaHref = _ref2.ctaHref,
|
|
68
|
+
ctaHref = _ref2$ctaHref === void 0 ? undefined : _ref2$ctaHref,
|
|
69
|
+
props = _objectWithoutPropertiesLoose(_ref2, _excluded);
|
|
70
|
+
var columns = ['24px', 'auto'];
|
|
71
|
+
var hasCta = ctaText && (ctaOnClick || ctaHref);
|
|
72
|
+
if (hasCta) {
|
|
73
|
+
columns.push('auto');
|
|
74
|
+
}
|
|
75
|
+
if (isDismissable) {
|
|
76
|
+
columns.push('24px');
|
|
77
|
+
}
|
|
78
|
+
return _jsxs(Grid, _extends({
|
|
79
|
+
columns: columns.join(' '),
|
|
80
|
+
variant: "notice",
|
|
81
|
+
sx: _extends({
|
|
82
|
+
p: 4,
|
|
83
|
+
boxShadow: 'none',
|
|
84
|
+
bg: 'snackbar.background',
|
|
85
|
+
border: '1px solid',
|
|
86
|
+
borderRadius: 1,
|
|
87
|
+
color: 'snackbar.text',
|
|
88
|
+
fontSize: 2,
|
|
89
|
+
a: {
|
|
90
|
+
color: 'snackbar.link'
|
|
91
|
+
},
|
|
92
|
+
ul: {
|
|
93
|
+
pl: 5
|
|
94
|
+
}
|
|
95
|
+
}, sx),
|
|
96
|
+
className: classNames('vip-snackbar-component', className),
|
|
97
|
+
ref: forwardRef
|
|
98
|
+
}, props, {
|
|
99
|
+
children: [_jsx(Box, {
|
|
100
|
+
children: _jsxs(Flex, {
|
|
101
|
+
sx: {
|
|
102
|
+
flexDirection: 'column',
|
|
103
|
+
// the trick here is to have a flex column with the icon at the bottom and an empty div that fills the space
|
|
104
|
+
minHeight: '24px',
|
|
105
|
+
maxHeight: '32px',
|
|
106
|
+
// we're forcing the max height so that the icon is, at max, aligned between the first and the second line of text
|
|
107
|
+
alignItems: 'flex-end',
|
|
108
|
+
// we want the icon to be aligned to the bottom
|
|
109
|
+
height: '100%' // specifying the height will allow the box to match the height of the content.
|
|
110
|
+
},
|
|
111
|
+
children: [_jsx(Box, {
|
|
112
|
+
sx: {
|
|
113
|
+
flex: '1 100%' // we need this empty div to make the icon align to the bottom
|
|
114
|
+
}
|
|
115
|
+
}), _jsx(SnackbarIcon, {
|
|
116
|
+
loading: loading,
|
|
117
|
+
color: "snackbar.icon." + (loading ? 'loading' : variant),
|
|
118
|
+
variant: variant
|
|
119
|
+
})]
|
|
120
|
+
})
|
|
121
|
+
}), _jsx(Box, {
|
|
122
|
+
children: _jsx(Grid, {
|
|
123
|
+
columns: ['auto auto'],
|
|
124
|
+
sx: {
|
|
125
|
+
justifyItems: 'flex-start flex-end'
|
|
126
|
+
},
|
|
127
|
+
children: _jsxs(Box, {
|
|
128
|
+
sx: {
|
|
129
|
+
justifyContent: 'flex-start'
|
|
130
|
+
},
|
|
131
|
+
children: [title && _jsx(Heading, {
|
|
132
|
+
as: "p",
|
|
133
|
+
sx: {
|
|
134
|
+
color: 'texts.inverse',
|
|
135
|
+
mb: 0,
|
|
136
|
+
fontSize: 1,
|
|
137
|
+
fontWeight: '700'
|
|
138
|
+
},
|
|
139
|
+
children: title
|
|
140
|
+
}), children && _jsx("span", {
|
|
141
|
+
sx: {
|
|
142
|
+
fontSize: 1,
|
|
143
|
+
color: 'texts.inverse'
|
|
144
|
+
},
|
|
145
|
+
children: children
|
|
146
|
+
})]
|
|
147
|
+
})
|
|
148
|
+
})
|
|
149
|
+
}), ctaText && (ctaOnClick || ctaHref) && _jsx(Box, {
|
|
150
|
+
sx: {
|
|
151
|
+
textAlign: 'right',
|
|
152
|
+
fontSize: 1,
|
|
153
|
+
px: 2
|
|
154
|
+
},
|
|
155
|
+
children: _jsx("a", {
|
|
156
|
+
href: ctaHref,
|
|
157
|
+
sx: {
|
|
158
|
+
cursor: 'pointer'
|
|
159
|
+
},
|
|
160
|
+
onClick: ctaOnClick,
|
|
161
|
+
children: ctaText
|
|
162
|
+
})
|
|
163
|
+
}), isDismissable && _jsx(Box, {})]
|
|
164
|
+
}));
|
|
165
|
+
});
|
|
166
|
+
Snackbar.displayName = 'Snackbar';
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/** @jsxImportSource theme-ui */
|
|
2
|
+
/**
|
|
3
|
+
* External dependencies
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Internal dependencies
|
|
7
|
+
*/
|
|
8
|
+
import React from 'react';
|
|
9
|
+
declare const _default: {
|
|
10
|
+
title: string;
|
|
11
|
+
component: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLDivElement> & {
|
|
12
|
+
children?: React.ReactNode;
|
|
13
|
+
sx?: import("theme-ui").ThemeUIStyleObject | undefined;
|
|
14
|
+
title?: React.ReactNode;
|
|
15
|
+
variant?: ("error" | "success" | "warning" | "info" | "system") | undefined;
|
|
16
|
+
loading?: boolean | undefined;
|
|
17
|
+
isDismissable?: boolean | undefined;
|
|
18
|
+
className?: string | undefined;
|
|
19
|
+
ctaOnClick?: (() => void) | undefined;
|
|
20
|
+
ctaText?: string | undefined;
|
|
21
|
+
ctaHref?: string | undefined;
|
|
22
|
+
} & React.RefAttributes<HTMLDivElement>>;
|
|
23
|
+
};
|
|
24
|
+
export default _default;
|
|
25
|
+
export declare const Default: () => React.JSX.Element;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/** @jsxImportSource theme-ui */
|
|
2
|
+
/**
|
|
3
|
+
* External dependencies
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Internal dependencies
|
|
8
|
+
*/
|
|
9
|
+
import React, { useState } from 'react';
|
|
10
|
+
import { Snackbar } from '..';
|
|
11
|
+
import { jsx as _jsx } from "theme-ui/jsx-runtime";
|
|
12
|
+
import { jsxs as _jsxs } from "theme-ui/jsx-runtime";
|
|
13
|
+
export default {
|
|
14
|
+
title: 'Snackbar',
|
|
15
|
+
component: Snackbar
|
|
16
|
+
};
|
|
17
|
+
export var Default = function Default() {
|
|
18
|
+
var _useState = useState(true),
|
|
19
|
+
visible = _useState[0],
|
|
20
|
+
setVisible = _useState[1];
|
|
21
|
+
return _jsxs(React.Fragment, {
|
|
22
|
+
children: [visible && _jsx(Snackbar, {
|
|
23
|
+
variant: "error",
|
|
24
|
+
sx: {
|
|
25
|
+
mb: 4
|
|
26
|
+
},
|
|
27
|
+
ctaText: "Resolve",
|
|
28
|
+
ctaOnClick: function ctaOnClick() {
|
|
29
|
+
setVisible(false);
|
|
30
|
+
},
|
|
31
|
+
children: "Error message."
|
|
32
|
+
}), _jsx(Snackbar, {
|
|
33
|
+
variant: "warning",
|
|
34
|
+
sx: {
|
|
35
|
+
mb: 4
|
|
36
|
+
},
|
|
37
|
+
ctaText: "View",
|
|
38
|
+
ctaOnClick: function ctaOnClick() {
|
|
39
|
+
setVisible(false);
|
|
40
|
+
},
|
|
41
|
+
children: "Warning message."
|
|
42
|
+
}), _jsx(Snackbar, {
|
|
43
|
+
variant: "info",
|
|
44
|
+
sx: {
|
|
45
|
+
mb: 4
|
|
46
|
+
},
|
|
47
|
+
ctaText: "View",
|
|
48
|
+
ctaOnClick: function ctaOnClick() {
|
|
49
|
+
setVisible(false);
|
|
50
|
+
},
|
|
51
|
+
children: "Tip or information."
|
|
52
|
+
}), _jsx(Snackbar, {
|
|
53
|
+
variant: "success",
|
|
54
|
+
sx: {
|
|
55
|
+
mb: 4
|
|
56
|
+
},
|
|
57
|
+
ctaText: "Preview",
|
|
58
|
+
ctaOnClick: function ctaOnClick() {
|
|
59
|
+
setVisible(false);
|
|
60
|
+
},
|
|
61
|
+
children: "Success message."
|
|
62
|
+
}), _jsx(Snackbar, {
|
|
63
|
+
loading: true,
|
|
64
|
+
variant: "warning",
|
|
65
|
+
sx: {
|
|
66
|
+
mb: 4
|
|
67
|
+
},
|
|
68
|
+
title: "Operation in progress...",
|
|
69
|
+
ctaText: "Pause",
|
|
70
|
+
ctaOnClick: function ctaOnClick() {
|
|
71
|
+
setVisible(false);
|
|
72
|
+
},
|
|
73
|
+
children: "Check back again in a few seconds."
|
|
74
|
+
}), _jsx(Snackbar, {
|
|
75
|
+
variant: "system",
|
|
76
|
+
sx: {
|
|
77
|
+
mb: 4
|
|
78
|
+
},
|
|
79
|
+
children: "System message."
|
|
80
|
+
})]
|
|
81
|
+
});
|
|
82
|
+
};
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
function _regeneratorRuntime() { "use strict"; /*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/facebook/regenerator/blob/main/LICENSE */ _regeneratorRuntime = function _regeneratorRuntime() { return e; }; var t, e = {}, r = Object.prototype, n = r.hasOwnProperty, o = Object.defineProperty || function (t, e, r) { t[e] = r.value; }, i = "function" == typeof Symbol ? Symbol : {}, a = i.iterator || "@@iterator", c = i.asyncIterator || "@@asyncIterator", u = i.toStringTag || "@@toStringTag"; function define(t, e, r) { return Object.defineProperty(t, e, { value: r, enumerable: !0, configurable: !0, writable: !0 }), t[e]; } try { define({}, ""); } catch (t) { define = function define(t, e, r) { return t[e] = r; }; } function wrap(t, e, r, n) { var i = e && e.prototype instanceof Generator ? e : Generator, a = Object.create(i.prototype), c = new Context(n || []); return o(a, "_invoke", { value: makeInvokeMethod(t, r, c) }), a; } function tryCatch(t, e, r) { try { return { type: "normal", arg: t.call(e, r) }; } catch (t) { return { type: "throw", arg: t }; } } e.wrap = wrap; var h = "suspendedStart", l = "suspendedYield", f = "executing", s = "completed", y = {}; function Generator() {} function GeneratorFunction() {} function GeneratorFunctionPrototype() {} var p = {}; define(p, a, function () { return this; }); var d = Object.getPrototypeOf, v = d && d(d(values([]))); v && v !== r && n.call(v, a) && (p = v); var g = GeneratorFunctionPrototype.prototype = Generator.prototype = Object.create(p); function defineIteratorMethods(t) { ["next", "throw", "return"].forEach(function (e) { define(t, e, function (t) { return this._invoke(e, t); }); }); } function AsyncIterator(t, e) { function invoke(r, o, i, a) { var c = tryCatch(t[r], t, o); if ("throw" !== c.type) { var u = c.arg, h = u.value; return h && "object" == typeof h && n.call(h, "__await") ? e.resolve(h.__await).then(function (t) { invoke("next", t, i, a); }, function (t) { invoke("throw", t, i, a); }) : e.resolve(h).then(function (t) { u.value = t, i(u); }, function (t) { return invoke("throw", t, i, a); }); } a(c.arg); } var r; o(this, "_invoke", { value: function value(t, n) { function callInvokeWithMethodAndArg() { return new e(function (e, r) { invoke(t, n, e, r); }); } return r = r ? r.then(callInvokeWithMethodAndArg, callInvokeWithMethodAndArg) : callInvokeWithMethodAndArg(); } }); } function makeInvokeMethod(e, r, n) { var o = h; return function (i, a) { if (o === f) throw new Error("Generator is already running"); if (o === s) { if ("throw" === i) throw a; return { value: t, done: !0 }; } for (n.method = i, n.arg = a;;) { var c = n.delegate; if (c) { var u = maybeInvokeDelegate(c, n); if (u) { if (u === y) continue; return u; } } if ("next" === n.method) n.sent = n._sent = n.arg;else if ("throw" === n.method) { if (o === h) throw o = s, n.arg; n.dispatchException(n.arg); } else "return" === n.method && n.abrupt("return", n.arg); o = f; var p = tryCatch(e, r, n); if ("normal" === p.type) { if (o = n.done ? s : l, p.arg === y) continue; return { value: p.arg, done: n.done }; } "throw" === p.type && (o = s, n.method = "throw", n.arg = p.arg); } }; } function maybeInvokeDelegate(e, r) { var n = r.method, o = e.iterator[n]; if (o === t) return r.delegate = null, "throw" === n && e.iterator["return"] && (r.method = "return", r.arg = t, maybeInvokeDelegate(e, r), "throw" === r.method) || "return" !== n && (r.method = "throw", r.arg = new TypeError("The iterator does not provide a '" + n + "' method")), y; var i = tryCatch(o, e.iterator, r.arg); if ("throw" === i.type) return r.method = "throw", r.arg = i.arg, r.delegate = null, y; var a = i.arg; return a ? a.done ? (r[e.resultName] = a.value, r.next = e.nextLoc, "return" !== r.method && (r.method = "next", r.arg = t), r.delegate = null, y) : a : (r.method = "throw", r.arg = new TypeError("iterator result is not an object"), r.delegate = null, y); } function pushTryEntry(t) { var e = { tryLoc: t[0] }; 1 in t && (e.catchLoc = t[1]), 2 in t && (e.finallyLoc = t[2], e.afterLoc = t[3]), this.tryEntries.push(e); } function resetTryEntry(t) { var e = t.completion || {}; e.type = "normal", delete e.arg, t.completion = e; } function Context(t) { this.tryEntries = [{ tryLoc: "root" }], t.forEach(pushTryEntry, this), this.reset(!0); } function values(e) { if (e || "" === e) { var r = e[a]; if (r) return r.call(e); if ("function" == typeof e.next) return e; if (!isNaN(e.length)) { var o = -1, i = function next() { for (; ++o < e.length;) if (n.call(e, o)) return next.value = e[o], next.done = !1, next; return next.value = t, next.done = !0, next; }; return i.next = i; } } throw new TypeError(typeof e + " is not iterable"); } return GeneratorFunction.prototype = GeneratorFunctionPrototype, o(g, "constructor", { value: GeneratorFunctionPrototype, configurable: !0 }), o(GeneratorFunctionPrototype, "constructor", { value: GeneratorFunction, configurable: !0 }), GeneratorFunction.displayName = define(GeneratorFunctionPrototype, u, "GeneratorFunction"), e.isGeneratorFunction = function (t) { var e = "function" == typeof t && t.constructor; return !!e && (e === GeneratorFunction || "GeneratorFunction" === (e.displayName || e.name)); }, e.mark = function (t) { return Object.setPrototypeOf ? Object.setPrototypeOf(t, GeneratorFunctionPrototype) : (t.__proto__ = GeneratorFunctionPrototype, define(t, u, "GeneratorFunction")), t.prototype = Object.create(g), t; }, e.awrap = function (t) { return { __await: t }; }, defineIteratorMethods(AsyncIterator.prototype), define(AsyncIterator.prototype, c, function () { return this; }), e.AsyncIterator = AsyncIterator, e.async = function (t, r, n, o, i) { void 0 === i && (i = Promise); var a = new AsyncIterator(wrap(t, r, n, o), i); return e.isGeneratorFunction(r) ? a : a.next().then(function (t) { return t.done ? t.value : a.next(); }); }, defineIteratorMethods(g), define(g, u, "Generator"), define(g, a, function () { return this; }), define(g, "toString", function () { return "[object Generator]"; }), e.keys = function (t) { var e = Object(t), r = []; for (var n in e) r.push(n); return r.reverse(), function next() { for (; r.length;) { var t = r.pop(); if (t in e) return next.value = t, next.done = !1, next; } return next.done = !0, next; }; }, e.values = values, Context.prototype = { constructor: Context, reset: function reset(e) { if (this.prev = 0, this.next = 0, this.sent = this._sent = t, this.done = !1, this.delegate = null, this.method = "next", this.arg = t, this.tryEntries.forEach(resetTryEntry), !e) for (var r in this) "t" === r.charAt(0) && n.call(this, r) && !isNaN(+r.slice(1)) && (this[r] = t); }, stop: function stop() { this.done = !0; var t = this.tryEntries[0].completion; if ("throw" === t.type) throw t.arg; return this.rval; }, dispatchException: function dispatchException(e) { if (this.done) throw e; var r = this; function handle(n, o) { return a.type = "throw", a.arg = e, r.next = n, o && (r.method = "next", r.arg = t), !!o; } for (var o = this.tryEntries.length - 1; o >= 0; --o) { var i = this.tryEntries[o], a = i.completion; if ("root" === i.tryLoc) return handle("end"); if (i.tryLoc <= this.prev) { var c = n.call(i, "catchLoc"), u = n.call(i, "finallyLoc"); if (c && u) { if (this.prev < i.catchLoc) return handle(i.catchLoc, !0); if (this.prev < i.finallyLoc) return handle(i.finallyLoc); } else if (c) { if (this.prev < i.catchLoc) return handle(i.catchLoc, !0); } else { if (!u) throw new Error("try statement without catch or finally"); if (this.prev < i.finallyLoc) return handle(i.finallyLoc); } } } }, abrupt: function abrupt(t, e) { for (var r = this.tryEntries.length - 1; r >= 0; --r) { var o = this.tryEntries[r]; if (o.tryLoc <= this.prev && n.call(o, "finallyLoc") && this.prev < o.finallyLoc) { var i = o; break; } } i && ("break" === t || "continue" === t) && i.tryLoc <= e && e <= i.finallyLoc && (i = null); var a = i ? i.completion : {}; return a.type = t, a.arg = e, i ? (this.method = "next", this.next = i.finallyLoc, y) : this.complete(a); }, complete: function complete(t, e) { if ("throw" === t.type) throw t.arg; return "break" === t.type || "continue" === t.type ? this.next = t.arg : "return" === t.type ? (this.rval = this.arg = t.arg, this.method = "return", this.next = "end") : "normal" === t.type && e && (this.next = e), y; }, finish: function finish(t) { for (var e = this.tryEntries.length - 1; e >= 0; --e) { var r = this.tryEntries[e]; if (r.finallyLoc === t) return this.complete(r.completion, r.afterLoc), resetTryEntry(r), y; } }, "catch": function _catch(t) { for (var e = this.tryEntries.length - 1; e >= 0; --e) { var r = this.tryEntries[e]; if (r.tryLoc === t) { var n = r.completion; if ("throw" === n.type) { var o = n.arg; resetTryEntry(r); } return o; } } throw new Error("illegal catch attempt"); }, delegateYield: function delegateYield(e, r, n) { return this.delegate = { iterator: values(e), resultName: r, nextLoc: n }, "next" === this.method && (this.arg = t), y; } }, e; }
|
|
2
|
+
function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { Promise.resolve(value).then(_next, _throw); } }
|
|
3
|
+
function _asyncToGenerator(fn) { return function () { var self = this, args = arguments; return new Promise(function (resolve, reject) { var gen = fn.apply(self, args); function _next(value) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); } function _throw(err) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); } _next(undefined); }); }; }
|
|
4
|
+
/** @jsxImportSource theme-ui */
|
|
5
|
+
/* eslint-disable @typescript-eslint/ban-ts-comment */
|
|
6
|
+
// @ts-nocheck
|
|
7
|
+
|
|
8
|
+
import { render, screen } from '@testing-library/react';
|
|
9
|
+
import userEvent from '@testing-library/user-event';
|
|
10
|
+
import { axe } from 'jest-axe';
|
|
11
|
+
import { ThemeUIProvider } from 'theme-ui';
|
|
12
|
+
import { Snackbar } from './Snackbar';
|
|
13
|
+
import { theme } from '../';
|
|
14
|
+
import { jsx as _jsx } from "theme-ui/jsx-runtime";
|
|
15
|
+
jest.mock('@theme-ui/match-media');
|
|
16
|
+
var renderWithTheme = function renderWithTheme(children) {
|
|
17
|
+
return render(_jsx(ThemeUIProvider, {
|
|
18
|
+
theme: theme,
|
|
19
|
+
children: children
|
|
20
|
+
}));
|
|
21
|
+
};
|
|
22
|
+
describe('<Snackbar />', function () {
|
|
23
|
+
it('renders the basic Snackbar component', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee() {
|
|
24
|
+
var _renderWithTheme, container;
|
|
25
|
+
return _regeneratorRuntime().wrap(function _callee$(_context) {
|
|
26
|
+
while (1) switch (_context.prev = _context.next) {
|
|
27
|
+
case 0:
|
|
28
|
+
_renderWithTheme = renderWithTheme(_jsx(Snackbar, {
|
|
29
|
+
title: "Test Title",
|
|
30
|
+
children: "Test content"
|
|
31
|
+
})), container = _renderWithTheme.container; // Check for title and content
|
|
32
|
+
expect(screen.getByText('Test Title')).toBeInTheDocument();
|
|
33
|
+
expect(screen.getByText('Test content')).toBeInTheDocument();
|
|
34
|
+
|
|
35
|
+
// Check for accessibility issues
|
|
36
|
+
_context.t0 = expect;
|
|
37
|
+
_context.next = 6;
|
|
38
|
+
return axe(container);
|
|
39
|
+
case 6:
|
|
40
|
+
_context.t1 = _context.sent;
|
|
41
|
+
(0, _context.t0)(_context.t1).toHaveNoViolations();
|
|
42
|
+
case 8:
|
|
43
|
+
case "end":
|
|
44
|
+
return _context.stop();
|
|
45
|
+
}
|
|
46
|
+
}, _callee);
|
|
47
|
+
})));
|
|
48
|
+
it('renders different variants correctly', function () {
|
|
49
|
+
var variants = ['error', 'info', 'success', 'system', 'warning'];
|
|
50
|
+
variants.forEach(function (variant) {
|
|
51
|
+
var _renderWithTheme2 = renderWithTheme(_jsx(Snackbar, {
|
|
52
|
+
variant: variant,
|
|
53
|
+
title: variant + " title",
|
|
54
|
+
children: variant + " content"
|
|
55
|
+
})),
|
|
56
|
+
container = _renderWithTheme2.container;
|
|
57
|
+
expect(screen.getByText(variant + " title")).toBeInTheDocument();
|
|
58
|
+
expect(screen.getByText(variant + " content")).toBeInTheDocument();
|
|
59
|
+
expect(container.querySelector('.vip-snackbar-component')).toBeInTheDocument();
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
it('renders with loading state', function () {
|
|
63
|
+
var _renderWithTheme3 = renderWithTheme(_jsx(Snackbar, {
|
|
64
|
+
loading: true,
|
|
65
|
+
children: "Loading content"
|
|
66
|
+
})),
|
|
67
|
+
container = _renderWithTheme3.container;
|
|
68
|
+
expect(screen.getByText('Loading content')).toBeInTheDocument();
|
|
69
|
+
expect(container.querySelector('.vip-spinner-component')).toBeInTheDocument();
|
|
70
|
+
});
|
|
71
|
+
it('handles CTA click correctly', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee2() {
|
|
72
|
+
var mockOnClick, user, ctaButton;
|
|
73
|
+
return _regeneratorRuntime().wrap(function _callee2$(_context2) {
|
|
74
|
+
while (1) switch (_context2.prev = _context2.next) {
|
|
75
|
+
case 0:
|
|
76
|
+
mockOnClick = jest.fn();
|
|
77
|
+
user = userEvent.setup();
|
|
78
|
+
renderWithTheme(_jsx(Snackbar, {
|
|
79
|
+
ctaText: "Click me",
|
|
80
|
+
ctaOnClick: mockOnClick,
|
|
81
|
+
children: "Content with CTA"
|
|
82
|
+
}));
|
|
83
|
+
ctaButton = screen.getByText('Click me');
|
|
84
|
+
_context2.next = 6;
|
|
85
|
+
return user.click(ctaButton);
|
|
86
|
+
case 6:
|
|
87
|
+
expect(mockOnClick).toHaveBeenCalledTimes(1);
|
|
88
|
+
case 7:
|
|
89
|
+
case "end":
|
|
90
|
+
return _context2.stop();
|
|
91
|
+
}
|
|
92
|
+
}, _callee2);
|
|
93
|
+
})));
|
|
94
|
+
it('renders CTA with href correctly', function () {
|
|
95
|
+
renderWithTheme(_jsx(Snackbar, {
|
|
96
|
+
ctaText: "Visit Link",
|
|
97
|
+
ctaHref: "https://example.com",
|
|
98
|
+
children: "Content with link"
|
|
99
|
+
}));
|
|
100
|
+
var link = screen.getByText('Visit Link');
|
|
101
|
+
expect(link).toHaveAttribute('href', 'https://example.com');
|
|
102
|
+
});
|
|
103
|
+
it('applies custom className and styles', function () {
|
|
104
|
+
var _renderWithTheme4 = renderWithTheme(_jsx(Snackbar, {
|
|
105
|
+
className: "custom-class",
|
|
106
|
+
sx: {
|
|
107
|
+
backgroundColor: 'red'
|
|
108
|
+
},
|
|
109
|
+
title: "Styled Snackbar",
|
|
110
|
+
children: "Styled content"
|
|
111
|
+
})),
|
|
112
|
+
container = _renderWithTheme4.container;
|
|
113
|
+
var snackbarElement = container.querySelector('.vip-snackbar-component');
|
|
114
|
+
expect(snackbarElement).toHaveClass('custom-class');
|
|
115
|
+
});
|
|
116
|
+
});
|
package/build/system/index.d.ts
CHANGED
|
@@ -57,8 +57,9 @@ import { TabsList } from './Tabs';
|
|
|
57
57
|
import { Toggle } from './Form';
|
|
58
58
|
import { ToggleRow } from './Form';
|
|
59
59
|
import { Toolbar } from './Toolbar';
|
|
60
|
+
import { Snackbar } from './Snackbar';
|
|
60
61
|
import { Validation } from './Form';
|
|
61
62
|
import { Wizard } from './Wizard';
|
|
62
63
|
import { WizardStep } from './Wizard';
|
|
63
64
|
import theme from './theme';
|
|
64
|
-
export { Accordion, Avatar, Badge, Box, Breadcrumbs, Button, ButtonSubmit, ButtonVariant, Card, Checkbox, Code, Dialog, NewDialog, Form, Drawer, Dropdown, DialogButton, DialogMenu, DialogMenuItem, DialogDivider, DialogContent, DialogTrigger, ConfirmationDialog, MobileMenu, MobileMenuTrigger, MobileMenuWrapper, NewConfirmationDialog, Grid, FilterDropdown, Flex, Notice, OptionRow, Heading, Hr, Input, Label, ScreenReaderText, Spinner, Table, TableRow, TableCell, Tooltip, Link, LinkExternal, Radio, RadioBoxGroup, RadioGroupChip, Textarea, Progress, Text, Tabs, Nav, NavItem, TabsTrigger, TabsContent, TabsList, Toggle, ToggleRow, Toolbar, Validation, Wizard, WizardStep, theme };
|
|
65
|
+
export { Accordion, Avatar, Badge, Box, Breadcrumbs, Button, ButtonSubmit, ButtonVariant, Card, Checkbox, Code, Dialog, NewDialog, Form, Drawer, Dropdown, DialogButton, DialogMenu, DialogMenuItem, DialogDivider, DialogContent, DialogTrigger, ConfirmationDialog, MobileMenu, MobileMenuTrigger, MobileMenuWrapper, NewConfirmationDialog, Grid, FilterDropdown, Flex, Notice, OptionRow, Heading, Hr, Input, Label, ScreenReaderText, Spinner, Table, TableRow, TableCell, Tooltip, Link, LinkExternal, Radio, RadioBoxGroup, RadioGroupChip, Textarea, Progress, Text, Tabs, Nav, NavItem, TabsTrigger, TabsContent, TabsList, Toggle, ToggleRow, Toolbar, Snackbar, Validation, Wizard, WizardStep, theme };
|
package/build/system/index.js
CHANGED
|
@@ -50,6 +50,7 @@ import { Notice } from './Notice';
|
|
|
50
50
|
import { OptionRow } from './OptionRow';
|
|
51
51
|
import { Progress } from './Progress';
|
|
52
52
|
import { ScreenReaderText } from './ScreenReaderText';
|
|
53
|
+
import { Snackbar } from './Snackbar';
|
|
53
54
|
import { Spinner } from './Spinner';
|
|
54
55
|
import { Table, TableRow, TableCell } from './Table';
|
|
55
56
|
import { Tabs, TabsList, TabsContent, TabsTrigger } from './Tabs';
|
|
@@ -119,6 +120,7 @@ export {
|
|
|
119
120
|
Toggle,
|
|
120
121
|
ToggleRow,
|
|
121
122
|
Toolbar,
|
|
123
|
+
Snackbar,
|
|
122
124
|
Validation,
|
|
123
125
|
Wizard,
|
|
124
126
|
WizardStep,
|
|
@@ -78,6 +78,17 @@ const getComponentColors = ( theme, gColor, gVariants ) => ( {
|
|
|
78
78
|
},
|
|
79
79
|
},
|
|
80
80
|
|
|
81
|
+
// Snackbar
|
|
82
|
+
snackbar: {
|
|
83
|
+
icon: {
|
|
84
|
+
loading: theme.text.inverse,
|
|
85
|
+
...theme.support.icon,
|
|
86
|
+
},
|
|
87
|
+
link: theme.text.inverse,
|
|
88
|
+
text: theme.text.inverse,
|
|
89
|
+
background: theme.layer.inverse,
|
|
90
|
+
},
|
|
91
|
+
|
|
81
92
|
// layer
|
|
82
93
|
layer: {
|
|
83
94
|
...theme.layer,
|
package/package.json
CHANGED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/** @jsxImportSource theme-ui */
|
|
2
|
+
/**
|
|
3
|
+
* External dependencies
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Internal dependencies
|
|
8
|
+
*/
|
|
9
|
+
import React, { useState } from 'react';
|
|
10
|
+
|
|
11
|
+
import { Snackbar } from '..';
|
|
12
|
+
|
|
13
|
+
export default {
|
|
14
|
+
title: 'Snackbar',
|
|
15
|
+
component: Snackbar,
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export const Default = () => {
|
|
19
|
+
const [ visible, setVisible ] = useState( true );
|
|
20
|
+
return (
|
|
21
|
+
<React.Fragment>
|
|
22
|
+
{ visible && (
|
|
23
|
+
<Snackbar
|
|
24
|
+
variant="error"
|
|
25
|
+
sx={ { mb: 4 } }
|
|
26
|
+
ctaText="Resolve"
|
|
27
|
+
ctaOnClick={ () => {
|
|
28
|
+
setVisible( false );
|
|
29
|
+
} }
|
|
30
|
+
>
|
|
31
|
+
Error message.
|
|
32
|
+
</Snackbar>
|
|
33
|
+
) }
|
|
34
|
+
|
|
35
|
+
<Snackbar
|
|
36
|
+
variant="warning"
|
|
37
|
+
sx={ { mb: 4 } }
|
|
38
|
+
ctaText="View"
|
|
39
|
+
ctaOnClick={ () => {
|
|
40
|
+
setVisible( false );
|
|
41
|
+
} }
|
|
42
|
+
>
|
|
43
|
+
Warning message.
|
|
44
|
+
</Snackbar>
|
|
45
|
+
|
|
46
|
+
<Snackbar
|
|
47
|
+
variant="info"
|
|
48
|
+
sx={ { mb: 4 } }
|
|
49
|
+
ctaText="View"
|
|
50
|
+
ctaOnClick={ () => {
|
|
51
|
+
setVisible( false );
|
|
52
|
+
} }
|
|
53
|
+
>
|
|
54
|
+
Tip or information.
|
|
55
|
+
</Snackbar>
|
|
56
|
+
|
|
57
|
+
<Snackbar
|
|
58
|
+
variant="success"
|
|
59
|
+
sx={ { mb: 4 } }
|
|
60
|
+
ctaText="Preview"
|
|
61
|
+
ctaOnClick={ () => {
|
|
62
|
+
setVisible( false );
|
|
63
|
+
} }
|
|
64
|
+
>
|
|
65
|
+
Success message.
|
|
66
|
+
</Snackbar>
|
|
67
|
+
|
|
68
|
+
<Snackbar
|
|
69
|
+
loading
|
|
70
|
+
variant="warning"
|
|
71
|
+
sx={ { mb: 4 } }
|
|
72
|
+
title="Operation in progress..."
|
|
73
|
+
ctaText="Pause"
|
|
74
|
+
ctaOnClick={ () => {
|
|
75
|
+
setVisible( false );
|
|
76
|
+
} }
|
|
77
|
+
>
|
|
78
|
+
Check back again in a few seconds.
|
|
79
|
+
</Snackbar>
|
|
80
|
+
|
|
81
|
+
<Snackbar variant="system" sx={ { mb: 4 } }>
|
|
82
|
+
System message.
|
|
83
|
+
</Snackbar>
|
|
84
|
+
</React.Fragment>
|
|
85
|
+
);
|
|
86
|
+
};
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/** @jsxImportSource theme-ui */
|
|
2
|
+
/* eslint-disable @typescript-eslint/ban-ts-comment */
|
|
3
|
+
// @ts-nocheck
|
|
4
|
+
|
|
5
|
+
import { render, screen } from '@testing-library/react';
|
|
6
|
+
import userEvent from '@testing-library/user-event';
|
|
7
|
+
import { axe } from 'jest-axe';
|
|
8
|
+
import { ThemeUIProvider } from 'theme-ui';
|
|
9
|
+
|
|
10
|
+
import { Snackbar } from './Snackbar';
|
|
11
|
+
import { theme } from '../';
|
|
12
|
+
|
|
13
|
+
jest.mock( '@theme-ui/match-media' );
|
|
14
|
+
|
|
15
|
+
const renderWithTheme = children =>
|
|
16
|
+
render( <ThemeUIProvider theme={ theme }>{ children }</ThemeUIProvider> );
|
|
17
|
+
|
|
18
|
+
describe( '<Snackbar />', () => {
|
|
19
|
+
it( 'renders the basic Snackbar component', async () => {
|
|
20
|
+
const { container } = renderWithTheme( <Snackbar title="Test Title">Test content</Snackbar> );
|
|
21
|
+
|
|
22
|
+
// Check for title and content
|
|
23
|
+
expect( screen.getByText( 'Test Title' ) ).toBeInTheDocument();
|
|
24
|
+
expect( screen.getByText( 'Test content' ) ).toBeInTheDocument();
|
|
25
|
+
|
|
26
|
+
// Check for accessibility issues
|
|
27
|
+
expect( await axe( container ) ).toHaveNoViolations();
|
|
28
|
+
} );
|
|
29
|
+
|
|
30
|
+
it( 'renders different variants correctly', () => {
|
|
31
|
+
const variants = [ 'error', 'info', 'success', 'system', 'warning' ] as const;
|
|
32
|
+
|
|
33
|
+
variants.forEach( variant => {
|
|
34
|
+
const { container } = renderWithTheme(
|
|
35
|
+
<Snackbar variant={ variant } title={ `${ variant } title` }>
|
|
36
|
+
{ `${ variant } content` }
|
|
37
|
+
</Snackbar>
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
expect( screen.getByText( `${ variant } title` ) ).toBeInTheDocument();
|
|
41
|
+
expect( screen.getByText( `${ variant } content` ) ).toBeInTheDocument();
|
|
42
|
+
expect( container.querySelector( '.vip-snackbar-component' ) ).toBeInTheDocument();
|
|
43
|
+
} );
|
|
44
|
+
} );
|
|
45
|
+
|
|
46
|
+
it( 'renders with loading state', () => {
|
|
47
|
+
const { container } = renderWithTheme( <Snackbar loading>Loading content</Snackbar> );
|
|
48
|
+
|
|
49
|
+
expect( screen.getByText( 'Loading content' ) ).toBeInTheDocument();
|
|
50
|
+
expect( container.querySelector( '.vip-spinner-component' ) ).toBeInTheDocument();
|
|
51
|
+
} );
|
|
52
|
+
|
|
53
|
+
it( 'handles CTA click correctly', async () => {
|
|
54
|
+
const mockOnClick = jest.fn();
|
|
55
|
+
const user = userEvent.setup();
|
|
56
|
+
|
|
57
|
+
renderWithTheme(
|
|
58
|
+
<Snackbar ctaText="Click me" ctaOnClick={ mockOnClick }>
|
|
59
|
+
Content with CTA
|
|
60
|
+
</Snackbar>
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
const ctaButton = screen.getByText( 'Click me' );
|
|
64
|
+
await user.click( ctaButton );
|
|
65
|
+
|
|
66
|
+
expect( mockOnClick ).toHaveBeenCalledTimes( 1 );
|
|
67
|
+
} );
|
|
68
|
+
|
|
69
|
+
it( 'renders CTA with href correctly', () => {
|
|
70
|
+
renderWithTheme(
|
|
71
|
+
<Snackbar ctaText="Visit Link" ctaHref="https://example.com">
|
|
72
|
+
Content with link
|
|
73
|
+
</Snackbar>
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
const link = screen.getByText( 'Visit Link' );
|
|
77
|
+
expect( link ).toHaveAttribute( 'href', 'https://example.com' );
|
|
78
|
+
} );
|
|
79
|
+
|
|
80
|
+
it( 'applies custom className and styles', () => {
|
|
81
|
+
const { container } = renderWithTheme(
|
|
82
|
+
<Snackbar className="custom-class" sx={ { backgroundColor: 'red' } } title="Styled Snackbar">
|
|
83
|
+
Styled content
|
|
84
|
+
</Snackbar>
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
const snackbarElement = container.querySelector( '.vip-snackbar-component' );
|
|
88
|
+
expect( snackbarElement ).toHaveClass( 'custom-class' );
|
|
89
|
+
} );
|
|
90
|
+
} );
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/** @jsxImportSource theme-ui */
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* External dependencies
|
|
5
|
+
*/
|
|
6
|
+
import classNames from 'classnames';
|
|
7
|
+
import React from 'react';
|
|
8
|
+
import { MdError, MdWarning, MdInfo, MdCheckCircle, MdLock } from 'react-icons/md';
|
|
9
|
+
import { Grid, ThemeUIStyleObject } from 'theme-ui';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Internal dependencies
|
|
13
|
+
*/
|
|
14
|
+
import { Box, Flex, Heading, Spinner } from '..';
|
|
15
|
+
|
|
16
|
+
interface SnackbarIconProps {
|
|
17
|
+
color: string;
|
|
18
|
+
variant: ColorVariants | 'loading';
|
|
19
|
+
loading?: boolean;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export type SnackbarProps = React.HTMLAttributes< HTMLDivElement > & {
|
|
23
|
+
children?: React.ReactNode;
|
|
24
|
+
sx?: ThemeUIStyleObject;
|
|
25
|
+
title?: React.ReactNode;
|
|
26
|
+
variant?: ColorVariants;
|
|
27
|
+
loading?: boolean;
|
|
28
|
+
isDismissable?: boolean;
|
|
29
|
+
className?: string;
|
|
30
|
+
ctaOnClick?: () => void;
|
|
31
|
+
ctaText?: string;
|
|
32
|
+
ctaHref?: string;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
type ColorVariants = 'error' | 'info' | 'success' | 'system' | 'warning';
|
|
36
|
+
|
|
37
|
+
const SnackbarIcon = ( { color, variant, loading }: SnackbarIconProps ) => {
|
|
38
|
+
const sx = { color, flex: '0 0 auto' };
|
|
39
|
+
const size = 24;
|
|
40
|
+
|
|
41
|
+
if ( loading ) {
|
|
42
|
+
return <Spinner strokeWidth={ 3 } sx={ sx } size={ size } variant="loading" />;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const elements = {
|
|
46
|
+
default: MdWarning,
|
|
47
|
+
error: MdError,
|
|
48
|
+
info: MdInfo,
|
|
49
|
+
success: MdCheckCircle,
|
|
50
|
+
system: MdLock,
|
|
51
|
+
warning: MdWarning,
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const Element = elements[ variant ] || elements.default;
|
|
55
|
+
|
|
56
|
+
return <Element sx={ sx } size={ size } aria-hidden="true" />;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
export const Snackbar = React.forwardRef< HTMLDivElement, SnackbarProps >(
|
|
60
|
+
(
|
|
61
|
+
{
|
|
62
|
+
children,
|
|
63
|
+
className = null,
|
|
64
|
+
sx = {},
|
|
65
|
+
title,
|
|
66
|
+
variant = 'warning',
|
|
67
|
+
loading = false,
|
|
68
|
+
isDismissable = false,
|
|
69
|
+
ctaOnClick,
|
|
70
|
+
ctaText,
|
|
71
|
+
ctaHref = undefined,
|
|
72
|
+
...props
|
|
73
|
+
},
|
|
74
|
+
forwardRef
|
|
75
|
+
) => {
|
|
76
|
+
const columns = [ '24px', 'auto' ];
|
|
77
|
+
const hasCta = ctaText && ( ctaOnClick || ctaHref );
|
|
78
|
+
if ( hasCta ) {
|
|
79
|
+
columns.push( 'auto' );
|
|
80
|
+
}
|
|
81
|
+
if ( isDismissable ) {
|
|
82
|
+
columns.push( '24px' );
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return (
|
|
86
|
+
<Grid
|
|
87
|
+
columns={ columns.join( ' ' ) }
|
|
88
|
+
variant="notice"
|
|
89
|
+
sx={ {
|
|
90
|
+
p: 4,
|
|
91
|
+
boxShadow: 'none',
|
|
92
|
+
bg: 'snackbar.background',
|
|
93
|
+
border: '1px solid',
|
|
94
|
+
borderRadius: 1,
|
|
95
|
+
color: 'snackbar.text',
|
|
96
|
+
fontSize: 2,
|
|
97
|
+
a: {
|
|
98
|
+
color: 'snackbar.link',
|
|
99
|
+
},
|
|
100
|
+
ul: {
|
|
101
|
+
pl: 5,
|
|
102
|
+
},
|
|
103
|
+
...sx,
|
|
104
|
+
} }
|
|
105
|
+
className={ classNames( 'vip-snackbar-component', className ) }
|
|
106
|
+
ref={ forwardRef }
|
|
107
|
+
{ ...props }
|
|
108
|
+
>
|
|
109
|
+
<Box>
|
|
110
|
+
<Flex
|
|
111
|
+
sx={ {
|
|
112
|
+
flexDirection: 'column', // the trick here is to have a flex column with the icon at the bottom and an empty div that fills the space
|
|
113
|
+
minHeight: '24px',
|
|
114
|
+
maxHeight: '32px', // we're forcing the max height so that the icon is, at max, aligned between the first and the second line of text
|
|
115
|
+
alignItems: 'flex-end', // we want the icon to be aligned to the bottom
|
|
116
|
+
height: '100%', // specifying the height will allow the box to match the height of the content.
|
|
117
|
+
} }
|
|
118
|
+
>
|
|
119
|
+
<Box
|
|
120
|
+
sx={ {
|
|
121
|
+
flex: '1 100%', // we need this empty div to make the icon align to the bottom
|
|
122
|
+
} }
|
|
123
|
+
></Box>
|
|
124
|
+
<SnackbarIcon
|
|
125
|
+
loading={ loading }
|
|
126
|
+
color={ `snackbar.icon.${ loading ? 'loading' : variant }` }
|
|
127
|
+
variant={ variant }
|
|
128
|
+
/>
|
|
129
|
+
</Flex>
|
|
130
|
+
</Box>
|
|
131
|
+
<Box>
|
|
132
|
+
<Grid columns={ [ 'auto auto' ] } sx={ { justifyItems: 'flex-start flex-end' } }>
|
|
133
|
+
<Box sx={ { justifyContent: 'flex-start' } }>
|
|
134
|
+
{ title && (
|
|
135
|
+
<Heading
|
|
136
|
+
as="p"
|
|
137
|
+
sx={ {
|
|
138
|
+
color: 'texts.inverse',
|
|
139
|
+
mb: 0,
|
|
140
|
+
fontSize: 1,
|
|
141
|
+
fontWeight: '700',
|
|
142
|
+
} }
|
|
143
|
+
>
|
|
144
|
+
{ title }
|
|
145
|
+
</Heading>
|
|
146
|
+
) }
|
|
147
|
+
{ children && (
|
|
148
|
+
<span sx={ { fontSize: 1, color: 'texts.inverse' } }>{ children }</span>
|
|
149
|
+
) }
|
|
150
|
+
</Box>
|
|
151
|
+
</Grid>
|
|
152
|
+
</Box>
|
|
153
|
+
{ ctaText && ( ctaOnClick || ctaHref ) && (
|
|
154
|
+
<Box sx={ { textAlign: 'right', fontSize: 1, px: 2 } }>
|
|
155
|
+
<a href={ ctaHref } sx={ { cursor: 'pointer' } } onClick={ ctaOnClick }>
|
|
156
|
+
{ ctaText }
|
|
157
|
+
</a>
|
|
158
|
+
</Box>
|
|
159
|
+
) }
|
|
160
|
+
{ isDismissable && <Box></Box> }
|
|
161
|
+
</Grid>
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
);
|
|
165
|
+
|
|
166
|
+
Snackbar.displayName = 'Snackbar';
|
package/src/system/index.js
CHANGED
|
@@ -50,6 +50,7 @@ import { Notice } from './Notice';
|
|
|
50
50
|
import { OptionRow } from './OptionRow';
|
|
51
51
|
import { Progress } from './Progress';
|
|
52
52
|
import { ScreenReaderText } from './ScreenReaderText';
|
|
53
|
+
import { Snackbar } from './Snackbar';
|
|
53
54
|
import { Spinner } from './Spinner';
|
|
54
55
|
import { Table, TableRow, TableCell } from './Table';
|
|
55
56
|
import { Tabs, TabsList, TabsContent, TabsTrigger } from './Tabs';
|
|
@@ -119,6 +120,7 @@ export {
|
|
|
119
120
|
Toggle,
|
|
120
121
|
ToggleRow,
|
|
121
122
|
Toolbar,
|
|
123
|
+
Snackbar,
|
|
122
124
|
Validation,
|
|
123
125
|
Wizard,
|
|
124
126
|
WizardStep,
|
|
@@ -78,6 +78,17 @@ const getComponentColors = ( theme, gColor, gVariants ) => ( {
|
|
|
78
78
|
},
|
|
79
79
|
},
|
|
80
80
|
|
|
81
|
+
// Snackbar
|
|
82
|
+
snackbar: {
|
|
83
|
+
icon: {
|
|
84
|
+
loading: theme.text.inverse,
|
|
85
|
+
...theme.support.icon,
|
|
86
|
+
},
|
|
87
|
+
link: theme.text.inverse,
|
|
88
|
+
text: theme.text.inverse,
|
|
89
|
+
background: theme.layer.inverse,
|
|
90
|
+
},
|
|
91
|
+
|
|
81
92
|
// layer
|
|
82
93
|
layer: {
|
|
83
94
|
...theme.layer,
|