Repository: wagtail/wagtail

Issue:
Refactor Status tag styles into UI component
### Is your proposal related to a problem?

We currently have a "status tag" component as reusable CSS styles that are copy-pasted with the relevant HTML across the admin.

### Describe the solution you'd like

This feels like a good candidate to refactor to a UI component, this is an approach where we use a template tag to abstract away the HTML.

This shouldn’t change the component’s rendering in any way, just how it’s included in different places (and introduce a way for us to test the variations more easily in Storybook). For example, we will not adjust the class names, even though these are somewhat confusing.

#### Requirements

- [ ] Create a new shared template partial (`wagtail/admin/templates/wagtailadmin/shared/status_tag.html`)
- [ ] Create a new template tag that uses this partial in `wagtail/admin/templatetags/wagtailadmin_tags.py`
- [ ] Ensure the template tag works with the `classname` kwarg
- [ ] Add a Storybook stories for different variations of this new component ( similar to `wagtail/admin/templates/wagtailadmin/shared/header.stories.tsx`)
- [ ] With a template partial in the `shared` folder for us to `{% include %}` where needed (update all locations)
- [ ] Ensure that the styleguide is also updated to use the new template partial `wagtail/contrib/styleguide/templates/wagtailstyleguide/base.html`

### Additional context

* The help_block is a good example of how this has been done before https://github.com/wagtail/wagtail/blob/main/wagtail/admin/templates/wagtailadmin/shared/help_block.html & https://github.com/wagtail/wagtail/blob/main/wagtail/admin/templatetags/wagtailadmin_tags.py#L1059-L1063
* Current primary SCSS https://github.com/wagtail/wagtail/blob/main/client/scss/components/_status-tag.scss
* Secondary class re-use https://github.com/wagtail/wagtail/blob/main/client/scss/components/_listing.scss
* See also https://github.com/wagtail/wagtail/blob/main/client/scss/layouts/_report.scss#L11
* It may be really tricky to do this one wagtail/admin/templates/wagtailadmin/shared/page_status_tag.html (we may need to put the link inside the status tag or the other way around
* Follow up items (once this is done) - we should ...
* [ ] Refactor `status' classes to align with `w-status-tag` and use BEM classes for the `primary` & `disabled` variants





Discussion:
We should also try to refactor the CSS to be consistently using the `w-` prefix and move styles files from scss/objects to scss/components


Same comment as #8655, Steve only has time to help with Storybook usage at the time so we’d need to do any refactoring outside templates separately to this.
No problems at all - that can be split / done when it if needed. Same goes for the others. I got a bit confused and saw this in up for grabs so figured anyone would pick this up. I didn't see Steve assigned. 
@salty-ivy let me know if this is something you may be keen on picking up - it's not really a JS thing but a Django templates thing but would be great to get this one resolved.

See related issue https://github.com/wagtail/wagtail/issues/5778 (status tag contrast)
> @salty-ivy let me know if this is something you may be keen on picking up - it's not really a JS thing but a Django templates thing but would be great to get this one resolved.
> 
> 

@lb- sure why not, I am fine with python/Django work got better experience with that.
Open to all kinds !
Would look into it asap.
> @salty-ivy let me know if this is something you may be keen on picking up - it's not really a JS thing but a Django templates thing but would be great to get this one resolved.

@lb- could you please give me a heads up, probably like  what exactly needs to be done (as i can see its little confusing page_status_tag html is already refactored to be included) ? would be appreciated 
@salty-ivy  issue is updated - hopefully it's a bit clearer
Alright thanks a lot ! @lb- @thibaudcolas 

Resolving patch:
diff --git a/wagtail/admin/templatetags/wagtailadmin_tags.py b/wagtail/admin/templatetags/wagtailadmin_tags.py
--- a/wagtail/admin/templatetags/wagtailadmin_tags.py
+++ b/wagtail/admin/templatetags/wagtailadmin_tags.py
@@ -779,6 +779,41 @@ def icon(name=None, classname=None, title=None, wrapped=False, class_name=None):
     }
 
 
+@register.inclusion_tag("wagtailadmin/shared/status_tag.html")
+def status(
+    label=None,
+    classname=None,
+    url=None,
+    title=None,
+    hidden_label=None,
+    attrs=None,
+):
+    """
+    Generates a status-tag css with <span></span> or <a><a/> implementation.
+
+    Usage:
+
+        {% status label="live" url="/test/" title="title" hidden_label="current status:" classname="primary" %}
+
+    :param label: the status test, (string)
+    :param classname: defaults to 'status-tag' if not provided (string)
+    :param url: the status url(to specify the use of anchor tag instead of default span), (string)
+    :param title: accessible label intended for screen readers (string)
+    :param hidden_label : the to specify the additional visually hidden span text, (string)
+    :param attrs: any additional HTML attributes (as a string) to append to the root element
+    :return: Rendered template snippet (string)
+
+    """
+    return {
+        "label": label,
+        "attrs": attrs,
+        "classname": classname,
+        "hidden_label": hidden_label,
+        "title": title,
+        "url": url,
+    }
+
+
 @register.filter()
 def timesince_simple(d):
     """


Test patch:
diff --git a/wagtail/admin/tests/test_templatetags.py b/wagtail/admin/tests/test_templatetags.py
--- a/wagtail/admin/tests/test_templatetags.py
+++ b/wagtail/admin/tests/test_templatetags.py
@@ -543,3 +543,77 @@ def test_with_classes_obsolete_keyword(self):
             ),
         ):
             self.assertHTMLEqual(expected, Template(template).render(Context()))
+
+
+class StatusTagTest(TestCase):
+    def test_render_block_component_span_variations(self):
+        template = """
+            {% load wagtailadmin_tags i18n %}
+            {% status "live" classname="primary" %}
+            {% status "live" %}
+            {% trans "hidden translated label" as trans_hidden_label %}
+            {% status "live" hidden_label=trans_hidden_label classname="primary" %}
+            {% status %}
+        """
+
+        expected = """
+            <span class="status-tag primary">live</span>
+            <span class="status-tag">live</span>
+            <span class="status-tag primary"><span class="visuallyhidden">hidden translated label</span>live</span>
+            <span class="status-tag"></span>
+        """
+
+        self.assertHTMLEqual(expected, Template(template).render(Context()))
+
+    def test_render_block_component_anchor_variations(self):
+        template = """
+            {% load wagtailadmin_tags i18n %}
+            {% trans "title" as trans_title %}
+            {% trans "hidden label" as trans_hidden_label %}
+            {% status "live" url="/test-url/" title=trans_title hidden_label=trans_hidden_label classname="primary" attrs='target="_blank" rel="noreferrer"' %}
+            {% status "live" url="/test-url/" title=trans_title classname="primary" %}
+            {% status "live" url="/test-url/" title=trans_title %}
+            {% status  url="/test-url/" title=trans_title attrs='id="my-status"' %}
+        """
+
+        expected = """
+            <a href="/test-url/" class="status-tag primary" title="title" target="_blank" rel="noreferrer">
+                <span class="visuallyhidden">hidden label</span>
+                live
+            </a>
+            <a href="/test-url/" class="status-tag primary" title="title">
+                live
+            </a>
+            <a href="/test-url/" class="status-tag" title="title">
+                live
+            </a>
+            <a href="/test-url/" class="status-tag" title="title" id="my-status">
+            </a>
+        """
+
+        self.assertHTMLEqual(expected, Template(template).render(Context()))
+
+    def test_render_as_fragment(self):
+        template = """
+            {% load wagtailadmin_tags i18n %}
+            {% fragment as var %}
+                {% trans "title" as trans_title %}
+                {% trans "hidden label" as trans_hidden_label %}
+                {% status "live" url="/test-url/" title=trans_title hidden_label=trans_hidden_label classname="primary" attrs='target="_blank" rel="noreferrer"' %}
+                {% status "live" hidden_label=trans_hidden_label classname="primary" attrs="data-example='present'" %}
+            {% endfragment %}
+            {{var}}
+        """
+
+        expected = """
+            <a href="/test-url/" class="status-tag primary" title="title" target="_blank" rel="noreferrer">
+                <span class="visuallyhidden">hidden label</span>
+                live
+            </a>
+            <span class="status-tag primary" data-example='present'>
+                <span class="visuallyhidden">hidden label</span>
+                live
+            </span>
+        """
+
+        self.assertHTMLEqual(expected, Template(template).render(Context()))
diff --git a/wagtail/snippets/tests/test_snippets.py b/wagtail/snippets/tests/test_snippets.py
--- a/wagtail/snippets/tests/test_snippets.py
+++ b/wagtail/snippets/tests/test_snippets.py
@@ -4100,12 +4100,18 @@ def test_with_live_and_draft_status(self):
 
         # Should show the "live version" status tag for the published revision
         self.assertContains(
-            response, '<span class="status-tag primary">Live version</span>', count=1
+            response,
+            '<span class="status-tag primary">Live version</span>',
+            count=1,
+            html=True,
         )
 
         # Should show the "current draft" status tag for the draft revision
         self.assertContains(
-            response, '<span class="status-tag primary">Current draft</span>', count=1
+            response,
+            '<span class="status-tag primary">Current draft</span>',
+            count=1,
+            html=True,
         )
 
         # Should use the latest draft title in the header subtitle
@@ -4685,19 +4691,25 @@ def test_choose_view_shows_status_column(self):
         response = self.get("choose")
         html = response.json()["html"]
         self.assertTagInHTML("<th>Status</th>", html)
-        self.assertTagInHTML('<div class="status-tag ">draft</div>', html)
-        self.assertTagInHTML('<div class="status-tag primary">live</div>', html)
-        self.assertTagInHTML('<div class="status-tag primary">live + draft</div>', html)
+        self.assertTagInHTML('<span class="status-tag">draft</span>', html)
+        self.assertTagInHTML('<span class="status-tag primary">live</span>', html)
+        self.assertTagInHTML(
+            '<span class="status-tag primary">live + draft</span>', html
+        )
 
     def test_choose_results_view_shows_status_column(self):
         response = self.get("choose_results")
         self.assertContains(response, "<th>Status</th>", html=True)
-        self.assertContains(response, '<div class="status-tag ">draft</div>', html=True)
         self.assertContains(
-            response, '<div class="status-tag primary">live</div>', html=True
+            response, '<span class="status-tag">draft</span>', html=True
+        )
+        self.assertContains(
+            response, '<span class="status-tag primary">live</span>', html=True
         )
         self.assertContains(
-            response, '<div class="status-tag primary">live + draft</div>', html=True
+            response,
+            '<span class="status-tag primary">live + draft</span>',
+            html=True,
         )