@truedat/core 8.12.11 → 8.13.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@truedat/core",
3
- "version": "8.12.11",
3
+ "version": "8.13.0",
4
4
  "description": "Truedat Web Core",
5
5
  "sideEffects": [
6
6
  "**/*.css",
@@ -57,7 +57,7 @@
57
57
  "@testing-library/jest-dom": "^6.6.3",
58
58
  "@testing-library/react": "^16.3.0",
59
59
  "@testing-library/user-event": "^14.6.1",
60
- "@truedat/test": "8.12.11",
60
+ "@truedat/test": "8.13.0",
61
61
  "identity-obj-proxy": "^3.0.0",
62
62
  "jest": "^29.7.0",
63
63
  "redux-saga-test-plan": "^4.0.6"
@@ -99,5 +99,5 @@
99
99
  "swr": "^2.3.3",
100
100
  "turndown": "^7.2.2"
101
101
  },
102
- "gitHead": "48671e0f1467f7713019ab8d729818c741fa9bc7"
102
+ "gitHead": "b9d1c2133cea6293e6a5ce5d198a4cd49133b4c1"
103
103
  }
@@ -1671,37 +1671,44 @@ const LayoutFlow = ({
1671
1671
  />
1672
1672
  </button>
1673
1673
  </div>
1674
- {!isMinimapCollapsed ? (
1675
- <div className="td-graph-minimap-body">
1676
- <MiniMap
1677
- position="bottom-left"
1678
- pannable
1679
- zoomable
1680
- maskColor="rgba(15, 23, 42, 0.08)"
1681
- nodeColor={minimapNodeColor}
1682
- nodeStrokeColor="rgba(15, 23, 42, 0.12)"
1683
- className="td-graph-minimap"
1684
- />
1685
- <button
1686
- type="button"
1687
- className="td-graph-minimap-expand"
1688
- aria-label={
1689
- isMinimapExpanded ? "Reducir mini-mapa" : "Expandir mini-mapa"
1690
- }
1691
- onClick={(event) => {
1692
- event.stopPropagation();
1693
- setIsMinimapExpanded((prev) => !prev);
1694
- }}
1695
- onMouseDown={(event) => event.stopPropagation()}
1696
- >
1697
- {isMinimapExpanded ? (
1698
- <Minimize2 size={12} strokeWidth={2} />
1699
- ) : (
1700
- <Expand size={12} strokeWidth={2} />
1701
- )}
1702
- </button>
1703
- </div>
1704
- ) : null}
1674
+ <div
1675
+ className="td-graph-minimap-body"
1676
+ aria-hidden={isMinimapCollapsed}
1677
+ inert={isMinimapCollapsed}
1678
+ >
1679
+ <MiniMap
1680
+ position="bottom-left"
1681
+ pannable
1682
+ zoomable
1683
+ maskColor="rgba(15, 23, 42, 0.08)"
1684
+ nodeColor={minimapNodeColor}
1685
+ nodeStrokeColor="rgba(15, 23, 42, 0.12)"
1686
+ className="td-graph-minimap"
1687
+ style={
1688
+ isMinimapExpanded
1689
+ ? { width: 288, height: 200 }
1690
+ : { width: 200, height: 150 }
1691
+ }
1692
+ />
1693
+ <button
1694
+ type="button"
1695
+ className="td-graph-minimap-expand"
1696
+ aria-label={
1697
+ isMinimapExpanded ? "Reducir mini-mapa" : "Expandir mini-mapa"
1698
+ }
1699
+ onClick={(event) => {
1700
+ event.stopPropagation();
1701
+ setIsMinimapExpanded((prev) => !prev);
1702
+ }}
1703
+ onMouseDown={(event) => event.stopPropagation()}
1704
+ >
1705
+ {isMinimapExpanded ? (
1706
+ <Minimize2 size={12} strokeWidth={2} />
1707
+ ) : (
1708
+ <Expand size={12} strokeWidth={2} />
1709
+ )}
1710
+ </button>
1711
+ </div>
1705
1712
  </div>
1706
1713
  )}
