@truedat/core 8.14.3 → 8.14.5

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.14.3",
3
+ "version": "8.14.5",
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.14.3",
60
+ "@truedat/test": "8.14.5",
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": "ca5cc79e39ed78a1b5e317d825832e0676b011f6"
102
+ "gitHead": "a792103a77b3b2392be5777a8dc6675ac3882ffd"
103
103
  }
@@ -1,7 +1,7 @@
1
1
  import _ from "lodash/fp";
2
2
  import { useState, useEffect } from "react";
3
3
  import PropTypes from "prop-types";
4
- import { FormattedMessage } from "react-intl";
4
+ import { FormattedMessage, useIntl } from "react-intl";
5
5
  import {
6
6
  Label,
7
7
  Icon,
@@ -25,6 +25,7 @@ export const FilterDropdown = ({
25
25
  removeFilter,
26
26
  toggleFilterValue,
27
27
  }) => {
28
+ const { formatMessage } = useIntl();
28
29
  const [selected, setSelected] = useState();
29
30
  const [query, setQuery] = useState();
30
31
 
@@ -64,6 +65,10 @@ export const FilterDropdown = ({
64
65
  return (
65
66
  <Dropdown
66
67
  item
68
+ aria-label={formatMessage({
69
+ id: `filters.${filter}`,
70
+ defaultMessage: removePrefix(filter),
71
+ })}
67
72
  floating
68
73
  scrolling
69
74
  icon={false}
@@ -76,6 +81,21 @@ export const FilterDropdown = ({
76
81
  />
77
82
  <Icon
78
83
  name="delete"
84
+ role="button"
85
+ tabIndex={0}
86
+ onFocus={(event) => event.stopPropagation()}
87
+ aria-hidden={false}
88
+ aria-label={formatMessage({
89
+ id: "actions.delete",
90
+ defaultMessage: "Delete",
91
+ })}
92
+ onKeyDown={(event) => {
93
+ if (event.key === "Enter" || event.key === " ") {
94
+ event.preventDefault();
95
+ event.stopPropagation();
96
+ removeFilter({ filter });
97
+ }
98
+ }}
79
99
  onClick={(e) => {
80
100
  e.preventDefault();
81
101
  e.stopPropagation();
@@ -33,6 +33,12 @@ export const FilterItem = ({
33
33
  option: { text, value },
34
34
  }) => (
35
35
  <Dropdown.Item
36
+ tabIndex={0}
37
+ onKeyDown={(event) => {
38
+ if (event.key === "Enter" || event.key === " ") {
39
+ preventDefault(event, () => toggleFilterValue({ filter, value }));
40
+ }
41
+ }}
36
42
  onClick={(e) =>
37
43
  preventDefault(e, () => toggleFilterValue({ filter, value }))
38
44
  }
@@ -1,19 +1,32 @@
1
- import { useEffect } from "react";
1
+ import { useEffect, useRef } from "react";
2
2
  import PropTypes from "prop-types";
3
3
  import { useLocation } from "react-router";
4
4
 
5
- function ScrollToTop({ children }) {
5
+ const NO_PRESERVED_ROUTES = [];
6
+ const withinRoute = (pathname, route) =>
7
+ pathname === route || pathname.startsWith(`${route}/`);
8
+
9
+ function ScrollToTop({ children, preserveWithin = NO_PRESERVED_ROUTES }) {
6
10
  const location = useLocation();
11
+ const previousLocation = useRef(null);
7
12
 
8
13
  useEffect(() => {
9
- window.scrollTo(0, 0);
10
- }, [location]);
14
+ const preserveScroll = preserveWithin.some(
15
+ (route) =>
16
+ previousLocation.current &&
17
+ withinRoute(previousLocation.current.pathname, route) &&
18
+ withinRoute(location.pathname, route),
19
+ );
20
+ if (!preserveScroll) window.scrollTo(0, 0);
21
+ previousLocation.current = location;
22
+ }, [location, preserveWithin]);
11
23
 
12
24
  return children;
13
25
  }
14
26
 
15
27
  ScrollToTop.propTypes = {
16
28
  children: PropTypes.node,
29
+ preserveWithin: PropTypes.arrayOf(PropTypes.string),
17
30
  };
18
31
 
19
32
  export default ScrollToTop;
@@ -1,6 +1,6 @@
1
1
  import _ from "lodash/fp";
2
2
  import { renderWithIntl as render } from "@truedat/test/renderWithIntl";
3
- import { waitFor, within } from "@testing-library/react";
3
+ import { act, waitFor, within } from "@testing-library/react";
4
4
  import userEvent from "@testing-library/user-event";
5
5
 
6
6
  import { FilterDropdown } from "../FilterDropdown";
@@ -181,3 +181,20 @@ describe("<FilterDropdown/>", () => {
181
181
  });
182
182
  });
183
183
  });
184
+
185
+ it("removes the filter with the keyboard without opening it", async () => {
186
+ const removeFilter = jest.fn();
187
+ const openFilter = jest.fn();
188
+ const rendered = render(
189
+ <FilterDropdown
190
+ {...props}
191
+ options={null}
192
+ removeFilter={removeFilter}
193
+ openFilter={openFilter}
194
+ />,
195
+ );
196
+ act(() => rendered.getByRole("button", { name: "Delete" }).focus());
197
+ await user.keyboard("{Enter}");
198
+ expect(removeFilter).toHaveBeenCalledWith({ filter: "foo" });
199
+ expect(openFilter).not.toHaveBeenCalled();
200
+ });
@@ -1,5 +1,6 @@
1
1
  import { renderWithIntl as render } from "@truedat/test/renderWithIntl";
2
2
  import { waitForLoad } from "@truedat/test/waitForLoad";
3
+ import userEvent from "@testing-library/user-event";
3
4
  import { FilterItem } from "../FilterItem";
4
5
 
5
6
  describe("<FilterItem/>", () => {
@@ -25,3 +26,23 @@ describe("<FilterItem/>", () => {
25
26
  expect(props.toggleFilterValue).toHaveBeenCalledTimes(1);
26
27
  });
27
28
  });
29
+
30
+ it("toggles an option with the keyboard", async () => {
31
+ const toggleFilterValue = jest.fn();
32
+ const rendered = render(
33
+ <FilterItem
34
+ filter="foo"
35
+ active={false}
36
+ option={{ value: "bar", text: "Bar" }}
37
+ toggleFilterValue={toggleFilterValue}
38
+ />,
39
+ );
40
+ const user = userEvent.setup({ delay: null });
41
+ rendered.getByRole("option").focus();
42
+ await user.keyboard("{Enter} ");
43
+ expect(toggleFilterValue).toHaveBeenCalledTimes(2);
44
+ expect(toggleFilterValue).toHaveBeenCalledWith({
45
+ filter: "foo",
46
+ value: "bar",
47
+ });
48
+ });
@@ -0,0 +1,54 @@
1
+ import { fireEvent, render, screen } from "@testing-library/react";
2
+ import { Link, MemoryRouter, useNavigate } from "react-router";
3
+ import ScrollToTop from "../ScrollToTop";
4
+
5
+ const Navigation = () => {
6
+ const navigate = useNavigate();
7
+ return (
8
+ <>
9
+ <Link to="/users/1">User</Link>
10
+ <Link to="/users/1/edit_password">Password</Link>
11
+ <Link to="/users">Close</Link>
12
+ <Link to="/groups">Groups</Link>
13
+ <Link to="/users-other">Other</Link>
14
+ <button onClick={() => navigate(-1)}>Back</button>
15
+ </>
16
+ );
17
+ };
18
+
19
+ it("preserves scroll between users and their modals, including browser back", () => {
20
+ const scroll = jest.spyOn(window, "scrollTo").mockImplementation(() => {});
21
+ render(
22
+ <MemoryRouter initialEntries={["/users"]}>
23
+ <ScrollToTop preserveWithin={["/users"]}>
24
+ <Navigation />
25
+ </ScrollToTop>
26
+ </MemoryRouter>,
27
+ );
28
+ expect(scroll).toHaveBeenCalledTimes(1);
29
+ ["User", "Password", "Close", "Back"].forEach((name) =>
30
+ fireEvent.click(screen.getByText(name)),
31
+ );
32
+ expect(scroll).toHaveBeenCalledTimes(1);
33
+ fireEvent.click(screen.getByText("Groups"));
34
+ expect(scroll).toHaveBeenCalledTimes(2);
35
+ fireEvent.click(screen.getByText("Close"));
36
+ expect(scroll).toHaveBeenCalledTimes(3);
37
+ fireEvent.click(screen.getByText("Other"));
38
+ expect(scroll).toHaveBeenCalledTimes(4);
39
+ scroll.mockRestore();
40
+ });
41
+
42
+ it("retains the default scroll reset behavior without preserved routes", () => {
43
+ const scroll = jest.spyOn(window, "scrollTo").mockImplementation(() => {});
44
+ render(
45
+ <MemoryRouter initialEntries={["/users"]}>
46
+ <ScrollToTop>
47
+ <Navigation />
48
+ </ScrollToTop>
49
+ </MemoryRouter>,
50
+ );
51
+ fireEvent.click(screen.getByText("User"));
52
+ expect(scroll).toHaveBeenCalledTimes(2);
53
+ scroll.mockRestore();
54
+ });
@@ -4,6 +4,7 @@ exports[`<FilterDropdown/> matches the latest snapshot 1`] = `
4
4
  <div>
5
5
  <div
6
6
  aria-expanded="true"
7
+ aria-label="foo"
7
8
  class="ui active visible floating item scrolling dropdown"
8
9
  role="listbox"
9
10
  tabindex="0"
@@ -13,8 +14,11 @@ exports[`<FilterDropdown/> matches the latest snapshot 1`] = `
13
14
  >
14
15
  foo
15
16
  <i
16
- aria-hidden="true"
17
+ aria-hidden="false"
18
+ aria-label="Delete"
17
19
  class="delete icon"
20
+ role="button"
21
+ tabindex="0"
18
22
  />
19
23
  </div>
20
24
  <div
@@ -24,6 +28,7 @@ exports[`<FilterDropdown/> matches the latest snapshot 1`] = `
24
28
  aria-checked="false"
25
29
  class="item"
26
30
  role="option"
31
+ tabindex="0"
27
32
  >
28
33
  <i
29
34
  aria-hidden="true"
@@ -35,6 +40,7 @@ exports[`<FilterDropdown/> matches the latest snapshot 1`] = `
35
40
  aria-checked="false"
36
41
  class="item"
37
42
  role="option"
43
+ tabindex="0"
38
44
  >
39
45
  <i
40
46
  aria-hidden="true"
@@ -6,6 +6,7 @@ exports[`<FilterItem/> matches the latest snapshot 1`] = `
6
6
  aria-checked="true"
7
7
  class="active item"
8
8
  role="option"
9
+ tabindex="0"
9
10
  >
10
11
  <i
11
12
  aria-hidden="true"
@@ -38,6 +38,7 @@ exports[`<SelectedFilters/> matches the latest snapshot 1`] = `
38
38
  </div>
39
39
  <div
40
40
  aria-expanded="true"
41
+ aria-label="foo"
41
42
  class="ui active visible floating item scrolling dropdown"
42
43
  role="listbox"
43
44
  tabindex="0"
@@ -47,8 +48,11 @@ exports[`<SelectedFilters/> matches the latest snapshot 1`] = `
47
48
  >
48
49
  foo
49
50
  <i
50
- aria-hidden="true"
51
+ aria-hidden="false"
52
+ aria-label="Delete"
51
53
  class="delete icon"
54
+ role="button"
55
+ tabindex="0"
52
56
  />
53
57
  </div>
54
58
  <div
@@ -58,6 +62,7 @@ exports[`<SelectedFilters/> matches the latest snapshot 1`] = `
58
62
  aria-checked="false"
59
63
  class="item"
60
64
  role="option"
65
+ tabindex="0"
61
66
  >
62
67
  <i
63
68
  aria-hidden="true"
@@ -71,6 +76,7 @@ exports[`<SelectedFilters/> matches the latest snapshot 1`] = `
71
76
  aria-checked="false"
72
77
  class="item"
73
78
  role="option"
79
+ tabindex="0"
74
80
  >
75
81
  <i
76
82
  aria-hidden="true"
@@ -84,6 +90,7 @@ exports[`<SelectedFilters/> matches the latest snapshot 1`] = `
84
90
  </div>
85
91
  <div
86
92
  aria-expanded="false"
93
+ aria-label="bar"
87
94
  class="ui floating item scrolling dropdown"
88
95
  role="listbox"
89
96
  tabindex="0"
@@ -93,8 +100,11 @@ exports[`<SelectedFilters/> matches the latest snapshot 1`] = `
93
100
  >
94
101
  bar
95
102
  <i
96
- aria-hidden="true"
103
+ aria-hidden="false"
104
+ aria-label="Delete"
97
105
  class="delete icon"
106
+ role="button"
107
+ tabindex="0"
98
108
  />
99
109
  </div>
100
110
  <div
@@ -144,6 +154,7 @@ exports[`<SelectedFilters/> taxonomy screens matches the latest snapshot 1`] = `
144
154
  </div>
145
155
  <div
146
156
  aria-expanded="true"
157
+ aria-label="foo"
147
158
  class="ui active visible floating item scrolling dropdown"
148
159
  role="listbox"
149
160
  tabindex="0"
@@ -153,8 +164,11 @@ exports[`<SelectedFilters/> taxonomy screens matches the latest snapshot 1`] = `
153
164
  >
154
165
  foo
155
166
  <i
156
- aria-hidden="true"
167
+ aria-hidden="false"
168
+ aria-label="Delete"
157
169
  class="delete icon"
170
+ role="button"
171
+ tabindex="0"
158
172
  />
159
173
  </div>
160
174
  <div
@@ -164,6 +178,7 @@ exports[`<SelectedFilters/> taxonomy screens matches the latest snapshot 1`] = `
164
178
  aria-checked="false"
165
179
  class="item"
166
180
  role="option"
181
+ tabindex="0"
167
182
  >
168
183
  <i
169
184
  aria-hidden="true"
@@ -177,6 +192,7 @@ exports[`<SelectedFilters/> taxonomy screens matches the latest snapshot 1`] = `
177
192
  aria-checked="false"
178
193
  class="item"
179
194
  role="option"
195
+ tabindex="0"
180
196
  >
181
197
  <i
182
198
  aria-hidden="true"
@@ -190,6 +206,7 @@ exports[`<SelectedFilters/> taxonomy screens matches the latest snapshot 1`] = `
190
206
  </div>
191
207
  <div
192
208
  aria-expanded="false"
209
+ aria-label="bar"
193
210
  class="ui floating item scrolling dropdown"
194
211
  role="listbox"
195
212
  tabindex="0"
@@ -199,8 +216,11 @@ exports[`<SelectedFilters/> taxonomy screens matches the latest snapshot 1`] = `
199
216
  >
200
217
  bar
201
218
  <i
202
- aria-hidden="true"
219
+ aria-hidden="false"
220
+ aria-label="Delete"
203
221
  class="delete icon"
222
+ role="button"
223
+ tabindex="0"
204
224
  />
205
225
  </div>
206
226
  <div
@@ -82,7 +82,7 @@ const LanguageContextProvider = ({ children, defaultMessages }) => {
82
82
 
83
83
  const getMessagesForLang = (requestedLang) => {
84
84
  if (loading || !requestedLang) return null;
85
- return messages[requestedLang] || defaultMessages[requestedLang];
85
+ return { ...defaultMessages[requestedLang], ...messages[requestedLang] };
86
86
  };
87
87
 
88
88
  const context = {
@@ -162,7 +162,7 @@ const Languages = () => {
162
162
  className="lang-manager-dropdown"
163
163
  placeholder={formatMessage({
164
164
  id: "i18n.messages.locale.lang_select",
165
- defaultMessage: "Select languaje",
165
+ defaultMessage: "Select language",
166
166
  })}
167
167
  name="add-locales"
168
168
  multiple={false}
@@ -0,0 +1,44 @@
1
+ import { render, screen } from "@testing-library/react";
2
+ import { FormattedMessage } from "react-intl";
3
+ import { useLocales } from "../../../hooks/useLocales";
4
+ import { LangProvider, useLanguage } from "../LangProvider";
5
+
6
+ jest.mock("../../../hooks/useLocales", () => ({ useLocales: jest.fn() }));
7
+
8
+ const defaults = {
9
+ en: { existing: "Local", added: "New local message" },
10
+ es: { added: "Mensaje local" },
11
+ };
12
+ const Catalogs = () => {
13
+ const { getMessagesForLang } = useLanguage();
14
+ return (
15
+ <>
16
+ <FormattedMessage id="existing" />
17
+ <FormattedMessage id="added" />
18
+ <output>{JSON.stringify(getMessagesForLang("es"))}</output>
19
+ </>
20
+ );
21
+ };
22
+
23
+ it("fills missing keys locally while preserving remote translations", () => {
24
+ useLocales.mockReturnValue({
25
+ locales: [
26
+ {
27
+ lang: "en",
28
+ is_default: true,
29
+ is_enabled: true,
30
+ messages: [{ message_id: "existing", definition: "Remote" }],
31
+ },
32
+ ],
33
+ loading: false,
34
+ });
35
+ render(
36
+ <LangProvider defaultMessages={defaults}>
37
+ <Catalogs />
38
+ </LangProvider>,
39
+ );
40
+ expect(screen.getByText(/Remote/)).toHaveTextContent("New local message");
41
+ expect(screen.getByRole("status")).toHaveTextContent(
42
+ '{"added":"Mensaje local"}',
43
+ );
44
+ });
@@ -87,7 +87,7 @@ exports[`<Messages /> matches the latest snapshot 1`] = `
87
87
  class="divider default text"
88
88
  role="alert"
89
89
  >
90
- Select languaje
90
+ Select language
91
91
  </div>
92
92
  <i
93
93
  aria-hidden="true"
@@ -1,4 +1,5 @@
1
1
  export default {
2
+ "actions.close": "Close",
2
3
  actions: "Actions",
3
4
  "actions.cancel": "Cancel",
4
5
  "actions.confirm": "Are you sure?",
@@ -1,4 +1,5 @@
1
1
  export default {
2
+ "actions.close": "Cerrar",
2
3
  actions: "Acciones",
3
4
  "actions.cancel": "Cancelar",
4
5
  "actions.confirm": "¿Estás seguro?",
@@ -0,0 +1,160 @@
1
+ :root {
2
+ --card-groups-popup-background: var(--td-card-accent, #002057);
3
+ --card-groups-popup-text-color: #ffffff;
4
+ --card-groups-popup-muted-text-color: rgba(255, 255, 255, 0.86);
5
+ --card-groups-popup-border-color: rgba(255, 255, 255, 0.24);
6
+ --card-groups-popup-header-background: rgba(255, 255, 255, 0.12);
7
+ --card-groups-popup-item-background: rgba(255, 255, 255, 0.14);
8
+ --card-groups-popup-item-border-color: rgba(255, 255, 255, 0.18);
9
+ --card-groups-popup-marker-background: rgba(255, 255, 255, 0.24);
10
+ --card-groups-popup-marker-color: #ffffff;
11
+ }
12
+
13
+ .ui.card {
14
+ --card-accent-color: var(--td-card-accent, #002057);
15
+ --card-accent-70: var(--td-card-accent-70, rgba(0, 32, 87, 0.7));
16
+ --card-border-color: #edf2f7;
17
+ --card-elevation-shadow: 0 12px 28px -10px rgba(15, 23, 42, 0.28);
18
+ --card-motion-duration: 300ms;
19
+ --card-motion-easing: cubic-bezier(0.22, 1, 0.36, 1);
20
+ border: 1px solid var(--card-border-color);
21
+ border-radius: 1.5rem;
22
+ transition:
23
+ transform var(--card-motion-duration) var(--card-motion-easing),
24
+ box-shadow var(--card-motion-duration) var(--card-motion-easing);
25
+ overflow: hidden;
26
+ }
27
+
28
+ .ui.cards > .card {
29
+ --card-border-color: #edf2f7;
30
+ --card-elevation-shadow: 0 12px 28px -10px rgba(15, 23, 42, 0.28);
31
+ --card-motion-duration: 300ms;
32
+ --card-motion-easing: cubic-bezier(0.22, 1, 0.36, 1);
33
+ border: 1px solid var(--card-border-color);
34
+ margin: 0.875em 0.5em;
35
+ }
36
+
37
+ /*******************************
38
+ Unified Card Base
39
+ *******************************/
40
+
41
+ .ui.card.td-card-base {
42
+ --card-accent-color: var(--td-card-accent, #002057);
43
+ --card-accent-shadow: var(--td-card-accent-shadow, rgba(0, 32, 87, 0.18));
44
+ --card-accent-soft: var(--td-card-accent-soft, rgba(0, 32, 87, 0.12));
45
+ --card-accent-70: var(--td-card-accent-70, rgba(0, 32, 87, 0.7));
46
+ --card-secondary-color: var(--td-card-secondary, #ed5c17);
47
+ --card-motion-duration: 300ms;
48
+ --card-motion-easing: cubic-bezier(0.22, 1, 0.36, 1);
49
+
50
+ position: relative;
51
+ overflow: hidden;
52
+ background: #ffffff;
53
+ border: 1px solid var(--card-border-color);
54
+ border-radius: var(--uc-top-left-radius, 1.5rem)
55
+ var(--uc-top-right-radius, 1.5rem) var(--uc-bottom-right-radius, 1.5rem)
56
+ var(--uc-bottom-left-radius, 1.5rem);
57
+ box-shadow: 0 2px 8px rgba(15, 23, 42, 0.08);
58
+ transition:
59
+ transform var(--card-motion-duration) var(--card-motion-easing),
60
+ box-shadow var(--card-motion-duration) var(--card-motion-easing),
61
+ border-color var(--card-motion-duration) var(--card-motion-easing);
62
+ will-change: transform;
63
+ }
64
+
65
+ .ui.card.td-card-base:hover,
66
+ .ui.card.td-card-base:focus-within {
67
+ border: 1px solid var(--card-border-color);
68
+ box-shadow: var(--card-elevation-shadow);
69
+ transform: translateY(-4px);
70
+ }
71
+
72
+ .ui.card.td-card-base:active {
73
+ transform: translateY(-1px);
74
+ }
75
+
76
+ .ui.card.td-card-base:focus-within {
77
+ outline: 2px solid var(--card-accent-soft);
78
+ outline-offset: 2px;
79
+ }
80
+
81
+ .ui.card.td-card-base > .hover-indicator {
82
+ display: none;
83
+ }
84
+
85
+ .ui.card.td-card-base.td-card--structure,
86
+ .ui.card.td-card-base.td-card--user-role {
87
+ width: 100%;
88
+ max-width: 300px;
89
+ height: auto;
90
+ min-height: 130px;
91
+ }
92
+
93
+ .ui.card.td-card-base.td-card--user {
94
+ width: 100%;
95
+ max-width: 330px;
96
+ height: auto;
97
+ min-height: 128px;
98
+ }
99
+
100
+ .ui.card.td-card-base.td-card--group {
101
+ width: 100%;
102
+ max-width: 300px;
103
+ height: auto;
104
+ min-height: 112px;
105
+ }
106
+
107
+ .ui.card.td-card-base.td-card--taxonomy {
108
+ width: 100%;
109
+ max-width: 290px;
110
+ height: auto;
111
+ min-height: 310px;
112
+ }
113
+
114
+ .ui.card.td-card-base.td-card--domain {
115
+ width: 100%;
116
+ max-width: 290px;
117
+ height: auto;
118
+ min-height: 240px;
119
+ }
120
+
121
+ @media (min-width: 768px) {
122
+ .ui.card.td-card-base.td-card--structure,
123
+ .ui.card.td-card-base.td-card--user-role {
124
+ width: 300px;
125
+ height: 130px;
126
+ }
127
+
128
+ .ui.card.td-card-base.td-card--user {
129
+ width: 330px;
130
+ height: auto;
131
+ min-height: 128px;
132
+ }
133
+
134
+ .ui.card.td-card-base.td-card--group {
135
+ width: 300px;
136
+ height: auto;
137
+ min-height: 112px;
138
+ }
139
+
140
+ .ui.card.td-card-base.td-card--taxonomy {
141
+ width: 290px;
142
+ height: 310px;
143
+ }
144
+
145
+ .ui.card.td-card-base.td-card--domain {
146
+ width: 290px;
147
+ height: 240px;
148
+ }
149
+ }
150
+
151
+ .ui.card:hover,
152
+ .ui.card:focus-within,
153
+ .ui.cards > .card:hover,
154
+ .ui.cards > .card:focus-within,
155
+ .ui.cards > .card.create.link:hover,
156
+ .ui.cards > .card.create.link:focus-within {
157
+ border: 1px solid var(--card-border-color);
158
+ box-shadow: var(--card-elevation-shadow);
159
+ transform: translateY(-4px);
160
+ }
@@ -30,3 +30,9 @@
30
30
  line-height: 1.2;
31
31
  }
32
32
  }
33
+
34
+ .button:not(.icon):not(.circular) > .icon {
35
+ height: auto;
36
+ line-height: 1;
37
+ flex-shrink: 0;
38
+ }
@@ -0,0 +1,28 @@
1
+ @import "./ModalAppearance.less";
2
+
3
+ :root {
4
+ --td-danger: #e42135;
5
+ }
6
+
7
+ .ui.small.modal:not(.user-modal):not(.execution-popup-modal):not(.true-modal),
8
+ .ui.mini.modal:not(.user-modal):not(.execution-popup-modal):not(.true-modal) {
9
+ .modal-appearance();
10
+
11
+ > .close.icon {
12
+ .modal-close-appearance();
13
+ width: auto;
14
+ height: auto;
15
+ }
16
+
17
+ > .actions {
18
+ display: flex;
19
+ flex-wrap: wrap;
20
+ justify-content: flex-end;
21
+ align-items: center;
22
+ gap: 0.75rem;
23
+
24
+ .button {
25
+ margin: 0;
26
+ }
27
+ }
28
+ }
@@ -0,0 +1,52 @@
1
+ .modal-appearance() {
2
+ padding: 0;
3
+ border: 0;
4
+ border-radius: 1rem;
5
+ box-shadow:
6
+ 0 20px 25px -5px rgba(0, 0, 0, 0.2),
7
+ 0 8px 10px -6px rgba(0, 0, 0, 0.15);
8
+
9
+ > .header {
10
+ padding: 1.5rem 3.5rem 1rem 1.5rem;
11
+ border-radius: 1rem 1rem 0 0;
12
+ border-bottom: 0;
13
+ background: @primaryColor;
14
+ color: #f8fafc;
15
+ }
16
+
17
+ > .content {
18
+ padding: 1.625rem 1.5rem 2rem;
19
+ }
20
+
21
+ > .actions {
22
+ padding: 1rem 1.5rem;
23
+ padding-bottom: 1rem !important;
24
+ border-radius: 0 0 1rem 1rem;
25
+ border-top: 1px solid #e2e8f0;
26
+ background: #f8fafc;
27
+ }
28
+ }
29
+
30
+ .modal-close-appearance() {
31
+ position: absolute;
32
+ top: 1.25rem;
33
+ right: 1rem;
34
+ padding: 6px;
35
+ border: none;
36
+ border-radius: 0.5rem;
37
+ background: none;
38
+ color: #f8fafc;
39
+ cursor: pointer;
40
+ font-size: 1.25rem;
41
+ line-height: 1;
42
+ opacity: 0.8;
43
+
44
+ &:hover {
45
+ opacity: 1;
46
+ }
47
+
48
+ &:focus-visible {
49
+ outline: 2px solid currentColor;
50
+ outline-offset: 2px;
51
+ }
52
+ }
@@ -1,28 +1,19 @@
1
1
  @import "./ModalEffects.less";
2
+ @import "./ModalAppearance.less";
2
3
 
3
4
  .ui.modal.true-modal {
5
+ .modal-appearance();
4
6
  display: flex !important;
5
7
  flex-direction: column;
6
8
  max-width: calc(100vw - 2rem);
7
9
  max-height: calc(100vh - 2rem);
8
10
  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
11
 
16
12
  > .header {
17
13
  display: flex;
18
14
  align-items: center;
19
15
  gap: 0.75rem;
20
16
  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
17
 
27
18
  > .icon {
28
19
  margin: 0;
@@ -43,34 +34,13 @@
43
34
  }
44
35
 
45
36
  > .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
- }
37
+ .modal-close-appearance();
67
38
  }
68
39
 
69
40
  > .content {
70
41
  flex: 1 1 auto;
71
42
  min-height: 0;
72
43
  overflow-y: auto;
73
- padding: 1.625rem 1.5rem 2rem;
74
44
  }
75
45
 
76
46
  > .actions {
@@ -80,11 +50,6 @@
80
50
  justify-content: flex-end;
81
51
  align-items: center;
82
52
  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
53
 
89
54
  .button {
90
55
  margin: 0;