@canonical/react-components 2.9.0 → 2.11.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/components/CustomLayout/CustomLayout.d.ts +22 -0
- package/dist/components/CustomLayout/CustomLayout.js +26 -0
- package/dist/components/CustomLayout/CustomLayout.stories.d.ts +7 -0
- package/dist/components/CustomLayout/CustomLayout.stories.js +28 -0
- package/dist/components/CustomLayout/CustomLayout.test.d.ts +1 -0
- package/dist/components/CustomLayout/index.d.ts +2 -0
- package/dist/components/CustomLayout/index.js +13 -0
- package/dist/components/Notifications/ToastNotification/ToastNotification.stories.js +1 -0
- package/dist/components/Notifications/ToastNotification/ToastNotificationProvider.d.ts +20 -8
- package/dist/components/Notifications/ToastNotification/ToastNotificationProvider.js +35 -10
- package/dist/components/Spinner/Spinner.d.ts +7 -1
- package/dist/components/Spinner/Spinner.js +6 -2
- package/dist/esm/components/CustomLayout/CustomLayout.d.ts +22 -0
- package/dist/esm/components/CustomLayout/CustomLayout.js +19 -0
- package/dist/esm/components/CustomLayout/CustomLayout.stories.d.ts +7 -0
- package/dist/esm/components/CustomLayout/CustomLayout.stories.js +21 -0
- package/dist/esm/components/CustomLayout/CustomLayout.test.d.ts +1 -0
- package/dist/esm/components/CustomLayout/index.d.ts +2 -0
- package/dist/esm/components/CustomLayout/index.js +1 -0
- package/dist/esm/components/Notifications/ToastNotification/ToastNotification.stories.js +1 -0
- package/dist/esm/components/Notifications/ToastNotification/ToastNotificationProvider.d.ts +20 -8
- package/dist/esm/components/Notifications/ToastNotification/ToastNotificationProvider.js +36 -11
- package/dist/esm/components/Spinner/Spinner.d.ts +7 -1
- package/dist/esm/components/Spinner/Spinner.js +8 -4
- package/dist/esm/index.d.ts +1 -0
- package/dist/esm/index.js +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +8 -0
- package/package.json +2 -2
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import type { HTMLProps, ReactNode } from "react";
|
|
3
|
+
export type Props = {
|
|
4
|
+
/**
|
|
5
|
+
* This prop can be used to replace the header area of the panel when the default implementation is not sufficient.
|
|
6
|
+
*/
|
|
7
|
+
header?: ReactNode;
|
|
8
|
+
/**
|
|
9
|
+
* The main content.
|
|
10
|
+
*/
|
|
11
|
+
children: ReactNode;
|
|
12
|
+
/**
|
|
13
|
+
* Classes to apply to the main content.
|
|
14
|
+
*/
|
|
15
|
+
mainClassName?: string;
|
|
16
|
+
/**
|
|
17
|
+
* Classes to apply to the main area.
|
|
18
|
+
*/
|
|
19
|
+
contentClassName?: string;
|
|
20
|
+
} & HTMLProps<HTMLDivElement>;
|
|
21
|
+
declare const CustomLayout: ({ header, children, mainClassName, contentClassName, }: Props) => React.JSX.Element;
|
|
22
|
+
export default CustomLayout;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.default = void 0;
|
|
7
|
+
var _react = _interopRequireDefault(require("react"));
|
|
8
|
+
var _ApplicationLayout = require("../ApplicationLayout");
|
|
9
|
+
var _Panel = _interopRequireDefault(require("../Panel"));
|
|
10
|
+
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
11
|
+
const CustomLayout = _ref => {
|
|
12
|
+
let {
|
|
13
|
+
header,
|
|
14
|
+
children,
|
|
15
|
+
mainClassName,
|
|
16
|
+
contentClassName
|
|
17
|
+
} = _ref;
|
|
18
|
+
return /*#__PURE__*/_react.default.createElement(_ApplicationLayout.AppMain, {
|
|
19
|
+
className: mainClassName,
|
|
20
|
+
id: "main-content"
|
|
21
|
+
}, /*#__PURE__*/_react.default.createElement(_Panel.default, {
|
|
22
|
+
contentClassName: contentClassName,
|
|
23
|
+
header: header
|
|
24
|
+
}, children));
|
|
25
|
+
};
|
|
26
|
+
var _default = exports.default = CustomLayout;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
2
|
+
import CustomLayout from "./CustomLayout";
|
|
3
|
+
declare const meta: Meta<typeof CustomLayout>;
|
|
4
|
+
export default meta;
|
|
5
|
+
type Story = StoryObj<typeof CustomLayout>;
|
|
6
|
+
export declare const Default: Story;
|
|
7
|
+
export declare const Content: Story;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.default = exports.Default = exports.Content = void 0;
|
|
7
|
+
var _react = _interopRequireDefault(require("react"));
|
|
8
|
+
var _Panel = _interopRequireDefault(require("../Panel"));
|
|
9
|
+
var _CustomLayout = _interopRequireDefault(require("./CustomLayout"));
|
|
10
|
+
var _AppMain = _interopRequireDefault(require("../ApplicationLayout/AppMain"));
|
|
11
|
+
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
12
|
+
const meta = {
|
|
13
|
+
component: _CustomLayout.default,
|
|
14
|
+
tags: ["autodocs"]
|
|
15
|
+
};
|
|
16
|
+
var _default = exports.default = meta;
|
|
17
|
+
const Default = exports.Default = {
|
|
18
|
+
args: {
|
|
19
|
+
children: "CustomLayout"
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
const Content = exports.Content = {
|
|
23
|
+
render: () => {
|
|
24
|
+
return /*#__PURE__*/_react.default.createElement(_AppMain.default, {
|
|
25
|
+
id: "main-content"
|
|
26
|
+
}, /*#__PURE__*/_react.default.createElement(_Panel.default, null, "test"));
|
|
27
|
+
}
|
|
28
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
Object.defineProperty(exports, "default", {
|
|
7
|
+
enumerable: true,
|
|
8
|
+
get: function () {
|
|
9
|
+
return _CustomLayout.default;
|
|
10
|
+
}
|
|
11
|
+
});
|
|
12
|
+
var _CustomLayout = _interopRequireDefault(require("./CustomLayout"));
|
|
13
|
+
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
@@ -34,6 +34,7 @@ const PreloadedList = () => {
|
|
|
34
34
|
toastNotify.success("Settings saved successfully");
|
|
35
35
|
toastNotify.info("Your changes are syncing in the background");
|
|
36
36
|
toastNotify.failure("Save failed", new Error("500 Internal Server Error"), "Please try again.");
|
|
37
|
+
toastNotify.caution("You have unsaved changes.");
|
|
37
38
|
toastNotify.toggleListView();
|
|
38
39
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
39
40
|
}, []);
|
|
@@ -8,9 +8,10 @@ export type ToastNotificationType = NotificationType & {
|
|
|
8
8
|
};
|
|
9
9
|
interface ToastNotificationHelper {
|
|
10
10
|
notifications: ToastNotificationType[];
|
|
11
|
-
success: (message: ReactNode, actions?: NotificationAction[]) => ToastNotificationType;
|
|
12
|
-
info: (message: ReactNode, title?: string) => ToastNotificationType;
|
|
11
|
+
success: (message: ReactNode, actions?: NotificationAction[], title?: string) => ToastNotificationType;
|
|
12
|
+
info: (message: ReactNode, title?: string, actions?: NotificationAction[]) => ToastNotificationType;
|
|
13
13
|
failure: (title: string, error: unknown, message?: ReactNode, actions?: NotificationAction[]) => ToastNotificationType;
|
|
14
|
+
caution: (message: ReactNode, actions?: NotificationAction[], title?: string) => ToastNotificationType;
|
|
14
15
|
clear: (notification?: ToastNotificationType[]) => void;
|
|
15
16
|
toggleListView: () => void;
|
|
16
17
|
isListView: boolean;
|
|
@@ -25,11 +26,12 @@ export type GroupedNotificationCount = {
|
|
|
25
26
|
Wrap your application with this provider, and in any child component you can get the helper with `const toastNotify = useToastNotification()` to trigger notifications.
|
|
26
27
|
Notifications automatically dismiss after a delay unless manually dismissed or expanded.
|
|
27
28
|
|
|
28
|
-
| **Values**
|
|
29
|
-
|
|
30
|
-
| `toastNotify.success()` | Displays a success toast. Optionally accepts actions.
|
|
29
|
+
| **Values** | **Description** |
|
|
30
|
+
|----------------------------------|--------------------------------------------------------------------------------|
|
|
31
|
+
| `toastNotify.success()` | Displays a success toast. Optionally accepts actions and a title. |
|
|
31
32
|
| `toastNotify.info()` | Displays an info toast. Optionally accepts a custom title. |
|
|
32
33
|
| `toastNotify.failure()` | Displays a failure toast with an error and optional message or actions. |
|
|
34
|
+
| `toastNotify.caution()` | Displays a caution toast. Optionally accepts actions and a title. |
|
|
33
35
|
| `toastNotify.clear()` | Clears specific toasts, or all toasts if none are specified. |
|
|
34
36
|
| `toastNotify.toggleListView()` | Toggles the notification list view open or closed. |
|
|
35
37
|
| `toastNotify.countBySeverity` | Returns the count of notifications grouped by severity (e.g., success, info). |
|
|
@@ -41,6 +43,7 @@ Some example usages:
|
|
|
41
43
|
toastNotify.success("Your changes have been saved.");
|
|
42
44
|
toastNotify.success("Your changes have been saved.", [{label: "Undo", onClick: () => console.log("Undo clicked")}]);
|
|
43
45
|
```
|
|
46
|
+
|
|
44
47
|
2. **Show an info toast:**
|
|
45
48
|
```
|
|
46
49
|
toastNotify.info("Your changes are syncing in the background.");
|
|
@@ -52,16 +55,25 @@ toastNotify.info("Your changes are syncing in the background.", "Syncing");
|
|
|
52
55
|
toastNotify.failure("Save failed", new Error("500 Internal Server Error"), "Please try again.");
|
|
53
56
|
toastNotify.failure("Save failed", new Error("500 Internal Server Error"), "Please try again.", [{label: "Retry", onClick: () => console.log("Retry clicked")}]);
|
|
54
57
|
```
|
|
55
|
-
|
|
58
|
+
|
|
59
|
+
4. **Show a caution toast:**
|
|
60
|
+
```
|
|
61
|
+
toastNotify.caution("Your changes have not been saved.");
|
|
62
|
+
toastNotify.caution("Your changes have not been saved.", [{label: "Undo", onClick: () => console.log("Undo clicked")}]);
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
5. **Clear notifications:**
|
|
56
66
|
```
|
|
57
67
|
toastNotify.clear(); // clears all toast notifications
|
|
58
68
|
toastNotify.clear([notificationId]); // clears specific toast notifications
|
|
59
69
|
```
|
|
60
|
-
|
|
70
|
+
|
|
71
|
+
6. **Toggle the notification list view:**
|
|
61
72
|
```
|
|
62
73
|
toastNotify.toggleListView();
|
|
63
74
|
```
|
|
64
|
-
|
|
75
|
+
|
|
76
|
+
7. **Get the count of notifications by severity:**
|
|
65
77
|
```
|
|
66
78
|
const count = toastNotify.countBySeverity;
|
|
67
79
|
console.log(count.positive);
|
|
@@ -21,12 +21,14 @@ const initialNotification = {
|
|
|
21
21
|
const ToastNotificationContext = /*#__PURE__*/(0, _react.createContext)({
|
|
22
22
|
/** List of all active toast notifications */
|
|
23
23
|
notifications: [],
|
|
24
|
-
/** Show a success toast. Optionally pass actions. */
|
|
24
|
+
/** Show a success toast. Optionally pass actions and a title. */
|
|
25
25
|
success: () => initialNotification,
|
|
26
|
-
/** Show an info toast. Optionally pass a custom title. */
|
|
26
|
+
/** Show an info toast. Optionally pass a custom title and actions. */
|
|
27
27
|
info: () => initialNotification,
|
|
28
28
|
/** Show a failure toast with an error and optional message/actions. */
|
|
29
29
|
failure: () => initialNotification,
|
|
30
|
+
/** Show a caution toast. Optionally pass actions and a title. */
|
|
31
|
+
caution: () => initialNotification,
|
|
30
32
|
/** Clear one or more specific toasts, or all if none provided. */
|
|
31
33
|
clear: () => null,
|
|
32
34
|
/** Toggle between single toast view and list view. */
|
|
@@ -43,11 +45,12 @@ const ToastNotificationContext = /*#__PURE__*/(0, _react.createContext)({
|
|
|
43
45
|
Wrap your application with this provider, and in any child component you can get the helper with `const toastNotify = useToastNotification()` to trigger notifications.
|
|
44
46
|
Notifications automatically dismiss after a delay unless manually dismissed or expanded.
|
|
45
47
|
|
|
46
|
-
| **Values**
|
|
47
|
-
|
|
48
|
-
| `toastNotify.success()` | Displays a success toast. Optionally accepts actions.
|
|
48
|
+
| **Values** | **Description** |
|
|
49
|
+
|----------------------------------|--------------------------------------------------------------------------------|
|
|
50
|
+
| `toastNotify.success()` | Displays a success toast. Optionally accepts actions and a title. |
|
|
49
51
|
| `toastNotify.info()` | Displays an info toast. Optionally accepts a custom title. |
|
|
50
52
|
| `toastNotify.failure()` | Displays a failure toast with an error and optional message or actions. |
|
|
53
|
+
| `toastNotify.caution()` | Displays a caution toast. Optionally accepts actions and a title. |
|
|
51
54
|
| `toastNotify.clear()` | Clears specific toasts, or all toasts if none are specified. |
|
|
52
55
|
| `toastNotify.toggleListView()` | Toggles the notification list view open or closed. |
|
|
53
56
|
| `toastNotify.countBySeverity` | Returns the count of notifications grouped by severity (e.g., success, info). |
|
|
@@ -59,6 +62,7 @@ Some example usages:
|
|
|
59
62
|
toastNotify.success("Your changes have been saved.");
|
|
60
63
|
toastNotify.success("Your changes have been saved.", [{label: "Undo", onClick: () => console.log("Undo clicked")}]);
|
|
61
64
|
```
|
|
65
|
+
|
|
62
66
|
2. **Show an info toast:**
|
|
63
67
|
```
|
|
64
68
|
toastNotify.info("Your changes are syncing in the background.");
|
|
@@ -70,16 +74,25 @@ toastNotify.info("Your changes are syncing in the background.", "Syncing");
|
|
|
70
74
|
toastNotify.failure("Save failed", new Error("500 Internal Server Error"), "Please try again.");
|
|
71
75
|
toastNotify.failure("Save failed", new Error("500 Internal Server Error"), "Please try again.", [{label: "Retry", onClick: () => console.log("Retry clicked")}]);
|
|
72
76
|
```
|
|
73
|
-
|
|
77
|
+
|
|
78
|
+
4. **Show a caution toast:**
|
|
79
|
+
```
|
|
80
|
+
toastNotify.caution("Your changes have not been saved.");
|
|
81
|
+
toastNotify.caution("Your changes have not been saved.", [{label: "Undo", onClick: () => console.log("Undo clicked")}]);
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
5. **Clear notifications:**
|
|
74
85
|
```
|
|
75
86
|
toastNotify.clear(); // clears all toast notifications
|
|
76
87
|
toastNotify.clear([notificationId]); // clears specific toast notifications
|
|
77
88
|
```
|
|
78
|
-
|
|
89
|
+
|
|
90
|
+
6. **Toggle the notification list view:**
|
|
79
91
|
```
|
|
80
92
|
toastNotify.toggleListView();
|
|
81
93
|
```
|
|
82
|
-
|
|
94
|
+
|
|
95
|
+
7. **Get the count of notifications by severity:**
|
|
83
96
|
```
|
|
84
97
|
const count = toastNotify.countBySeverity;
|
|
85
98
|
console.log(count.positive);
|
|
@@ -174,12 +187,24 @@ const ToastNotificationProvider = _ref => {
|
|
|
174
187
|
const helper = {
|
|
175
188
|
notifications,
|
|
176
189
|
failure: (title, error, message, actions) => addNotification((0, _NotificationProvider.failure)(title, error, message, actions)),
|
|
177
|
-
info: (message, title) => addNotification(
|
|
178
|
-
success: (message, actions) => addNotification({
|
|
190
|
+
info: (message, title, actions) => addNotification({
|
|
179
191
|
message,
|
|
180
192
|
actions,
|
|
193
|
+
title,
|
|
194
|
+
type: _.NotificationSeverity.INFORMATION
|
|
195
|
+
}),
|
|
196
|
+
success: (message, actions, title) => addNotification({
|
|
197
|
+
message,
|
|
198
|
+
actions,
|
|
199
|
+
title,
|
|
181
200
|
type: _.NotificationSeverity.POSITIVE
|
|
182
201
|
}),
|
|
202
|
+
caution: (message, actions, title) => addNotification({
|
|
203
|
+
message,
|
|
204
|
+
actions,
|
|
205
|
+
title,
|
|
206
|
+
type: _.NotificationSeverity.CAUTION
|
|
207
|
+
}),
|
|
183
208
|
clear,
|
|
184
209
|
toggleListView,
|
|
185
210
|
isListView: showList,
|
|
@@ -21,9 +21,15 @@ export type Props = {
|
|
|
21
21
|
* The politeness setting of the spinner alert.
|
|
22
22
|
*/
|
|
23
23
|
ariaLive?: "assertive" | "off" | "polite";
|
|
24
|
+
/**
|
|
25
|
+
* Whether the Loader is replacing the main component in the application layout.
|
|
26
|
+
* In that case, the loader has to be wrapped with a main panel of the application
|
|
27
|
+
* layout. This is important for the mobile view. This prop enables the wrapping.
|
|
28
|
+
*/
|
|
29
|
+
isMainComponent?: boolean;
|
|
24
30
|
} & HTMLProps<HTMLSpanElement>;
|
|
25
31
|
/**
|
|
26
32
|
* This is a [React](https://reactjs.org/) component for the Vanilla [Spin](https://docs.vanillaframework.io/settings/animation-settings/#spin) animation.
|
|
27
33
|
*/
|
|
28
|
-
declare const Spinner: ({ className, text, isLight, ariaLive, role, ...props }: Props) => React.JSX.Element;
|
|
34
|
+
declare const Spinner: ({ className, text, isLight, ariaLive, role, isMainComponent, ...props }: Props) => React.JSX.Element;
|
|
29
35
|
export default Spinner;
|
|
@@ -7,6 +7,7 @@ exports.default = void 0;
|
|
|
7
7
|
var _propTypes = _interopRequireDefault(require("prop-types"));
|
|
8
8
|
var _react = _interopRequireDefault(require("react"));
|
|
9
9
|
var _classnames = _interopRequireDefault(require("classnames"));
|
|
10
|
+
var _CustomLayout = _interopRequireDefault(require("../CustomLayout"));
|
|
10
11
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
11
12
|
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); }
|
|
12
13
|
/**
|
|
@@ -19,9 +20,10 @@ const Spinner = _ref => {
|
|
|
19
20
|
isLight = false,
|
|
20
21
|
ariaLive = "polite",
|
|
21
22
|
role = "alert",
|
|
23
|
+
isMainComponent = false,
|
|
22
24
|
...props
|
|
23
25
|
} = _ref;
|
|
24
|
-
|
|
26
|
+
const loader = /*#__PURE__*/_react.default.createElement("span", _extends({}, props, {
|
|
25
27
|
className: (0, _classnames.default)(className, "p-text--default"),
|
|
26
28
|
role: role,
|
|
27
29
|
"aria-live": ariaLive
|
|
@@ -30,11 +32,13 @@ const Spinner = _ref => {
|
|
|
30
32
|
"is-light": isLight
|
|
31
33
|
})
|
|
32
34
|
}, text ? "" : "Loading"), text && /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, "\u2002", /*#__PURE__*/_react.default.createElement("span", null, text)));
|
|
35
|
+
return isMainComponent ? /*#__PURE__*/_react.default.createElement(_CustomLayout.default, null, loader) : loader;
|
|
33
36
|
};
|
|
34
37
|
Spinner.propTypes = {
|
|
35
38
|
isLight: _propTypes.default.bool,
|
|
36
39
|
text: _propTypes.default.string,
|
|
37
40
|
role: _propTypes.default.string,
|
|
38
|
-
ariaLive: _propTypes.default.oneOf(["assertive", "off", "polite"])
|
|
41
|
+
ariaLive: _propTypes.default.oneOf(["assertive", "off", "polite"]),
|
|
42
|
+
isMainComponent: _propTypes.default.bool
|
|
39
43
|
};
|
|
40
44
|
var _default = exports.default = Spinner;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import type { HTMLProps, ReactNode } from "react";
|
|
3
|
+
export type Props = {
|
|
4
|
+
/**
|
|
5
|
+
* This prop can be used to replace the header area of the panel when the default implementation is not sufficient.
|
|
6
|
+
*/
|
|
7
|
+
header?: ReactNode;
|
|
8
|
+
/**
|
|
9
|
+
* The main content.
|
|
10
|
+
*/
|
|
11
|
+
children: ReactNode;
|
|
12
|
+
/**
|
|
13
|
+
* Classes to apply to the main content.
|
|
14
|
+
*/
|
|
15
|
+
mainClassName?: string;
|
|
16
|
+
/**
|
|
17
|
+
* Classes to apply to the main area.
|
|
18
|
+
*/
|
|
19
|
+
contentClassName?: string;
|
|
20
|
+
} & HTMLProps<HTMLDivElement>;
|
|
21
|
+
declare const CustomLayout: ({ header, children, mainClassName, contentClassName, }: Props) => React.JSX.Element;
|
|
22
|
+
export default CustomLayout;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { AppMain } from "../ApplicationLayout";
|
|
3
|
+
import Panel from "../Panel";
|
|
4
|
+
var CustomLayout = _ref => {
|
|
5
|
+
var {
|
|
6
|
+
header,
|
|
7
|
+
children,
|
|
8
|
+
mainClassName,
|
|
9
|
+
contentClassName
|
|
10
|
+
} = _ref;
|
|
11
|
+
return /*#__PURE__*/React.createElement(AppMain, {
|
|
12
|
+
className: mainClassName,
|
|
13
|
+
id: "main-content"
|
|
14
|
+
}, /*#__PURE__*/React.createElement(Panel, {
|
|
15
|
+
contentClassName: contentClassName,
|
|
16
|
+
header: header
|
|
17
|
+
}, children));
|
|
18
|
+
};
|
|
19
|
+
export default CustomLayout;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
2
|
+
import CustomLayout from "./CustomLayout";
|
|
3
|
+
declare const meta: Meta<typeof CustomLayout>;
|
|
4
|
+
export default meta;
|
|
5
|
+
type Story = StoryObj<typeof CustomLayout>;
|
|
6
|
+
export declare const Default: Story;
|
|
7
|
+
export declare const Content: Story;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import Panel from "../Panel";
|
|
3
|
+
import CustomLayout from "./CustomLayout";
|
|
4
|
+
import AppMain from "../ApplicationLayout/AppMain";
|
|
5
|
+
var meta = {
|
|
6
|
+
component: CustomLayout,
|
|
7
|
+
tags: ["autodocs"]
|
|
8
|
+
};
|
|
9
|
+
export default meta;
|
|
10
|
+
export var Default = {
|
|
11
|
+
args: {
|
|
12
|
+
children: "CustomLayout"
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
export var Content = {
|
|
16
|
+
render: () => {
|
|
17
|
+
return /*#__PURE__*/React.createElement(AppMain, {
|
|
18
|
+
id: "main-content"
|
|
19
|
+
}, /*#__PURE__*/React.createElement(Panel, null, "test"));
|
|
20
|
+
}
|
|
21
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from "./CustomLayout";
|
|
@@ -25,6 +25,7 @@ var PreloadedList = () => {
|
|
|
25
25
|
toastNotify.success("Settings saved successfully");
|
|
26
26
|
toastNotify.info("Your changes are syncing in the background");
|
|
27
27
|
toastNotify.failure("Save failed", new Error("500 Internal Server Error"), "Please try again.");
|
|
28
|
+
toastNotify.caution("You have unsaved changes.");
|
|
28
29
|
toastNotify.toggleListView();
|
|
29
30
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
30
31
|
}, []);
|
|
@@ -8,9 +8,10 @@ export type ToastNotificationType = NotificationType & {
|
|
|
8
8
|
};
|
|
9
9
|
interface ToastNotificationHelper {
|
|
10
10
|
notifications: ToastNotificationType[];
|
|
11
|
-
success: (message: ReactNode, actions?: NotificationAction[]) => ToastNotificationType;
|
|
12
|
-
info: (message: ReactNode, title?: string) => ToastNotificationType;
|
|
11
|
+
success: (message: ReactNode, actions?: NotificationAction[], title?: string) => ToastNotificationType;
|
|
12
|
+
info: (message: ReactNode, title?: string, actions?: NotificationAction[]) => ToastNotificationType;
|
|
13
13
|
failure: (title: string, error: unknown, message?: ReactNode, actions?: NotificationAction[]) => ToastNotificationType;
|
|
14
|
+
caution: (message: ReactNode, actions?: NotificationAction[], title?: string) => ToastNotificationType;
|
|
14
15
|
clear: (notification?: ToastNotificationType[]) => void;
|
|
15
16
|
toggleListView: () => void;
|
|
16
17
|
isListView: boolean;
|
|
@@ -25,11 +26,12 @@ export type GroupedNotificationCount = {
|
|
|
25
26
|
Wrap your application with this provider, and in any child component you can get the helper with `const toastNotify = useToastNotification()` to trigger notifications.
|
|
26
27
|
Notifications automatically dismiss after a delay unless manually dismissed or expanded.
|
|
27
28
|
|
|
28
|
-
| **Values**
|
|
29
|
-
|
|
30
|
-
| `toastNotify.success()` | Displays a success toast. Optionally accepts actions.
|
|
29
|
+
| **Values** | **Description** |
|
|
30
|
+
|----------------------------------|--------------------------------------------------------------------------------|
|
|
31
|
+
| `toastNotify.success()` | Displays a success toast. Optionally accepts actions and a title. |
|
|
31
32
|
| `toastNotify.info()` | Displays an info toast. Optionally accepts a custom title. |
|
|
32
33
|
| `toastNotify.failure()` | Displays a failure toast with an error and optional message or actions. |
|
|
34
|
+
| `toastNotify.caution()` | Displays a caution toast. Optionally accepts actions and a title. |
|
|
33
35
|
| `toastNotify.clear()` | Clears specific toasts, or all toasts if none are specified. |
|
|
34
36
|
| `toastNotify.toggleListView()` | Toggles the notification list view open or closed. |
|
|
35
37
|
| `toastNotify.countBySeverity` | Returns the count of notifications grouped by severity (e.g., success, info). |
|
|
@@ -41,6 +43,7 @@ Some example usages:
|
|
|
41
43
|
toastNotify.success("Your changes have been saved.");
|
|
42
44
|
toastNotify.success("Your changes have been saved.", [{label: "Undo", onClick: () => console.log("Undo clicked")}]);
|
|
43
45
|
```
|
|
46
|
+
|
|
44
47
|
2. **Show an info toast:**
|
|
45
48
|
```
|
|
46
49
|
toastNotify.info("Your changes are syncing in the background.");
|
|
@@ -52,16 +55,25 @@ toastNotify.info("Your changes are syncing in the background.", "Syncing");
|
|
|
52
55
|
toastNotify.failure("Save failed", new Error("500 Internal Server Error"), "Please try again.");
|
|
53
56
|
toastNotify.failure("Save failed", new Error("500 Internal Server Error"), "Please try again.", [{label: "Retry", onClick: () => console.log("Retry clicked")}]);
|
|
54
57
|
```
|
|
55
|
-
|
|
58
|
+
|
|
59
|
+
4. **Show a caution toast:**
|
|
60
|
+
```
|
|
61
|
+
toastNotify.caution("Your changes have not been saved.");
|
|
62
|
+
toastNotify.caution("Your changes have not been saved.", [{label: "Undo", onClick: () => console.log("Undo clicked")}]);
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
5. **Clear notifications:**
|
|
56
66
|
```
|
|
57
67
|
toastNotify.clear(); // clears all toast notifications
|
|
58
68
|
toastNotify.clear([notificationId]); // clears specific toast notifications
|
|
59
69
|
```
|
|
60
|
-
|
|
70
|
+
|
|
71
|
+
6. **Toggle the notification list view:**
|
|
61
72
|
```
|
|
62
73
|
toastNotify.toggleListView();
|
|
63
74
|
```
|
|
64
|
-
|
|
75
|
+
|
|
76
|
+
7. **Get the count of notifications by severity:**
|
|
65
77
|
```
|
|
66
78
|
const count = toastNotify.countBySeverity;
|
|
67
79
|
console.log(count.positive);
|
|
@@ -3,7 +3,7 @@ function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t =
|
|
|
3
3
|
function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
|
|
4
4
|
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : String(i); }
|
|
5
5
|
function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
|
|
6
|
-
import { failure as _failure
|
|
6
|
+
import { failure as _failure } from "../../NotificationProvider";
|
|
7
7
|
import { NotificationSeverity } from "./..";
|
|
8
8
|
import ToastNotification from "./ToastNotification";
|
|
9
9
|
import ToastNotificationList from "./ToastNotificationList";
|
|
@@ -18,12 +18,14 @@ var initialNotification = {
|
|
|
18
18
|
var ToastNotificationContext = /*#__PURE__*/createContext({
|
|
19
19
|
/** List of all active toast notifications */
|
|
20
20
|
notifications: [],
|
|
21
|
-
/** Show a success toast. Optionally pass actions. */
|
|
21
|
+
/** Show a success toast. Optionally pass actions and a title. */
|
|
22
22
|
success: () => initialNotification,
|
|
23
|
-
/** Show an info toast. Optionally pass a custom title. */
|
|
23
|
+
/** Show an info toast. Optionally pass a custom title and actions. */
|
|
24
24
|
info: () => initialNotification,
|
|
25
25
|
/** Show a failure toast with an error and optional message/actions. */
|
|
26
26
|
failure: () => initialNotification,
|
|
27
|
+
/** Show a caution toast. Optionally pass actions and a title. */
|
|
28
|
+
caution: () => initialNotification,
|
|
27
29
|
/** Clear one or more specific toasts, or all if none provided. */
|
|
28
30
|
clear: () => null,
|
|
29
31
|
/** Toggle between single toast view and list view. */
|
|
@@ -40,11 +42,12 @@ var ToastNotificationContext = /*#__PURE__*/createContext({
|
|
|
40
42
|
Wrap your application with this provider, and in any child component you can get the helper with `const toastNotify = useToastNotification()` to trigger notifications.
|
|
41
43
|
Notifications automatically dismiss after a delay unless manually dismissed or expanded.
|
|
42
44
|
|
|
43
|
-
| **Values**
|
|
44
|
-
|
|
45
|
-
| `toastNotify.success()` | Displays a success toast. Optionally accepts actions.
|
|
45
|
+
| **Values** | **Description** |
|
|
46
|
+
|----------------------------------|--------------------------------------------------------------------------------|
|
|
47
|
+
| `toastNotify.success()` | Displays a success toast. Optionally accepts actions and a title. |
|
|
46
48
|
| `toastNotify.info()` | Displays an info toast. Optionally accepts a custom title. |
|
|
47
49
|
| `toastNotify.failure()` | Displays a failure toast with an error and optional message or actions. |
|
|
50
|
+
| `toastNotify.caution()` | Displays a caution toast. Optionally accepts actions and a title. |
|
|
48
51
|
| `toastNotify.clear()` | Clears specific toasts, or all toasts if none are specified. |
|
|
49
52
|
| `toastNotify.toggleListView()` | Toggles the notification list view open or closed. |
|
|
50
53
|
| `toastNotify.countBySeverity` | Returns the count of notifications grouped by severity (e.g., success, info). |
|
|
@@ -56,6 +59,7 @@ Some example usages:
|
|
|
56
59
|
toastNotify.success("Your changes have been saved.");
|
|
57
60
|
toastNotify.success("Your changes have been saved.", [{label: "Undo", onClick: () => console.log("Undo clicked")}]);
|
|
58
61
|
```
|
|
62
|
+
|
|
59
63
|
2. **Show an info toast:**
|
|
60
64
|
```
|
|
61
65
|
toastNotify.info("Your changes are syncing in the background.");
|
|
@@ -67,16 +71,25 @@ toastNotify.info("Your changes are syncing in the background.", "Syncing");
|
|
|
67
71
|
toastNotify.failure("Save failed", new Error("500 Internal Server Error"), "Please try again.");
|
|
68
72
|
toastNotify.failure("Save failed", new Error("500 Internal Server Error"), "Please try again.", [{label: "Retry", onClick: () => console.log("Retry clicked")}]);
|
|
69
73
|
```
|
|
70
|
-
|
|
74
|
+
|
|
75
|
+
4. **Show a caution toast:**
|
|
76
|
+
```
|
|
77
|
+
toastNotify.caution("Your changes have not been saved.");
|
|
78
|
+
toastNotify.caution("Your changes have not been saved.", [{label: "Undo", onClick: () => console.log("Undo clicked")}]);
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
5. **Clear notifications:**
|
|
71
82
|
```
|
|
72
83
|
toastNotify.clear(); // clears all toast notifications
|
|
73
84
|
toastNotify.clear([notificationId]); // clears specific toast notifications
|
|
74
85
|
```
|
|
75
|
-
|
|
86
|
+
|
|
87
|
+
6. **Toggle the notification list view:**
|
|
76
88
|
```
|
|
77
89
|
toastNotify.toggleListView();
|
|
78
90
|
```
|
|
79
|
-
|
|
91
|
+
|
|
92
|
+
7. **Get the count of notifications by severity:**
|
|
80
93
|
```
|
|
81
94
|
const count = toastNotify.countBySeverity;
|
|
82
95
|
console.log(count.positive);
|
|
@@ -170,12 +183,24 @@ var ToastNotificationProvider = _ref => {
|
|
|
170
183
|
var helper = {
|
|
171
184
|
notifications,
|
|
172
185
|
failure: (title, error, message, actions) => addNotification(_failure(title, error, message, actions)),
|
|
173
|
-
info: (message, title) => addNotification(
|
|
174
|
-
success: (message, actions) => addNotification({
|
|
186
|
+
info: (message, title, actions) => addNotification({
|
|
175
187
|
message,
|
|
176
188
|
actions,
|
|
189
|
+
title,
|
|
190
|
+
type: NotificationSeverity.INFORMATION
|
|
191
|
+
}),
|
|
192
|
+
success: (message, actions, title) => addNotification({
|
|
193
|
+
message,
|
|
194
|
+
actions,
|
|
195
|
+
title,
|
|
177
196
|
type: NotificationSeverity.POSITIVE
|
|
178
197
|
}),
|
|
198
|
+
caution: (message, actions, title) => addNotification({
|
|
199
|
+
message,
|
|
200
|
+
actions,
|
|
201
|
+
title,
|
|
202
|
+
type: NotificationSeverity.CAUTION
|
|
203
|
+
}),
|
|
179
204
|
clear,
|
|
180
205
|
toggleListView,
|
|
181
206
|
isListView: showList,
|
|
@@ -21,9 +21,15 @@ export type Props = {
|
|
|
21
21
|
* The politeness setting of the spinner alert.
|
|
22
22
|
*/
|
|
23
23
|
ariaLive?: "assertive" | "off" | "polite";
|
|
24
|
+
/**
|
|
25
|
+
* Whether the Loader is replacing the main component in the application layout.
|
|
26
|
+
* In that case, the loader has to be wrapped with a main panel of the application
|
|
27
|
+
* layout. This is important for the mobile view. This prop enables the wrapping.
|
|
28
|
+
*/
|
|
29
|
+
isMainComponent?: boolean;
|
|
24
30
|
} & HTMLProps<HTMLSpanElement>;
|
|
25
31
|
/**
|
|
26
32
|
* This is a [React](https://reactjs.org/) component for the Vanilla [Spin](https://docs.vanillaframework.io/settings/animation-settings/#spin) animation.
|
|
27
33
|
*/
|
|
28
|
-
declare const Spinner: ({ className, text, isLight, ariaLive, role, ...props }: Props) => React.JSX.Element;
|
|
34
|
+
declare const Spinner: ({ className, text, isLight, ariaLive, role, isMainComponent, ...props }: Props) => React.JSX.Element;
|
|
29
35
|
export default Spinner;
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import _pt from "prop-types";
|
|
2
|
-
var _excluded = ["className", "text", "isLight", "ariaLive", "role"];
|
|
2
|
+
var _excluded = ["className", "text", "isLight", "ariaLive", "role", "isMainComponent"];
|
|
3
3
|
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); }
|
|
4
4
|
function _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }
|
|
5
5
|
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; }
|
|
6
6
|
import React from "react";
|
|
7
7
|
import classNames from "classnames";
|
|
8
|
+
import CustomLayout from "../CustomLayout";
|
|
8
9
|
/**
|
|
9
10
|
* This is a [React](https://reactjs.org/) component for the Vanilla [Spin](https://docs.vanillaframework.io/settings/animation-settings/#spin) animation.
|
|
10
11
|
*/
|
|
@@ -14,10 +15,11 @@ var Spinner = _ref => {
|
|
|
14
15
|
text,
|
|
15
16
|
isLight = false,
|
|
16
17
|
ariaLive = "polite",
|
|
17
|
-
role = "alert"
|
|
18
|
+
role = "alert",
|
|
19
|
+
isMainComponent = false
|
|
18
20
|
} = _ref,
|
|
19
21
|
props = _objectWithoutProperties(_ref, _excluded);
|
|
20
|
-
|
|
22
|
+
var loader = /*#__PURE__*/React.createElement("span", _extends({}, props, {
|
|
21
23
|
className: classNames(className, "p-text--default"),
|
|
22
24
|
role: role,
|
|
23
25
|
"aria-live": ariaLive
|
|
@@ -26,11 +28,13 @@ var Spinner = _ref => {
|
|
|
26
28
|
"is-light": isLight
|
|
27
29
|
})
|
|
28
30
|
}, text ? "" : "Loading"), text && /*#__PURE__*/React.createElement(React.Fragment, null, "\u2002", /*#__PURE__*/React.createElement("span", null, text)));
|
|
31
|
+
return isMainComponent ? /*#__PURE__*/React.createElement(CustomLayout, null, loader) : loader;
|
|
29
32
|
};
|
|
30
33
|
Spinner.propTypes = {
|
|
31
34
|
isLight: _pt.bool,
|
|
32
35
|
text: _pt.string,
|
|
33
36
|
role: _pt.string,
|
|
34
|
-
ariaLive: _pt.oneOf(["assertive", "off", "polite"])
|
|
37
|
+
ariaLive: _pt.oneOf(["assertive", "off", "polite"]),
|
|
38
|
+
isMainComponent: _pt.bool
|
|
35
39
|
};
|
|
36
40
|
export default Spinner;
|
package/dist/esm/index.d.ts
CHANGED
|
@@ -71,6 +71,7 @@ export { ToastNotification, ToastNotificationList, ToastNotificationProvider, us
|
|
|
71
71
|
export { default as Tooltip } from "./components/Tooltip";
|
|
72
72
|
export { default as TablePagination } from "./components/TablePagination";
|
|
73
73
|
export { default as TablePaginationControls } from "./components/TablePagination/TablePaginationControls";
|
|
74
|
+
export { default as CustomLayout } from "./components/CustomLayout";
|
|
74
75
|
export { default as CustomSelect } from "./components/CustomSelect";
|
|
75
76
|
export type { AccordionProps } from "./components/Accordion";
|
|
76
77
|
export type { ActionButtonProps } from "./components/ActionButton";
|
package/dist/esm/index.js
CHANGED
|
@@ -71,6 +71,7 @@ export { ToastNotification, ToastNotificationList, ToastNotificationProvider, us
|
|
|
71
71
|
export { default as Tooltip } from "./components/Tooltip";
|
|
72
72
|
export { default as TablePagination } from "./components/TablePagination";
|
|
73
73
|
export { default as TablePaginationControls } from "./components/TablePagination/TablePaginationControls";
|
|
74
|
+
export { default as CustomLayout } from "./components/CustomLayout";
|
|
74
75
|
export { default as CustomSelect } from "./components/CustomSelect";
|
|
75
76
|
export { useOnClickOutside, useClickOutside, useId, useListener, useOnEscapePressed, usePagination, usePrevious, usePrefersReducedMotion, useThrottle, useWindowFitment } from "./hooks";
|
|
76
77
|
export { isNavigationAnchor, isNavigationButton } from "./utils";
|
package/dist/index.d.ts
CHANGED
|
@@ -71,6 +71,7 @@ export { ToastNotification, ToastNotificationList, ToastNotificationProvider, us
|
|
|
71
71
|
export { default as Tooltip } from "./components/Tooltip";
|
|
72
72
|
export { default as TablePagination } from "./components/TablePagination";
|
|
73
73
|
export { default as TablePaginationControls } from "./components/TablePagination/TablePaginationControls";
|
|
74
|
+
export { default as CustomLayout } from "./components/CustomLayout";
|
|
74
75
|
export { default as CustomSelect } from "./components/CustomSelect";
|
|
75
76
|
export type { AccordionProps } from "./components/Accordion";
|
|
76
77
|
export type { ActionButtonProps } from "./components/ActionButton";
|
package/dist/index.js
CHANGED
|
@@ -94,6 +94,7 @@ var _exportNames = {
|
|
|
94
94
|
Tooltip: true,
|
|
95
95
|
TablePagination: true,
|
|
96
96
|
TablePaginationControls: true,
|
|
97
|
+
CustomLayout: true,
|
|
97
98
|
CustomSelect: true,
|
|
98
99
|
useOnClickOutside: true,
|
|
99
100
|
useClickOutside: true,
|
|
@@ -248,6 +249,12 @@ Object.defineProperty(exports, "ContextualMenu", {
|
|
|
248
249
|
return _ContextualMenu.default;
|
|
249
250
|
}
|
|
250
251
|
});
|
|
252
|
+
Object.defineProperty(exports, "CustomLayout", {
|
|
253
|
+
enumerable: true,
|
|
254
|
+
get: function () {
|
|
255
|
+
return _CustomLayout.default;
|
|
256
|
+
}
|
|
257
|
+
});
|
|
251
258
|
Object.defineProperty(exports, "CustomSelect", {
|
|
252
259
|
enumerable: true,
|
|
253
260
|
get: function () {
|
|
@@ -823,6 +830,7 @@ var _ThemeSwitcher = _interopRequireWildcard(require("./components/ThemeSwitcher
|
|
|
823
830
|
var _Tooltip = _interopRequireDefault(require("./components/Tooltip"));
|
|
824
831
|
var _TablePagination = _interopRequireDefault(require("./components/TablePagination"));
|
|
825
832
|
var _TablePaginationControls = _interopRequireDefault(require("./components/TablePagination/TablePaginationControls"));
|
|
833
|
+
var _CustomLayout = _interopRequireDefault(require("./components/CustomLayout"));
|
|
826
834
|
var _CustomSelect = _interopRequireDefault(require("./components/CustomSelect"));
|
|
827
835
|
var _hooks = require("./hooks");
|
|
828
836
|
var _utils = require("./utils");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@canonical/react-components",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.11.0",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"module": "dist/index.js",
|
|
6
6
|
"author": {
|
|
@@ -93,7 +93,7 @@
|
|
|
93
93
|
"tsc-alias": "1.8.10",
|
|
94
94
|
"typescript": "5.7.3",
|
|
95
95
|
"typescript-eslint": "8.24.1",
|
|
96
|
-
"vanilla-framework": "4.26.
|
|
96
|
+
"vanilla-framework": "4.26.1",
|
|
97
97
|
"wait-on": "8.0.2",
|
|
98
98
|
"webpack": "5.98.0"
|
|
99
99
|
},
|