Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
35f693b
Give heading sources a `key` so we can add data to them.
brianjhanson Apr 30, 2025
8f0f1da
Add collapsible field
brianjhanson Apr 30, 2025
285abc6
Ugly working version
brianjhanson Apr 30, 2025
c7934c9
Style adjustments
brianjhanson Apr 30, 2025
900fff4
WIP Refactor to `craft-disclosure`
brianjhanson Apr 30, 2025
ac3fada
Finish up 5.x styling
brianjhanson May 5, 2025
95f024f
Active indicator on collapsed nav items
brianjhanson May 6, 2025
02efbb1
Translations
brianjhanson May 6, 2025
d3e4820
Build
brianjhanson May 6, 2025
de644f4
Save state to cookies
brianjhanson May 6, 2025
0b14a02
Merge branch '5.8' of github.com:craftcms/cms into feature/CMS-1339-c…
brianjhanson May 6, 2025
806b9b2
Merge branch '5.8' into feature/CMS-1339-collapsible-index-headings
brandonkelly May 6, 2025
d159c23
Remove collapsible setting
brianjhanson May 8, 2025
05f20be
Remove translations
brianjhanson May 8, 2025
12888ce
Adjust spacing a bit
brianjhanson May 8, 2025
bd0aafc
Cleanup
brianjhanson May 8, 2025
060a3a4
Merge branch 'feature/CMS-1339-collapsible-index-headings' of github.…
brianjhanson May 8, 2025
8e4b6eb
Build
brianjhanson May 8, 2025
d5316e7
Don't toggle blank headings
brianjhanson May 14, 2025
702eda4
Add text for screen readers
brianjhanson May 14, 2025
60e778f
Tweak language
brianjhanson May 14, 2025
ffef73f
Increase margin for accessibility
brianjhanson May 14, 2025
2cf53a4
Add `aria-current=true` to parent `li` when a child is selected
brianjhanson May 14, 2025
6f8d3da
Build
brianjhanson May 14, 2025
2695d61
Merge branch '5.8' of github.com:craftcms/cms into feature/CMS-1339-c…
brianjhanson May 14, 2025
4f08dac
Merge branch '5.8' into feature/CMS-1339-collapsible-index-headings
brandonkelly Jun 8, 2025
ceee9b8
Merge branch '5.8' of github.com:craftcms/cms into feature/CMS-1339-c…
brianjhanson Jun 16, 2025
3e6d88b
Discord style active children
brianjhanson Jun 16, 2025
e51ecdd
Merge branch '5.8' into feature/CMS-1339-collapsible-index-headings
brandonkelly Jun 19, 2025
bb29b07
Release note
brandonkelly Jun 19, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG-WIP.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
- Improved the wording of validation errors caused by relational fields’ “Validate related [type]” settings. ([#9960](https://github.com/craftcms/cms/discussions/9960))
- URL chips within Link fields are now truncated if wider than the container, and have a “Copy URL” action. ([#17339](https://github.com/craftcms/cms/pull/17339))
- Users with “Moderate users” permission can now send activation emails. ([#17362](https://github.com/craftcms/cms/pull/17362))
- Source headings within element indexes are now collapsible. ([#17226](https://github.com/craftcms/cms/pull/17226))
- Element condition builders now show condition rules for custom fields with duplicate names. ([#17361](https://github.com/craftcms/cms/pull/17361))

### Administration
Expand Down
26 changes: 16 additions & 10 deletions src/controllers/ElementIndexSettingsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,7 @@ public function actionSaveCustomizeSourcesModalSettings(): Response
// Get the old source configs
$projectConfig = Craft::$app->getProjectConfig();
$oldSourceConfigs = $projectConfig->get(ProjectConfig::PATH_ELEMENT_SOURCES . ".$elementType") ?? [];
$oldSourceConfigs = ArrayHelper::index(array_filter($oldSourceConfigs, fn($s) => $s['type'] !== ElementSources::TYPE_HEADING), 'key');
$oldSourceConfigs = ArrayHelper::index($oldSourceConfigs, 'key');

$conditionsService = Craft::$app->getConditions();

Expand All @@ -254,22 +254,26 @@ public function actionSaveCustomizeSourcesModalSettings(): Response

// Normalize to the way it's stored in the DB
foreach ($sourceOrder as $source) {
if (isset($source['heading'])) {
$newSourceConfigs[] = [
'type' => ElementSources::TYPE_HEADING,
'heading' => $source['heading'],
];
} elseif (isset($source['key'])) {
$isCustom = str_starts_with($source['key'], 'custom:');
if (isset($source['key'])) {
$type = match (true) {
str_starts_with($source['key'], 'custom:') => ElementSources::TYPE_CUSTOM,
str_starts_with($source['key'], 'heading:') => ElementSources::TYPE_HEADING,
default => ElementSources::TYPE_NATIVE,
};

$isCustom = $type === ElementSources::TYPE_CUSTOM;
$sourceConfig = [
'type' => $isCustom ? ElementSources::TYPE_CUSTOM : ElementSources::TYPE_NATIVE,
'type' => $type,
'key' => $source['key'],
];

// Were new settings posted?
if (isset($sourceSettings[$source['key']])) {
$postedSettings = $sourceSettings[$source['key']];
$sourceConfig['tableAttributes'] = array_values(array_filter($postedSettings['tableAttributes'] ?? [])) ?: '-';

if ($type !== ElementSources::TYPE_HEADING) {
$sourceConfig['tableAttributes'] = array_values(array_filter($postedSettings['tableAttributes'] ?? [])) ?: '-';
}

if (isset($postedSettings['defaultSort'])) {
$sourceConfig['defaultSort'] = $postedSettings['defaultSort'];
Expand All @@ -292,6 +296,8 @@ public function actionSaveCustomizeSourcesModalSettings(): Response
if (isset($postedSettings['userGroups']) && $postedSettings['userGroups'] !== '*') {
$sourceConfig['userGroups'] = is_array($postedSettings['userGroups']) ? $postedSettings['userGroups'] : false;
}
} elseif ($type === ElementSources::TYPE_HEADING) {
$sourceConfig['heading'] = $postedSettings['heading'];
} elseif (isset($postedSettings['enabled'])) {
$sourceConfig['disabled'] = !$postedSettings['enabled'];
if ($sourceConfig['disabled']) {
Expand Down
52 changes: 48 additions & 4 deletions src/templates/_elements/sources.twig
Original file line number Diff line number Diff line change
Expand Up @@ -83,29 +83,73 @@

{% tag 'ul' with {
class: keyPrefix ? 'nested' : null,
id: id ?? ''
} %}
{% for source in sources %}
{% if (source.type ?? null) == 'heading' %}
{% set key = source.key ?? source.heading | kebab %}
{% if nestedUnderHeading %}
</ul>
</li>
{% endif %}
<li class="heading">
<span>{{ source.heading|t('site') }}</span>
<ul>
<div class="source-item source-item--heading">
<span class="type-heading-small">{{ source.heading|t('site') }}</span>
{# Don't toggle blank headings #}
{% if source.heading | length %}
{% embed '_includes/disclosure-toggle' with {
cookieName: 'sources-list:'~ key,
controls: key,
attributes: {
class: 'source-item__toggle'
}
} %}
{% block content %}
<span class="cp-icon puny">
{{ iconSvg('angle-down') }}
</span>
<span class="visually-hidden">{{ 'Expand {heading} sources' | t('app', {
heading: source.heading
}) }}</span>
{% endblock %}
{% endembed %}
{% endif %}
</div>
<ul id="{{ key }}">
{% set nestedUnderHeading = true %}
{% elseif (source.sites ?? null) is not same as([]) %}
{% set key = source.keyPath ?? (keyPrefix ~ source.key) %}
{% tag 'li' with {
class: [
(source.nested ?? false) ? 'collapsible' : null,
(source.disabled ?? false) ? 'hidden' : null,
]|filter,
} %}
{{ _self.sourceLink(key, source, isTopLevel, elementType, sortOptions ?? null, baseSortOptions ?? null, tableColumns ?? null, defaultTableColumns ?? null, viewModes ?? null) }}
<div class="source-item source-item--link">
{{ _self.sourceLink(key, source, isTopLevel, elementType, sortOptions ?? null, baseSortOptions ?? null, tableColumns ?? null, defaultTableColumns ?? null, viewModes ?? null) }}
{% if source.nested is defined and source.nested is not empty %}
{% embed '_includes/disclosure-toggle' with {
cookieName: 'sources-list:'~ key,
controls: key,
attributes: {
class: 'source-item__toggle'
}
} %}
{% block content %}
<span class="cp-icon puny">
{{ iconSvg('angle-down') }}
</span>
<span class="visually-hidden">{{ 'Expand {heading} sources' | t('app', {
heading: source.label
}) }}</span>
{% endblock %}
{% endembed %}
{% endif %}
</div>
{% if source.nested is defined and source.nested is not empty %}
<button class="toggle" aria-expanded="false" aria-label="{{ 'Show nested sources'|t('app') }}"></button>
{% include "_elements/sources" with {
keyPrefix: key ~ '/',
id: key,
sources: source.nested
} %}
{% endif %}
Expand Down
1 change: 1 addition & 0 deletions src/translations/en/app.php
Original file line number Diff line number Diff line change
Expand Up @@ -695,6 +695,7 @@
'Everything in {edition}, plus…' => 'Everything in {edition}, plus…',
'Existing {type}' => 'Existing {type}',
'Expand all blocks' => 'Expand all blocks',
'Expand {heading} sources' => 'Expand {heading} sources',
'Expand' => 'Expand',
'Expanded' => 'Expanded',
'Expired' => 'Expired',
Expand Down
2 changes: 1 addition & 1 deletion src/web/assets/cp/dist/cp.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/web/assets/cp/dist/cp.js.map

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/web/assets/cp/dist/css/cp.css

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/web/assets/cp/dist/css/cp.css.map

Large diffs are not rendered by default.

81 changes: 67 additions & 14 deletions src/web/assets/cp/src/css/_cp.scss
Original file line number Diff line number Diff line change
Expand Up @@ -1624,6 +1624,46 @@ li.breadcrumb-toggle-wrapper {
}
}

%type-heading-small {
text-transform: uppercase;
color: var(--medium-text-color);
font-size: 11px;
font-weight: bold;
}

.source-item {
--source-item-toggle-size: calc(20rem / 16);
position: relative;

// Toggle button
.source-item__toggle {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
position: absolute;
inset-inline-start: 0;
inset-block-start: 50%;
transform: translateY(-50%);
width: var(--source-item-toggle-size);
height: var(--source-item-toggle-size);
z-index: 1;
color: inherit;
}

&:has(.sel) {
--icon-color: var(--white);
}
}

.source-item--heading {
margin-block-end: var(--s);
}

.source-item--heading {
@extend %type-heading-small;
}

/* Sidebar */
.sidebar {
width: $sidebarWidth;
Expand All @@ -1639,38 +1679,38 @@ li.breadcrumb-toggle-wrapper {
.heading {
position: relative;
margin-block: 0;
margin-inline: var(--xl);
line-height: 1.2;

&:not(:first-child) {
margin-block-start: var(--m);
}

.type-heading-small,
& > span {
// Backwards compatibility
@extend %type-heading-small;
display: inline-block;
position: relative;
z-index: 1;
padding-block: 0;
padding-inline: 5px;
padding-inline: var(--xl) var(--m);
margin-block: 0;
margin-inline: -5px;
text-transform: uppercase;
color: var(--medium-text-color);
font-size: 11px;
font-weight: bold;
}

& > ul {
margin-block: 2px;
margin-inline: calc(var(--xl) * -1);
margin-block: var(--xs) 2px;
}
}

@mixin nav-level($level) {
& > a {
& > a,
.type-heading-small,
[data-source-item] {
padding-inline-start: 24px + 14 * $level;
}

& > .toggle {
& > .toggle,
[aria-controls] {
inset-inline-start: calc(var(--m) * #{$level});
}
}
Expand Down Expand Up @@ -1705,7 +1745,8 @@ li.breadcrumb-toggle-wrapper {
li {
position: relative;

&:not(.has-subnav) > a {
&:not(.has-subnav) > a,
&:not(.has-subnav) [data-source-item] {
&:not(.sel):hover {
text-decoration: none;
}
Expand Down Expand Up @@ -1737,7 +1778,7 @@ li.breadcrumb-toggle-wrapper {
flex-direction: row;
align-items: center;
padding-block: 7px;
padding-inline: var(--xl);
padding-inline: var(--xl) var(--m);
min-height: var(--xl);
box-sizing: border-box;
color: var(--text-color);
Expand Down Expand Up @@ -1818,14 +1859,26 @@ li.breadcrumb-toggle-wrapper {
justify-content: center;
}

ul {
ul:not([data-state]) {
display: none;
}

ul[data-state='collapsed'] {
display: none;
}

ul[data-state='expanded'] {
display: block;
}

&.heading,
&.expanded {
& > ul {
display: block;

&[data-state='collapsed'] a:not(.sel) {
display: none;
}
}
}
}
Expand Down
9 changes: 9 additions & 0 deletions src/web/assets/cp/src/css/_craft-disclosure.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
craft-disclosure {
[aria-expanded='true'] .cp-icon {
transform: rotate(180deg);
}

[aria-expanded='false'] .cp-icon {
transform: rotate(0deg);
}
}
1 change: 1 addition & 0 deletions src/web/assets/cp/src/css/_main.scss
Original file line number Diff line number Diff line change
Expand Up @@ -1109,6 +1109,7 @@ a.fieldtoggle::before {
}

.toggle.expanded::before,
.toggle[aria-expanded='true']::before,
button.fieldtoggle:not(.lightswitch).expanded::before,
a.fieldtoggle.expanded::before,
.sidebar nav li.expanded > .toggle::before,
Expand Down
1 change: 1 addition & 0 deletions src/web/assets/cp/src/css/craft.scss
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
@use 'cp';
@use 'range';
@use 'global-sidebar';
@use 'craft-disclosure';
@use 'craft-spinner';
@use 'craft-tooltip';
@use 'preview';
Expand Down
19 changes: 14 additions & 5 deletions src/web/assets/cp/src/js/BaseElementIndex.js
Original file line number Diff line number Diff line change
Expand Up @@ -1791,7 +1791,9 @@ Craft.BaseElementIndex = Garnish.Base.extend(
: Craft.t('app', 'Descending');
const sortLabel = this.getSortLabel(attribute);

if (!attribute && !direction && !sortLabel) return;
if (!attribute && !direction && !sortLabel) {
return;
}

return Craft.t('app', '{name} sorted by {attribute}, {direction}', {
name: this.getSourceLabel(),
Expand All @@ -1801,7 +1803,9 @@ Craft.BaseElementIndex = Garnish.Base.extend(
},

updateLiveRegion: function (message) {
if (!message) return;
if (!message) {
return;
}

this.$srStatusContainer.empty().text(message);

Expand All @@ -1810,7 +1814,9 @@ Craft.BaseElementIndex = Garnish.Base.extend(
const currentMessage = this.$srStatusContainer.text();

// Check that this is the same message and hasn't been updated since
if (message !== currentMessage) return;
if (message !== currentMessage) {
return;
}

this.$srStatusContainer.empty();
}, 5000);
Expand Down Expand Up @@ -3160,9 +3166,11 @@ Craft.BaseElementIndex = Garnish.Base.extend(
// -------------------------------------------------------------------------

_getSourcesInList: function ($list, topLevel) {
let $sources = $list.find('> li:not(.heading) > a');
let $sources = $list.find('> li:not(.heading) [data-source-item]');
if (topLevel) {
$sources = $sources.add($list.find('> li.heading > ul > li > a'));
$sources = $sources.add(
$list.find('> li.heading > ul > li [data-source-item]')
);
}
return $sources;
},
Expand Down Expand Up @@ -3965,6 +3973,7 @@ const SourceNav = Garnish.Base.extend(
$container: null,
$items: null,
$selectedItem: null,
$disclosures: null,

init: function (container, settings) {
this.$container = $(container);
Expand Down
Loading