mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2026-05-13 06:20:24 +00:00
feat(ui): convert disable/enable workflow menu to JS-less dropdown (#10133)
* convert the dropdown (overflow menu) to the JS-less one
* the "link" still relies on JS to make a POST, changing this is not in scope of this PR
* fixed the weird behavior where opener changes it's color when hovering over the "link"
* the bug where the script that refreshes the list once in a while closes an open dropdown is not fixed and is not in scope of this PR
* use border in the opener
* it might not look as sleek but it is easier to see and better for the user to be able to understand that this is an active intractable element
* global dropdown improvements
* add rounding rules for dropdowns with only one item - the first such case
* add testing for rounding rules
* added a devtest page to play with the dropdown component on
Preview
B: https://codeberg.org/forgejo/forgejo/attachments/1462cdda-71f5-45d0-a206-33bb17740cb8
A: https://codeberg.org/forgejo/forgejo/attachments/d3c265cb-6b77-40c8-9944-d9327f3bec65
B: https://codeberg.org/forgejo/forgejo/attachments/17f17c29-4dcd-4015-b5b9-6d438bd2eb0b
A: https://codeberg.org/forgejo/forgejo/attachments/d94e196c-725e-47de-b4de-ed97b148ceb6
B: https://codeberg.org/forgejo/forgejo/attachments/1813ded9-f619-47d9-bf15-ad4bcd3535ab
A: https://codeberg.org/forgejo/forgejo/attachments/09042e58-331e-414d-ac8f-0f1091033b7f
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/10133
Reviewed-by: Otto <otto@codeberg.org>
This commit is contained in:
parent
f4e3c0aaac
commit
de3f376882
7 changed files with 173 additions and 9 deletions
74
templates/devtest/dropdown.tmpl
Normal file
74
templates/devtest/dropdown.tmpl
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
{{template "base/head" .}}
|
||||
|
||||
<div class="page-content devtest ui container">
|
||||
<h1>Dropdown</h1>
|
||||
a.k.a. overflow menu, ellipsis menu
|
||||
|
||||
<div class="button-sequence">
|
||||
<details class="dropdown" id="dropdown-1">
|
||||
<summary class="Options">
|
||||
Options {{svg "octicon-triangle-down" 14}}
|
||||
</summary>
|
||||
<div class="content">
|
||||
<ul>
|
||||
<li>
|
||||
<a id="dd1_g1_i1" href="#noscroll">Item 1</a>
|
||||
</li>
|
||||
<li>
|
||||
<a id="dd1_g1_i2" href="#noscroll">Item 2</a>
|
||||
</li>
|
||||
<li>
|
||||
<a id="dd1_g1_i3" href="#noscroll">Item 3</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<details class="dropdown" id="dropdown-2">
|
||||
<summary class="Options">
|
||||
Options {{svg "octicon-triangle-down" 14}}
|
||||
</summary>
|
||||
<div class="content">
|
||||
<ul>
|
||||
<li>
|
||||
<a id="dd2_g1_i1" href="#noscroll">Item 1</a>
|
||||
</li>
|
||||
<li>
|
||||
<a id="dd2_g1_i2" href="#noscroll">Item 2</a>
|
||||
</li>
|
||||
<li>
|
||||
<a id="dd2_g1_i3" href="#noscroll">Item 3</a>
|
||||
</li>
|
||||
</ul>
|
||||
<hr>
|
||||
<ul>
|
||||
<li>
|
||||
<a id="dd2_g2_i1" href="#noscroll">Item 4</a>
|
||||
</li>
|
||||
<li>
|
||||
<a id="dd2_g2_i2" href="#noscroll">Item 5</a>
|
||||
</li>
|
||||
<li>
|
||||
<a id="dd2_g2_i3" href="#noscroll">Item 6</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<details class="dropdown" id="dropdown-3">
|
||||
<summary class="Options">
|
||||
Options {{svg "octicon-triangle-down" 14}}
|
||||
</summary>
|
||||
<div class="content">
|
||||
<ul>
|
||||
<li>
|
||||
<a id="dd3_g1_i1" href="#noscroll">Only item</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
{{template "base/footer" .}}
|
||||
|
|
@ -11,7 +11,7 @@
|
|||
<details class="dropdown dir-rtl">
|
||||
<summary class="options">
|
||||
{{ctx.Locale.Tr "repo.issues.filter_sort"}}
|
||||
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
|
||||
{{svg "octicon-triangle-down" 14}}
|
||||
</summary>
|
||||
<div class="content">
|
||||
<ul>
|
||||
|
|
|
|||
|
|
@ -62,14 +62,24 @@
|
|||
</div>
|
||||
|
||||
{{if .AllowDisableOrEnableWorkflow}}
|
||||
<button class="ui jump dropdown btn interact-bg tw-p-2">
|
||||
{{svg "octicon-kebab-horizontal"}}
|
||||
<div class="menu">
|
||||
<a class="item link-action" data-url="{{$.Link}}/{{if .CurWorkflowDisabled}}enable{{else}}disable{{end}}?workflow={{$.CurWorkflow}}&actor={{.CurActor}}&status={{$.CurStatus}}">
|
||||
{{if .CurWorkflowDisabled}}{{ctx.Locale.Tr "actions.workflow.enable"}}{{else}}{{ctx.Locale.Tr "actions.workflow.disable"}}{{end}}
|
||||
</a>
|
||||
<details class="dropdown dir-rtl">
|
||||
<summary class="border">
|
||||
{{svg "octicon-kebab-horizontal"}}
|
||||
</summary>
|
||||
<div class="content">
|
||||
<ul>
|
||||
<li>
|
||||
<a class="link-action" data-url="{{$.Link}}/{{if .CurWorkflowDisabled}}enable{{else}}disable{{end}}?workflow={{$.CurWorkflow}}&actor={{.CurActor}}&status={{$.CurStatus}}" href="#noscroll">
|
||||
{{if .CurWorkflowDisabled}}
|
||||
{{ctx.Locale.Tr "actions.workflow.enable"}}
|
||||
{{else}}
|
||||
{{ctx.Locale.Tr "actions.workflow.disable"}}
|
||||
{{end}}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</button>
|
||||
</details>
|
||||
{{end}}
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -70,6 +70,53 @@ test.describe('Workflow Authenticated user2', () => {
|
|||
test('dispatch success', async ({page}) => {
|
||||
await dispatchSuccess(page);
|
||||
});
|
||||
|
||||
test('Disable/enable workflow', async ({page}) => {
|
||||
await page.goto('/user2/test_workflows/actions?workflow=test-dispatch.yml');
|
||||
|
||||
const menuOpener = page.locator('.filter.menu details.dropdown > summary');
|
||||
const disableButton = page.locator('a[data-url^="/user2/test_workflows/actions/disable"]');
|
||||
const enableButton = page.locator('a[data-url^="/user2/test_workflows/actions/enable"]');
|
||||
const disabledLabel = page.locator('.vertical.menu .item.active .ui.label').getByText('Disabled');
|
||||
const flashBanner = page.locator('#flash-message');
|
||||
|
||||
// Overflow menu is hidden
|
||||
await expect(disableButton).toBeHidden();
|
||||
await expect(enableButton).toBeHidden();
|
||||
|
||||
await menuOpener.click();
|
||||
|
||||
// The current "Enabled" state is what previous tests left, but this test is built to not care
|
||||
if (await disableButton.isVisible()) {
|
||||
// Assert elemeents on page
|
||||
await expect(enableButton).toBeHidden();
|
||||
await expect(disabledLabel).toBeHidden();
|
||||
|
||||
// Flip the state
|
||||
await disableButton.click();
|
||||
await flashBanner.waitFor();
|
||||
await menuOpener.click();
|
||||
|
||||
// Assert elemeents on page
|
||||
await expect(enableButton).toBeVisible();
|
||||
await expect(disableButton).toBeHidden();
|
||||
await expect(disabledLabel).toBeVisible();
|
||||
} else {
|
||||
// Assert elemeents on page
|
||||
await expect(enableButton).toBeVisible();
|
||||
await expect(disabledLabel).toBeVisible();
|
||||
|
||||
// Flip the state
|
||||
await enableButton.click();
|
||||
await flashBanner.waitFor();
|
||||
await menuOpener.click();
|
||||
|
||||
// Assert elemeents on page
|
||||
await expect(enableButton).toBeHidden();
|
||||
await expect(disableButton).toBeVisible();
|
||||
await expect(disabledLabel).toBeHidden();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test('workflow dispatch box not available for unauthenticated users', async ({page}) => {
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
// templates/shared/user/actions_menu.tmpl
|
||||
// templates/org/header.tmpl
|
||||
// templates/explore/search.tmpl
|
||||
// templates/devtest/dropdown.tmpl
|
||||
// web_src/js/modules/dropdown.ts
|
||||
// @watch end
|
||||
|
||||
|
|
@ -226,4 +227,33 @@ test.describe(`Visual properties`, () => {
|
|||
expect(await activeItem.evaluate((el) => getComputedStyle(el).backgroundColor)).toBe('rgb(226, 226, 229)');
|
||||
expect(await inactiveItem.evaluate((el) => getComputedStyle(el).backgroundColor)).toBe('rgba(0, 0, 0, 0)');
|
||||
});
|
||||
|
||||
test('Devtest', async ({browser}) => {
|
||||
const context = await browser.newContext({javaScriptEnabled: false});
|
||||
const page = await context.newPage();
|
||||
|
||||
// `/devtest` has dropdowns with various combinations of items
|
||||
await page.goto('/devtest/dropdown');
|
||||
|
||||
// Dropdown with just 3 items and nothing special
|
||||
await page.locator(`#dropdown-1 > summary`).click();
|
||||
expect(await page.locator(`#dd1_g1_i1`).evaluate((el) => getComputedStyle(el).borderRadius)).toBe('4px 4px 0px 0px');
|
||||
expect(await page.locator(`#dd1_g1_i2`).evaluate((el) => getComputedStyle(el).borderRadius)).toBe('0px');
|
||||
expect(await page.locator(`#dd1_g1_i3`).evaluate((el) => getComputedStyle(el).borderRadius)).toBe('0px 0px 4px 4px');
|
||||
await page.keyboard.press('Enter'); // Exit dropdown - page is in noJS mode
|
||||
|
||||
// Dropdown with two groups of items separated with an <hr>
|
||||
await page.locator(`#dropdown-2 > summary`).click();
|
||||
expect(await page.locator(`#dd2_g1_i1`).evaluate((el) => getComputedStyle(el).borderRadius)).toBe('4px 4px 0px 0px');
|
||||
expect(await page.locator(`#dd2_g1_i2`).evaluate((el) => getComputedStyle(el).borderRadius)).toBe('0px');
|
||||
expect(await page.locator(`#dd2_g1_i3`).evaluate((el) => getComputedStyle(el).borderRadius)).toBe('0px');
|
||||
expect(await page.locator(`#dd2_g2_i1`).evaluate((el) => getComputedStyle(el).borderRadius)).toBe('0px');
|
||||
expect(await page.locator(`#dd2_g2_i2`).evaluate((el) => getComputedStyle(el).borderRadius)).toBe('0px');
|
||||
expect(await page.locator(`#dd2_g2_i3`).evaluate((el) => getComputedStyle(el).borderRadius)).toBe('0px 0px 4px 4px');
|
||||
await page.keyboard.press('Enter'); // Exit dropdown - page is in noJS mode
|
||||
|
||||
// Dropdown with only one item, which should be completely round
|
||||
await page.locator(`#dropdown-3 > summary`).click();
|
||||
expect(await page.locator(`#dd3_g1_i1`).evaluate((el) => getComputedStyle(el).borderRadius)).toBe('4px');
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -107,7 +107,7 @@ func TestE2e(t *testing.T) {
|
|||
defer test.MockVariableValue(&setting.Quota.Enabled, true)()
|
||||
defer test.MockVariableValue(&testE2eWebRoutes, routers.NormalRoutes())()
|
||||
}
|
||||
if testname == "buttons.test.e2e" {
|
||||
if testname == "buttons.test.e2e" || testname == "dropdown.test.e2e" {
|
||||
defer test.MockVariableValue(&setting.IsProd, false)()
|
||||
defer test.MockVariableValue(&testE2eWebRoutes, routers.NormalRoutes())()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -107,6 +107,9 @@ details.dropdown > .content > ul {
|
|||
&:last-of-type > li:last-child {
|
||||
border-radius: 0 0 var(--border-radius) var(--border-radius);
|
||||
}
|
||||
&:only-of-type > li:only-child {
|
||||
border-radius: var(--border-radius);
|
||||
}
|
||||
}
|
||||
|
||||
/* General styling of list items */
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue