Skip to content
Snippets Groups Projects
Commit bfe68a8b authored by Shelane French's avatar Shelane French
Browse files

adds cards option for Bootstrap 3

parent 17459b36
No related branches found
Tags 8.x-3.6
No related merge requests found
.card {
position: relative;
display: -ms-flexbox;
display: flex;
-ms-flex-direction: column;
flex-direction: column;
min-width: 0;
word-wrap: break-word;
background-color: #fff;
background-clip: border-box;
border: 1px solid rgba(0, 0, 0, 0.125);
border-radius: 0.25rem;
}
.card > hr {
margin-right: 0;
margin-left: 0;
}
.card > .list-group {
border-top: inherit;
border-bottom: inherit;
}
.card > .list-group:first-child {
border-top-width: 0;
border-top-left-radius: calc(0.25rem - 1px);
border-top-right-radius: calc(0.25rem - 1px);
}
.card > .list-group:last-child {
border-bottom-width: 0;
border-bottom-right-radius: calc(0.25rem - 1px);
border-bottom-left-radius: calc(0.25rem - 1px);
}
.card > .card-header + .list-group,
.card > .list-group + .card-footer {
border-top: 0;
}
.card-body {
-ms-flex: 1 1 auto;
flex: 1 1 auto;
min-height: 1px;
padding: 1.25rem;
}
.card-title {
margin-bottom: 0.75rem;
}
.card-subtitle {
margin-top: -0.375rem;
margin-bottom: 0;
}
.card-text:last-child {
margin-bottom: 0;
}
.card-link:hover {
text-decoration: none;
}
.card-link + .card-link {
margin-left: 1.25rem;
}
.card-header {
padding: 0.75rem 1.25rem;
margin-bottom: 0;
background-color: rgba(0, 0, 0, 0.03);
border-bottom: 1px solid rgba(0, 0, 0, 0.125);
}
.card-header:first-child {
border-radius: calc(0.25rem - 1px) calc(0.25rem - 1px) 0 0;
}
.card-footer {
padding: 0.75rem 1.25rem;
background-color: rgba(0, 0, 0, 0.03);
border-top: 1px solid rgba(0, 0, 0, 0.125);
}
.card-footer:last-child {
border-radius: 0 0 calc(0.25rem - 1px) calc(0.25rem - 1px);
}
.card-header-tabs {
margin-right: -0.625rem;
margin-bottom: -0.75rem;
margin-left: -0.625rem;
border-bottom: 0;
}
.card-header-pills {
margin-right: -0.625rem;
margin-left: -0.625rem;
}
.card-img-overlay {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
padding: 1.25rem;
border-radius: calc(0.25rem - 1px);
}
.card-img,
.card-img-top,
.card-img-bottom {
-ms-flex-negative: 0;
flex-shrink: 0;
width: 100%;
}
.card-img,
.card-img-top {
border-top-left-radius: calc(0.25rem - 1px);
border-top-right-radius: calc(0.25rem - 1px);
}
.card-img,
.card-img-bottom {
border-bottom-right-radius: calc(0.25rem - 1px);
border-bottom-left-radius: calc(0.25rem - 1px);
}
.card-deck .card {
margin-bottom: 15px;
}
@media (min-width: 576px) {
.card-deck {
display: -ms-flexbox;
display: flex;
-ms-flex-flow: row wrap;
flex-flow: row wrap;
margin-right: -15px;
margin-left: -15px;
}
.card-deck .card {
-ms-flex: 1 0 0%;
flex: 1 0 0%;
margin-right: 15px;
margin-bottom: 0;
margin-left: 15px;
}
}
.card-group > .card {
margin-bottom: 15px;
}
@media (min-width: 576px) {
.card-group {
display: -ms-flexbox;
display: flex;
-ms-flex-flow: row wrap;
flex-flow: row wrap;
}
.card-group > .card {
-ms-flex: 1 0 0%;
flex: 1 0 0%;
margin-bottom: 0;
}
.card-group > .card + .card {
margin-left: 0;
border-left: 0;
}
.card-group > .card:not(:last-child) {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
.card-group > .card:not(:last-child) .card-img-top,
.card-group > .card:not(:last-child) .card-header {
border-top-right-radius: 0;
}
.card-group > .card:not(:last-child) .card-img-bottom,
.card-group > .card:not(:last-child) .card-footer {
border-bottom-right-radius: 0;
}
.card-group > .card:not(:first-child) {
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}
.card-group > .card:not(:first-child) .card-img-top,
.card-group > .card:not(:first-child) .card-header {
border-top-left-radius: 0;
}
.card-group > .card:not(:first-child) .card-img-bottom,
.card-group > .card:not(:first-child) .card-footer {
border-bottom-left-radius: 0;
}
}
.card-columns .card {
margin-bottom: 0.75rem;
}
@media (min-width: 576px) {
.card-columns {
-webkit-column-count: 3;
-moz-column-count: 3;
column-count: 3;
-webkit-column-gap: 1.25rem;
-moz-column-gap: 1.25rem;
column-gap: 1.25rem;
orphans: 1;
widows: 1;
}
.card-columns .card {
display: inline-block;
width: 100%;
}
}
.accordion > .card {
overflow: hidden;
}
.accordion > .card:not(:last-of-type) {
border-bottom: 0;
border-bottom-right-radius: 0;
border-bottom-left-radius: 0;
}
.accordion > .card:not(:first-of-type) {
border-top-left-radius: 0;
border-top-right-radius: 0;
}
.accordion > .card > .card-header {
border-radius: 0;
margin-bottom: -1px;
}
<?php
namespace Drupal\views_bootstrap\Plugin\views\style;
use Drupal\Component\Utility\Html;
use Drupal\Core\Form\FormStateInterface;
use Drupal\views\Plugin\views\style\StylePluginBase;
/**
* Style plugin to render each item in an ordered or unordered list.
*
* @ingroup views_style_plugins
*
* @ViewsStyle(
* id = "views_bootstrap_cards",
* title = @Translation("Bootstrap Cards"),
* help = @Translation("Displays rows in a Bootstrap Card Group layout"),
* theme = "views_bootstrap_cards",
* theme_file = "../views_bootstrap.theme.inc",
* display_types = {"normal"}
* )
*/
class ViewsBootstrapCards extends StylePluginBase {
/**
* Does the style plugin for itself support to add fields to it's output.
*
* @var bool
*/
protected $usesFields = TRUE;
/**
* Does the style plugin allows to use style plugins.
*
* @var bool
*/
protected $usesRowPlugin = TRUE;
/**
* Return the token-replaced row or column classes for the specified result.
*
* @param int $result_index
* The delta of the result item to get custom classes for.
* @param string $type
* The type of custom grid class to return, either "card_group" or "card".
*
* @return string
* A space-delimited string of classes.
*/
public function getCustomClass($result_index, $type) {
if (isset($this->options[$type . '_class_custom'])) {
$class = $this->options[$type . '_class_custom'];
if ($this->usesFields() && $this->view->field) {
$class = strip_tags($this->tokenizeValue($class, $result_index));
}
$classes = explode(' ', $class);
foreach ($classes as &$class) {
$class = Html::cleanCssIdentifier($class);
}
return implode(' ', $classes);
}
return '';
}
/**
* Definition.
*/
protected function defineOptions() {
$options = parent::defineOptions();
$options['card_title_field'] = ['default' => NULL];
$options['card_content_field'] = ['default' => NULL];
$options['card_image_field'] = ['default' => NULL];
$options['card_group_class_custom'] = ['default' => NULL];
$options['card_class_custom'] = ['default' => NULL];
return $options;
}
/**
* Render the given style.
*/
public function buildOptionsForm(&$form, FormStateInterface $form_state) {
parent::buildOptionsForm($form, $form_state);
if (isset($form['grouping'])) {
unset($form['grouping']);
$form['card_title_field'] = [
'#type' => 'select',
'#title' => $this->t('Card title field'),
'#options' => $this->displayHandler->getFieldLabels(TRUE),
'#required' => TRUE,
'#default_value' => $this->options['card_title_field'],
'#description' => $this->t('Select the field that will be used for the card title.'),
];
$form['card_content_field'] = [
'#type' => 'select',
'#title' => $this->t('Card content field'),
'#options' => $this->displayHandler->getFieldLabels(TRUE),
'#required' => TRUE,
'#default_value' => $this->options['card_content_field'],
'#description' => $this->t('Select the field that will be used for the card content.'),
];
$form['card_image_field'] = [
'#type' => 'select',
'#title' => $this->t('Card image field'),
'#options' => $this->displayHandler->getFieldLabels(TRUE),
'#required' => TRUE,
'#default_value' => $this->options['card_image_field'],
'#description' => $this->t('Select the field that will be used for the card image.'),
];
$form['card_group_class_custom'] = [
'#title' => $this->t('Custom card group class'),
'#description' => $this->t('Additional classes to provide on the card group. Separated by a space.'),
'#type' => 'textfield',
'#default_value' => $this->options['card_group_class_custom'],
];
$form['card_class_custom'] = [
'#title' => $this->t('Custom card group class'),
'#description' => $this->t('Additional classes to provide on each card. Separated by a space.'),
'#type' => 'textfield',
'#default_value' => $this->options['card_class_custom'],
];
}
}
}
......@@ -30,6 +30,12 @@ class ViewsBootstrap {
],
'file' => 'views_bootstrap.theme.inc',
];
$hooks['views_bootstrap_cards'] = [
'preprocess functions' => [
'template_preprocess_views_bootstrap_cards',
'template_preprocess_views_view_cards',
],
];
$hooks['views_bootstrap_grid'] = [
'preprocess functions' => [
'template_preprocess_views_bootstrap_grid',
......
{#
/**
* @file views-bootstrap-cards.html.twig
* Default simple view template to display Bootstrap Cards.
*
*
* - rows: Contains a nested array of rows. Each row contains an array of
* columns.
*
* @ingroup views_templates
*/
#}
{{ attach_library('views_bootstrap/cards') }}
<div {{ attributes.addClass(classes) }}>
{% for key, row in rows -%}
<div {{ row.attributes.addClass(classes) }}>
{# Show image #}
{% if row.image %}
{{ row.image }}
{% endif %}
{% if row.title %}
<div class="card-header">{{ row.title }}</div>
{% endif %}
{% if row.content %}
<div class="card-body">
{{ row.content }}
</div>
{% endif %}
</div>
{%- endfor %}
</div>
......@@ -17,3 +17,7 @@ tabs:
- core/jquery.once
- core/drupal
- core/drupalSettings
cards:
css:
theme:
css/cards.css: {}
......@@ -24,6 +24,7 @@ function views_bootstrap_help($route_name, RouteMatchInterface $route_match) {
$output .= '<p>' . t('<a href=":views">Create a view</a> using one of the following styles:', [':views' => '/admin/structure/views/add']) . '</p>';
$output .= '<ul>';
$output .= '<li>' . t('<a href=":docs">Accordion</a>', [':docs' => 'https://www.drupal.org/docs/contributed-modules/views-bootstrap-for-bootstrap-3/accordion']) . '</li>';
$output .= '<li>' . t('<a href=":docs">Cards</a>', [':docs' => 'https://www.drupal.org/docs/contributed-modules/views-bootstrap-for-bootstrap-3/cards']) . '</li>';
$output .= '<li>' . t('<a href=":docs">Carousel</a>', [':docs' => 'https://www.drupal.org/docs/contributed-modules/views-bootstrap-for-bootstrap-3/carousel']) . '</li>';
$output .= '<li>' . t('<a href=":docs">Dropdown</a>', [':docs' => 'https://www.drupal.org/docs/contributed-modules/views-bootstrap-for-bootstrap-3/dropdown']) . '</li>';
$output .= '<li>' . t('<a href=":docs">Grid</a>', [':docs' => 'https://www.drupal.org/docs/contributed-modules/views-bootstrap-for-bootstrap-3/grid']) . '</li>';
......
......@@ -66,6 +66,49 @@ function template_preprocess_views_bootstrap_accordion(array &$vars) {
// and views-view-unformatted.html.twig
}
/**
* Prepares variables for views cards templates.
*
* Default template: views-bootstrap-cards.html.twig.
*
* @param array $vars
* An associative array containing:
* - view: A ViewExecutable object.
* - rows: The raw row data.
*/
function template_preprocess_views_bootstrap_cards(array &$vars) {
$view = $vars['view'];
$vars['id'] = ViewsBootstrap::getUniqueId($view);
$wrapper_attributes = ['class' => ['card-group']];
$classes = array_filter(explode(' ', $view->style_plugin->options['card_group_class_custom']));
foreach ($classes as &$class) {
$class = Html::cleanCssIdentifier($class);
}
if (!empty($classes)) {
$wrapper_attributes['class'] = array_merge($wrapper_attributes['class'], $classes);
}
$vars['attributes'] = new Attribute($wrapper_attributes);
// Card rows.
$image = $view->style_plugin->options['card_image_field'];
$title = $view->style_plugin->options['card_title_field'];
$content = $view->style_plugin->options['card_content_field'];
foreach ($vars['rows'] as $id => $row) {
$vars['rows'][$id] = [];
$vars['rows'][$id]['image'] = $view->style_plugin->getField($id, $image);
$vars['rows'][$id]['title'] = $view->style_plugin->getField($id, $title);
$vars['rows'][$id]['content'] = $view->style_plugin->getField($id, $content);
$row_attributes = ['class' => ['card']];
// Add custom card classes.
$row_class = array_filter(explode(' ', $view->style_plugin->getCustomClass($id, 'card')));
if (!empty($row_class)) {
$row_attributes['class'] = array_merge($row_attributes['class'], $row_class);
}
$vars['rows'][$id]['attributes'] = new Attribute($row_attributes);
}
}
/**
* Prepares variables for views carousel template.
*
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment