@truedat/auth 8.14.4 → 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.
Files changed (28) hide show
  1. package/package.json +4 -4
  2. package/src/groups/components/GroupCard.js +2 -2
  3. package/src/groups/components/GroupCards.js +129 -37
  4. package/src/groups/components/GroupForm.js +13 -7
  5. package/src/groups/components/GroupFormModal.js +8 -7
  6. package/src/groups/components/GroupInfoModal.js +6 -6
  7. package/src/groups/components/GroupMemberPicker.js +2 -4
  8. package/src/groups/components/GroupRouteModal.js +15 -10
  9. package/src/groups/components/__tests__/GroupCards.spec.js +94 -0
  10. package/src/groups/components/__tests__/GroupForm.spec.js +2 -2
  11. package/src/groups/components/__tests__/GroupModals.spec.js +42 -16
  12. package/src/groups/components/__tests__/__snapshots__/GroupCards.spec.js.snap +14 -0
  13. package/src/styles/Groups.less +96 -131
  14. package/src/styles/UserCard.less +60 -226
  15. package/src/styles/UserModals.less +6 -1
  16. package/src/styles/Users.less +40 -7
  17. package/src/users/components/EditUserModal.js +3 -3
  18. package/src/users/components/UserAcls.js +61 -0
  19. package/src/users/components/UserCard.js +4 -1
  20. package/src/users/components/UserCards.js +35 -120
  21. package/src/users/components/UserInfoModal.js +22 -62
  22. package/src/users/components/__tests__/EditUser.spec.js +1 -1
  23. package/src/users/components/__tests__/UserAcls.spec.js +40 -1
  24. package/src/users/components/__tests__/UserCard.spec.js +1 -1
  25. package/src/users/components/__tests__/UserCards.spec.js +0 -45
  26. package/src/users/components/__tests__/UserModals.spec.js +54 -7
  27. package/src/users/components/__tests__/__snapshots__/UserCard.spec.js.snap +2 -0
  28. package/src/users/components/__tests__/__snapshots__/UserCards.spec.js.snap +20 -69
@@ -86,7 +86,7 @@ describe("Group modals", () => {
86
86
  ).toBeInTheDocument();
87
87
  else
88
88
  expect(
89
- await ui.findByRole("textbox", { name: "Nombre del grupo" }),
89
+ await ui.findByRole("textbox", { name: "Group name" }),
90
90
  ).toBeInTheDocument();
91
91
  },
92
92
  );
