Skip to content

Commit

Permalink
Merge pull request #14968 from craftcms/feature/pt-24-redesign-status…
Browse files Browse the repository at this point in the history
…-indicator

Element status labels
  • Loading branch information
brandonkelly authored May 22, 2024
2 parents bcaf822 + db878e8 commit 1938d17
Show file tree
Hide file tree
Showing 14 changed files with 345 additions and 65 deletions.
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

0 comments on commit 1938d17

Please sign in to comment.