diff --git a/cfgov/unprocessed/apps/homepage/css/main.scss b/cfgov/unprocessed/apps/homepage/css/main.scss index 2b3b17d12a7..90c08c48512 100644 --- a/cfgov/unprocessed/apps/homepage/css/main.scss +++ b/cfgov/unprocessed/apps/homepage/css/main.scss @@ -20,3 +20,34 @@ padding-top: 0; margin-top: 0; } + +.alternate-homepage { + .u-content-level-1 { + max-width: 1230px; + margin-left: auto; + margin-right: auto; + } + + .u-content-level-2 { + max-width: 930px; + margin-left: auto; + margin-right: auto; + } + + .u-content-level-3 { + max-width: 870px; + margin-left: auto; + margin-right: auto; + } + + .content__main--flush-inner { + padding-top: 0; + margin-top: 0; + } + + .m-info-unit__image { + width: 60px; + height: 40px; + margin: 0 auto 0.6em 0; + } +} diff --git a/cfgov/v1/atomic_elements/molecules.py b/cfgov/v1/atomic_elements/molecules.py index 52312525d8e..929f73412a2 100644 --- a/cfgov/v1/atomic_elements/molecules.py +++ b/cfgov/v1/atomic_elements/molecules.py @@ -11,6 +11,28 @@ from v1.feeds import get_appropriate_rss_feed_url_for_page +class CardBlock(blocks.StructBlock): + card_type = blocks.ChoiceBlock( + choices=[ + ("topic", "Topic"), + ("topic-action", "Topic Action"), + ("breakout", "Breakout"), + ], + required=False, + help_text="Choose the specialized card type.", + ) + icon = blocks.CharBlock(required=False) + image = ImageChooserBlock(required=False) + header = blocks.CharBlock(required=False) + text = blocks.CharBlock(required=False) + link_text = blocks.CharBlock() + link_url = blocks.CharBlock() + + class Meta: + icon = "image" + template = "v1/includes/molecules/cardb.html" + + class InfoUnit(blocks.StructBlock): image = atoms.ImageBasic( required=False, diff --git a/cfgov/v1/atomic_elements/organisms.py b/cfgov/v1/atomic_elements/organisms.py index 9361622c2e8..5f4d2cab9fe 100644 --- a/cfgov/v1/atomic_elements/organisms.py +++ b/cfgov/v1/atomic_elements/organisms.py @@ -28,6 +28,28 @@ from v1.util import ref +class CardBlockGroup(blocks.StructBlock): + group_type = blocks.ChoiceBlock( + choices=[ + ("flow", "Natural flow"), + ("column-2", "Two columns"), + ("column-3", "Three columns"), + ("count-2", "Two cards"), + ("count-3", "Three cards"), + ("count-4", "Four cards"), + ], + default="flow", + help_text="Choose how to group the cards", + ) + has_well = blocks.BooleanBlock(required=False) + heading = blocks.CharBlock(required=False) + cards = blocks.ListBlock(molecules.CardBlock(), default=list()) + + class Meta: + icon = "form" + template = "v1/includes/organisms/cardgroupb.html" + + class Well(blocks.StructBlock): content = blocks.RichTextBlock(required=False, label="Well") diff --git a/cfgov/v1/jinja2/v1/home_page/home_pageb.html b/cfgov/v1/jinja2/v1/home_page/home_pageb.html new file mode 100644 index 00000000000..57817718279 --- /dev/null +++ b/cfgov/v1/jinja2/v1/home_page/home_pageb.html @@ -0,0 +1,40 @@ +{% extends "v1/layouts/layout-full.html" %} + +{% import "v1/includes/organisms/card-group.html" as card_group with context %} + +{% block title -%} + {{ _('Consumer Financial Protection Bureau') }} +{%- endblock %} + +{% block css %} +{{ super() }} + +{% endblock %} + +{% block content_main_modifiers -%} +content__main--flush-inner alternate-homepage +{%- endblock %} + +{% block content_intro %} +
+
+ {% include_block page.hero %} +
+ {% include_block page.highlights %} +
+
+
+{% endblock %} + +{% block content_main %} +
+ {% include_block page.topics %} +
+
+ {% include_block page.callouts %} +
+
+
+ {% include_block page.breakouts %} +
+{% endblock content_main %} diff --git a/cfgov/v1/jinja2/v1/includes/molecules/cardb.html b/cfgov/v1/jinja2/v1/includes/molecules/cardb.html new file mode 100644 index 00000000000..be97cad4eb9 --- /dev/null +++ b/cfgov/v1/jinja2/v1/includes/molecules/cardb.html @@ -0,0 +1,73 @@ +{# ========================================================================== + + Card + + ========================================================================== + + Description: + + Creates markup for a Card molecule, which displays a link + with optional heading, icon, text, or image. + + value.card_type: (Optional) The type of card. Values can be: + topic, topic-action, breakout + + value.icon: (Optional) Name of an icon. See list of available icon names at: + https://cfpb.github.io/design-system/foundation/iconography. + + value.image (Optional) If using a breakout card, required. The image to breakout. + + value.heading: (Optional) Card heading. + + value.text: (Optional) Card body text. + + value.link_text: Card link text. + + value.link_url: Card link URL. + + ========================================================================== #} + +{# We want to make the whole card clickable. #} +
+ + {% if value.heading %} +

+ {% if value.icon -%} + {{ svg_icon( value.icon ) }} + {%- endif %} + {% if value.heading -%} + {{ value.heading }} + {%- else -%} + {{ value.link_text }} + {%- endif %} +

+ {% else %} + {% if value.icon -%} +
{{ svg_icon( value.icon ) }}
+ {%- endif %} + {% endif %} + + {% if value.text %} +

{{ value.text }}

+ {% endif %} + {% if value.card_type == 'breakout' %} + + {% endif %} + + {% if value.card_type == 'breakout' %} +
+ {% endif %} + + {% if value.card_type == 'breakout' %} +
+ {% endif %} +
+
diff --git a/cfgov/v1/jinja2/v1/includes/molecules/hero.html b/cfgov/v1/jinja2/v1/includes/molecules/hero.html index 4b7af476532..83e030d7cdc 100644 --- a/cfgov/v1/jinja2/v1/includes/molecules/hero.html +++ b/cfgov/v1/jinja2/v1/includes/molecules/hero.html @@ -66,7 +66,7 @@ {{- ' m-hero--jumbo' if value.is_jumbo else '' }}">
-

{{ value.heading }} +

{{ value.heading | safe }} {% if value.heading_continued %}
{{ value.heading_continued }} {% endif %} diff --git a/cfgov/v1/jinja2/v1/includes/organisms/cardgroupb.html b/cfgov/v1/jinja2/v1/includes/organisms/cardgroupb.html new file mode 100644 index 00000000000..633c4313d56 --- /dev/null +++ b/cfgov/v1/jinja2/v1/includes/organisms/cardgroupb.html @@ -0,0 +1,38 @@ +{# ========================================================================== + + CardGroup + + ========================================================================== + + Description: + + Creates markup for a Card Group organism, which displays a set of cards + with an optional heading. + + value.group_type: The group type. Can be: + flow, column-2, column-3, + count-2, count-3, count-4 + + value.has_well (Optional) Whether the cards should + be in a well + + value.heading: (Optional) Group heading. + + value.cards: List of card molecules + + ========================================================================== #} + +{% import "v1/includes/molecules/cardb.html" as card with context %} + +
+
+ {% if value.heading %}

{{ value.heading }}

{% endif %} +
+ {% if value.has_well %}
{% endif %} +
+ {% for _card in value.cards %} + {% include_block _card %} + {% endfor %} +
+ {% if value.has_well %}
{% endif %} +
diff --git a/cfgov/v1/migrations/0021_homepage_breakouts_homepage_callouts_homepage_hero_and_more.py b/cfgov/v1/migrations/0021_homepage_breakouts_homepage_callouts_homepage_hero_and_more.py new file mode 100644 index 00000000000..ef71e47207c --- /dev/null +++ b/cfgov/v1/migrations/0021_homepage_breakouts_homepage_callouts_homepage_hero_and_more.py @@ -0,0 +1,39 @@ +# Generated by Django 4.2.17 on 2025-02-06 20:33 + +from django.db import migrations +import wagtail.fields + + +class Migration(migrations.Migration): + + dependencies = [ + ('v1', '0020_alter_cfgovpagecategory_name'), + ] + + operations = [ + migrations.AddField( + model_name='homepage', + name='breakouts', + field=wagtail.fields.StreamField([('breakout', 8)], blank=True, block_lookup={0: ('wagtail.blocks.ChoiceBlock', [], {'choices': [('flow', 'Natural flow'), ('column-2', 'Two columns'), ('column-3', 'Three columns'), ('count-2', 'Two cards'), ('count-3', 'Three cards'), ('count-4', 'Four cards')], 'help_text': 'Choose how to group the cards'}), 1: ('wagtail.blocks.BooleanBlock', (), {'required': False}), 2: ('wagtail.blocks.CharBlock', (), {'required': False}), 3: ('wagtail.blocks.ChoiceBlock', [], {'choices': [('topic', 'Topic'), ('topic-action', 'Topic Action'), ('breakout', 'Breakout')], 'help_text': 'Choose the specialized card type.', 'required': False}), 4: ('wagtail.images.blocks.ImageChooserBlock', (), {'required': False}), 5: ('wagtail.blocks.CharBlock', (), {}), 6: ('wagtail.blocks.StructBlock', [[('card_type', 3), ('icon', 2), ('image', 4), ('header', 2), ('text', 2), ('link_text', 5), ('link_url', 5)]], {}), 7: ('wagtail.blocks.ListBlock', (6,), {'default': []}), 8: ('wagtail.blocks.StructBlock', [[('group_type', 0), ('has_well', 1), ('heading', 2), ('cards', 7)]], {})}), + ), + migrations.AddField( + model_name='homepage', + name='callouts', + field=wagtail.fields.StreamField([('callout', 22)], blank=True, block_lookup={0: ('wagtail.blocks.ChoiceBlock', [], {'choices': [('50-50', '50/50'), ('33-33-33', '33/33/33'), ('25-75', '25/75')], 'help_text': 'Choose the number and width of info unit columns.', 'label': 'Format'}), 1: ('wagtail.blocks.CharBlock', (), {'required': False}), 2: ('wagtail.blocks.ChoiceBlock', [], {'choices': [('h2', 'H2'), ('h3', 'H3'), ('h4', 'H4'), ('h5', 'H5')]}), 3: ('wagtail.blocks.CharBlock', (), {'help_text': 'Input the name of an icon to appear to the left of the heading. E.g., approved, help-round, etc. See full list of icons', 'required': False}), 4: ('wagtail.blocks.StructBlock', [[('text', 1), ('level', 2), ('icon', 3)]], {'required': False}), 5: ('wagtail.blocks.RichTextBlock', (), {'required': False}), 6: ('wagtail.blocks.BooleanBlock', (), {'default': True, 'help_text': "Check this to link all images and headings to the URL of the first link in their unit's list, if there is a link.", 'required': False}), 7: ('wagtail.blocks.BooleanBlock', (), {'default': False, 'help_text': 'Check this to add a horizontal rule line to top of info unit group.', 'required': False}), 8: ('wagtail.blocks.BooleanBlock', (), {'default': False, 'help_text': 'Check this to show horizontal rule lines between info units.', 'label': 'Show rule lines between items', 'required': False}), 9: ('wagtail.blocks.ChoiceBlock', [], {'choices': [('none', 'None'), ('rounded', 'Rounded corners'), ('circle', 'Circle')], 'help_text': 'Adds a border-radius class to images in this group, allowing for a rounded or circular border.', 'label': 'Border radius for images?', 'required': False}), 10: ('wagtail.images.blocks.ImageChooserBlock', (), {'required': False}), 11: ('wagtail.blocks.CharBlock', (), {'help_text': "No character limit, but be as succinct as possible. If the image is decorative (i.e., a screenreader wouldn't have anything useful to say about it), leave this field blank.", 'required': False}), 12: ('wagtail.blocks.StructBlock', [[('upload', 10), ('alt', 11)]], {}), 13: ('wagtail.blocks.StructBlock', [[('text', 1), ('level', 2), ('icon', 3)]], {'default': {'level': 'h3'}, 'required': False}), 14: ('wagtail.blocks.RichTextBlock', (), {'blank': True, 'required': False}), 15: ('wagtail.blocks.CharBlock', (), {'help_text': 'Add an ARIA label if the link text does not describe the destination of the link (e.g. has ambiguous text like "Learn more" that is not descriptive on its own).', 'required': False}), 16: ('wagtail.blocks.CharBlock', (), {'default': '/', 'required': False}), 17: ('wagtail.blocks.BooleanBlock', (), {'default': False, 'required': False}), 18: ('wagtail.blocks.StructBlock', [[('text', 1), ('aria_label', 15), ('url', 16), ('is_link_boldface', 17)]], {}), 19: ('wagtail.blocks.ListBlock', (18,), {'required': False}), 20: ('wagtail.blocks.StructBlock', [[('image', 12), ('heading', 13), ('body', 14), ('links', 19)]], {}), 21: ('wagtail.blocks.ListBlock', (20,), {'default': []}), 22: ('wagtail.blocks.StructBlock', [[('format', 0), ('heading', 4), ('intro', 5), ('link_image_and_heading', 6), ('has_top_rule_line', 7), ('lines_between_items', 8), ('border_radius_image', 9), ('info_units', 21)]], {})}), + ), + migrations.AddField( + model_name='homepage', + name='hero', + field=wagtail.fields.StreamField([('hero', 8)], blank=True, block_lookup={0: ('wagtail.blocks.CharBlock', (), {'help_text': 'For guidelines on creating heroes, visit our Design System. Character counts (including spaces) at largest breakpoint:
  • • 41 characters max (one-line heading)
  • • 82 characters max (two-line heading)
', 'required': False}), 1: ('wagtail.blocks.CharBlock', (), {'help_text': 'Use if the heading needs to break to a second lineat a specific point in the text.', 'required': False}), 2: ('wagtail.blocks.RichTextBlock', (), {'help_text': 'Character counts (including spaces) at largest breakpoint:
  • • 165-186 characters (after a one-line heading)
  • • 108-124 characters (after a two-line heading)
', 'label': 'Sub-heading', 'required': False}), 3: ('wagtail.images.blocks.ImageChooserBlock', (), {'help_text': 'When saving illustrations, use a transparent background. ', 'label': 'Large image', 'required': False}), 4: ('wagtail.images.blocks.ImageChooserBlock', (), {'help_text': 'Optional. Provides an alternate image for small displays when using a photo or bleeding hero. Not required for the standard illustration. ', 'required': False}), 5: ('wagtail.blocks.CharBlock', (), {'help_text': 'Specify a hex value (including the # sign) from our official color palette.', 'required': False}), 6: ('wagtail.blocks.BooleanBlock', (), {'help_text': 'Optional. Turns the hero text white. Useful if using a dark background color or background image.', 'label': 'White text', 'required': False}), 7: ('wagtail.blocks.BooleanBlock', (), {'help_text': 'Optional. Set both the hero text and image to 50% width, prevent the background color from appearing in the left/right margins of the hero, and also add a border to the hero area. Requires the uploaded large image to be 755px x 575px.', 'label': '50/50 layout', 'required': False}), 8: ('wagtail.blocks.StructBlock', [[('heading', 0), ('heading_continued', 1), ('body', 2), ('image', 3), ('small_image', 4), ('background_color', 5), ('is_white_text', 6), ('is_50_50', 7)]], {})}), + ), + migrations.AddField( + model_name='homepage', + name='highlights', + field=wagtail.fields.StreamField([('highlight', 22)], blank=True, block_lookup={0: ('wagtail.blocks.ChoiceBlock', [], {'choices': [('50-50', '50/50'), ('33-33-33', '33/33/33'), ('25-75', '25/75')], 'help_text': 'Choose the number and width of info unit columns.', 'label': 'Format'}), 1: ('wagtail.blocks.CharBlock', (), {'required': False}), 2: ('wagtail.blocks.ChoiceBlock', [], {'choices': [('h2', 'H2'), ('h3', 'H3'), ('h4', 'H4'), ('h5', 'H5')]}), 3: ('wagtail.blocks.CharBlock', (), {'help_text': 'Input the name of an icon to appear to the left of the heading. E.g., approved, help-round, etc. See full list of icons', 'required': False}), 4: ('wagtail.blocks.StructBlock', [[('text', 1), ('level', 2), ('icon', 3)]], {'required': False}), 5: ('wagtail.blocks.RichTextBlock', (), {'required': False}), 6: ('wagtail.blocks.BooleanBlock', (), {'default': True, 'help_text': "Check this to link all images and headings to the URL of the first link in their unit's list, if there is a link.", 'required': False}), 7: ('wagtail.blocks.BooleanBlock', (), {'default': False, 'help_text': 'Check this to add a horizontal rule line to top of info unit group.', 'required': False}), 8: ('wagtail.blocks.BooleanBlock', (), {'default': False, 'help_text': 'Check this to show horizontal rule lines between info units.', 'label': 'Show rule lines between items', 'required': False}), 9: ('wagtail.blocks.ChoiceBlock', [], {'choices': [('none', 'None'), ('rounded', 'Rounded corners'), ('circle', 'Circle')], 'help_text': 'Adds a border-radius class to images in this group, allowing for a rounded or circular border.', 'label': 'Border radius for images?', 'required': False}), 10: ('wagtail.images.blocks.ImageChooserBlock', (), {'required': False}), 11: ('wagtail.blocks.CharBlock', (), {'help_text': "No character limit, but be as succinct as possible. If the image is decorative (i.e., a screenreader wouldn't have anything useful to say about it), leave this field blank.", 'required': False}), 12: ('wagtail.blocks.StructBlock', [[('upload', 10), ('alt', 11)]], {}), 13: ('wagtail.blocks.StructBlock', [[('text', 1), ('level', 2), ('icon', 3)]], {'default': {'level': 'h3'}, 'required': False}), 14: ('wagtail.blocks.RichTextBlock', (), {'blank': True, 'required': False}), 15: ('wagtail.blocks.CharBlock', (), {'help_text': 'Add an ARIA label if the link text does not describe the destination of the link (e.g. has ambiguous text like "Learn more" that is not descriptive on its own).', 'required': False}), 16: ('wagtail.blocks.CharBlock', (), {'default': '/', 'required': False}), 17: ('wagtail.blocks.BooleanBlock', (), {'default': False, 'required': False}), 18: ('wagtail.blocks.StructBlock', [[('text', 1), ('aria_label', 15), ('url', 16), ('is_link_boldface', 17)]], {}), 19: ('wagtail.blocks.ListBlock', (18,), {'required': False}), 20: ('wagtail.blocks.StructBlock', [[('image', 12), ('heading', 13), ('body', 14), ('links', 19)]], {}), 21: ('wagtail.blocks.ListBlock', (20,), {'default': []}), 22: ('wagtail.blocks.StructBlock', [[('format', 0), ('heading', 4), ('intro', 5), ('link_image_and_heading', 6), ('has_top_rule_line', 7), ('lines_between_items', 8), ('border_radius_image', 9), ('info_units', 21)]], {})}), + ), + migrations.AddField( + model_name='homepage', + name='topics', + field=wagtail.fields.StreamField([('topic', 8)], blank=True, block_lookup={0: ('wagtail.blocks.ChoiceBlock', [], {'choices': [('flow', 'Natural flow'), ('column-2', 'Two columns'), ('column-3', 'Three columns'), ('count-2', 'Two cards'), ('count-3', 'Three cards'), ('count-4', 'Four cards')], 'help_text': 'Choose how to group the cards'}), 1: ('wagtail.blocks.BooleanBlock', (), {'required': False}), 2: ('wagtail.blocks.CharBlock', (), {'required': False}), 3: ('wagtail.blocks.ChoiceBlock', [], {'choices': [('topic', 'Topic'), ('topic-action', 'Topic Action'), ('breakout', 'Breakout')], 'help_text': 'Choose the specialized card type.', 'required': False}), 4: ('wagtail.images.blocks.ImageChooserBlock', (), {'required': False}), 5: ('wagtail.blocks.CharBlock', (), {}), 6: ('wagtail.blocks.StructBlock', [[('card_type', 3), ('icon', 2), ('image', 4), ('header', 2), ('text', 2), ('link_text', 5), ('link_url', 5)]], {}), 7: ('wagtail.blocks.ListBlock', (6,), {'default': []}), 8: ('wagtail.blocks.StructBlock', [[('group_type', 0), ('has_well', 1), ('heading', 2), ('cards', 7)]], {})}), + ), + ] diff --git a/cfgov/v1/models/home_page.py b/cfgov/v1/models/home_page.py index b534998a9c6..d8528640e0e 100644 --- a/cfgov/v1/models/home_page.py +++ b/cfgov/v1/models/home_page.py @@ -1,17 +1,40 @@ -from wagtail.admin.panels import ObjectList, TabbedInterface -from wagtail.models import Page +from wagtail.admin.panels import ( + FieldPanel, + ObjectList, + TabbedInterface, +) +from wagtail.fields import StreamField +from v1.atomic_elements import molecules, organisms from v1.models.base import CFGOVPage class HomePage(CFGOVPage): + hero = StreamField([("hero", molecules.JumboHero())], blank=True) + highlights = StreamField( + [("highlight", organisms.InfoUnitGroup())], blank=True + ) + topics = StreamField([("topic", organisms.CardBlockGroup())], blank=True) + callouts = StreamField( + [("callout", organisms.InfoUnitGroup())], blank=True + ) + breakouts = StreamField( + [("breakout", organisms.CardBlockGroup())], blank=True + ) + + template = "v1/home_page/home_page.html" + + content_panels = CFGOVPage.content_panels + [ + FieldPanel("hero"), + FieldPanel("highlights"), + FieldPanel("topics"), + FieldPanel("callouts"), + FieldPanel("breakouts"), + ] + edit_handler = TabbedInterface( [ - # This is required to support editing of the page's title field. - # HomePages have no other Wagtail-editable content fields. - ObjectList(Page.content_panels, heading="General Content"), + ObjectList(content_panels, heading="General Content"), ObjectList(CFGOVPage.settings_panels, heading="Configuration"), ] ) - - template = "v1/home_page/home_page.html" diff --git a/test.sql.gz b/test.sql.gz index 4a35bce2d08..13c080a660a 100644 Binary files a/test.sql.gz and b/test.sql.gz differ