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