Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Element status labels #14968

Merged
merged 10 commits into from
May 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
7 changes: 7 additions & 0 deletions src/base/Element.php
Original file line number Diff line number Diff line change
Expand Up @@ -1403,6 +1403,10 @@ protected static function defineTableAttributes(): array
'uid' => ['label' => Craft::t('app', 'UID')],
];

if (static::hasStatuses()) {
$attributes['status'] = ['label' => Craft::t('app', 'Status')];
}

if (static::hasUris()) {
$attributes = array_merge($attributes, [
'link' => ['label' => Craft::t('app', 'Link'), 'icon' => 'world'],
Expand Down Expand Up @@ -5226,6 +5230,9 @@ protected function attributeHtml(string $attribute): string
$parent = $this->getParent();
return $parent ? Cp::elementChipHtml($parent) : '';

case 'status':
return Cp::componentStatusLabelHtml($this);

case 'link':
if (ElementHelper::isDraftOrRevision($this)) {
return '';
Expand Down
4 changes: 0 additions & 4 deletions src/controllers/ElementIndexesController.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
use craft\events\ElementActionEvent;
use craft\helpers\ArrayHelper;
use craft\helpers\Component;
use craft\helpers\Cp;
use craft\helpers\ElementHelper;
use craft\helpers\Html;
use craft\helpers\StringHelper;
Expand Down Expand Up @@ -1064,9 +1063,6 @@ public function actionElementTableHtml(): Response
}

return $this->asJson([
'elementHtml' => Cp::elementChipHtml($element, [
'context' => $this->context,
]),
'attributeHtml' => $attributeHtml,
]);
}
Expand Down
1 change: 1 addition & 0 deletions src/elements/Category.php
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,7 @@ protected static function defineTableAttributes(): array
protected static function defineDefaultTableAttributes(string $source): array
{
return [
'status',
'link',
];
}
Expand Down
2 changes: 1 addition & 1 deletion src/elements/Entry.php
Original file line number Diff line number Diff line change
Expand Up @@ -608,7 +608,7 @@ protected static function defineTableAttributes(): array
*/
protected static function defineDefaultTableAttributes(string $source): array
{
$attributes = [];
$attributes = ['status'];

if ($source === '*') {
$attributes[] = 'section';
Expand Down
1 change: 1 addition & 0 deletions src/elements/User.php
Original file line number Diff line number Diff line change
Expand Up @@ -473,6 +473,7 @@ protected static function defineTableAttributes(): array
protected static function defineDefaultTableAttributes(string $source): array
{
return [
'status',
'fullName',
'email',
'dateCreated',
Expand Down
19 changes: 19 additions & 0 deletions src/enums/Color.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,25 @@ enum Color: string
case Gray = 'gray';
case Black = 'black';

/**
* Returns the color associated with a given status name, if known.
*
* @param string $status
* @return self|null
* @since 5.2.0
*/
public static function tryFromStatus(string $status): ?self
{
return match ($status) {
'on', 'live', 'active', 'enabled', 'turquoise' => self::Teal,
'off', 'suspended', 'expired' => self::Red,
'warning' => self::Amber,
'pending' => self::Orange,
'grey' => self::Gray,
default => self::tryFrom($status),
};
}

/**
* Returns the color’s CSS `var()` property for a given shade (50, 100, 200, ... 900).
*
Expand Down
167 changes: 133 additions & 34 deletions src/helpers/Cp.php
Original file line number Diff line number Diff line change
Expand Up @@ -387,7 +387,7 @@ public static function chipHtml(Chippable $component, array $config = []): strin

if ($config['showStatus']) {
/** @var Chippable&Statusable $component */
$html .= self::componentStatusHtml($component) ?? '';
$html .= self::componentStatusIndicatorHtml($component) ?? '';
}

if ($config['showLabel']) {
Expand Down Expand Up @@ -560,6 +560,11 @@ public static function elementCardHtml(ElementInterface $element, array $config
$headingContent = self::elementLabelHtml($element, $config, $attributes, fn() => Html::encode($element->getUiLabel()));
$bodyContent = $element->getCardBodyHtml() ?? '';

$statusLabel = $element::hasStatuses() ? static::componentStatusLabelHtml($element) : null;
if ($statusLabel) {
$bodyContent .= Html::tag('div', $statusLabel, ['class' => 'flex']);
}

$thumb = $element->getThumbHtml(128);
if ($thumb === null && $element instanceof Iconic) {
$icon = $element->getIcon();
Expand All @@ -582,7 +587,6 @@ public static function elementCardHtml(ElementInterface $element, array $config
Html::endTag('div') . // .card-content
Html::beginTag('div', ['class' => 'card-actions-container']) .
Html::beginTag('div', ['class' => 'card-actions']) .
(self::componentStatusHtml($element) ?? '') .
($config['selectable'] ? self::componentCheckboxHtml(sprintf('%s-label', $config['id'])) : '') .
($config['showActionMenu'] ? self::componentActionMenu($element) : '') .
($config['sortable'] ? Html::button('', [
Expand Down Expand Up @@ -616,47 +620,160 @@ public static function elementCardHtml(ElementInterface $element, array $config
}

/**
* Renders accessible HTML for status indicators.
* Renders status indicator HTML.
*
* When the `status` is equal to "draft" the draft icon will be displayed. The attributes passed as the
* second argument should be a status definition from [[\craft\base\ElementInterface::statuses]]
*
* @param string $status Status string
* @param array|null $attributes Attributes to be passed along.
* @param array $attributes Attributes to be passed along.
* @return string|null
* @since 5.0.0
*/
public static function statusIndicatorHtml(string $status, array $attributes = null): ?string
public static function statusIndicatorHtml(string $status, array $attributes = []): ?string
{
$attributes += [
'color' => null,
'label' => ucfirst($status),
'class' => $status,
];

if ($status === 'draft') {
return Html::tag('span', '', [
'data' => ['icon' => 'draft'],
'class' => 'icon',
'role' => 'img',
'aria' => [
'label' => sprintf('%s %s', Craft::t('app', 'Status:'), Craft::t('app', 'Draft')),
'label' => sprintf('%s %s',
Craft::t('app', 'Status:'),
$attributes['label'] ?? Craft::t('app', 'Draft'),
),
],
]);
}

$color = $attributes['color'] ?? null;
if ($color instanceof Color) {
$color = $color->value;
if ($attributes['color'] instanceof Color) {
$attributes['color'] = $attributes['color']->value;
}

return Html::tag('span', '', [
$options = [
'class' => array_filter([
'status',
$status,
$color,
$attributes['class'],
$attributes['color'],
]),
];

if ($attributes['label'] !== null) {
$options['role'] = 'img';
$options['aria']['label'] = sprintf('%s %s', Craft::t('app', 'Status:'), $attributes['label']);
}

return Html::tag('span', '', $options);
}

/**
* Renders status indicator HTML for a [[Statusable]] component.
*
* @param Statusable $component
* @return string|null
* @since 5.2.0
*/
public static function componentStatusIndicatorHtml(Statusable $component): ?string
{
$status = $component->getStatus();

if ($status === 'draft') {
return self::statusIndicatorHtml('draft');
}

$statusDef = $component::statuses()[$status] ?? null;

// Just to give the `statusIndicatorHtml` clean types
if (is_string($statusDef)) {
$statusDef = ['label' => $statusDef];
}

return self::statusIndicatorHtml($status, $statusDef);
}

/**
* Renders status label HTML.
*
* When the `status` is equal to "draft" the draft icon will be displayed. The attributes passed as the
* second argument should be a status definition from [[\craft\base\ElementInterface::statuses]]
*
* @param array $config Config options
* @return string|null
* @since 5.2.0
*/
public static function statusLabelHtml(array $config = []): ?string
{
$config += [
'color' => Color::Gray->value,
'icon' => null,
'label' => null,
'indicatorClass' => null,
];

if ($config['color'] instanceof Color) {
$config['color'] = $config['color']->value;
}

if ($config['icon']) {
$html = Html::tag('span', static::iconSvg($config['icon']), [
'class' => ['cp-icon', 'puny', $config['color']],
]);
} else {
$html = static::statusIndicatorHtml($config['color'], [
'label' => null,
'class' => $config['indicatorClass'] ?? $config['color'],
]);
}

if ($config['label']) {
$html .= ' ' . Html::tag('span', Html::encode($config['label']), ['class' => 'status-label-text']);
}

return Html::tag('span', $html, [
'class' => array_filter([
'status-label',
$config['color'],
]),
'role' => 'img',
'aria' => [
'label' => sprintf('%s %s', Craft::t('app', 'Status:'), $attributes['label'] ?? ucfirst($status)),
],
]);
}

/**
* Renders status label HTML for a [[Statusable]] component.
*
* @param Statusable $component
* @return string|null
* @since 5.2.0
*/
public static function componentStatusLabelHtml(Statusable $component): ?string
{
$status = $component->getStatus();

if (!$status) {
return null;
}

$config = $component::statuses()[$status] ?? [];
if (is_string($config)) {
$config = ['label' => $config];
}
$config['color'] ??= Color::tryFromStatus($status) ?? Color::Gray;
$config['label'] ??= match ($status) {
'draft' => Craft::t('app', 'Draft'),
default => ucfirst($status),
};
$config['indicatorClass'] = match ($status) {
'pending', 'off', 'suspended', 'expired' => $status,
default => $config['color']->value,
};

return self::statusLabelHtml($config);
}

private static function baseElementAttributes(ElementInterface $element, array $config): array
{
Expand Down Expand Up @@ -708,24 +825,6 @@ private static function componentCheckboxHtml(string $labelId): string
]);
}

private static function componentStatusHtml(Statusable $component): ?string
{
$status = $component->getStatus();

if ($status === 'draft') {
return self::statusIndicatorHtml('draft');
}

$statusDef = $component::statuses()[$status] ?? null;

// Just to give the `statusIndicatorHtml` clean types
if (is_string($statusDef)) {
$statusDef = ['label' => $statusDef];
}

return self::statusIndicatorHtml($status, $statusDef);
}

private static function elementLabelHtml(ElementInterface $element, array $config, array $attributes, callable $uiLabel): string
{
$content = implode('', array_map(
Expand Down
2 changes: 2 additions & 0 deletions src/templates/_elements/tableview/elements.twig
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
{% set structureEditable = structureEditable is defined and not inlineEditing ? structureEditable : false -%}
{% set padding = craft.app.locale.getOrientation() == 'ltr' ? 'left' : 'right' -%}
{% set elementsService = craft.app.elements %}
{% set hasStatusCol = attributes|contains(0, 'status') %}

{% for element in elements %}
{% set totalDescendants = structure
Expand Down Expand Up @@ -57,6 +58,7 @@
{% endif %}
{% set chip = elementChip(element, {
context: context ?? 'index',
showStatus: not hasStatusCol,
}) %}
{% if not (showHeaderColumn ?? false) %}
{% set chip = chip|attr({class: 'hidden'}) %}
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.

Loading
Loading