diff --git a/frontend/playwright/data/dashboard/get-team-members-owner.json b/frontend/playwright/data/dashboard/get-team-members-owner.json new file mode 100644 index 0000000000..10771dcd4d --- /dev/null +++ b/frontend/playwright/data/dashboard/get-team-members-owner.json @@ -0,0 +1,30 @@ +[ + { + "~:is-admin": false, + "~:email": "bar@example.com", + "~:team-id": "~udd33ff88-f4e5-8033-8003-8096cc07bdf3", + "~:name": "Han Solo", + "~:fullname": "Han Solo", + "~:is-owner": false, + "~:modified-at": "~m1713533116365", + "~:can-edit": true, + "~:is-active": true, + "~:id": "~u1e162163-87b7-805b-8005-5fd05514b6d3", + "~:profile-id": "~u1e162163-87b7-805b-8005-5fd05514b6d3", + "~:created-at": "~m1733324626956" + }, + { + "~:is-admin": true, + "~:email": "foo@example.com", + "~:team-id": "~udd33ff88-f4e5-8033-8003-8096cc07bdf3", + "~:name": "Princesa Leia", + "~:fullname": "Princesa Leia", + "~:is-owner": true, + "~:modified-at": "~m1713533116365", + "~:can-edit": true, + "~:is-active": true, + "~:id": "~uc7ce0794-0992-8105-8004-38e630f29a9b", + "~:profile-id": "~uf56647eb-19a7-8115-8003-b6bc939ecd1b", + "~:created-at": "~m1713533116365" + } +] diff --git a/frontend/playwright/data/dashboard/get-teams-second-owner.json b/frontend/playwright/data/dashboard/get-teams-second-owner.json new file mode 100644 index 0000000000..cb8eb7f114 --- /dev/null +++ b/frontend/playwright/data/dashboard/get-teams-second-owner.json @@ -0,0 +1,48 @@ +[ + { + "~:features": { + "~#set": [ + "layout/grid", + "styles/v2", + "fdata/pointer-map", + "fdata/objects-map", + "components/v2", + "fdata/shape-data-type" + ] + }, + "~:permissions": { + "~:type": "~:membership", + "~:is-owner": true, + "~:is-admin": true, + "~:can-edit": true + }, + "~:name": "Default", + "~:modified-at": "~m1713533116375", + "~:id": "~uc7ce0794-0992-8105-8004-38e630f40f6d", + "~:created-at": "~m1713533116375", + "~:is-default": true + }, + { + "~:features": { + "~#set": [ + "layout/grid", + "styles/v2", + "fdata/pointer-map", + "fdata/objects-map", + "components/v2", + "fdata/shape-data-type" + ] + }, + "~:permissions": { + "~:type": "~:membership", + "~:is-owner": true, + "~:is-admin": true, + "~:can-edit": true + }, + "~:name": "Second team", + "~:modified-at": "~m1701164272671", + "~:id": "~udd33ff88-f4e5-8033-8003-8096cc07bdf3", + "~:created-at": "~m1701164272671", + "~:is-default": false + } +] diff --git a/frontend/playwright/ui/visual-specs/visual-dashboard.spec.js b/frontend/playwright/ui/visual-specs/visual-dashboard.spec.js index ee79bdfc75..5b271306cb 100644 --- a/frontend/playwright/ui/visual-specs/visual-dashboard.spec.js +++ b/frontend/playwright/ui/visual-specs/visual-dashboard.spec.js @@ -438,3 +438,51 @@ test.describe("Subscription controls", () => { await expect(page).toHaveScreenshot(shot); }); }); + +test.describe("Team modals controls", () => { + const shot = { maxDiffPixelRatio: 0, threshold: 0 }; + + test("Create team modal", async ({ page }) => { + const dashboardPage = new DashboardPage(page); + await dashboardPage.goToDashboard(); + await dashboardPage.teamDropdown.click(); + await page.getByText("Create new team").click(); + + const modal = page.locator("[class*='modal-container']"); + await expect(modal).toBeVisible(); + await page.mouse.move(10, 700); + await expect(modal).toHaveScreenshot(shot); + + await modal.locator("[class*='modal-header'] button").hover(); + await expect(modal).toHaveScreenshot(shot); + }); + + test("Leave and reassign modal", async ({ page }) => { + const dashboardPage = new DashboardPage(page); + await dashboardPage.setupDashboardFull(); + // The logged-in user owns the second team, which has another member. + await dashboardPage.mockRPC( + "get-teams", + "dashboard/get-teams-second-owner.json", + ); + await dashboardPage.mockRPC( + "get-team-members?team-id=*", + "dashboard/get-team-members-owner.json", + ); + await dashboardPage.goToSecondTeamMembersSection(); + await expect(page.getByText("role")).toBeVisible(); + + const rows = page.locator("[class*='table-rows']"); + await rows + .locator("[class*='menu-btn']") + .or(rows.getByRole("button", { name: "Options" })) + .first() + .click(); + await rows.getByText("Leave team").click(); + + const modal = page.locator("[class*='modal-container']"); + await expect(modal).toBeVisible(); + await page.mouse.move(10, 700); + await expect(modal).toHaveScreenshot(shot); + }); +}); diff --git a/frontend/src/app/main/ui/dashboard/change_owner.cljs b/frontend/src/app/main/ui/dashboard/change_owner.cljs index b6eb800030..e4a1cc3498 100644 --- a/frontend/src/app/main/ui/dashboard/change_owner.cljs +++ b/frontend/src/app/main/ui/dashboard/change_owner.cljs @@ -12,7 +12,9 @@ [app.common.uuid :as uuid] [app.main.data.modal :as modal] [app.main.ui.components.forms :as fm] - [app.main.ui.icons :as deprecated-icon] + [app.main.ui.ds.buttons.button :refer [button*]] + [app.main.ui.ds.buttons.icon-button :refer [icon-button*]] + [app.main.ui.ds.foundations.assets.icon :as i] [app.util.i18n :as i18n :refer [tr]] [cuerdas.core :as str] [rumext.v2 :as mf])) @@ -45,8 +47,11 @@ [:div {:class (stl/css :modal-container)} [:div {:class (stl/css :modal-header)} [:h2 {:class (stl/css :modal-title)} (tr "modals.leave-and-reassign.title")] - [:button {:class (stl/css :modal-close-btn) - :on-click modal/hide!} deprecated-icon/close]] + [:> icon-button* {:variant "ghost" + :tooltip-class (stl/css :modal-close-btn) + :aria-label (tr "labels.close") + :icon i/close + :on-click modal/hide!}]] [:div {:class (stl/css :modal-content)} [:p {:class (stl/css :modal-msg)} @@ -62,19 +67,16 @@ [:div {:class (stl/css :modal-footer)} [:div {:class (stl/css :action-buttons)} - [:input {:class (stl/css :cancel-button) - :type "button" - :value (tr "labels.cancel") - :on-click modal/hide!}] + [:> button* {:variant "secondary" + :type "button" + :on-click modal/hide!} + (tr "labels.cancel")] - [:input.accept-button - {:type "button" - :class (stl/css-case :accept-btn true - :danger (:valid @form) - :global/disabled (not (:valid @form))) - :disabled (not (:valid @form)) - :value (tr "modals.leave-and-reassign.promote-and-leave") - :on-click on-accept}]]]]])) + [:> button* {:variant (if (:valid @form) "destructive" "primary") + :type "button" + :disabled (not (:valid @form)) + :on-click on-accept} + (tr "modals.leave-and-reassign.promote-and-leave")]]]]])) @@ -149,8 +151,11 @@ [:div {:class (stl/css :modal-organization-container)} [:div {:class (stl/css :modal-header)} [:h2 {:class (stl/css :modal-organization-title)} (tr "modals.before-leave-organization.title")] - [:button {:class (stl/css :modal-close-btn) - :on-click modal/hide!} deprecated-icon/close]] + [:> icon-button* {:variant "ghost" + :tooltip-class (stl/css :modal-close-btn) + :aria-label (tr "labels.close") + :icon i/close + :on-click modal/hide!}]] [:div {:class (stl/css :modal-content)} (if (zero? num-teams-to-delete) @@ -169,16 +174,13 @@ [:div {:class (stl/css :modal-footer)} [:div {:class (stl/css :action-buttons)} - [:input {:class (stl/css :cancel-button) - :type "button" - :value (tr "labels.cancel") - :on-click modal/hide!}] + [:> button* {:variant "secondary" + :type "button" + :on-click modal/hide!} + (tr "labels.cancel")] - [:input.accept-button - {:type "button" - :class (stl/css-case :accept-btn true - :danger all-valid? - :global/disabled (not all-valid?)) - :disabled (not all-valid?) - :value (tr "modals.leave-and-reassign.promote-and-leave") - :on-click on-accept}]]]]])) + [:> button* {:variant (if all-valid? "destructive" "primary") + :type "button" + :disabled (not all-valid?) + :on-click on-accept} + (tr "modals.leave-and-reassign.promote-and-leave")]]]]])) diff --git a/frontend/src/app/main/ui/dashboard/change_owner.scss b/frontend/src/app/main/ui/dashboard/change_owner.scss index df51c6e86a..d066c372d4 100644 --- a/frontend/src/app/main/ui/dashboard/change_owner.scss +++ b/frontend/src/app/main/ui/dashboard/change_owner.scss @@ -4,8 +4,10 @@ // // Copyright (c) KALEIDOS SUBSIDIARY SL +// refactor/ stays for the modal placeholders (%modal-*, to move with the +// modals refactor) and the input placeholder (%input-with-label, with the inputs). @use "refactor/common-refactor.scss" as deprecated; -@use "ds/typography.scss" as t; +@use "ds/typography.scss" as *; @use "ds/_sizes.scss" as *; @use "ds/z-index.scss" as *; @@ -20,48 +22,34 @@ } .modal-header { - margin-bottom: deprecated.$s-24; + margin-block-end: var(--sp-xxl); } .modal-title { - @include deprecated.uppercase-title-typography; + @include use-typography("headline-small"); - color: var(--modal-title-foreground-color); + color: var(--color-foreground-primary); } +// Tooltip wrapper of the close button: it places it in the corner. .modal-close-btn { - @extend %modal-close-btn-base; + position: absolute; + inset-block-start: var(--sp-s); + inset-inline-end: var(--sp-xs); } .modal-content { - @include deprecated.body-small-typography; + @include use-typography("body-small"); - margin-bottom: deprecated.$s-24; -} - -.input-wrapper { - @extend %input-with-label; - @include deprecated.body-small-typography; + margin-block-end: var(--sp-xxl); } .action-buttons { @extend %modal-action-btns; } -.cancel-button { - @extend %modal-cancel-btn; -} - -.accept-btn { - @extend %modal-accept-btn; - - &.danger { - @extend %modal-danger-btn; - } -} - .modal-msg { - color: var(--modal-text-foreground-color); + color: var(--color-foreground-secondary); } .teams-container { @@ -75,34 +63,34 @@ display: grid; grid-template-columns: 1fr 2fr; align-items: center; - width: 100%; + inline-size: 100%; } .modal-organization-container { @extend %modal-container-base; overflow-y: auto; - max-height: $sz-512; + max-block-size: $sz-512; } .modal-organization-title { - @include t.use-typography("headline-large"); + @include use-typography("headline-large"); - color: var(--modal-title-foreground-color); + color: var(--color-foreground-primary); } .modal-organization-msg { - @include t.use-typography("body-large"); + @include use-typography("body-large"); - color: var(--modal-text-foreground-color); + color: var(--color-foreground-secondary); } .team-name { - @include t.use-typography("body-medium"); + @include use-typography("body-medium"); - color: var(--modal-text-foreground-color); + color: var(--color-foreground-secondary); } .team-member { - @include t.use-typography("body-medium"); + @include use-typography("body-medium"); } diff --git a/frontend/src/app/main/ui/dashboard/team_form.cljs b/frontend/src/app/main/ui/dashboard/team_form.cljs index 81803fa4a1..08f6dcaad6 100644 --- a/frontend/src/app/main/ui/dashboard/team_form.cljs +++ b/frontend/src/app/main/ui/dashboard/team_form.cljs @@ -18,7 +18,8 @@ [app.main.refs :as refs] [app.main.store :as st] [app.main.ui.components.forms :as fm] - [app.main.ui.icons :as deprecated-icon] + [app.main.ui.ds.buttons.icon-button :refer [icon-button*]] + [app.main.ui.ds.foundations.assets.icon :as i] [app.util.dom :as dom] [app.util.i18n :as i18n :refer [tr]] [app.util.keyboard :as kbd] @@ -118,8 +119,11 @@ [:h2 {:class (stl/css :modal-title)} (tr "labels.create-team")]) - [:button {:class (stl/css :modal-close-btn) - :on-click modal/hide!} deprecated-icon/close]] + [:> icon-button* {:variant "ghost" + :tooltip-class (stl/css :modal-close-btn) + :aria-label (tr "labels.close") + :icon i/close + :on-click modal/hide!}]] [:div {:class (stl/css :modal-content)} [:& fm/input {:type "text" @@ -162,7 +166,10 @@ [:div {:class (stl/css :modal-container)} [:div {:class (stl/css :modal-header)} [:h2 {:class (stl/css :modal-title)} title] - [:button {:class (stl/css :modal-close-btn) - :on-click modal/hide!} deprecated-icon/close]] + [:> icon-button* {:variant "ghost" + :tooltip-class (stl/css :modal-close-btn) + :aria-label (tr "labels.close") + :icon i/close + :on-click modal/hide!}]] [:div {:class (stl/css :modal-content)} [:div message]]]])) diff --git a/frontend/src/app/main/ui/dashboard/team_form.scss b/frontend/src/app/main/ui/dashboard/team_form.scss index 946057fa95..1047c8f3d8 100644 --- a/frontend/src/app/main/ui/dashboard/team_form.scss +++ b/frontend/src/app/main/ui/dashboard/team_form.scss @@ -4,9 +4,11 @@ // // Copyright (c) KALEIDOS SUBSIDIARY SL +// refactor/ stays for the modal placeholders (%modal-*, to move with the +// modals refactor) and the input placeholder (%input-element-label, with the inputs). @use "refactor/common-refactor.scss" as deprecated; @use "ds/_utils.scss" as *; -@use "ds/typography.scss" as t; +@use "ds/typography.scss" as *; .modal-overlay { @extend %modal-overlay-base; @@ -21,20 +23,23 @@ } .modal-title { - @include t.use-typography("headline-large"); + @include use-typography("headline-large"); - color: var(--modal-title-foreground-color); + color: var(--color-foreground-primary); } +// Tooltip wrapper of the close button: it places it in the corner. .modal-close-btn { - @extend %modal-close-btn-base; + position: absolute; + inset-block-start: var(--sp-s); + inset-inline-end: var(--sp-xs); } .modal-content { margin-block-end: var(--sp-xxl); color: var(--color-foreground-secondary); - @include t.use-typography("body-large"); + @include use-typography("body-large"); } .team-form { @@ -52,24 +57,16 @@ flex-direction: column; gap: var(--sp-xs); align-items: flex-start; - width: 100%; + inline-size: 100%; border: none; background-color: transparent; - height: 100%; + block-size: 100%; } .action-buttons { @extend %modal-action-btns; } -.cancel-button { - @extend %modal-cancel-btn; -} - .accept-btn { @extend %modal-accept-btn; - - &.danger { - @extend %modal-danger-btn; - } }