@@ -95,7 +95,9 @@ describe("Group modals", () => {
95
95
  const ui = setup("/groups/1");
96
96
  await userEvent
97
97
  .setup({ delay: null })
98
- .click(ui.getByRole("button", { name: "Close" }));
98
+ .click(
99
+ within(ui.getByRole("dialog")).getByRole("button", { name: "Close" }),
100
+ );
99
101
  await waitFor(() =>
100
102
  expect(ui.queryByRole("dialog")).not.toBeInTheDocument(),
101
103
  );
@@ -105,20 +107,20 @@ describe("Group modals", () => {
105
107
  it("preserves edits after a failed write", async () => {
106
108
  apiJsonPut.mockRejectedValue(new Error("offline"));
107
109
  const ui = setup("/groups/1/edit");
108
- const input = await ui.findByRole("textbox", { name: "Nombre del grupo" });
110
+ const input = await ui.findByRole("textbox", { name: "Group name" });
109
111
  const user = userEvent.setup({ delay: null });
110
112
  await user.clear(input);
111
113
  await user.type(input, "Updated team");
112
- await user.click(ui.getByRole("button", { name: "actions.save" }));
114
+ await user.click(ui.getByRole("button", { name: "actions.edit" }));
113
115
  await ui.findByText(
114
- "No se ha podido completar la operación. Inténtalo de nuevo.",
116
+ "The operation could not be completed. Please try again.",
115
117
  );
116
118
  expect(input).toHaveValue("Updated team");
117
119
  });
118
120
 
119
121
  it("preserves an edit draft if directory revalidation fails", async () => {
120
122
  const ui = setup("/groups/1/edit");
121
- const input = await ui.findByRole("textbox", { name: "Nombre del grupo" });
123
+ const input = await ui.findByRole("textbox", { name: "Group name" });
122
124
  const user = userEvent.setup({ delay: null });
123
125
  await user.clear(input);
124
126
  await user.type(input, "Draft team");
@@ -126,9 +128,7 @@ describe("Group modals", () => {
126
128
  apiJson.mockRejectedValue(new Error("offline"));
127
129
  await user.click(ui.getByRole("button", { name: "Refresh users" }));
128
130
 
129
- expect(
130
- await ui.findByText("No se han podido cargar los usuarios."),
131
- ).toBeInTheDocument();
131
+ expect(await ui.findByText("Could not load users.")).toBeInTheDocument();
132
132
  expect(input).toHaveValue("Draft team");
133
133
  });
134
134
 
@@ -140,17 +140,19 @@ describe("Group modals", () => {
140
140
  }),
141
141
  );
142
142
  const ui = setup("/groups/1/edit");
143
- const input = await ui.findByRole("textbox", { name: "Nombre del grupo" });
143
+ const input = await ui.findByRole("textbox", { name: "Group name" });
144
144
  const user = userEvent.setup({ delay: null });
145
145
  await user.type(input, " updated");
146
146
  await waitFor(() =>
147
147
  expect(
148
- ui.getByRole("button", { name: "actions.save" }),
148
+ ui.getByRole("button", { name: "actions.edit" }),
149
149
  ).not.toBeDisabled(),
150
150
  );
151
- await user.click(ui.getByRole("button", { name: "actions.save" }));
151
+ await user.click(ui.getByRole("button", { name: "actions.edit" }));
152
152
  await waitFor(() =>
153
- expect(ui.getByRole("button", { name: "Close" })).toBeDisabled(),
153
+ expect(
154
+ within(ui.getByRole("dialog")).getByRole("button", { name: "Close" }),
155
+ ).toBeDisabled(),
154
156
  );
155
157
  await act(async () => deferred.resolve({ data: { data: group } }));
156
158
  });
@@ -163,7 +165,7 @@ describe("Group modals", () => {
163
165
  }),
164
166
  );
165
167
  const ui = setup("/groups/1/edit");
166
- await ui.findByRole("textbox", { name: "Nombre del grupo" });
168
+ await ui.findByRole("textbox", { name: "Group name" });
167
169
  const user = userEvent.setup({ delay: null });
168
170
  await user.click(document.querySelector(".group-delete-action"));
169
171
  const confirmation = document.querySelector(".group-modal--delete");
@@ -194,7 +196,7 @@ describe("Group modals", () => {
194
196
  );
195
197
  const ui = setup("/groups/1");
196
198
  expect(
197
- await ui.findByText("Este grupo no tiene miembros"),
199
+ await ui.findByText("This group has no members"),
198
200
  ).toBeInTheDocument();
199
201
  });
200
202
 
@@ -203,8 +205,32 @@ describe("Group modals", () => {
203
205
  const ui = setup("/groups/1");
204
206
  expect(
205
207
  await within(ui.getByRole("dialog")).findByRole("button", {
206
- name: "Reintentar",
208
+ name: "Retry",
207
209
  }),
208
210
  ).toBeInTheDocument();
209
211
  });
210
212
  });