1707
1714
  {bottomControls && (
@@ -0,0 +1,137 @@
1
+ import { useId } from "react";
2
+ import PropTypes from "prop-types";
3
+ import { useIntl, FormattedMessage } from "react-intl";
4
+ import { Button, Icon, Modal } from "semantic-ui-react";
5
+ import _ from "lodash/fp";
6
+
7
+ const bannerIcons = {
8
+ warning: "warning sign",
9
+ danger: "times circle",
10
+ success: "check circle",
11
+ info: "info circle",
12
+ };
13
+
14
+ export const TrueModal = ({
15
+ title,
16
+ subtitle,
17
+ icon,
18
+ headerColor,
19
+ headerTextColor,
20
+ children,
21
+ banner,
22
+ bannerType = "warning",
23
+ bannerIcon,
24
+ actions,
25
+ footer,
26
+ onCancel,
27
+ onCreate,
28
+ className,
29
+ ...modalProps
30
+ }) => {
31
+ const titleId = useId();
32
+ const { formatMessage } = useIntl();
33
+ const defaultActions = _.compact([
34
+ onCancel && {
35
+ key: "cancel",
36
+ content: <FormattedMessage id="actions.cancel" defaultMessage="Cancel" />,
37
+ secondary: true,
38
+ onClick: onCancel,
39
+ },
40
+ onCreate && {
41
+ key: "create",
42
+ content: <FormattedMessage id="actions.create" defaultMessage="Create" />,
43
+ primary: true,
44
+ onClick: onCreate,
45
+ },
46
+ ]);
47
+ const resolvedActions = actions?.length ? actions : defaultActions;
48
+
49
+ return (
50
+ <Modal
51
+ {..._.omit(["header", "content", "closeIcon"])(modalProps)}
52
+ className={_.compact(["true-modal", className]).join(" ")}
53
+ role="dialog"
54
+ aria-labelledby={
55
+ modalProps["aria-labelledby"] ||
56
+ (modalProps["aria-label"] ? undefined : titleId)
57
+ }
58
+ closeIcon={
59
+ <button
60
+ type="button"
61
+ className="true-modal-close"
62
+ style={{ color: headerTextColor }}
63
+ aria-label={formatMessage({
64
+ id: "actions.close",
65
+ defaultMessage: "Close",
66
+ })}
67
+ >
68
+ &times;
69
+ </button>
70
+ }
71
+ >
72
+ <Modal.Header style={{ background: headerColor, color: headerTextColor }}>
73
+ {icon ? <Icon name={icon} size="large" /> : null}
74
+ <div className="true-modal-heading">
75
+ <span id={titleId}>{title}</span>
76
+ {subtitle != null ? (
77
+ <p className="true-modal-subtitle">{subtitle}</p>
78
+ ) : null}
79
+ </div>
80
+ </Modal.Header>
81
+ <Modal.Content className="true-modal-content">{children}</Modal.Content>
82
+ {banner != null && banner !== false ? (
83
+ <div className={`true-modal-banner ${bannerType}`}>
84
+ <Icon name={bannerIcon || bannerIcons[bannerType]} />
85
+ <div>{banner}</div>
86
+ </div>
87
+ ) : null}
88
+ {footer != null ? (
89
+ footer
90
+ ) : resolvedActions.length ? (
91
+ <Modal.Actions className="true-modal-actions">
92
+ {resolvedActions.map(({ key, disabled, loading, ...buttonProps }) => (
93
+ <Button
94
+ key={key}
95
+ type="button"
96
+ {...buttonProps}
97
+ disabled={disabled || loading}
98
+ loading={loading}
99
+ />
100
+ ))}
101
+ </Modal.Actions>
102
+ ) : null}
103
+ </Modal>
104
+ );
105
+ };
106
+
107
+ TrueModal.propTypes = {
108
+ title: PropTypes.node.isRequired,
109
+ subtitle: PropTypes.node,
110
+ icon: PropTypes.string,
111
+ headerColor: PropTypes.string,
112
+ headerTextColor: PropTypes.string,
113
+ children: PropTypes.node,
114
+ banner: PropTypes.node,
115
+ bannerType: PropTypes.oneOf(["warning", "danger", "success", "info"]),
116
+ bannerIcon: PropTypes.string,
117
+ actions: PropTypes.arrayOf(
118
+ PropTypes.shape({
119
+ key: PropTypes.string.isRequired,
120
+ content: PropTypes.node,
121
+ onClick: PropTypes.func,
122
+ disabled: PropTypes.bool,
123
+ loading: PropTypes.bool,
124
+ primary: PropTypes.bool,
125
+ secondary: PropTypes.bool,
126
+ negative: PropTypes.bool,
127
+ type: PropTypes.string,
128
+ "aria-label": PropTypes.string,
129
+ }),
130
+ ),
131
+ footer: PropTypes.node,
132
+ onCancel: PropTypes.func,
133
+ onCreate: PropTypes.func,
134
+ className: PropTypes.string,
135
+ };
136
+
137
+ export default TrueModal;
@@ -61,6 +61,9 @@ jest.mock("@xyflow/react", () => ({
61
61
  {children}
62
62
  </button>
63
63
  ),
64
+ MiniMap: ({ style, className, ...props }) => (
65
+ <div data-testid="mini-map" className={className} style={style} />
66
+ ),
64
67
  Background: () => null,
65
68
  BackgroundVariant: {
66
69
  Dots: "dots",
@@ -286,4 +289,57 @@ describe("<Graph />", () => {
286
289
  expect(mockSetCenter).not.toHaveBeenCalled();
287
290
  expect(mockSetViewport).not.toHaveBeenCalled();
288
291
  });
292
+
293
+ describe("mini-map", () => {
294
+ const minimapProps = {
295
+ ...props,
296
+ graphOptions: {
297
+ minimapNodeColor: () => "#ffffff",
298
+ },
299
+ };
300
+
301
+ it("is not rendered without minimapNodeColor", () => {
302
+ render(<Graph {...props} />);
303
+
304
+ expect(screen.queryByTestId("mini-map")).not.toBeInTheDocument();
305
+ });
306
+
307
+ it("collapses and expands the shell from the header toggle", () => {
308
+ const { container } = render(<Graph {...minimapProps} />);
309
+
310
+ const shell = container.querySelector(".td-graph-minimap-shell");
311
+ const toggle = container.querySelector(".td-graph-minimap-toggle");
312
+ const body = container.querySelector(".td-graph-minimap-body");
313
+
314
+ expect(shell).not.toHaveClass("td-graph-minimap-shell--collapsed");
315
+ expect(toggle).toHaveAttribute("aria-expanded", "true");
316
+ expect(body).not.toHaveAttribute("inert");
317
+ expect(body).toHaveAttribute("aria-hidden", "false");
318
+
319
+ fireEvent.click(toggle);
320
+
321
+ expect(shell).toHaveClass("td-graph-minimap-shell--collapsed");
322
+ expect(toggle).toHaveAttribute("aria-expanded", "false");
323
+ expect(body).toHaveAttribute("inert");
324
+ expect(body).toHaveAttribute("aria-hidden", "true");
325
+ });
326
+
327
+ it("resizes the mini-map with the expand button", () => {
328
+ const { container } = render(<Graph {...minimapProps} />);
329
+
330
+ const expandButton = container.querySelector(".td-graph-minimap-expand");
331
+
332
+ expect(screen.getByTestId("mini-map")).toHaveStyle({
333
+ width: "200px",
334
+ height: "150px",
335
+ });
336
+
337
+ fireEvent.click(expandButton);
338
+
339
+ expect(screen.getByTestId("mini-map")).toHaveStyle({
340
+ width: "288px",
341
+ height: "200px",
342
+ });
343
+ });
344
+ });
289
345
  });
@@ -0,0 +1,193 @@
1
+ import { screen } from "@testing-library/react";
2
+ import userEvent from "@testing-library/user-event";
3
+ import { render } from "@truedat/test/render";
4
+ import { TrueModal } from "../index";
5
+
6
+ const setupUser = () =>
7
+ userEvent.setup({ advanceTimers: jest.advanceTimersByTime });
8
+ const renderModal = (props = {}) =>
9
+ render(<TrueModal open title="Title" {...props} />);
10
+
11
+ describe("<TrueModal />", () => {
12
+ it("renders accessible content and preserves consumer classes and size", () => {
13
+ renderModal({
14
+ subtitle: "Subtitle",
15
+ icon: "rocket",
16
+ children: <p>Content</p>,
17
+ className: "custom-modal",
18
+ size: "large",
19
+ header: "Wrong header",
20
+ content: "Wrong content",
21
+ closeIcon: false,
22
+ });
23
+ const dialog = screen.getByRole("dialog", { name: "Title" });
24
+ expect(dialog).toHaveClass("true-modal", "custom-modal", "large");
25
+ expect(screen.getByText("Subtitle")).toBeInTheDocument();
26
+ expect(screen.getByText("Content")).toBeInTheDocument();
27
+ expect(dialog.querySelector(".rocket.icon")).toBeInTheDocument();
28
+ expect(screen.queryByText(/Wrong/)).not.toBeInTheDocument();
29
+ expect(screen.getByRole("button", { name: "Close" })).toBeInTheDocument();
30
+ expect(dialog.querySelector(".actions")).not.toBeInTheDocument();
31
+ expect(dialog.querySelector(".true-modal-banner")).not.toBeInTheDocument();
32
+ });
33
+
34
+ it.each([
35
+ [{ headerColor: "#123456" }, { background: "#123456" }],
36
+ [{ headerTextColor: "#ffffff" }, { color: "#ffffff" }],
37
+ ])("applies independent header colors %o", (props, style) => {
38
+ renderModal(props);
39
+ expect(screen.getByText("Title").closest(".header")).toHaveStyle(style);
40
+ });
41
+
42
+ it.each([
43
+ ["warning", "warning.sign"],
44
+ ["danger", "times.circle"],
45
+ ["success", "check.circle"],
46
+ ["info", "info.circle"],
47
+ ])("renders %s banner", (bannerType, iconClass) => {
48
+ renderModal({ bannerType, banner: <strong>Notice</strong> });
49
+ const banner = screen.getByText("Notice").closest(".true-modal-banner");
50
+ expect(banner).toHaveClass(bannerType);
51
+ expect(banner.querySelector(`.${iconClass}.icon`)).toBeInTheDocument();
52
+ });
53
+
54
+ it("supports a custom banner icon and an explicit accessible name", () => {
55
+ renderModal({
56
+ banner: "Notice",
57
+ bannerIcon: "rocket",
58
+ "aria-label": "Custom",
59
+ });
60
+ const dialog = screen.getByRole("dialog", { name: "Custom" });
61
+ expect(dialog).not.toHaveAttribute("aria-labelledby");
62
+ expect(
63
+ dialog.querySelector(".true-modal-banner .rocket.icon"),
64
+ ).toBeInTheDocument();
65
+ });
66
+
67
+ it.each([undefined, []])(
68
+ "resolves default callbacks for actions %o",
69
+ async (actions) => {
70
+ const onCancel = jest.fn();
71
+ const onCreate = jest.fn();
72
+ const onClose = jest.fn();
73
+ renderModal({ actions, onCancel, onCreate, onClose });
74
+ const user = setupUser();
75
+ await user.click(screen.getByRole("button", { name: "Cancel" }));
76
+ await user.click(screen.getByRole("button", { name: "Create" }));
77
+ expect(onCancel).toHaveBeenCalledTimes(1);
78
+ expect(onCreate).toHaveBeenCalledTimes(1);
79
+ expect(onClose).not.toHaveBeenCalled();
80
+ },
81
+ );
82
+
83
+ it.each(["onCancel", "onCreate"])(
84
+ "renders only the supplied %s callback",
85
+ (callback) => {
86
+ renderModal({ [callback]: jest.fn() });
87
+ expect(screen.getAllByRole("button")).toHaveLength(2);
88
+ },
89
+ );
90
+
91
+ it("renders three actions and blocks disabled and loading callbacks", async () => {
92
+ const onClick = jest.fn();
93
+ renderModal({
94
+ actions: [
95
+ { key: "one", content: "One", onClick },
96
+ { key: "two", content: "Two", disabled: true, onClick },
97
+ {
98
+ key: "three",
99
+ content: "Three",
100
+ loading: true,
101
+ disabled: false,
102
+ onClick,
103
+ type: "submit",
104
+ },
105
+ ],
106
+ });
107
+ const user = setupUser();
108
+ expect(screen.getByRole("button", { name: "One" })).toHaveAttribute(
109
+ "type",
110
+ "button",
111
+ );
112
+ expect(screen.getByRole("button", { name: "Two" })).toBeDisabled();
113
+ expect(screen.getByRole("button", { name: "Three" })).toBeDisabled();
114
+ expect(screen.getByRole("button", { name: "Three" })).toHaveClass(
115
+ "loading",
116
+ );
117
+ expect(screen.getByRole("button", { name: "Three" })).toHaveAttribute(
118
+ "type",
119
+ "submit",
120
+ );
121
+ await user.click(screen.getByRole("button", { name: "Two" }));
122
+ await user.click(screen.getByRole("button", { name: "Three" }));
123
+ expect(onClick).not.toHaveBeenCalled();
124
+ await user.click(screen.getByRole("button", { name: "One" }));
125
+ expect(onClick).toHaveBeenCalledTimes(1);
126
+ });
127
+
128
+ it("gives custom footer precedence over both configured and default actions", () => {
129
+ renderModal({
130
+ footer: <button type="button">Custom</button>,
131
+ actions: [{ key: "one", content: "One" }],
132
+ onCancel: jest.fn(),
133
+ });
134
+ expect(screen.getByRole("button", { name: "Custom" })).toBeInTheDocument();
135
+ expect(
136
+ screen.queryByRole("button", { name: "One" }),
137
+ ).not.toBeInTheDocument();
138
+ expect(
139
+ screen.queryByRole("button", { name: "Cancel" }),
140
+ ).not.toBeInTheDocument();
141
+ });
142
+
143
+ it.each(["button", "escape", "overlay"])(
144
+ "closes through %s with the native callback contract",
145
+ async (method) => {
146
+ const onOpen = jest.fn();
147
+ const onClose = jest.fn();
148
+ render(
149
+ <TrueModal
150
+ title="Title"
151
+ trigger={<button type="button">Open</button>}
152
+ onOpen={onOpen}
153
+ onClose={onClose}
154
+ />,
155
+ );
156
+ const user = setupUser();
157
+ await user.click(screen.getByRole("button", { name: "Open" }));
158
+ expect(onOpen).toHaveBeenCalledWith(
159
+ expect.anything(),
160
+ expect.objectContaining({ open: true }),
161
+ );
162
+ if (method === "button")
163
+ await user.click(screen.getByRole("button", { name: "Close" }));
164
+ if (method === "escape") await user.keyboard("{Escape}");
165
+ if (method === "overlay")
166
+ await user.click(screen.getByRole("dialog").parentElement);
167
+ expect(onClose).toHaveBeenCalledTimes(1);
168
+ expect(onClose).toHaveBeenCalledWith(
169
+ expect.anything(),
170
+ expect.objectContaining({ open: false }),
171
+ );
172
+ expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
173
+ },
174
+ );
175
+
176
+ it("respects controlled open and disabled Escape and overlay closing", async () => {
177
+ const onClose = jest.fn();
178
+ const { rerender } = renderModal({
179
+ onClose,
180
+ closeOnEscape: false,
181
+ closeOnDimmerClick: false,
182
+ });
183
+ const user = setupUser();
184
+ await user.keyboard("{Escape}");
185
+ await user.click(screen.getByRole("dialog").parentElement);
186
+ expect(onClose).not.toHaveBeenCalled();
187
+ await user.click(screen.getByRole("button", { name: "Close" }));
188
+ expect(onClose).toHaveBeenCalledTimes(1);
189
+ expect(screen.getByRole("dialog")).toBeInTheDocument();
190
+ rerender(<TrueModal open={false} title="Title" />);
191
+ expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
192
+ });
193
+ });
@@ -1,3 +1,4 @@
1
+ import TrueModal from "./TrueModal";
1
2
  import ActiveRoute from "./ActiveRoute";
2
3
  import AdminMenu from "./AdminMenu";
3
4
  import Alert from "./Alert";
@@ -120,6 +121,7 @@ export {
120
121
  TaxonomyMenu,
121
122
  TemplateSelector,
122
123
  TreeSelector,
124
+ TrueModal,
123
125
  Unauthorized,
124
126
  UploadModal,
125
127
  };
@@ -1,8 +1,3 @@
1
- .td-graph-minimap-shell--expanded .td-graph-minimap {
2
- width: 288px;
3
- height: 200px;
4
- }
5
-
6
1
  .td-graph__space-pan-active {
7
2
  .react-flow__node,
8
3
  .react-flow__node *,
@@ -0,0 +1,43 @@
1
+ .ui.modal.active {
2
+ animation: modal-appear 240ms cubic-bezier(0.16, 1, 0.3, 1);
3
+ }
4
+
5
+ @keyframes modal-appear {
6
+ from {
7
+ opacity: 0;
8
+ }
9
+ to {
10
+ opacity: 1;
11
+ }
12
+ }
13
+
14
+ @media (prefers-reduced-motion: reduce) {
15
+ .ui.modal.active {
16
+ animation-duration: 80ms;
17
+ }
18
+ }
19
+
20
+ .ui.modals.dimmer {
21
+ background-color: rgba(0, 0, 0, 0.55);
22
+ -webkit-backdrop-filter: blur(3px);
23
+ backdrop-filter: blur(3px);
24
+ }
25
+
26
+ .ui.modals.dimmer.active {
27
+ animation: modal-backdrop-appear 240ms ease-out;
28
+ }
29
+
30
+ @keyframes modal-backdrop-appear {
31
+ from {
32
+ background-color: rgba(0, 0, 0, 0);
33
+ }
34
+ to {
35
+ background-color: rgba(0, 0, 0, 0.55);
36
+ }
37
+ }
38
+
39
+ @media (prefers-reduced-motion: reduce) {
40
+ .ui.modals.dimmer.active {
41
+ animation-duration: 80ms;
42
+ }
43
+ }
@@ -0,0 +1,138 @@
1
+ @import "./ModalEffects.less";
2
+
3
+ .ui.modal.true-modal {
4
+ display: flex !important;
5
+ flex-direction: column;
6
+ max-width: calc(100vw - 2rem);
7
+ max-height: calc(100vh - 2rem);
8
+ max-height: calc(100dvh - 2rem);
9
+ padding: 0;
10
+ border: 0;
11
+ border-radius: 1rem;
12
+ box-shadow:
13
+ 0 20px 25px -5px rgba(0, 0, 0, 0.2),
14
+ 0 8px 10px -6px rgba(0, 0, 0, 0.15);
15
+
16
+ > .header {
17
+ display: flex;
18
+ align-items: center;
19
+ gap: 0.75rem;
20
+ flex-shrink: 0;
21
+ padding: 1.5rem 3.5rem 1rem 1.5rem;
22
+ border-radius: 1rem 1rem 0 0;
23
+ border-bottom: 0;
24
+ background: @primaryColor;
25
+ color: #f8fafc;
26
+
27
+ > .icon {
28
+ margin: 0;
29
+ flex-shrink: 0;
30
+ }
31
+ }
32
+
33
+ .true-modal-heading {
34
+ min-width: 0;
35
+ overflow-wrap: anywhere;
36
+ }
37
+
38
+ .true-modal-subtitle {
39
+ font-size: 0.75rem;
40
+ font-weight: 400;
41
+ color: inherit;
42
+ margin: 0.15rem 0 0;
43
+ }
44
+
45
+ > .true-modal-close {
46
+ position: absolute;
47
+ top: 1.25rem;
48
+ right: 1rem;
49
+ padding: 6px;
50
+ border: none;
51
+ border-radius: 0.5rem;
52
+ background: none;
53
+ color: #f8fafc;
54
+ cursor: pointer;
55
+ font-size: 1.25rem;
56
+ line-height: 1;
57
+ opacity: 0.8;
58
+
59
+ &:hover {
60
+ opacity: 1;
61
+ }
62
+
63
+ &:focus-visible {
64
+ outline: 2px solid currentColor;
65
+ outline-offset: 2px;
66
+ }
67
+ }
68
+
69
+ > .content {
70
+ flex: 1 1 auto;
71
+ min-height: 0;
72
+ overflow-y: auto;
73
+ padding: 1.625rem 1.5rem 2rem;
74
+ }
75
+
76
+ > .actions {
77
+ display: flex;
78
+ flex-wrap: wrap;
79
+ flex-shrink: 0;
80
+ justify-content: flex-end;
81
+ align-items: center;
82
+ gap: 0.75rem;
83
+ padding: 1rem 1.5rem;
84
+ padding-bottom: 1rem !important;
85
+ border-radius: 0 0 1rem 1rem;
86
+ border-top: 1px solid #e2e8f0;
87
+ background: #f8fafc;
88
+
89
+ .button {
90
+ margin: 0;
91
+ }
92
+ }
93
+
94
+ .true-modal-banner {
95
+ display: flex;
96
+ align-items: center;
97
+ flex-shrink: 0;
98
+ gap: 0.625rem;
99
+ margin: 0 1.5rem 1rem;
100
+ padding: 0.75rem;
101
+ border-radius: 0.375rem;
102
+ border: 1px solid;
103
+ overflow-wrap: anywhere;
104
+
105
+ > .icon {
106
+ margin: 0;
107
+ flex-shrink: 0;
108
+ }
109
+
110
+ > div {
111
+ min-width: 0;
112
+ }
113
+
114
+ &.warning {
115
+ background: #fffbeb;
116
+ border-color: #fde68a;
117
+ color: #78350f;
118
+ }
119
+
120
+ &.danger {
121
+ background: #fef2f2;
122
+ border-color: #fecaca;
123
+ color: #991b1b;
124
+ }
125
+
126
+ &.success {
127
+ background: #f0fdf4;
128
+ border-color: #bbf7d0;
129
+ color: #166534;
130
+ }
131
+
132
+ &.info {
133
+ background: #eff6ff;
134
+ border-color: #bfdbfe;
135
+ color: #1e40af;
136
+ }
137
+ }
138
+ }