diff --git a/claro.info.yml b/claro.info.yml index 9dfbbea..edc5122 100644 --- a/claro.info.yml +++ b/claro.info.yml @@ -52,6 +52,8 @@ libraries-override: component: css/components/details.css: false css/components/form.css: false + css/components/tableselect.css: css/dist/components/tableselect.css + css/components/tabledrag.css: css/dist/components/tabledrag.css libraries-extend: classy/image-widget: diff --git a/claro.theme b/claro.theme index ac056e6..7409aa3 100644 --- a/claro.theme +++ b/claro.theme @@ -6,8 +6,11 @@ */ use Drupal\Component\Utility\Html; -use Drupal\Core\Link; +use Drupal\Component\Utility\UrlHelper; use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\GeneratedLink; +use Drupal\Core\Link; +use Drupal\Core\Url; use Drupal\media\MediaForm; /** @@ -112,7 +115,7 @@ function claro_preprocess_admin_block(&$variables) { * Implements template_preprocess_HOOK() for admin_block. */ function claro_preprocess_admin_block_content(&$variables) { - foreach ($variables['content'] as $id => &$item) { + foreach ($variables['content'] as &$item) { $link_attributes = $item['url']->getOption('attributes') ?: []; $link_attributes['class'][] = 'admin-item__link'; $item['url']->setOption('attributes', $link_attributes); @@ -324,3 +327,63 @@ function claro_preprocess_checkboxes(&$variables) { function claro_preprocess_radios(&$variables) { $variables['attributes']['class'][] = 'form-boolean-group'; } + +/** + * Implements template_preprocess_HOOK() for table. + * + * @todo Revisit after https://www.drupal.org/node/3025726 or + * https://www.drupal.org/node/1973418 is in. + */ +function claro_preprocess_table(&$variables) { + if (!empty($variables['header'])) { + foreach ($variables['header'] as &$header_cell) { + /* For 8.6.x and below. */ + if ($header_cell['content'] instanceof GeneratedLink) { + $dom_doc = Html::load($header_cell['content']->getGeneratedLink()); + $anchors = $dom_doc->getElementsByTagName('a'); + + if (!empty($anchors)) { + foreach ($anchors as $anchor) { + $anchor_href = $anchor->getAttribute('href'); + $parsed_url = UrlHelper::parse($anchor_href); + $query = !empty($parsed_url['query']) ? $parsed_url['query'] : []; + + if (isset($query['order']) && isset($query['sort'])) { + $header_cell['attributes']->addClass('th--sortable'); + } + } + } + } + + /* For 8.7.x and above. */ + if ($header_cell['content'] instanceof Link) { + $query = $header_cell['content']->getUrl()->getOption('query') ?: []; + + if (isset($query['order']) && isset($query['sort'])) { + $header_cell['attributes']->addClass('th--sortable'); + } + } + } + } +} + +/** + * Implements template_preprocess_HOOK() for views_view_table. + * + * @todo Revisit after https://www.drupal.org/node/3025726 or + * https://www.drupal.org/node/1973418 is in. + */ +function claro_preprocess_views_view_table(&$variables) { + if (!empty($variables['header'])) { + foreach ($variables['header'] as &$header_cell) { + if (!empty($header_cell['url'])) { + $parsed_url = UrlHelper::parse($header_cell['url']); + $query = !empty($parsed_url['query']) ? $parsed_url['query'] : []; + + if (isset($query['order']) && isset($query['sort'])) { + $header_cell['attributes']->addClass('th--sortable'); + } + } + } + } +} diff --git a/css/src/components/tabledrag.css b/css/src/components/tabledrag.css new file mode 100644 index 0000000..8f6d92f --- /dev/null +++ b/css/src/components/tabledrag.css @@ -0,0 +1,12 @@ +/** + * @file + * Visual styles for table drag. + */ + +.draggable.drag, +.draggable.drag:focus { + background-color: #fffff0; +} +.draggable.drag-previous { + background-color: #ffd; +} diff --git a/css/src/components/tables.css b/css/src/components/tables.css index e616e93..dfe3054 100644 --- a/css/src/components/tables.css +++ b/css/src/components/tables.css @@ -4,9 +4,14 @@ */ table { - width: 100%; - margin: 0 0 10px; + min-width: 100%; + margin-top: 1.5em; + margin-bottom: 1.5em; } +.sticky-header { + min-width: 0; +} + caption { text-align: left; /* LTR */ } @@ -14,101 +19,141 @@ caption { text-align: right; } th { + box-sizing: border-box; + height: 3em; + padding: 0.5em 1em; + background: var(--color-whitesmoke); + line-height: 1.25em; text-align: left; /* LTR */ - padding: 10px 12px; + position: relative; } [dir="rtl"] th { text-align: right; } -thead th { - background: #f5f5f2; - border: solid #bfbfba; - border-width: 1px 0; - color: #333; - text-transform: uppercase; + +/** + * Table sort. + */ +/* Table head cell containing sort link. */ +.th--sortable { + padding: 0 1em; } -tr { - border-bottom: 1px solid #e6e4df; - padding: 0.1em 0.6em; +/* The actual sort link. */ +.th--sortable > a { + display: block; + padding: 0.5em 1.5em 0.5em 0; /* LTR */ + color: inherit; } -thead > tr { - border-bottom: 1px solid #000; +[dir="rtl"] .th--sortable > a { + padding-right: 0; + padding-left: 1.5em; } -tbody tr:hover, -tbody tr:focus { - background: #f7fcff; +.th--sortable > a::before { + position: absolute; + top: 0; + right: 1em; + bottom: 0; + left: 1em; + z-index: 0; + display: block; + border-bottom: 0.125em solid transparent; + content: ""; } -/* See colors.css */ -tbody tr.color-warning:hover, -tbody tr.color-warning:focus { - background: #fdf8ed; +/* stylelint-disable-next-line selector-type-no-unknown */ +_:-ms-fullscreen, /* Only IE 11 */ +.th--sortable > a::before { + top: auto; + height: 100%; } -tbody tr.color-error:hover, -tbody tr.color-error:focus { - background: #fcf4f2; +.th--sortable > a::after { + position: absolute; + top: 50%; + right: 1em; + width: 0.875em; + height: 1em; + margin-top: -0.5em; + background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='8' viewBox='0 0 14 8'%3E%3Cpath d='m1.75,0.25v1.5h10.5v-1.5z m0,3v1.5h7.5v-1.5z m0,3v1.5h4.5v-1.5z' fill='%23222330'/%3E%3C/svg%3E") no-repeat 50% 50%; + background-size: contain; + opacity: 0.5; + content: ""; +} +/* stylelint-disable-next-line selector-type-no-unknown */ +_:-ms-fullscreen, /* Only IE 11 */ +.th--sortable > a::after { + position: static; + float: right; + margin-top: 0.1em; + margin-right: -1.5em; +} +[dir="rtl"] .th--sortable > a::after { + right: auto; + left: 1em; + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='8' viewBox='0 0 14 8'%3E%3Cpath d='m12.25,0.25v1.5H1.75v-1.5z m0,3v1.5h-7.5v-1.5z m0,3v1.5h-4.5v-1.5z' fill='%23222330'/%3E%3C/svg%3E"); +} +/* stylelint-disable-next-line selector-type-no-unknown */ +_:-ms-fullscreen, /* Only IE 11 */ +[dir="rtl"] .th--sortable > a::after { + float: left; + margin-right: 0; + margin-left: -1.5em; +} +/* Sort link focus/hover state. */ +.th--sortable > a:focus, +.th--sortable > a:hover { + text-decoration: none; } - -table.no-highlight tr.selected td { - background: transparent; +.th--sortable > a:focus::before, +.th--sortable > a:hover::before { + border-color: inherit; } - -td, -th { - vertical-align: middle; +.th--sortable > a:focus::after, +.th--sortable > a:hover::after { + opacity: 1; } -td { - padding: 10px 12px; - text-align: left; /* LTR */ +/* Sort link's active state. */ +.th--sortable.is-active > a { + color: var(--color-absolutezero); } -[dir="rtl"] td { - text-align: right; +.th--sortable.is-active > a::before { + border-color: var(--color-absolutezero); } -th > a { - position: relative; - display: block; +.th--sortable.is-active > a:focus::before, +.th--sortable.is-active > a:hover::before { + border-color: var(--color-text); +} +.th--sortable.is-active > a::after { + content: none; } -/* 1. Must match negative bottom padding of the parent */ -th > a:after { - content: ''; - display: block; - position: absolute; - top: 0; - bottom: -10px; /* 1. */ - left: 0; - right: 0; - border-bottom: 2px solid transparent; - -webkit-transition: all 0.1s; - transition: all 0.1s; +tr { + border-bottom: 0.0625em solid var(--color-lightgray); } -th.is-active > a { - color: #004875; +thead tr { + border: 0; } -th.is-active img { - position: absolute; - right: 0; /* LTR */ - top: 50%; +tr:hover, +tr:focus { + background: var(--color-whitesmoke); } -[dir="rtl"] th.is-active img { - right: auto; - left: 0; +tr.color-warning:hover, +tr.color-warning:focus { + background: #fdf8ed; } -th.is-active > a:after { - border-bottom-color: #004875; +tr.color-error:hover, +tr.color-error:focus { + background: #fcf4f2; } -th > a:hover, -th > a:focus, -th.is-active > a:hover, -th.is-active > a:focus { - color: #008ee6; - text-decoration: none; + +td { + padding: 0.5em 1em; + text-align: left; /* LTR */ + box-sizing: border-box; + min-height: 4em; } -th > a:hover:after, -th > a:focus:after, -th.is-active > a:hover:after, -th.is-active > a:focus:after { - border-bottom-color: #008ee6; +[dir="rtl"] td { + text-align: right; } + td .item-list ul { margin: 0; } @@ -132,21 +177,36 @@ th.select-all { margin-bottom: 1.2em; } +tfoot { + font-weight: bold; +} +tfoot tr:last-child { + border-bottom: 0; +} +tfoot tr:first-child td { + border-top: 0.0625em solid var(--color-grayblue); +} + /** - * Responsive tables. + * Responsive table cells. */ -@media screen and (max-width: 37.5em) { /* 600px */ - th.priority-low, - td.priority-low, +th.priority-low, +th.priority-medium, +td.priority-low, +td.priority-medium { + display: none; +} + +@media screen and (min-width: 38em) { th.priority-medium, td.priority-medium { - display: none; + display: table-cell; } } -@media screen and (max-width: 60em) { /* 920px */ +@media screen and (min-width: 60em) { th.priority-low, td.priority-low { - display: none; + display: table-cell; } } diff --git a/css/src/components/tableselect.css b/css/src/components/tableselect.css new file mode 100644 index 0000000..4e67d23 --- /dev/null +++ b/css/src/components/tableselect.css @@ -0,0 +1,16 @@ +/** + * @file + * Table select — replaces implementation of Classy theme. + * + * @see tableselect.js + */ + +td.checkbox, +th.checkbox { + text-align: center; +} +[dir="rtl"] td.checkbox, +[dir="rtl"] th.checkbox { + /* This is required to win over specificity of [dir="rtl"] td */ + text-align: center; +} diff --git a/css/src/components/tablesort-indicator.css b/css/src/components/tablesort-indicator.css index fa49af7..4ffa543 100644 --- a/css/src/components/tablesort-indicator.css +++ b/css/src/components/tablesort-indicator.css @@ -4,23 +4,43 @@ */ .tablesort { - float: right; /* LTR */ - margin-top: 5px; - width: 10px; - height: 10px; + margin-top: -8px; + position: absolute; + top: 50%; + right: 1em; + width: 14px; + height: 16px; + background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='8' viewBox='0 0 14 8'%3E%3Cpath d='m1.75,0.25v1.5h10.5v-1.5z m0,3v1.5h7.5v-1.5z m0,3v1.5h4.5v-1.5z' fill='%23222330'/%3E%3C/svg%3E") no-repeat 0 50%; + background-size: auto; + opacity: 0.5; } -[dir="rtl"] .tablesort { - float: left; +/* stylelint-disable-next-line selector-type-no-unknown */ +_:-ms-fullscreen, /* Only IE 11 */ +.tablesort { + position: static; + float: right; + margin-top: 0.1em; + margin-right: -1.5em; } -.tablesort--asc { - background-image: url(../../../../misc/icons/004875/twistie-down.svg); +[dir="rtl"] .tablesort { + right: auto; + left: 1em; + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='8' viewBox='0 0 14 8'%3E%3Cpath d='m12.25,0.25v1.5H1.75v-1.5z m0,3v1.5h-7.5v-1.5z m0,3v1.5h-4.5v-1.5z' fill='%23222330'/%3E%3C/svg%3E"); } -a:hover .tablesort--asc { - background-image: url(../../../../misc/icons/008ee6/twistie-down.svg); +/* stylelint-disable-next-line selector-type-no-unknown */ +_:-ms-fullscreen, /* Only IE 11 */ +[dir="rtl"] .tablesort { + margin-left: -1.5em; + margin-right: 0; + float: left; } -.tablesort--desc { - background-image: url(../../../../misc/icons/004875/twistie-up.svg); +.tablesort--asc, +[dir="rtl"] .tablesort--asc { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='16' viewBox='0 0 10 12'%3E%3Cpath d='M 5 0.43945312 L 0.71875 4.71875 L 1.78125 5.78125 L 4.25 3.3125 L 4.25 11.25 L 5.75 11.25 L 5.75 3.3125 L 8.21875 5.78125 L 9.28125 4.71875 L 5 0.43945312 z' fill='%23004adc'/%3E%3C/svg%3E"); + opacity: 1; } -a:hover .tablesort--desc { - background-image: url(../../../../misc/icons/008ee6/twistie-up.svg); +.tablesort--desc, +[dir="rtl"] .tablesort--desc { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='16' viewBox='0 0 10 12'%3E%3Cpath d='M 4.25 0.75 L 4.25 8.6875 L 1.78125 6.21875 L 0.71875 7.28125 L 5 11.560547 L 9.28125 7.28125 L 8.21875 6.21875 L 5.75 8.6875 L 5.75 0.75 L 4.25 0.75 z' fill='%23004adc'/%3E%3C/svg%3E"); + opacity: 1; } diff --git a/images/src/sort--asc.svg b/images/src/sort--asc.svg new file mode 100644 index 0000000..3e0a627 --- /dev/null +++ b/images/src/sort--asc.svg @@ -0,0 +1,9 @@ + + + diff --git a/images/src/sort--desc.svg b/images/src/sort--desc.svg new file mode 100644 index 0000000..d84a5d5 --- /dev/null +++ b/images/src/sort--desc.svg @@ -0,0 +1,9 @@ + + + diff --git a/images/src/sort--inactive--ltr.svg b/images/src/sort--inactive--ltr.svg new file mode 100644 index 0000000..f34a82b --- /dev/null +++ b/images/src/sort--inactive--ltr.svg @@ -0,0 +1,9 @@ + + + diff --git a/images/src/sort--inactive--rtl.svg b/images/src/sort--inactive--rtl.svg new file mode 100644 index 0000000..b50377f --- /dev/null +++ b/images/src/sort--inactive--rtl.svg @@ -0,0 +1,9 @@ + + + diff --git a/templates/admin/tablesort-indicator.html.twig b/templates/admin/tablesort-indicator.html.twig new file mode 100644 index 0000000..b681af3 --- /dev/null +++ b/templates/admin/tablesort-indicator.html.twig @@ -0,0 +1,28 @@ +{# +/** + * @file + * Theme override for displaying a tablesort indicator. + * + * Available variables: + * - style: Either 'asc' or 'desc', indicating the sorting direction. + * + * @todo Remove after https://www.drupal.org/node/1973418 is in. + */ +#} +{% + set classes = [ + 'tablesort', + 'tablesort--' ~ style, + ] +%} + + {% if style in ['asc', 'desc'] %} + + {% if style == 'asc' -%} + {{ 'Sort ascending'|t }} + {% else -%} + {{ 'Sort descending'|t }} + {% endif %} + + {% endif %} +