Commit 764d9fb2 authored by bgm's avatar bgm Committed by Aegir user
Browse files

Remove shoreditch (was not enabled)

parent afc3298a
# Coding
## Project structure
* `base/` Where vanilla Bootstrap is located. Contains the original Bootstrap SASS partials (`scss/vendor/bootstrap/`) but also custom partials (`scss/overrides`) and mixins (`scss/mixins`) that allow using Bootstrap alongside the original CiviCRM style
* `css/` Contains the two post-processed css files, `bootstrap.css` and `custom-civicrm.css`
* `fonts/` Font files used by the theme
* `js/` Any custom javascript code that the theme relies on
* `scss/` Contains the SASS partials that make up Shoreditch. It's split into two sub-folders
* `scss/civicrm` Contains the style that the theme applies to the existing core screens, in order to make them look like as if they were also Bootstrap-based pages.
* `scss/bootstrap` Contains the custom Bootstrap theme. In here not only the base Bootstrap theme is customized, but also Bootstrap elements are augmented with new modifiers, and completely custom component are added.
## Build
If you are doing development on this extension, then you may need to build
new CSS files. This requires the toolchain for SCSS=>CSS compilation.
First of all you need [NodeJS]( Please be aware that currently Shoreditch *does not* support NodeJS v9 or higher! It is recommended that you install NodeJS LTS version **v8.9.4** (you can use [nvm]( for managing multiple node versions on the same machine.
Once you have NodeJS installed, run
npm install
Once you have the tools, you can run `npx gulp watch`. This will monitor the SCSS files and automatically recompile whenever they are changed.
# npx command ensures you run a local repository Gulp and not the global one
npx gulp watch
If you would like to just compile files without watching, simply run `npx gulp`.
npx gulp
## Guidelines for `custom-civicrm.css`
Any style changes that are aimed at making the core screens look like they are part of the Bootstrap theme, should go here.
### Disable stylelint rules
Any .scss file under the `civicrm/` folder must have this annotation at the very top
/* stylelint-disable max-nesting-depth, selector-max-compound-selectors, selector-no-qualifying-type, selector-max-id */
in order to tell [stylelint]( to be more a bit more lax when enforcing style conventions
## Guidelines for `bootstrap.css`
While `custom-civicrm.css` can be thought of as the "legacy mode" of Shoreditch, this component can be considered as the actual theme.
There are three main types of changes you might want to introduce to the theme
1. **Tweak the overall look-and-feel.** *(i.e., color palette, border radius, line height)* These are usually accomplished by working on the `overrides/_variables.scss` partial, as Bootstrap "exposes" many basic traits of the style as SASS variables.
2. **Customize a Bootstrap component** *(i.e., changing the padding of `.panel`, adding a custom variation of `.btn`)* You can do that in the `overrides/_variables.scss` file, or if Bootstrap doesn't provide a SASS variable for the change you need to implement, you can write the overriding style in the `overrides/style/_{component}.scss` partial.
3. **Add a custom component** *(i.e., wizard component)* There are some component in CiviCRM that do not have any equivalent in the Bootstrap component library, and as such they have to be written from scratch. Those components go in the `components/` folder, each in an individual partial.
### Things to keep in mind
#### Keep the exceptions to a minimum
Shoreditch is first and foremost a customized Bootstrap theme, and as such it should not deviate from the original Bootstrap as much as possible.
Any custom variable, override, modifier, or component should be considered exceptions to the basic theme and should be justified and carefully planned, as each adds complexity that over time would make the theme harder to maintain and to work on.
#### Use original Bootstrap variables as much as possible
Bootstrap comes packaged with plenty of SASS variables that covers many of the traits of the whole theme and of the individual components (like `$padding-base-vertical`, `$modal-inner-padding`, `$badge-font-weight`, etc), so always make sure to check first if the changes that you want to implement can be done so by simply tweaking a variable value.
#### Prefix the custom variables
Sometimes you might need to add custom SASS variables to the `overrides/_variable.scss` partial. In order to differentiate them from the original variables, always prefix them with `$crm-` (`$crm-badge-min-width`, `$crm-gray-lightest`, etc).
#### Match the overriding partials with the original partials
The `overriding/style` folder contains the partials that are meant to override and augment the original style, and the partials are organized so that they reflect the structure in `base/vendor/bootstrap/`.
If you need to add a partial in `overriding/style` make sure that it is matching one of the originals.
#### CSS naming convention
For custom modifiers, stick with the dasherized bootstrap convention. So `.panel-bigger`, `.badge-square`, etc
For custom components, use the [BEM]( convention, making sure to prefix the block name with `crm_`. So `.crm_custom-component`, `.crm_foo__bar--baz`, etc.
#### Add a custom component as a last resort
Adding a custom components to achieve a specific design should be considered as a last resort, when no suitable Bootstrap component had been found that could either be used as-is or being augmented with a custom modifier.
#### No ad-hoc changes
The theme is meant to be as generic as possible, and as such no site/extension specific changes and/or components should be added to it.
# Contributing
First of all, thank you for considering contributing to the Shoreditch theme!
## Things to know before you start
1. Bear in mind that the theme is currently in an **alpha stage** and at such many (even fundamental) things can be subject to change!
2. Please read the [README]( to get an overview of what the theme is and how it works.
3. The [org.civicrm.styleguide]( extension is a very useful companion to the theme, please make sure to have it installed and enabled locally when working on Shoreditch.
## Reporting a bug / Suggesting enhancements
Both bugs and enhancements are tracked as [GitHub issues](
If you want to report a bug, open an issue and apply the "bug" label to it. Please use a clear and descriptive title, provide screenshots (if it relates to styling issues) and report on what OS/browser the bug is reproducible.
If you want to suggest an enhancement, open an issue and apply the "enhancement" label to it. Please use a clear and descriptive title and provide a description of the suggested enhancement and why it would be beneficial for the theme.
## Code contributions
If you'd like to contribute to the project, please make sure to familiarize with the [coding guidelines]( and with how to appropriately [test]( your style changes before submitting your contribution!
### Git commit messages guidelines
* Use the present tense ("Customize .panel-primary" not "Customized .panel-primary")
* Use the imperative mood ("Add padding to .panel" not "Adds padding to .panel")
* First line should have a length of max 72 characters
* Reference any issues or pull requests that you think is useful to mention
### Submitting a PR
When your work is complete and you have tested it locally, you can open a PR against the `master` branch.
In the PR give a description of what you changed (and why!), and attach before & after screenshots to show the effects of your changes.
Currently you are also required, for any work done on the style, to attach a screenshot of the BackstopJS report (see [here](, showing how many tests passed, how many failed and which ones.
The PR will then undergo code review and, once the changes are accepted, it will be merged and become part of the theme's next release!
This diff is collapsed.
# org.civicrm.shoreditch (developmental)
The "Shoreditch" extension is a theme for CiviCRM based on a contemporary [flat design]( and
the [Bootstrap v3]( framework.
Please note that this extension is currrently in **alpha stage** and under **active development**. Significant elements may change.
## Supported CMSs
At the moment the theme is being developed to work only in Drupal (with default theme set to "Seven"). WordPress and Joomla are not currently supported.
## Components
The theme includes two major components:
* "`bootstrap.css`" is a build of Bootstrap based on the standard Bootstrap style-guide. It can be used with other CiviCRM extensions which satisfy the Bootstrap style-guide.
* "`custom-civicrm.css`" is an optional replacement for "civicrm.css". It uses the same visual conventions and SCSS metadata, but it applies to existing core screens.
### Using `bootstrap.css`
This extension provides the CSS for Bootstrap. Other extensions should output compliant HTML, e.g.
<div id="bootstrap-theme">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">Hello World</h3>
<div class="panel-body">
This is the Hello World example.
Note the use of `id="bootstrap-theme"`. To avoid conflicts with CMS UIs, the CSS rules are
restricted to `#bootstrap-theme`.
### Using `custom-civicrm.css`
This extension includes an optional file `custom-civicrm.css` which can replace the default
`civicrm.css`. This uses the same CSS classes as traditional CiviCRM screens, but the
look-and-feel matches the Bootstrap look-and-feel. It is implemented with the same SCSS variables
and mixins. To use it, navigate to **Administer -> System Settings -> Resource URLs** And click on
"Shoreditch" as the Custom CSS Url option.
Or from the command line:
cv api setting.create customCSSURL=$(cv url -x shoreditch/css/custom-civicrm.css --out=list)
## Contributing
Want to report a bug, suggest enhancements, or contribute to the project? Please read [here](!
# Testing
In order to make sure that your work does not introduce regression issues or unexpected changes, there are two tools that you are encouraged to use: [org.civicrm.styleguide]( and [BackstopJS](
The styleguide provides you with a page that showcases the entire base Bootstrap theme + custom modifiers in a single page, so that you can check how they look like in Shoreditch and how your changes affect the theme and its components.
To run BackstopJS on the styleguide, follows the [instructions]( included in the extension. Make sure to always use BackstopJS on the styleguide whenever you work on `bootstrap.css`.
To run BackstopJS on the existing core screens, you can temporarily use [this setup]( Of course this should be used only if you are introducing changes to `custom-civicrm.css`.
/* ========================================================================
* Bootstrap: affix.js v3.3.5
* ========================================================================
* Copyright 2011-2015 Twitter, Inc.
* Licensed under MIT (
* ======================================================================== */
+function ($) {
'use strict';
// ======================
var Affix = function (element, options) {
this.options = $.extend({}, Affix.DEFAULTS, options)
this.$target = $(
.on('', $.proxy(this.checkPosition, this))
.on('', $.proxy(this.checkPositionWithEventLoop, this))
this.$element = $(element)
this.affixed = null
this.unpin = null
this.pinnedOffset = null
Affix.VERSION = '3.3.5'
Affix.RESET = 'affix affix-top affix-bottom'
Affix.DEFAULTS = {
offset: 0,
target: window
Affix.prototype.getState = function (scrollHeight, height, offsetTop, offsetBottom) {
var scrollTop = this.$target.scrollTop()
var position = this.$element.offset()
var targetHeight = this.$target.height()
if (offsetTop != null && this.affixed == 'top') return scrollTop < offsetTop ? 'top' : false
if (this.affixed == 'bottom') {
if (offsetTop != null) return (scrollTop + this.unpin <= ? false : 'bottom'
return (scrollTop + targetHeight <= scrollHeight - offsetBottom) ? false : 'bottom'
var initializing = this.affixed == null
var colliderTop = initializing ? scrollTop :
var colliderHeight = initializing ? targetHeight : height
if (offsetTop != null && scrollTop <= offsetTop) return 'top'
if (offsetBottom != null && (colliderTop + colliderHeight >= scrollHeight - offsetBottom)) return 'bottom'
return false
Affix.prototype.getPinnedOffset = function () {
if (this.pinnedOffset) return this.pinnedOffset
var scrollTop = this.$target.scrollTop()
var position = this.$element.offset()
return (this.pinnedOffset = - scrollTop)
Affix.prototype.checkPositionWithEventLoop = function () {
setTimeout($.proxy(this.checkPosition, this), 1)
Affix.prototype.checkPosition = function () {
if (!this.$':visible')) return
var height = this.$element.height()
var offset = this.options.offset
var offsetTop =
var offsetBottom = offset.bottom
var scrollHeight = Math.max($(document).height(), $(document.body).height())
if (typeof offset != 'object') offsetBottom = offsetTop = offset
if (typeof offsetTop == 'function') offsetTop =$element)
if (typeof offsetBottom == 'function') offsetBottom = offset.bottom(this.$element)
var affix = this.getState(scrollHeight, height, offsetTop, offsetBottom)
if (this.affixed != affix) {
if (this.unpin != null) this.$element.css('top', '')
var affixType = 'affix' + (affix ? '-' + affix : '')
var e = $.Event(affixType + '.bs.affix')
if (e.isDefaultPrevented()) return
this.affixed = affix
this.unpin = affix == 'bottom' ? this.getPinnedOffset() : null
.trigger(affixType.replace('affix', 'affixed') + '.bs.affix')
if (affix == 'bottom') {
top: scrollHeight - height - offsetBottom
// =======================
function Plugin(option) {
return this.each(function () {
var $this = $(this)
var data = $'bs.affix')
var options = typeof option == 'object' && option
if (!data) $'bs.affix', (data = new Affix(this, options)))
if (typeof option == 'string') data[option]()
var old = $.fn.affix
$.fn.affix = Plugin
$.fn.affix.Constructor = Affix
// =================
$.fn.affix.noConflict = function () {
$.fn.affix = old
return this
// ==============
$(window).on('load', function () {
$('[data-spy="affix"]').each(function () {
var $spy = $(this)
var data = $
data.offset = data.offset || {}
if (data.offsetBottom != null) data.offset.bottom = data.offsetBottom
if (data.offsetTop != null) = data.offsetTop$spy, data)
/* ========================================================================
* Bootstrap: alert.js v3.3.5
* ========================================================================
* Copyright 2011-2015 Twitter, Inc.
* Licensed under MIT (
* ======================================================================== */
+function ($) {
'use strict';
// ======================
var dismiss = '[data-dismiss="alert"]'
var Alert = function (el) {
$(el).on('click', dismiss, this.close)
Alert.VERSION = '3.3.5'
Alert.prototype.close = function (e) {
var $this = $(this)
var selector = $this.attr('data-target')
if (!selector) {
selector = $this.attr('href')
selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7
var $parent = $(selector)
if (e) e.preventDefault()
if (!$parent.length) {
$parent = $this.closest('.alert')
$parent.trigger(e = $.Event(''))
if (e.isDefaultPrevented()) return
function removeElement() {
// detach from parent, fire event then clean up data
$.support.transition && $parent.hasClass('fade') ?
.one('bsTransitionEnd', removeElement)
.emulateTransitionEnd(Alert.TRANSITION_DURATION) :
// =======================
function Plugin(option) {
return this.each(function () {
var $this = $(this)
var data = $'bs.alert')
if (!data) $'bs.alert', (data = new Alert(this)))
if (typeof option == 'string') data[option].call($this)
var old = $.fn.alert
$.fn.alert = Plugin
$.fn.alert.Constructor = Alert
// =================
$.fn.alert.noConflict = function () {
$.fn.alert = old
return this
// ==============
$(document).on('', dismiss, Alert.prototype.close)
/* ========================================================================
* Bootstrap: button.js v3.3.5
* ========================================================================
* Copyright 2011-2015 Twitter, Inc.
* Licensed under MIT (
* ======================================================================== */
+function ($) {
'use strict';
// ==============================
var Button = function (element, options) {
this.$element = $(element)
this.options = $.extend({}, Button.DEFAULTS, options)
this.isLoading = false
Button.VERSION = '3.3.5'
Button.DEFAULTS = {
loadingText: 'loading...'
Button.prototype.setState = function (state) {
var d = 'disabled'
var $el = this.$element
var val = $'input') ? 'val' : 'html'
var data = $
state += 'Text'
if (data.resetText == null) $'resetText', $el[val]())
// push to event loop to allow forms to submit
setTimeout($.proxy(function () {
$el[val](data[state] == null ? this.options[state] : data[state])
if (state == 'loadingText') {
this.isLoading = true
$el.addClass(d).attr(d, d)
} else if (this.isLoading) {
this.isLoading = false
}, this), 0)
Button.prototype.toggle = function () {
var changed = true
var $parent = this.$element.closest('[data-toggle="buttons"]')
if ($parent.length) {
var $input = this.$element.find('input')
if ($input.prop('type') == 'radio') {
if ($input.prop('checked')) changed = false
} else if ($input.prop('type') == 'checkbox') {
if (($input.prop('checked')) !== this.$element.hasClass('active')) changed = false
$input.prop('checked', this.$element.hasClass('active'))
if (changed) $input.trigger('change')
} else {
this.$element.attr('aria-pressed', !this.$element.hasClass('active'))
// ========================
function Plugin(option) {
return this.each(function () {
var $this = $(this)
var data = $'bs.button')
var options = typeof option == 'object' && option
if (!data) $'bs.button', (data = new Button(this, options)))
if (option == 'toggle') data.toggle()
else if (option) data.setState(option)
var old = $.fn.button
$.fn.button = Plugin
$.fn.button.Constructor = Button
// ==================
$.fn.button.noConflict = function () {
$.fn.button = old
return this
// ===============
.on('', '[data-toggle^="button"]', function (e) {
var $btn = $(
if (!$btn.hasClass('btn')) $btn = $btn.closest('.btn')$btn, 'toggle')
if (!($('input[type="radio"]') || $('input[type="checkbox"]'))) e.preventDefault()
.on('', '[data-toggle^="button"]', function (e) {
$('.btn').toggleClass('focus', /^focus(in)?$/.test(e.type))
/* ========================================================================
* Bootstrap: carousel.js v3.3.5
* ========================================================================
* Copyright 2011-2015 Twitter, Inc.
* Licensed under MIT (
* ======================================================================== */