From f5312eaae7c5757ce0f98192ef54dc07decf9f3e Mon Sep 17 00:00:00 2001 From: Rias Van der Veken Date: Fri, 13 Nov 2026 08:28:04 +0000 Subject: [PATCH 1/2] Show workflow stage type icons in the picker Keep Iconic optional for workflow stages and give User Review its user-magnifying-glass icon. Amp-Thread-ID: https://ampcode.com/threads/T-01a1d3f3-a29b-7489-ace2-82616452f915 Co-authored-by: Amp --- .../components/WorkflowStagesInput.test.ts | 19 +++++++++++++++++++ .../components/WorkflowStagesInput.vue | 2 +- src/Http/ViewModels/WorkflowEditViewModel.php | 4 +++- src/Workflow/UserReview/UserReviewStage.php | 8 +++++++- tests/Feature/Workflow/WorkflowsTest.php | 2 ++ 5 files changed, 32 insertions(+), 3 deletions(-) diff --git a/resources/js/modules/workflows/components/WorkflowStagesInput.test.ts b/resources/js/modules/workflows/components/WorkflowStagesInput.test.ts index d9e04003803..da3f41978c2 100644 --- a/resources/js/modules/workflows/components/WorkflowStagesInput.test.ts +++ b/resources/js/modules/workflows/components/WorkflowStagesInput.test.ts @@ -13,12 +13,14 @@ const stageTypes = [ { type, label: 'User Review', + icon: 'user-magnifying-glass', settings: {approvalsRequired: 1, approvalMode: 'total', userGroups: []}, settingsUi: null, }, { type: 'plugin\\AutomatedStage', label: 'Automated review', + icon: null, settings: {rule: 'passing'}, settingsUi: null, }, @@ -93,6 +95,23 @@ describe('WorkflowStagesInput', () => { await nextTick(); } + it('shows stage type icons and falls back to plus for types without icons', async () => { + mount([stage('editorial', 'Editorial')]); + await nextTick(); + + const menu = + container!.querySelectorAll('craft-action-menu')[1]!; + await vi.waitFor(() => + expect(menu.querySelectorAll('craft-action-item')).toHaveLength(2) + ); + + const items = [...menu.querySelectorAll('craft-action-item')]; + expect(items.map((item) => [item.textContent?.trim(), item.icon])).toEqual([ + ['User Review', 'user-magnifying-glass'], + ['Automated review', 'plus'], + ]); + }); + it('reorders complete stages', async () => { const updates = mount([ stage('editorial', 'Editorial'), diff --git a/resources/js/modules/workflows/components/WorkflowStagesInput.vue b/resources/js/modules/workflows/components/WorkflowStagesInput.vue index 0da98523715..e220a4828a9 100644 --- a/resources/js/modules/workflows/components/WorkflowStagesInput.vue +++ b/resources/js/modules/workflows/components/WorkflowStagesInput.vue @@ -28,7 +28,7 @@ const addStageActions = computed(() => props.stageTypes.map((stageType) => ({ label: stageType.label, - icon: 'plus', + icon: stageType.icon ?? 'plus', onClick: () => addStage(stageType.type), })) ); diff --git a/src/Http/ViewModels/WorkflowEditViewModel.php b/src/Http/ViewModels/WorkflowEditViewModel.php index 2789074c11c..1240440d66c 100644 --- a/src/Http/ViewModels/WorkflowEditViewModel.php +++ b/src/Http/ViewModels/WorkflowEditViewModel.php @@ -5,6 +5,7 @@ namespace CraftCms\Cms\Http\ViewModels; use CraftCms\Cms\Component\ComponentHelper; +use CraftCms\Cms\Component\Contracts\Iconic; use CraftCms\Cms\Http\Controllers\Settings\WorkflowsController; use CraftCms\Cms\Support\Str; use CraftCms\Cms\Ui\Controls\Table; @@ -54,7 +55,7 @@ public function ui(): UiPayload )); } - /** @return list, settingsUi: UiPayload|null}> */ + /** @return list, settingsUi: UiPayload|null}> */ public function stageTypes(): array { return $this->workflowStageTypes->types() @@ -66,6 +67,7 @@ public function stageTypes(): array return [ 'type' => $type, 'label' => $type::displayName(), + 'icon' => $stage instanceof Iconic ? $stage->getIcon() : null, 'settings' => $stage->getSettings(), 'settingsUi' => $this->settingsUi($stage), ]; diff --git a/src/Workflow/UserReview/UserReviewStage.php b/src/Workflow/UserReview/UserReviewStage.php index 6d799fa99fa..15481c692e7 100644 --- a/src/Workflow/UserReview/UserReviewStage.php +++ b/src/Workflow/UserReview/UserReviewStage.php @@ -4,6 +4,7 @@ namespace CraftCms\Cms\Workflow\UserReview; +use CraftCms\Cms\Component\Contracts\Iconic; use CraftCms\Cms\Database\Table; use CraftCms\Cms\Ui\Controls\Choice; use CraftCms\Cms\Ui\Controls\Number; @@ -25,7 +26,7 @@ /** * @since 6.0.0 */ -class UserReviewStage extends WorkflowStage +class UserReviewStage extends WorkflowStage implements Iconic { #[\Override] protected bool $showDefaultReviewActions = false; @@ -42,6 +43,11 @@ public static function displayName(): string return t('User Review'); } + public function getIcon(): ?string + { + return 'user-magnifying-glass'; + } + public function getRules(): array { return [ diff --git a/tests/Feature/Workflow/WorkflowsTest.php b/tests/Feature/Workflow/WorkflowsTest.php index 838a99c2105..edac4d4e243 100644 --- a/tests/Feature/Workflow/WorkflowsTest.php +++ b/tests/Feature/Workflow/WorkflowsTest.php @@ -142,6 +142,7 @@ public function evaluate(WorkflowStageContext $context): WorkflowStageResult $automaticApproval = collect($payload['stageTypes'])->firstWhere('type', AutomaticApprovalStage::class); expect($userReview['label'])->toBe('User Review') + ->and($userReview['icon'])->toBe('user-magnifying-glass') ->and($userReview['settings'])->toBe([ 'approvalsRequired' => 1, 'approvalMode' => 'total', @@ -151,6 +152,7 @@ public function evaluate(WorkflowStageContext $context): WorkflowStageResult ->and($userReview['settingsUi']->nodes[0]->children[0]->control->component)->toBe('craft:user-group-select') ->and($automaticApproval)->toMatchArray([ 'label' => 'Automatic Approval', + 'icon' => null, 'settings' => [], 'settingsUi' => null, ]); From ff6857993f52f89b31d6cf344112eae101087b97 Mon Sep 17 00:00:00 2001 From: Rias Van der Veken Date: Fri, 13 Nov 2026 08:30:00 +0000 Subject: [PATCH 2/2] Document workflow stage type icons Reference #20660 in the Craft 6 release notes. [ci skip] Amp-Thread-ID: https://ampcode.com/threads/T-01a1d3f3-a29b-7489-ace2-82616452f915 Co-authored-by: Amp --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d1d5254933d..05eacdbe870 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ - Breadcrumb separators are now lighter. ([#20612](https://github.com/craftcms/cms/pull/20612)) - Pages no longer show breadcrumbs when the only one would be the current page, such as the Dashboard. ([#20612](https://github.com/craftcms/cms/pull/20612)) - Fields’ translation indicators now show their descriptions when pressed, including on touch devices, rather than on hover. ([#20612](https://github.com/craftcms/cms/pull/20612)) +- Updated the workflow stage type picker to show each type’s icon when available. ([#20660](https://github.com/craftcms/cms/pull/20660)) - Fixed a bug where focus was lost after choosing elements in a relation field’s selector modal, or canceling it. Focus now returns to the field’s add button, or to the chosen element when the field is full. ([#20653](https://github.com/craftcms/cms/pull/20653)) - Fixed a bug where typing in fields, especially Markdown fields, lagged on entries with many fields. ([#20637](https://github.com/craftcms/cms/issues/20637)) - Fixed a bug where the user menu showed a “Permissions” item on Craft Solo, which led to a 403 error. ([#20621](https://github.com/craftcms/cms/issues/20621), [#20635](https://github.com/craftcms/cms/pull/20635))