213
+
214
+ it("keeps member autocomplete disabled and uses common edit actions", async () => {
215
+ const ui = setup("/groups/1/edit");
216
+ const name = await ui.findByRole("textbox", { name: "Group name" });
217
+ expect(name.closest("form").querySelector("input.search")).toHaveAttribute(
218
+ "autocomplete",
219
+ "off",
220
+ );
221
+ expect(ui.getByRole("button", { name: "actions.close" })).toBeEnabled();
222
+ expect(ui.getByRole("button", { name: "actions.edit" })).toBeDisabled();
223
+ });
224
+
225
+ it("returns keyboard focus to the group after closing a directly opened dialog", async () => {
226
+ const ui = setup("/groups/1");
227
+ await ui.findByText("Full description", { selector: ".true-modal-subtitle" });
228
+ const keyboard = userEvent.setup({ delay: null });
229
+ await keyboard.keyboard("{Escape}");
230
+ await waitFor(() => {
231
+ expect(ui.queryByRole("dialog")).not.toBeInTheDocument();
232
+ expect(
233
+ ui.container.querySelector('.gc-content[href="/groups/1"]'),
234
+ ).toHaveFocus();
235
+ });
236
+ });
@@ -5,6 +5,20 @@ exports[`<GroupCards /> matches the latest snapshot 1`] = `
5
5
  <div
6
6
  class="groups-toolbar"
7
7
  >
8
+ <div
9
+ class="ui left icon input"
10
+ >
11
+ <input
12
+ aria-label="Search groups or users…"
13
+ placeholder="Search groups or users…"
14
+ type="text"
15
+ value=""
16
+ />
17
+ <i
18
+ aria-hidden="true"
19
+ class="search icon"
20
+ />
21
+ </div>
8
22
  <a
9
23
  class="ui primary button group-create-button"
10
24
  data-discover="true"
@@ -1,8 +1,21 @@
1
1
  .groups-toolbar {
2
2
  display: flex;
3
3
  justify-content: flex-end;
4
- margin-bottom: 1rem;
4
+ align-items: center;
5
+ flex-wrap: wrap;
6
+ gap: 0.75rem;
7
+ margin-bottom: 0.5rem;
8
+
9
+ > .ui.input {
10
+ flex: 1 1 12rem;
11
+ min-width: 0;
12
+ > input {
13
+ min-width: 0;
14
+ }
15
+ }
16
+
5
17
  .group-create-button.ui.button {
18
+ margin: 0;
6
19
  display: inline-flex;
7
20
  align-items: center;
8
21
  gap: 0.65rem;
@@ -21,6 +34,39 @@
21
34
  }
22
35
  }
23
36
  }
37
+ .groups-search-hint {
38
+ position: relative;
39
+ padding-right: 1.5rem;
40
+
41
+ .groups-search-hint-close.ui.button {
42
+ position: absolute;
43
+ top: -0.55rem;
44
+ right: -0.6rem;
45
+ width: 1.5rem;
46
+ height: 1.5rem;
47
+ margin: 0;
48
+ padding: 0;
49
+ color: #64748b;
50
+ opacity: 0.65;
51
+ box-shadow: none;
52
+
53
+ &:hover,
54
+ &:focus-visible {
55
+ color: #334155;
56
+ opacity: 1;
57
+ background: transparent;
58
+ box-shadow: none;
59
+ }
60
+
61
+ > .icon {
62
+ margin: 0;
63
+ font-size: 0.75rem;
64
+ }
65
+ }
66
+ }
67
+ .ui.popup.groups-search-hint-popup {
68
+ z-index: 999 !important;
69
+ }
24
70
  .groups-card-list {
25
71
  display: grid;
26
72
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 280px), 1fr));
@@ -42,89 +88,49 @@
42
88
  }
43
89
  .td-group-card {
44
90
  --card-accent-color: var(--td-card-accent, #002057);
45
- --card-secondary-color: var(--td-card-secondary, #ed5c17);
46
- --gc-action-tint: rgba(100, 116, 139, 0.07);
91
+ --card-border-color: #edf2f7;
92
+ --card-elevation-shadow: 0 12px 28px -10px rgba(15, 23, 42, 0.28);
93
+ --card-motion-duration: 300ms;
94
+ --card-motion-easing: cubic-bezier(0.22, 1, 0.36, 1);
47
95
  position: relative;
48
96
  height: auto;
49
- min-height: 160px;
97
+ min-height: 152px;
50
98
  overflow: hidden;
51
99
  min-width: 0;
52
100
  background: #fff;
53
- border: 1px solid #edf2f7;
54
- border-radius: 1.25rem 0.5rem 1.5rem 0.25rem;
55
- box-shadow: 0 2px 6px rgba(15, 23, 42, 0.15);
101
+ border: 1px solid var(--card-border-color);
102
+ border-radius: 0 0.5rem 1.5rem 0.25rem;
103
+ box-shadow: 0 2px 8px rgba(15, 23, 42, 0.08);
56
104
  color: #334155;
57
105
  transition:
58
- transform 300ms cubic-bezier(0.22, 1, 0.36, 1),
59
- border-color 140ms ease,
60
- box-shadow 140ms ease;
106
+ transform var(--card-motion-duration) var(--card-motion-easing),
107
+ box-shadow var(--card-motion-duration) var(--card-motion-easing),
108
+ border-color var(--card-motion-duration) var(--card-motion-easing);
61
109
 
62
- &::before {
63
- content: "";
64
- position: absolute;
65
- inset: 0;
66
- border-left: 8px solid var(--card-accent-color);
67
- border-radius: inherit;
68
- opacity: 0;
69
- transform: translateX(-8px);
70
- transition:
71
- opacity 300ms cubic-bezier(0.22, 1, 0.36, 1),
72
- transform 300ms cubic-bezier(0.22, 1, 0.36, 1);
73
- pointer-events: none;
74
- }
75
- &::after {
76
- content: "";
77
- position: absolute;
78
- inset: 0;
79
- border-radius: inherit;
80
- background: linear-gradient(270deg, var(--gc-action-tint), transparent 75%);
81
- opacity: 0;
82
- pointer-events: none;
83
- transition: opacity 180ms ease;
84
- }
85
- &:has(.gc-actions a:hover),
86
- &:has(.gc-actions a:focus-visible) {
87
- --gc-action-tint: rgba(37, 99, 235, 0.09);
88
- border-color: rgba(37, 99, 235, 0.7);
89
- }
90
110
  &:hover,
91
111
  &:focus-within {
92
112
  transform: translateY(-4px);
93
- border-color: var(--card-accent-color);
94
- box-shadow: 0 5px 14px rgba(15, 23, 42, 0.18);
95
- &::after {
96
- opacity: 1;
113
+ box-shadow: var(--card-elevation-shadow);
114
+ .gc-actions {
115
+ visibility: visible;
97
116
  }
98
117
  .gc-avatar {
99
- background: var(--card-secondary-color);
118
+ background: @primaryColor;
100
119
  color: #fff;
101
- box-shadow: -0.3rem 0.2rem 0 var(--card-accent-color);
102
- transform: translate(0.3rem, -0.2rem);
103
- }
104
- .gc-content h3 {
105
- color: var(--card-secondary-color);
106
- transform: translate(0.3rem, -0.2rem);
107
- }
108
- &::before {
109
- opacity: 1;
110
- transform: translateX(0);
111
- }
112
- .gc-actions {
113
- opacity: 1;
114
- transform: translateX(0);
115
- pointer-events: auto;
116
- transition-delay: 0ms;
117
120
  }
118
121
  }
122
+ &:active {
123
+ transform: translateY(-1px);
124
+ }
119
125
  .gc-content {
120
126
  display: grid;
121
127
  grid-template-columns: 2.75rem minmax(0, 1fr);
122
128
  align-content: start;
123
129
  align-items: center;
124
- column-gap: 0.75rem;
130
+ column-gap: 0.625rem;
125
131
  row-gap: 0.3rem;
126
132
  min-height: inherit;
127
- padding: calc(1.45em - 0.28rem) 2rem 2.25rem calc(1em + 0.35rem);
133
+ padding: calc(1.25em - 0.28rem) 2rem 2.25rem calc(1em + 0.35rem);
128
134
  border-radius: inherit;
129
135
  color: inherit;
130
136
  text-align: left;
@@ -138,9 +144,6 @@
138
144
  font-weight: 800;
139
145
  line-height: 1.25;
140
146
  color: #1e293b;
141
- transition:
142
- color 300ms cubic-bezier(0.22, 1, 0.36, 1),
143
- transform 300ms cubic-bezier(0.22, 1, 0.36, 1);
144
147
  }
145
148
  h3,
146
149
  .gc-name,
@@ -168,13 +171,11 @@
168
171
  grid-row: 1;
169
172
  border-radius: 50%;
170
173
  background: rgba(0, 0, 0, 0.08);
171
- font-size: 1.5rem;
174
+ font-size: 1.625rem;
172
175
  color: var(--card-accent-color);
173
176
  transition:
174
- background 300ms cubic-bezier(0.22, 1, 0.36, 1),
175
- color 300ms cubic-bezier(0.22, 1, 0.36, 1),
176
- box-shadow 300ms cubic-bezier(0.22, 1, 0.36, 1),
177
- transform 300ms cubic-bezier(0.22, 1, 0.36, 1);
177
+ background-color 200ms ease,
178
+ color 200ms ease;
178
179
  > .icon {
179
180
  margin: 0;
180
181
  }
@@ -185,14 +186,14 @@
185
186
  padding-left: 0.5rem;
186
187
  }
187
188
  .gc-name {
188
- font-size: 0.85rem;
189
+ font-size: 0.9375rem;
189
190
  color: #64748b;
190
191
  }
191
192
  .gc-description {
192
- margin: 0.75rem 0 0;
193
- padding-top: 0.75rem;
193
+ margin: 0.625rem 0 0;
194
+ padding-top: 0.625rem;
194
195
  border-top: 1px solid #e2e8f0;
195
- font-size: 13px;
196
+ font-size: 14px;
196
197
  line-height: 1.2;
197
198
  color: #64748b;
198
199
  }
@@ -205,17 +206,13 @@
205
206
  flex-direction: column;
206
207
  align-items: flex-end;
207
208
  gap: 0.25rem;
208
- opacity: 0;
209
- pointer-events: none;
210
- transform: translateX(0.45rem);
211
- transition:
212
- opacity 420ms cubic-bezier(0.22, 1, 0.36, 1) 160ms,
213
- transform 420ms cubic-bezier(0.22, 1, 0.36, 1) 160ms;
209
+ visibility: hidden;
214
210
  a {
215
211
  display: flex;
216
212
  align-items: center;
217
213
  width: auto;
218
214
  max-width: 1.75rem;
215
+ justify-content: flex-start;
219
216
  height: 1.75rem;
220
217
  padding: 0;
221
218
  border: 1px solid #64748b;
@@ -223,21 +220,17 @@
223
220
  border-radius: 0.5rem 0 0 0.5rem;
224
221
  background: #fff;
225
222
  color: #64748b;
226
- box-shadow: 0 6px 14px rgba(15, 23, 42, 0.14);
227
223
  cursor: pointer;
228
224
  overflow: hidden;
229
225
  white-space: nowrap;
230
226
  transition:
231
227
  max-width 420ms cubic-bezier(0.22, 1, 0.36, 1),
232
- padding 420ms cubic-bezier(0.22, 1, 0.36, 1),
233
- color 140ms ease,
234
- background-color 140ms ease,
235
- border-color 140ms ease;
228
+ padding 420ms cubic-bezier(0.22, 1, 0.36, 1);
236
229
  i.icon {
237
230
  flex: 0 0 1.75rem;
238
231
  width: 1.75rem;
239
232
  margin: 0;
240
- font-size: 0.8rem;
233
+ font-size: 1rem;
241
234
  display: inline-flex;
242
235
  align-items: center;
243
236
  justify-content: center;
@@ -248,13 +241,13 @@
248
241
  &:focus-visible {
249
242
  max-width: 14rem;
250
243
  padding: 0 1rem 0 0.08rem;
251
- color: #1d4ed8;
252
- background: #e8f1ff;
253
- border-color: rgba(37, 99, 235, 0.62);
254
244
  .gc-action-label {
255
245
  max-width: 11rem;
256
246
  opacity: 1;
257
247
  }
248
+ color: #fff;
249
+ background: var(--card-accent-color);
250
+ border-color: var(--card-accent-color);
258
251
  }
259
252
  &:focus-visible {
260
253
  outline: 2px solid currentColor;
@@ -292,12 +285,9 @@
292
285
  line-height: 1;
293
286
  white-space: nowrap;
294
287
  pointer-events: none;
295
- transition:
296
- background-color 300ms cubic-bezier(0.22, 1, 0.36, 1),
297
- color 300ms cubic-bezier(0.22, 1, 0.36, 1);
298
288
  > i.icon {
299
289
  margin: 0 0.4em 0 0;
300
- font-size: 0.9rem;
290
+ font-size: 1rem;
301
291
  line-height: 1;
302
292
  }
303
293
  }
@@ -305,9 +295,6 @@
305
295
  max-width: 1em;
306
296
  overflow: hidden;
307
297
  opacity: 1;
308
- transition:
309
- max-width 300ms cubic-bezier(0.22, 1, 0.36, 1),
310
- opacity 300ms ease;
311
298
  }
312
299
  .gc-count--empty .gc-count-label {
313
300
  text-transform: none;
@@ -318,32 +305,13 @@
318
305
  overflow: hidden;
319
306
  opacity: 0;
320
307
  text-transform: lowercase;
321
- transition:
322
- max-width 300ms cubic-bezier(0.22, 1, 0.36, 1),
323
- margin-left 300ms cubic-bezier(0.22, 1, 0.36, 1),
324
- opacity 300ms ease;
325
- }
326
- &:hover .gc-count,
327
- &:focus-within .gc-count {
328
- background: var(--card-accent-color);
329
- color: #ffffff;
330
- .gc-count-label {
331
- max-width: 8rem;
332
- margin-left: 0.4em;
333
- opacity: 1;
334
- }
335
- &.gc-count--empty {
336
- .gc-count-empty-marker {
337
- max-width: 0;
338
- opacity: 0;
339
- }
340
- .gc-count-label {
341
- margin-left: 0;
342
- }
343
- }
344
308
  }
345
309
  }
346
310
  .ui.modal.group-modal {
311
+ > .content i.icon,
312
+ > .actions .button > i.icon {
313
+ font-size: 1.125em;
314
+ }
347
315
  --group-modal-gap: clamp(16px, 6vh, 56px);
348
316
  --group-modal-gap: clamp(16px, 6dvh, 56px);
349
317
  max-height: calc(100vh - 2 * var(--group-modal-gap));
@@ -424,9 +392,7 @@
424
392
  min-height: 12rem;
425
393
  }
426
394
  .td-group-card .gc-actions {
427
- opacity: 1;
428
- transform: none;
429
- pointer-events: auto;
395
+ visibility: visible;
430
396
  a {
431
397
  min-width: 2.75rem;
432
398
  max-width: 2.75rem;
@@ -436,19 +402,18 @@
436
402
  }
437
403
  }
438
404
  @media (prefers-reduced-motion: reduce) {
439
- .td-group-card,
440
- .td-group-card::before,
441
- .td-group-card::after,
442
405
  .td-group-card .gc-avatar,
443
- .td-group-card .gc-content h3,
444
- .td-group-card .gc-actions,
445
406
  .td-group-card .gc-actions a,
446
- .td-group-card .gc-action-label,
447
- .td-group-card .gc-count,
448
- .td-group-card .gc-count-empty-marker,
449
- .td-group-card .gc-count-label {
407
+ .td-group-card .gc-action-label {
450
408
  transition: none;
451
409
  }
410
+ .td-group-card,
411
+ .td-group-card:hover,
412
+ .td-group-card:focus-within,
413
+ .td-group-card:active {
414
+ transition: none;
415
+ transform: none;
416
+ }
452
417
  }
453
418
 
454
419
  .ui.modal.group-modal--info {
@@ -1004,6 +969,6 @@
1004
969
  }
1005
970
 
1006
971
  .ui.modal.group-modal--new > .header:not(.ui) {
1007
- background: @secondaryColor;
972
+ background: @primaryColor;
1008
973
  color: #fff;
1009
974
  }