Publish Vulpes Preview 15 sources for desktop, Android and Tundra

Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
This commit is contained in:
OverSu 2026-09-30 23:13:03 +02:00
commit b38dea0597
25901 changed files with 9982925 additions and 369 deletions

View file

@ -0,0 +1 @@
window.COMPONENTS_BASE_URL = '/shared/elements/';

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,40 @@
/* Generated by grunt-webfont */
/* Based on https://github.com/endtwist/fontcustom/blob/master/lib/fontcustom/templates/fontcustom.css */
@font-face {
font-family: "gaia-icons";
src: url("fonts/gaia-icons.ttf") format("truetype");
font-weight: 500;
font-style: normal;
}
[data-icon]:before,
.ligature-icons {
font-family: "gaia-icons";
content: attr(data-icon);
display: inline-block;
font-weight: 500;
font-style: normal;
text-decoration: inherit;
text-transform: none;
text-rendering: optimizeLegibility;
font-size: 30px;
-webkit-font-smoothing: antialiased;
}
[data-icon]:not([data-l10n-id]):not([aria-label]):not([aria-hidden="true"]):before {
/* Highlight cases where icons are not localized and still visible to screen
reader */
background: rgba(255, 0, 0, 0.4);
}
[data-icon]:not([data-l10n-id]):not([aria-label]):not([aria-hidden="true"]):after {
background: #333;
color: #fff;
position: absolute;
width: 20rem;
border-radius: 0.5rem;
padding: 0.5rem;
content: "Icon is not localized and is still visible to screen reader. Please add data-l10n-id or aria-label for localization or use aria-hidden to hide the icon from screen reader.";
z-index: 9999;
}

View file

@ -0,0 +1,23 @@
'use strict';
(function(exports) {
var AccessibilityHelper = {
/**
* For a set of tab elements, set aria-selected attribute in accordance with
* the current selection.
* @param {Object} selectedTab a tab to select object.
* @param {Array} tabs an array of tabs.
*/
setAriaSelected: function ah_setAriaSelected(selectedTab, tabs) {
// In case tabs is a NodeList, that does not have forEach.
Array.prototype.forEach.call(tabs, function setAriaSelectedAttr(tab) {
tab.setAttribute('aria-selected',
tab === selectedTab ? 'true' : 'false');
});
}
};
exports.AccessibilityHelper = AccessibilityHelper;
})(window);

View file

@ -0,0 +1,322 @@
/* exported BluetoothHelper */
'use strict';
var BluetoothHelper = function() {
var profiles = {
'HFP': 0x111E,
'A2DP': 0x110D
};
var _bluetooth = window.navigator.mozBluetooth;
var _isReady = false;
var _callbacks = [];
var _adapter = null;
var _oldAdapter = null; // The varaible is accessed for v1 only.
var _v2 = true;
var _ready = function(callback) {
if (!callback || !_bluetooth) {
return;
}
if (_isReady) {
callback();
} else {
_callbacks.push(callback);
}
};
var _handleRequest = function(request, callback, errorcb) {
request.onsuccess = function() {
if (callback) {
callback(request.result);
}
};
request.onerror = function() {
console.log('Error handling bluetooth request');
if (errorcb) {
errorcb();
}
};
};
// run callbacks when adapter is ready
var _processCallbacks = function() {
if (_adapter) {
_isReady = true;
_callbacks.forEach(function(callback) {
callback();
});
// clean up the _callback queue
_callbacks = [];
} else {
// We can do nothing without default adapter.
console.log('BluetoothHelper(): connot get default adapter yet');
}
};
// API v2 get adapter via bluetooth
var _fetchAdapterV2 = function() {
// need time to get bluetooth adapter at first run
_bluetooth.onattributechanged = function onManagerAttributeChanged(evt) {
for (var i in evt.attrs) {
switch (evt.attrs[i]) {
case 'defaultAdapter':
console.log('defaultAdapter changed. address:',
_bluetooth.defaultAdapter.address);
_adapter = _bluetooth.defaultAdapter;
_processCallbacks();
break;
default:
break;
}
}
};
_adapter = _bluetooth.defaultAdapter;
if (_adapter) {
_processCallbacks();
}
};
// API v1 get adapter via bluetooth
var _fetchAdapter = function() {
var req = _bluetooth.getDefaultAdapter();
if (req) {
req.onsuccess = function() {
if (_adapter) {
_oldAdapter = _adapter;
}
_isReady = true;
_adapter = req.result;
// Put the callback function of onpairedstatuschanged to the new adapter
// because the new adapter won't remember those callback function which
// is registered before. In other word, we get a new adpater after
// turned on/off Bluetooth. The new adapter have no registered callback.
if (_oldAdapter && _oldAdapter.onpairedstatuschanged) {
_adapter.onpairedstatuschanged = _oldAdapter.onpairedstatuschanged;
}
_callbacks.forEach(function(callback) {
callback();
});
// clean up the _callback queue
_callbacks = [];
};
req.onerror = function() {
// We can do nothing without default adapter.
console.log('BluetoothHelper(): connot get default adapter!!!');
};
}
};
var _getAdapter = function() {
if (_v2) {
_fetchAdapterV2();
} else {
_fetchAdapter();
}
};
var _resetAdapter = function() {
if (_adapter) {
_oldAdapter = _adapter;
}
// clean up state and adapter
_isReady = false;
_adapter = null;
};
// init
if (_bluetooth) {
// detect API version
if (typeof(_bluetooth.onattributechanged) === 'undefined') {
_v2 = false;
}
if (_v2) {
_bluetooth.onadapteradded = function onAdapterAdded(evt) {
_getAdapter();
};
} else {
_bluetooth.addEventListener('enabled', _getAdapter);
_bluetooth.addEventListener('adapteradded', _getAdapter);
_bluetooth.addEventListener('disabled', _resetAdapter);
}
_getAdapter();
}
return {
profiles: profiles,
answerWaitingCall: function() {
_ready(function() {
_adapter.answerWaitingCall();
});
},
ignoreWaitingCall: function() {
_ready(function() {
_adapter.ignoreWaitingCall();
});
},
toggleCalls: function() {
_ready(function() {
_adapter.toggleCalls();
});
},
getConnectedDevicesByProfile: function(profileID, cb) {
_ready(function() {
_handleRequest(_adapter.getConnectedDevices(profileID), cb);
});
},
connectSco: function(cb) {
_ready(function() {
_handleRequest(_adapter.connectSco(), cb);
});
},
disconnectSco: function(cb) {
_ready(function() {
_handleRequest(_adapter.disconnectSco(), cb);
});
},
getPairedDevices: function(cb) {
_ready(function() {
_handleRequest(_adapter.getPairedDevices(), cb);
});
},
getAddress: function(cb) {
if (_v2) {
console.log('getAddress function is deprecated');
return;
}
_ready(function() {
var address = _adapter.address;
cb(address);
});
},
setPairingConfirmation: function(address, confirmed) {
if (_v2) {
console.log('setPairingConfirmation API is deprecated');
return;
}
_ready(function() {
_adapter.setPairingConfirmation(address, confirmed);
});
},
setPinCode: function(address, pincode) {
if (_v2) {
console.log('setPairingConfirmation API is deprecated');
return;
}
_ready(function() {
_adapter.setPinCode(address, pincode);
});
},
setPasskey: function(address, key) {
if (_v2) {
console.log('setPairingConfirmation API is deprecated');
return;
}
_ready(function() {
_adapter.setPasskey(address, key);
});
},
isScoConnected: function(cb, errorcb) {
_ready(function() {
_handleRequest(_adapter.isScoConnected(), cb, errorcb);
});
},
sendMediaMetaData: function(metadata, cb, errorcb) {
_ready(function() {
_handleRequest(_adapter.sendMediaMetaData(metadata), cb, errorcb);
});
},
sendMediaPlayStatus: function(metadata, cb, errorcb) {
_ready(function() {
_handleRequest(_adapter.sendMediaPlayStatus(metadata), cb, errorcb);
});
},
set onhfpstatuschanged(callback) {
_ready(function() {
_adapter.onhfpstatuschanged = callback;
});
},
set onscostatuschanged(callback) {
_ready(function() {
_adapter.onscostatuschanged = callback;
});
},
set ona2dpstatuschanged(callback) {
_ready(function() {
_adapter.ona2dpstatuschanged = callback;
});
},
set onrequestmediaplaystatus(callback) {
_ready(function() {
_adapter.onrequestmediaplaystatus = callback;
});
},
set onpairedstatuschanged(callback) {
if (_v2) {
console.log('onpairedstatuschanged API is deprecated');
return;
}
_ready(function() {
_adapter.onpairedstatuschanged = callback;
});
},
v2: _v2, // expose API version for app reference
// bypass the enable/disable state if works in APIv1
enable: function() {
if (_v2) {
_ready(function() {
_adapter.enable();
});
} else {
console.log('enable is not support in v1 API!');
}
},
disable: function() {
if (_v2) {
_ready(function() {
_adapter.disable();
});
} else {
console.log('disable is not support in v1 API!');
}
}
};
};

View file

@ -0,0 +1,84 @@
/* global mozIntl */
/**
* This lib relies on `l10n.js' to implement localizable date/time strings.
*
* The proposed `DownloadFormatter' object provides features for formatting
* the data retrieved from the new API for Downloads, taking into account
* the structure defined by the API itself.
*/
(function(exports) {
'use strict';
var NUMBER_OF_DECIMALS = 2;
var BYTE_SCALE = ['B', 'KB', 'MB', 'GB', 'TB'];
function _getFormattedSize(bytes) {
if (bytes === undefined || isNaN(bytes)) {
return Promise.resolve(null);
}
var index = 0;
while (bytes >= 1024 && index < BYTE_SCALE.length) {
bytes /= 1024;
++index;
}
return document.l10n.
formatValue('byteUnit-' + BYTE_SCALE[index]).then(unit => {
return document.l10n.formatValue('fileSize', {
size: bytes.toFixed(NUMBER_OF_DECIMALS),
unit: unit
});
});
}
function _calcPercentage(currently, total) {
if (total === 0) {
return 0;
}
return parseInt((100 * currently) / total);
}
var DownloadFormatter = {
getFormattedSize: function(bytes) {
return _getFormattedSize(bytes);
},
getPercentage: function(download) {
return _calcPercentage(download.currentBytes, download.totalBytes);
},
getFileName: function(download) {
return download.path.split('/').pop(); // filename.ext
},
getTotalSize: function(download) {
var bytes = download.totalBytes;
return _getFormattedSize(bytes);
},
getDownloadedSize: function(download) {
var bytes = download.currentBytes;
return _getFormattedSize(bytes);
},
getDate: function(download) {
var date;
try {
date = download.startTime;
} catch (ex) {
date = new Date();
console.error(ex);
}
var formatter = mozIntl._gaia.RelativeDate(navigator.languages);
return formatter.format(date);
},
getUUID: function(download) {
return download.id || this.getFileName(download);
}
};
exports.DownloadFormatter = DownloadFormatter;
}(this));

View file

@ -0,0 +1,685 @@
'use strict';
/* exported DownloadHelper */
/* global DownloadFormatter */
/* global DownloadStore */
/* global DownloadUI */
/* global LazyLoader */
/* global MimeMapper */
/* global MozActivity */
/* global SettingsListener */
/*
* DownloadHelper.js: Perform some utility functions with DOMDownload
* objects.
*
* - You have to include in you HTML:
*
* <!-- <script src="shared/js/mime_mapper.js"></script> -->
* <!-- <script src="shared/js/download/download_store.js"></script> -->
* <script defer src="shared/js/lazy_loader.js"></script>
*
* - How to use this component.
*
* For launching a download
*
* var req = DownloadHelper.open(download);
*
* req.onsuccess = function req_onsuccess() {
* alert('The download was opened so we can remove the notification');
* }
*
* req.onerror = function req_onerror() {
* alert('Something wrong!: ' + req.error.message);
* }
*
*/
var DownloadHelper = (function() {
// Exception code constants
var CODE = {
FILE_NOT_FOUND: 'FILE_NOT_FOUND',
DEVICE_STORAGE: 'DEVICE_STORAGE',
MIME_TYPE_NOT_SUPPORTED: 'MIME_TYPE_NOT_SUPPORTED',
INVALID_STATE: 'INVALID_STATE',
NO_SDCARD: 'NO_SDCARD',
NO_PROVIDER: 'NO_PROVIDER',
UNMOUNTED_SDCARD: 'UNMOUNTED_SDCARD'
};
/*
* Request auxiliary object to support asynchronous calls
*/
var Request = function() {
this.done = function(result) {
if (typeof this.onsuccess === 'function') {
this.result = result;
window.setTimeout(function() {
this.onsuccess({
target: this
});
}.bind(this), 0);
}
};
this.failed = function(error) {
if (typeof this.onerror === 'function') {
this.error = error;
window.setTimeout(function() {
this.onerror({
target: this
});
}.bind(this), 0);
}
};
};
// Storage name by default
var STORAGE_NAME_BY_DEFAULT = 'sdcard';
// Storage name settings key
var STORAGE_NAME_KEY = 'device.storage.writable.name';
// Current storage name
var storageName = STORAGE_NAME_BY_DEFAULT;
LazyLoader.load('shared/js/settings_listener.js', function settingsLoaded() {
SettingsListener.observe(STORAGE_NAME_KEY, STORAGE_NAME_BY_DEFAULT,
function setStorageName(evt) {
var settingValue = evt.settingValue;
if (settingValue) {
storageName = settingValue;
}
}
);
});
/*
* Error auxiliary method
*/
function sendError(req, message, code) {
req.failed({
message: message,
code: code
});
console.error(message);
}
/*
* Returns a blob from the sdcard
*
* @param{Object} The download object
*
*/
function getBlob(download) {
var req = new Request();
var storage = getVolume(download.storageName);
var storeAvailableReq = storage.available();
storeAvailableReq.onsuccess = function available_onsuccess(e) {
var path = download.storagePath;
switch (storeAvailableReq.result) {
case 'unavailable':
sendError(req, ' Could not open the file: ' + path + ' from ' +
download.storageName, CODE.NO_SDCARD);
break;
case 'shared':
sendError(req, ' Could not open the file: ' + path + ' from ' +
download.storageName, CODE.UNMOUNTED_SDCARD);
break;
default:
try {
var storeGetReq = storage.get(path);
storeGetReq.onsuccess = function store_onsuccess() {
req.done(storeGetReq.result);
};
storeGetReq.onerror = function store_onerror() {
sendError(req, storeGetReq.error.name +
' Could not open the file: ' + path + ' from ' +
download.storageName, CODE.FILE_NOT_FOUND);
};
} catch (ex) {
sendError(req, 'Error getting the file ' + path + ' from ' +
download.storageName, CODE.DEVICE_STORAGE);
}
}
};
storeAvailableReq.onerror = function available_onerror() {
sendError(req, 'Error getting storage state ', CODE.DEVICE_STORAGE);
};
return req;
}
/*
* Base action that implements the commom logic for actions based on the
* MozActivity API
*
* @param{Object} Configuration parameters
*/
var Action = function Action(params) {
this.req = params.request;
this.name = params.actionType.activityName;
};
Action.prototype = {
/*
* It performs the action based on MozActivity
*/
run: function a_run() {
var activity = new MozActivity({
name: this.name,
data: this.data
});
activity.onsuccess = this._onsuccess.bind(this);
activity.onerror = this._onerror.bind(this);
},
/*
* This method implements the generic onsuccess callback
*/
_onsuccess: function a_onsuccess() {
this.req.done();
},
/*
* This method implements the generic onerror callback
*/
_onerror: function a_onerror(evt) {
this.req.failed({
message: evt.target.error.name
});
}
};
/*
* This action opens downloads extending the Action Object
*
* @param{Object} Configuration parameters
*/
var OpenAction = function OpenAction(params) {
Action.call(this, params);
this.data = {
url: params.download.path,
filename: DownloadFormatter.getFileName(params.download),
type: params.type,
blob: params.blob
};
};
OpenAction.prototype = {
__proto__: Action.prototype
};
/*
* This action shares downloads extending the Action Object
*
* @param{Object} Configuration parameters
*/
var ShareAction = function ShareAction(params) {
Action.call(this, params);
this.data = {
// 'share' activities do not work with specific mime types
type: params.type.split('/')[0] + '/*',
blobs: [params.blob],
filenames: [DownloadFormatter.getFileName(params.download)]
};
};
ShareAction.prototype = {
__proto__: Action.prototype,
/*
* It overrides the generic onerror callback for another more suitable
*/
_onerror: function sa_onerror(evt) {
if (evt.target.error.name !== 'NO_PROVIDER') {
return;
}
sendError(this.req, 'No provider to share file', CODE.NO_PROVIDER);
}
};
/*
* This action gets the info for a download
*
* @param{Object} Configuration parameters
*/
var InfoAction = function InfoAction(params) {
Action.call(this, params);
this.data = {
name: DownloadFormatter.getFileName(params.download),
type: params.type,
blob: params.blob,
size: params.download.totalBytes,
path: params.download.path
};
};
InfoAction.prototype = {
__proto__: Action.prototype,
/*
* It overrides the generic run method
*/
run: function ia_run() {
this.req.done(this.data);
}
};
// This is a factory that deals with different <Action> objects
var ActionsFactory = {
TYPE: {
OPEN: {
activityName: 'open',
actionClass: OpenAction
},
SHARE: {
activityName: 'share',
actionClass: ShareAction
},
INFO: {
actionClass: InfoAction
},
WALLPAPER: {
activityName: 'setwallpaper',
actionClass: ShareAction
},
RINGTONE: {
activityName: 'setringtone',
actionClass: ShareAction
}
},
create: function af_create(params) {
return new params.actionType.actionClass(params);
}
};
/*
* Returns the mime type
*
* @param{Object} It represents a DOMDownload object
*
* @returns(String) Mime type
*/
function getType(download) {
var fileName = DownloadFormatter.getFileName(download);
var type = MimeMapper.guessTypeFromFileProperties(fileName,
download.contentType);
return type;
}
/*
* This method allows third-parties to open or share downloads
*
* @param{Object} Action types: <ActionsFactory.TYPE.OPEN> or
* <ActionsFactory.TYPE.SHARE>
*
* @param{Object} It represents a DOMDownload object
*/
function runAction(actionType, download) {
var req = new Request();
window.setTimeout(function launching() {
var state = download.state;
if (state === 'succeeded' || state === 'finalized') {
LazyLoader.load(['shared/js/mime_mapper.js',
'shared/js/download/download_formatter.js'],
function loaded() {
var type = getType(download);
//
// The 'open' action will always launch an activity using the original
// content type to allow for third party applications to handle
// arbitrary types of content.
//
// The 'share' action on the other hand only works with known mime
// types at this time.
//
if (type.length === 0) {
type = download.contentType;
if (actionType !== ActionsFactory.TYPE.OPEN &&
actionType !== ActionsFactory.TYPE.INFO) {
sendError(req, 'Mime type not supported: ' + type,
CODE.MIME_TYPE_NOT_SUPPORTED);
return;
}
}
var blobReq = getBlob(download);
blobReq.onsuccess = function() {
ActionsFactory.create({
actionType: actionType,
download: download,
type: type,
blob: blobReq.result,
request: req
}).run();
};
blobReq.onerror = function() {
// Problem getting the blob from the sdcard
req.failed(blobReq.error);
};
});
return;
}
sendError(req, 'Becareful, the download is not finished!',
CODE.INVALID_STATE);
}, 0);
return req;
}
/*
* This method allows clients to remove a download, from the
* list and the phone.
*
* @param{Object} It represents a DOMDownload object
*/
function remove(download) {
var req = new Request();
var incompleteDownload = download.state !== 'succeeded';
// If is not done, use download manager to remove it,
// otherwise, deal with the datastore.
setTimeout(function() {
if (incompleteDownload) {
if (!navigator.mozDownloadManager) {
sendError(req, 'DownloadManager not present', CODE.INVALID_STATE);
} else {
// First we pause the download so that everyone knows it's being
// stopped. The Downloads API itself won't stop the download first,
// it will simply kill it.
// XXXAus: Remove when we fix bug #1090551
download.pause().then(
function() {
navigator.mozDownloadManager.remove(download).then(
function success() {
req.done(download);
},
function error() {
sendError(req,
'DownloadManager doesnt know about this download',
CODE.INVALID_STATE);
}
);
},
function() {
sendError(req,
'Failed to pause download before removal',
CODE.INVALID_STATE);
}
);
}
} else {
req.done(download);
}
}, 0);
return incompleteDownload ? req : doRemoveFromPhone(req, download);
}
/*
* Performs the proper delete of the physical file, also
* from the datastore if the download has finished.
*
* @param{Object} This is the Request object
* @param{Object} It represents a DOMDownload object
*/
function doRemoveFromPhone(deleteRequest, download) {
var req = new Request();
deleteRequest.onsuccess = function() {
var storage = getVolume(download.storageName);
var storeAvailableReq = storage.available();
storeAvailableReq.onsuccess = function available_onsuccess(e) {
var path = download.path;
switch (storeAvailableReq.result) {
case 'unavailable':
sendError(req, ' Could not delete the file: ' + path + ' from ' +
storageName, CODE.NO_SDCARD);
break;
case 'shared':
sendError(req, ' Could not delete the file: ' + path + ' from ' +
storageName, CODE.UNMOUNTED_SDCARD);
break;
default:
var storeDeleteReq = storage.delete(download.storagePath);
storeDeleteReq.onsuccess = function store_onsuccess() {
// Remove from the datastore if status is 'succeeded'
// if we find any problem with the datastore, don't send
// an error, since the physical remove already happened
if (download.state === 'succeeded') {
LazyLoader.load(['/shared/js/download/download_store.js'],
function() {
DownloadStore.remove(download);
}
);
}
req.done(storeDeleteReq.result);
};
storeDeleteReq.onerror = function store_onerror() {
sendError(req, storeDeleteReq.error.name +
' Could not remove the file: ' + download.path + ' from ' +
storageName, CODE.FILE_NOT_FOUND);
};
}
};
storeAvailableReq.onerror = function available_onerror() {
sendError(req, 'Error getting storage state ', CODE.DEVICE_STORAGE);
};
};
deleteRequest.onerror = function(error) {
sendError(req, error.message, error.code);
};
return req;
}
function handlerError(error, download, cb) {
LazyLoader.load('shared/js/download/download_ui.js', (function loaded() {
var req;
var show = DownloadUI.show;
// Canceled activites are normal and shouldn't be interpreted as errors.
// Unfortunately, this isn't reported in a standard way by our
// applications (or third party apps for that matter). This is why we
// have this lazy filter here that may need to be updated in the future
// but hopefully will just get removed.
if (error.message &&
(error.message.endsWith('canceled') ||
error.message.endsWith('cancelled'))) {
// Since this isn't actually an error, we invoke the callback with null.
cb && cb(null);
// and return early.
return;
}
switch (error.code) {
case CODE.NO_SDCARD:
case CODE.UNMOUNTED_SDCARD:
case CODE.FILE_NOT_FOUND:
case CODE.NO_PROVIDER:
req = show(DownloadUI.TYPE[error.code], download, true);
req.onconfirm = cb;
break;
case CODE.MIME_TYPE_NOT_SUPPORTED:
req = show(DownloadUI.TYPE.UNSUPPORTED_FILE_TYPE, download, true);
req.onconfirm = function tuftOnConfirm() {
showRemoveFileUI(download, cb);
};
break;
default:
req = show(DownloadUI.TYPE.FILE_OPEN_ERROR, download, true);
req.onconfirm = function tfoeOnConfirm() {
showRemoveFileUI(download, cb);
};
break;
}
// We have to remove the notification if the user cancels
req.oncancel = cb;
}));
}
function showRemoveFileUI(download, cb) {
var req = DownloadUI.show(DownloadUI.TYPE.DELETE, download, true);
req.oncancel = cb;
req.onconfirm = function doRemove() {
if (typeof cb === 'function') {
cb(download);
} else {
remove(download);
}
};
}
function getFreeSpace(cb) {
var storage = navigator.getDeviceStorage(storageName);
if (!storage) {
console.error('Cannot get free space size in sdcard');
cb(null);
return;
}
var req = storage.freeSpace();
req.onsuccess = function(e) {
cb(e.target.result);
};
req.onerror = function() {
cb(null);
};
}
/**
* Gets the storage for the download based on the volumen
* it was saved (storageName)
*/
function getVolume(volumeName) {
// Per API design, all media type return the same volumes.
// So we use 'sdcard' here for no reason.
// https://bugzilla.mozilla.org/show_bug.cgi?id=856782#c10
var volumes = navigator.getDeviceStorages('sdcard');
if (!volumeName || volumeName === '') {
return volumes[0];
}
for (var i = 0; i < volumes.length; ++i) {
if (volumes[i].storageName === volumeName) {
return volumes[i];
}
}
return volumes[0];
}
return {
/*
* This method allows clients to open a downlaod
*
* @param{Object} It represents a DOMDownload object
*/
open: function(download) {
return runAction(ActionsFactory.TYPE.OPEN, download);
},
/*
* This method allows clients to share a downlaoded file
*
* @param{Object} It represents a DOMDownload object
*/
share: function(download) {
return runAction(ActionsFactory.TYPE.SHARE, download);
},
/*
* This method allows clients to set as wallaper a downlaoded file
*
* @param{Object} It represents a DOMDownload object
*/
wallpaper: function(download) {
return runAction(ActionsFactory.TYPE.WALLPAPER, download);
},
/*
* This method allows clients to set as ringtone a downlaoded file
*
* @param{Object} It represents a DOMDownload object
*/
ringtone: function(download) {
return runAction(ActionsFactory.TYPE.RINGTONE, download);
},
/*
* This method returns information about a download
*
* @param{Object} It represents a DOMDownload object
*/
info: function(download) {
return runAction(ActionsFactory.TYPE.INFO, download);
},
/*
* Given a download, remove it from the DownloadManager
* list, and the file system.
*
* @param{Object} It represents a DOMDownload object
*/
remove: remove,
/*
* Returns exception code constants
*/
get CODE() {
return CODE;
},
/*
* This method handles different errors when users attemp to open files
*
* @param{Object} Error object
*
* @param{Object} It represents a DOMDownload object
*
* @param{Function} This function is performed when the flow is finished
*/
handlerError: handlerError,
/*
* Returns the free memory size in bytes
*
* @param{Function} This function is performed when the free memory size has
* been calculated
*/
getFreeSpace: getFreeSpace
};
}());

View file

@ -0,0 +1,451 @@
'use strict';
/* jshint moz:true */
/* global Promise */
/* global indexedDB */
/*
* DownloadStore.js: Allows to store download finished ordered by timestamp
*
* - Adding a new download:
*
* var req = DownloadStore.add(download);
*
* req.onsuccess = function() {
* console.log('Complete download stored successfully');
* };
*
* req.onerror = function(e) {
* console.error(e);
* };
*
* - Getting all downloads:
*
* var reqStore = DownloadStore.getAll();
*
* reqStore.onsuccess = function(ev) {
* ev.target.result.forEach(function(download) {
* // Do something cool with the download
* });
* };
*
* reqStore.onerror = function(e) {
* console.error(e);
* };
*
* - Removing a existing download:
*
* var req = DownloadStore.remove(download);
*
* req.onsuccess = function() {
* console.log('Deleted download successfully');
* };
*
* req.onerror = function(e) {
* console.error(e);
* };
*
* WARNING:
*
* The system app should add this to its manifest file
*
* "datastores-owned": {
* "download_store": {
* "description": "Stores download finished"
* }
* }
*
* The settings app should add this to its manifest file
*
* "datastores-access": {
* "download_store": {
* "description": "Stores download finished"
* }
* }
*
*/
/* exported DownloadStore */
var DownloadStore = (function() {
var datastore;
// Datastore name declared on the manifest.webapp
const DATASTORE_NAME = 'download_store';
var downloadListDB;
var downloadList = [];
var lastRevisionId;
const LIST_DB = 'download_list';
const LIST_STORE = 'download_list';
const LIST_KEY = 0;
const REVISION_KEY = 1;
/*
* Request auxiliary object to support asynchronous calls
*/
var Request = function() {
this.done = function(result) {
if (typeof this.onsuccess === 'function') {
this.result = result;
window.setTimeout(function() {
this.onsuccess({
target: this
});
}.bind(this), 0);
}
};
this.failed = function(error) {
if (typeof this.onerror === 'function') {
this.error = error;
window.setTimeout(function() {
this.onerror({
target: this
});
}.bind(this), 0);
}
};
};
function promisify(request) {
return new Promise(function(resolve, reject) {
request.onsuccess = (event) => {resolve(event.target.result);};
request.onerror = (event) => {reject(event.target.errorCode);};
});
}
// Indicates the initialization state
var readyState;
function init(success, fail) {
if (readyState === 'initialized') {
success();
return;
}
if (readyState === 'initializing') {
document.addEventListener('ds-initialized', function oninitalized() {
document.removeEventListener('ds-initialized', oninitalized);
success();
});
return;
}
readyState = 'initializing';
if (!navigator.getDataStores) {
var messageError = 'Data store: DataStore API is not working';
console.error(messageError);
fail({
message: messageError
});
return;
}
navigator.getDataStores(DATASTORE_NAME).then(function(ds) {
var messageError = 'Download Store: Cannot get access to the DataStore';
if (ds.length < 1) {
console.error(messageError);
throw ({
message: messageError
});
}
datastore = ds[0];
// Checking the length as the index should be there
}).then(function() {
return openDownloadListIndexedDB();
}).then(function(db) {
downloadListDB = db;
var listStore = downloadListDB.transaction(LIST_STORE, 'readonly')
.objectStore(LIST_STORE);
return Promise.all([
promisify(listStore.get(LIST_KEY))
.then((value) => {downloadList = value;}),
promisify(listStore.get(REVISION_KEY))
.then((value) => {lastRevisionId = value;})
]);
}).then(function() {
readyState = 'initialized';
// Remove the legacy index element, if it exists. See Bug 1180250.
const LEGACY_INDEX_ID = 1;
if (downloadList && downloadList.includes(LEGACY_INDEX_ID)) {
return promisify(get(LEGACY_INDEX_ID)).then((download) => {
if ('byTimestamp' in download) {
downloadList.splice(downloadList.indexOf(LEGACY_INDEX_ID), 1);
}
});
}
}).then(function() {
notifyOpenSuccess(success);
}).catch(function(e) {
console.error('Error while opening the Download Store: ', e.message);
fail(e);
});
}
function openDownloadListIndexedDB() {
return new Promise(function(resolve, reject) {
var request = indexedDB.open(LIST_DB);
request.onsuccess = function(event) {
var db = event.target.result;
resolve(db);
};
request.onerror = function(event) {
reject(event.target.errorCode);
};
request.onupgradeneeded = function(event) {
var db = event.target.result;
promisify(db.createObjectStore(LIST_STORE)).then(() => {
var listStore = db.transaction(LIST_STORE, 'readwrite')
.objectStore(LIST_STORE);
return Promise.all([
promisify(listStore.add([], LIST_KEY)),
promisify(listStore.add(0, REVISION_KEY))
]);
}).catch(reject);
};
request.onblocked = function(event) {
reject(event.target.errorCode);
};
});
}
function notifyOpenSuccess(cb) {
window.setTimeout(cb, 0);
document.dispatchEvent(new CustomEvent('ds-initialized'));
}
// These fields will be stored in our datastore
var fieldsToPropagate = ['url', 'path', 'totalBytes', 'contentType',
'startTime', 'state', 'storageName', 'storagePath'];
function cookDownload(download) {
var ret = Object.create(null);
fieldsToPropagate.forEach(function(field) {
ret[field] = download[field];
});
// This is the timestamp when the download finished
ret.finalizeTime = new Date();
return ret;
}
function defaultError(req) {
return defaultErrorCb.bind(null, req);
}
function defaultErrorCb(request, error) {
request.failed(error);
}
function defaultSuccess(req) {
return defaultSuccessCb.bind(null, req);
}
function defaultSuccessCb(request, result) {
request.done(result);
}
function updateDownloadList() {
var cursor = datastore.sync(lastRevisionId);
function cursorResolve(task) {
switch (task.operation) {
case 'done':
lastRevisionId = task.revisionId;
var listStore = downloadListDB.transaction(LIST_STORE, 'readwrite')
.objectStore(LIST_STORE);
return Promise.all([
promisify(listStore.put(downloadList, LIST_KEY)),
promisify(listStore.put(lastRevisionId, REVISION_KEY))
]);
case 'clear':
downloadList = [];
break;
case 'add':
downloadList.push(task.id);
break;
case 'update':
// This doesn't matter for us.
break;
case 'remove':
var i = downloadList.indexOf(task.id);
if (i >= 0) {
downloadList.splice(i, 1);
}
break;
}
return cursor.next().then(cursorResolve);
}
return cursor.next().then(cursorResolve);
}
function doAdd(download, req) {
var downloadCooked = cookDownload(download);
// Adding our cooked download
datastore.add(downloadCooked).then(function(id) {
// Enriched object with the id provided by the datastore
downloadCooked.id = id;
datastore.put(downloadCooked, id)
.then(function() { req.done(downloadCooked); },
defaultError(req));
}, defaultError(req));
}
function add(download) {
var req = new Request();
window.setTimeout(function() {
init(doAdd.bind(null, download, req), req.failed.bind(req));
});
return req;
}
function doGet(id, req) {
datastore.get(id).then(function(download) { req.done(download); },
defaultError(req));
}
function get(id) {
var req = new Request();
window.setTimeout(function() {
init(doGet.bind(null, id, req), req.failed.bind(req));
});
return req;
}
function doGetAll(req) {
updateDownloadList().then(function() {
datastore.get.apply(datastore, downloadList).then(defaultSuccess(req),
defaultError(req));
}, defaultError(req));
}
function getAll() {
var req = new Request();
window.setTimeout(function() {
init(doGetAll.bind(null, req), req.failed.bind(req));
});
return req;
}
function doRemove(id, req) {
// Removing the download object from datastore
datastore.remove(id).then(function(success) {
if (success) {
defaultSuccess(req);
} else {
req.failed({
message: 'The download with id: ' + id + 'does not exist'
});
}
}, defaultError(req));
}
function remove(download) {
var req = new Request();
window.setTimeout(function() {
init(doRemove.bind(null, download.id, req), req.failed.bind(req));
});
return req;
}
function doAddListener(listener) {
datastore.addEventListener('change', listener);
}
function addListener(listener) {
var req = new Request();
window.setTimeout(function() {
init(doAddListener.bind(null, listener), req.failed.bind(req));
});
return req;
}
function doRemoveListener(listener) {
datastore.removeEventListener('change', listener);
}
function removeListener(listener) {
var req = new Request();
window.setTimeout(function() {
init(doRemoveListener.bind(null, listener), req.failed.bind(req));
});
return req;
}
return {
/*
* This method returns the download with the specified id.
*/
get: get,
/*
* This method returns an array of complete downloads
*/
getAll: getAll,
/*
* It adds a new download object and returns the new datastore
* download object
*
* @param{Object} Download object provided by the API
*
*/
add: add,
/*
* It removes a download object
*
* @param{Object} The download object to be removed from the datastore
*
*/
remove: remove,
/*
* Add a listener that will receive datastore change events.
*
* @param{Function} The listener to be added.
*/
addListener: addListener,
/*
* Remove a listener previously added via addListener.
*
* @param{Function} The listener to be removed.
*/
removeListener: removeListener
};
}());

View file

@ -0,0 +1,154 @@
/* exported LazyLoader */
/* globals HtmlImports, Promise */
'use strict';
/**
* This contains a simple LazyLoader implementation
* To use:
*
* LazyLoader.load(
* ['/path/to/file.js', '/path/to/file.css', 'domNode'], callback
* );
*/
var LazyLoader = (function() {
function LazyLoader() {
this._loaded = {};
this._isLoading = {};
}
LazyLoader.prototype = {
_js: function(file, callback) {
var script = document.createElement('script');
script.src = file;
// until bug 916255 lands async is the default so
// we must disable it so scripts load in the order they where
// required.
script.async = false;
script.addEventListener('load', callback);
document.head.appendChild(script);
this._isLoading[file] = script;
},
_css: function(file, callback) {
var style = document.createElement('link');
style.type = 'text/css';
style.rel = 'stylesheet';
style.href = file;
document.head.appendChild(style);
callback();
},
_html: function(domNode, callback) {
// The next few lines are for loading html imports in DEBUG mode
if (domNode.getAttribute('is')) {
this.load(['/shared/js/html_imports.js'], function() {
HtmlImports.populate(callback);
}.bind(this));
return;
}
for (var i = 0; i < domNode.childNodes.length; i++) {
if (domNode.childNodes[i].nodeType == document.COMMENT_NODE) {
domNode.innerHTML = domNode.childNodes[i].nodeValue;
break;
}
}
window.dispatchEvent(new CustomEvent('lazyload', {
detail: domNode
}));
callback();
},
/**
* Retrieves content of JSON file.
*
* @param {String} file Path to JSON file
* @param {Boolean} mozSystem If xhr should use mozSystem permissions
* @return {Promise} A promise that resolves to the JSON content
* or null in case of invalid path. Rejects if an error occurs.
*/
getJSON: function(file, mozSystem) {
return new Promise(function(resolve, reject) {
var xhr;
if (mozSystem) {
xhr = new XMLHttpRequest({mozSystem: true});
} else {
xhr = new XMLHttpRequest();
}
xhr.open('GET', file, true);
xhr.responseType = 'json';
xhr.onerror = function(error) {
reject(error);
};
xhr.onload = function() {
if (xhr.response !== null) {
resolve(xhr.response);
} else {
reject(new Error('No valid JSON object was found (' +
xhr.status + ' ' + xhr.statusText + ')'));
}
};
xhr.send();
});
},
load: function(files, callback) {
var deferred = {};
deferred.promise = new Promise(resolve => {
deferred.resolve = resolve;
});
if (!Array.isArray(files)) {
files = [files];
}
var loadsRemaining = files.length, self = this;
function perFileCallback(file) {
if (self._isLoading[file]) {
delete self._isLoading[file];
}
self._loaded[file] = true;
if (--loadsRemaining === 0) {
deferred.resolve();
if (callback) {
callback();
}
}
}
for (var i = 0; i < files.length; i++) {
var file = files[i];
if (this._loaded[file.id || file]) {
perFileCallback(file);
} else if (this._isLoading[file]) {
this._isLoading[file].addEventListener(
'load', perFileCallback.bind(null, file));
} else {
var method, idx;
if (typeof file === 'string') {
method = file.match(/\.([^.]+)$/)[1];
idx = file;
} else {
method = 'html';
idx = file.id;
}
this['_' + method](file, perFileCallback.bind(null, idx));
}
}
return deferred.promise;
}
};
return new LazyLoader();
}());

View file

@ -0,0 +1,135 @@
/* exported MimeMapper */
'use strict';
/**
* MimeMapper helps gaia apps to decide the mapping of mimetype and extension.
* The use cases often happen when apps need to know about the exact
* mimetypes or extensions, such as to delegate the open web activity, we must
* have suitable mimetypes or extensions to request the right activity
*
* The mapping is basically created according to:
* http://en.wikipedia.org/wiki/Internet_media_type
*
* The supported formats are considered base on the deviceStorage properties:
* http://dxr.mozilla.org/mozilla-central/toolkit/content/
* devicestorage.properties
*
*/
var MimeMapper = {
// This list only contains the extensions we currently supported
// We should make it more complete for further usages
_typeToExtensionMap: {
// Image
'image/jpeg': 'jpg',
'image/png': 'png',
'image/gif': 'gif',
'image/bmp': 'bmp',
// Audio
'audio/mpeg': 'mp3',
'audio/mp4': 'm4a',
'audio/ogg': 'ogg',
'audio/webm': 'webm',
'audio/3gpp': '3gp',
'audio/amr': 'amr',
// Video
'video/mp4': 'mp4',
'video/mpeg': 'mpg',
'video/ogg': 'ogg',
'video/webm': 'webm',
'video/3gpp': '3gp',
'video/3gpp2': '3g2',
// Application
// If we want to support some types, like pdf, just add
'application/pdf': 'pdf',
'application/vcard': 'vcf',
// Text
'text/vcard': 'vcf',
'text/x-vcard': 'vcf'
},
// This list only contains the mimetypes we currently supported
// We should make it more complete for further usages
_extensionToTypeMap: {
// Image
'jpg': 'image/jpeg',
'jpeg': 'image/jpeg',
'jpe': 'image/jpeg',
'png': 'image/png',
'gif': 'image/gif',
'bmp': 'image/bmp',
// Audio
'mp3': 'audio/mpeg',
'm4a': 'audio/mp4',
'm4b': 'audio/mp4',
'm4p': 'audio/mp4',
'm4r': 'audio/mp4',
'aac': 'audio/aac',
'opus': 'audio/ogg',
'amr': 'audio/amr',
// Video
'mp4': 'video/mp4',
'mpeg': 'video/mpeg',
'mpg': 'video/mpeg',
'ogv': 'video/ogg',
'ogx': 'video/ogg',
'webm': 'video/webm',
'3gp': 'video/3gpp',
'3g2': 'video/3gpp2',
'ogg': 'video/ogg',
// Application
// If we want to support some extensions, like pdf, just add
'pdf': 'application/pdf',
// Text
'vcf': 'text/vcard'
},
_parseExtension: function(filename) {
var array = filename.split('.');
return array.length > 1 ? array.pop() : '';
},
isSupportedType: function(mimetype) {
return (mimetype in this._typeToExtensionMap);
},
isSupportedExtension: function(extension) {
return (extension in this._extensionToTypeMap);
},
isFilenameMatchesType: function(filename, mimetype) {
var extension = this._parseExtension(filename);
var guessedType = this.guessTypeFromExtension(extension);
return (guessedType == mimetype);
},
guessExtensionFromType: function(mimetype) {
return this._typeToExtensionMap[mimetype];
},
guessTypeFromExtension: function(extension) {
return this._extensionToTypeMap[extension];
},
// If mimetype is not in the supported list, we will try to
// predict the possible valid mimetype based on extension.
guessTypeFromFileProperties: function(filename, mimetype) {
var extension = this._parseExtension(filename);
var type = this.isSupportedType(mimetype) ?
mimetype : this.guessTypeFromExtension(extension);
return type || '';
},
// if mimetype is not supported, preserve the original extension
// and add the predict result as new extension.
// If both filename and mimetype are not supported, return the original
// filename.
ensureFilenameMatchesType: function(filename, mimetype) {
if (!this.isFilenameMatchesType(filename, mimetype)) {
var guessedExt = this.guessExtensionFromType(mimetype);
if (guessedExt) {
filename += '.' + guessedExt;
}
}
return filename;
}
};

View file

@ -0,0 +1,76 @@
/* exported SettingsListener */
'use strict';
var SettingsListener = {
/* lock stores here */
_lock: null,
/* keep record of observers in order to remove them in the future */
_observers: [],
/**
* getSettingsLock: create a lock or retrieve one that we saved.
* mozSettings.createLock() is expensive and lock should be reused
* whenever possible.
*/
getSettingsLock: function sl_getSettingsLock() {
// If there is a lock present we return that
if (this._lock && !this._lock.closed) {
return this._lock;
}
// If there isn't we return one.
var settings = window.navigator.mozSettings;
return (this._lock = settings.createLock());
},
observe: function sl_observe(name, defaultValue, callback) {
var settings = window.navigator.mozSettings;
if (!settings) {
window.setTimeout(function() { callback(defaultValue); });
return;
}
var req;
try {
req = this.getSettingsLock().get(name);
} catch (e) {
// It is possible (but rare) for getSettingsLock() to return
// a SettingsLock object that is no longer valid.
// Until https://bugzilla.mozilla.org/show_bug.cgi?id=793239
// is fixed, we just catch the resulting exception and try
// again with a fresh lock
console.warn('Stale lock in settings_listener.js.',
'See https://bugzilla.mozilla.org/show_bug.cgi?id=793239');
this._lock = null;
req = this.getSettingsLock().get(name);
}
req.addEventListener('success', (function onsuccess() {
callback(typeof(req.result[name]) != 'undefined' ?
req.result[name] : defaultValue);
}));
var settingChanged = function settingChanged(evt) {
callback(evt.settingValue);
};
settings.addObserver(name, settingChanged);
this._observers.push({
name: name,
callback: callback,
observer: settingChanged
});
},
unobserve: function sl_unobserve(name, callback) {
var settings = window.navigator.mozSettings;
var that = this;
this._observers.forEach(function(value, index) {
if (value.name === name && value.callback === callback) {
settings.removeObserver(name, value.observer);
that._observers.splice(index, 1);
}
});
}
};

View file

@ -0,0 +1,33 @@
/* exported SettingsURL */
'use strict';
// This library helps with managing settings that can either send back
// string uris or blobs that need to be turned into blob: uris.
// Usage is as follows:
// var blobURL = new SettingsURL();
// element.src = blobURL.set(settingValue);
function SettingsURL() {
this._url = null;
this._isBlob = false;
}
SettingsURL.prototype = {
set: function(value) {
if (this._isBlob) {
window.URL.revokeObjectURL(this._url);
}
if (value instanceof Blob) {
this._isBlob = true;
this._url = window.URL.createObjectURL(value);
} else {
this._isBlob = false;
this._url = value;
}
return this._url;
},
get: function() {
return this._url;
}
};

View file

@ -0,0 +1,428 @@
/* ----------------------------------
* Buttons
* ---------------------------------- */
.button::-moz-focus-inner,
.bb-button::-moz-focus-inner,
button::-moz-focus-inner {
border: none;
outline: none;
}
button,
.bb-button,
.button {
font-family: sans-serif;
font-style: italic;
width: 100%;
height: 4rem;
margin: 0 0 1rem;
padding: 0 1.2rem;
box-sizing: border-box;
display: inline-block;
vertical-align: middle;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
background: #d8d8d8;
border: none;
border-radius: 2rem;
font-weight: normal;
font-size: 1.6rem;
line-height: 4rem;
color: #333;
text-align: center;
text-decoration: none;
outline: none;
}
/* Recommend */
button.recommend,
.bb-button.recommend {
background-color: #00caf2;
color: #fff;
}
/* Danger */
button.danger,
.danger.bb-button {
background-color: #e51e1e;
color: #fff;
}
/* Pressed */
button:active,
.bb-button:active,
.button:active {
background-color: #b2f2ff;
color: #fff;
}
/* Icons */
button[data-icon],
.bb-button[data-icon],
.button[data-icon] {
position: relative;
}
button[data-icon]:before,
.bb-button[data-icon]:before,
.button[data-icon]:before {
position: absolute;
top: 50%;
margin-top: -1.5rem;
offset-inline-end: 1.5rem;
line-height: 3rem;
}
button[data-icon]:active:before,
.bb-button[data-icon]:active:before,
.button[data-icon]:active:before {
color: #fff;
}
/* Pressed with dark skin */
.skin-dark button:active,
.skin-dark .bb-button:active,
.skin-dark .button:active {
background-color: #00aacc;
}
/* Disabled */
button[disabled],
.bb-button[aria-disabled="true"],
.button[aria-disabled="true"] {
background-color: rgba(0,0,0,0.05);
color: #c7c7c7;
pointer-events: none;
}
button[disabled].recommend,
.bb-button[aria-disabled="true"].recommend,
.button[aria-disabled="true"].recommend {
background-color: #97cbd5;
color: rgba(255,255,255,0.5);
}
button[disabled].danger,
.bb-button[aria-disabled="true"].danger,
.button[aria-disabled="true"].danger {
background-color: #eb7474;
color: rgba(255,255,255,0.5);
}
/* Disabled with dark background */
.skin-dark button[disabled],
.skin-dark .button[aria-disabled="true"],
.skin-dark .bb-button[aria-disabled="true"] {
background-color: #565656;
color: rgba(255,255,255,0.4);
}
.skin-dark button[disabled].recommend,
.skin-dark .button[aria-disabled="true"].recommend,
.skin-dark .bb-button[aria-disabled="true"].recommend {
background-color: #006579;
}
.skin-dark button[disabled].danger,
.skin-dark .button[aria-disabled="true"].danger,
.skin-dark .bb-button[aria-disabled="true"].danger {
background-color: #730f0f;
}
button[disabled]::-moz-selection,
.button[aria-disabled="true"]::-moz-selection,
.bb-button[aria-disabled="true"]::-moz-selection {
-moz-user-select: none;
}
/* ----------------------------------
* Buttons inside lists
* ---------------------------------- */
li button,
li .bb-button,
li .button {
position: relative;
border: 0.1rem solid #c7c7c7;
background: rgba(0,0,0,0.05);
margin: 0 0 1rem;
overflow: hidden;
border-radius: 0;
display: flex;
height: auto;
min-height: 4rem;
text-overflow: initial;
white-space: normal;
line-height: 2rem;
padding: 1rem 1.2rem;
}
html[dir="ltr"] li button,
html[dir="ltr"] li .bb-button,
html[dir="ltr"] li .button {
text-align: left;
}
html[dir="rtl"] li button,
html[dir="rtl"] li .bb-button,
html[dir="rtl"] li .button {
text-align: right;
}
/* Press */
li .bb-button:active:after,
li .button:active:after,
li button:active:after {
opacity: 0;
}
/* Disabled */
li button:disabled,
li .bb-button[aria-disabled="true"],
li .button[aria-disabled="true"] {
opacity: 0.5;
}
/* Icons */
li button.icon,
li .bb-button.icon,
li .button.icon {
padding-inline-end: 4rem;
}
li button.icon:before,
li .bb-button.icon:before,
li .button.icon:before {
content: "";
width: 3rem;
height: 3rem;
position: absolute;
top: 50%;
offset-inline-end: 0.5rem;
margin-top: -1.5rem;
background: transparent no-repeat center center / 100% auto;
pointer-events: none;
}
li button[data-icon]:before,
li .bb-button[data-icon]:before,
li .button[data-icon]:before {
offset-inline-end: 1rem;
}
li button.icon-view:active,
li .bb-button.icon-view:active,
li .button.icon-view:active {
background-color: #b2f2ff;
}
li button[data-icon="expand"]:before,
li .bb-button[data-icon="expand"]:before,
li .button[data-icon="expand"]:before {
color: #00aac5;
top: unset;
bottom: 0;
}
li button[data-icon="expand"][disabled]:before,
li .bb-button[data-icon="expand"][aria-disabled="true"]:before,
li .button[data-icon="expand"][aria-disabled="true"]:before {
color: #A9A9A9;
}
li button[data-icon="forward"]:before,
li .bb-button[data-icon="forward"]:before,
li .button[data-icon="forward"]:before {
color: #686f72;
}
/* Getting more space for text, as icon is shorter */
li button.icon-dialog,
li .bb-button.icon-dialog,
li .button.icon-dialog {
padding-inline-end: 3rem;
}
li button.icon-dialog:before,
li .bb-button.icon-dialog:before,
li .button.icon-dialog:before {
width: 0;
height: 0;
top: auto;
offset-inline-end: 0.8rem;
bottom: 0.5rem;
border-top: 0.7rem solid transparent;
border-bottom: 0.7rem solid transparent;
border-inline-start: 0.7rem solid #00aac5;
z-index: 1;
}
html[dir="ltr"] li button.icon-dialog:before,
html[dir="ltr"] li .bb-button.icon-dialog:before,
html[dir="ltr"] li .button.icon-dialog:before {
transform: rotate(45deg);
}
html[dir="rtl"] li button.icon-dialog:before,
html[dir="rtl"] li .bb-button.icon-dialog:before,
html[dir="rtl"] li .button.icon-dialog:before {
transform: rotate(-45deg);
}
li button.icon-dialog:disabled:before,
li .bb-button[aria-disabled="true"].icon-dialog:before,
li .button[aria-disabled="true"].icon-dialog:before {
border-inline-start: 0.7rem solid #a9a9a9;
}
/* ----------------------------------
* Theme: Dark
* ---------------------------------- */
.skin-dark li button,
.skin-dark li .bb-button,
.skin-dark li .button {
background-color: #454545;
border: none;
color: #fff;
font-weight: normal;
}
.skin-dark li button:active,
.skin-dark li .button:active,
.skin-dark li .bb-button:active {
background-color: #00aacc;
}
.skin-dark li button.icon-view:active,
.skin-dark li .button.icon-view:active,
.skin-dark li .bb-button.icon-view:active {
background-color: #00aacc;
}
.skin-dark li button.icon-dialog:active:before,
.skin-dark li .bb-button.icon-dialog:active:before,
.skin-dark li .button.icon-dialog:active:before {
border-inline-start: 0.7rem solid #b2f2ff;
}
.skin-dark li button:disabled,
.skin-dark li .bb-button[aria-disabled="true"],
.skin-dark li .button[aria-disabled="true"] {
background-color: #454545;
opacity: 0.5;
}
/* Inputs inside of .button */
.button input,
body[role="application"] .bb-button input,
body[role="application"] .button input {
border: 0;
background: none;
}
/* select */
select {
width: 100%;
box-sizing: border-box;
font-family: sans-serif;
font-style: italic;
font-size: 1.6rem;
color: #333;
margin: 0 0 1rem;
padding: 0;
padding-inline-start: 1.1rem;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
height: 4rem;
border: 0.1rem solid #c7c7c7;
background: rgba(0,0,0,0.05);
}
.skin-dark select {
background-color: rgba(87, 87, 87, 0.8);
border: none;
color: #fff;
}
/* remove dotted outline */
select:-moz-focusring {
color: transparent;
text-shadow: 0 0 0 #333;
}
.skin-dark select:-moz-focusring {
text-shadow: 0 0 0 #fff;
}
/* Hides dropdown arrow until bug #649849 is fixed */
.bb-button.icon select,
.button.icon select,
.bb-button[data-icon] select,
.button[data-icon] select {
position: absolute;
top: -0.6rem;
offset-inline-start: 0;
width: calc(100% + 3rem);
height: 100%;
margin: 0;
padding: 0;
padding-inline-end: 5rem;
text-indent: 0;
font-weight: 400;
font-size: 1.7rem;
color: rgba(0,0,0,0);
border: none;
background: none;
}
/**
* The select element uses the text color to determine the color of the outline.
* We put a text shadow with needed offset and no blur, so that replaces the text.
*/
html[dir="ltr"] .bb-button.icon select,
html[dir="ltr"] .button.icon select,
html[dir="ltr"] .bb-button[data-icon] select,
html[dir="ltr"] .button[data-icon] select,
html[dir="ltr"] .bb-button.icon-dialog select,
html[dir="ltr"] .button.icon-dialog select {
text-shadow: 1rem 0.5rem 0 #333;
}
html[dir="rtl"] .bb-button.icon select,
html[dir="rtl"] .button.icon select,
html[dir="rtl"] .bb-button[data-icon] select,
html[dir="rtl"] .button[data-icon] select,
html[dir="rtl"] .bb-button.icon-dialog select,
html[dir="rtl"] .button.icon-dialog select {
text-shadow: -1rem 0.5rem 0 #333;
}
.bb-button.icon select option,
.button.icon select option,
.bb-button[data-icon] select option,
.button[data-icon] select option {
color: #000;
border: 0;
padding: 0.6rem 1.3rem;
text-shadow: 0 0 0 rgba(0,0,0,0);
}
.bb-button.icon-dialog,
.button.icon-dialog {
overflow: hidden;
}
html[dir="ltr"] .skin-dark .bb-button.icon select,
html[dir="ltr"] .skin-dark .button.icon select,
html[dir="ltr"] .skin-dark .bb-button[data-icon] select,
html[dir="ltr"] .skin-dark .button[data-icon] select,
html[dir="ltr"] .skin-dark .bb-button.icon-dialog select,
html[dir="ltr"] .skin-dark .button.icon-dialog select {
text-shadow: 1rem 0.5rem 0 #fff;
}
html[dir="rtl"] .skin-dark .bb-button.icon select,
html[dir="rtl"] .skin-dark .button.icon select,
html[dir="rtl"] .skin-dark .bb-button[data-icon] select,
html[dir="rtl"] .skin-dark .button[data-icon] select,
html[dir="rtl"] .skin-dark .bb-button.icon-dialog select,
html[dir="rtl"] .skin-dark .button.icon-dialog select {
text-shadow: -1rem 0.5rem 0 #fff;
}

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="30" height="30" viewBox="0 0 30 30"><path fill="#667174" fill-opacity=".7" d="m 18.2995,19.66 -5.056,-5.057 5.056,-5.056 c -0.31,-0.516 -0.62,-1.032 -0.93,-1.34 l -5.672,5.673 v 1.652 l 5.675,5.676 c 0.31,-0.515 0.722,-0.928 0.93,-1.547 z"/></svg>

After

Width:  |  Height:  |  Size: 294 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="30" height="30" viewBox="0 0 30 30"><path fill="#667174" fill-opacity=".7" d="m 11.698,19.66 5.056,-5.057 -5.056,-5.056 c 0.31,-0.516 0.62,-1.032 0.93,-1.34 L 18.3,13.88 v 1.652 l -5.675,5.676 c -0.31,-0.515 -0.722,-0.928 -0.93,-1.547 z"/></svg>

After

Width:  |  Height:  |  Size: 292 B

View file

@ -0,0 +1,280 @@
<!DOCTYPE html>
<html dir="ltr">
<head>
<meta charset="utf-8">
<title>Buttons</title>
<link rel="stylesheet" href="../buttons.css">
<link rel="stylesheet" href="../../elements/gaia-icons/gaia-icons-embedded.css">
<link rel="stylesheet" href="../../elements/gaia-icons/bidi-helper.css">
<!--
- These <style> and <script> elements are only used for the example code,
- they are not needed for the real case.
-->
<style>
html, body {
margin: 0;
padding: 0;
font-size: 10px;
background-color: #fff;
}
label.bb-docs {
font-family: sans-serif;
font-size: 1.2rem;
margin-left: 1.5rem;
}
h2.bb-docs {
font-size: 1.8rem;
font-weight: 300;
color: #666;
margin: -0.1rem 0 0;
background-color: #f5f5f5;
padding: 0.4rem 3rem;
border: solid 0.1rem #e8e8e8;
font-family: sans-serif;
}
div.bb-docs {
padding: 1.5rem;
}
div.bb-docs.skin-organic {
background: #f4f4f4;
}
div.bb-docs.skin-dark {
background: #000;
}
ul {
padding: 0;
margin: 0;
list-style: none;
}
body[role="application"] > [role="region"] {
height: 100%;
position: relative;
}
[data-icon]:before {
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
</style>
<script>
window.onload = function() {
document.getElementById('dir').checked = false;
};
</script>
</head>
<body role="application">
<p dir="ltr">
<label class="bb-docs">
<input id="dir" type="checkbox" checked="false"
onchange="document.documentElement.dir = this.checked ? 'rtl' : 'ltr'">
RTL mode
</label>
</p>
<h2 class="bb-docs">Special buttons</h2>
<div class="bb-docs skin-organic">
<button>Default</button>
<button class="recommend">Recommend</button>
<button class="danger">Danger</button>
</div>
<h2 class="bb-docs">Buttons with icons</h2>
<div class="bb-docs skin-organic">
<button data-icon="forward" aria-label="forward" data-l10n-id="">Default</button>
<button data-icon="forward" aria-label="forward" data-l10n-id="" class="recommend">Recommend</button>
<button data-icon="forward" aria-label="forward" data-l10n-id="" class="danger">Danger</button>
<button data-icon="forward-light" aria-label="forward-light" data-l10n-id="">Default</button>
<button data-icon="back" aria-label="back" data-l10n-id="">Default</button>
<button data-icon="back-light" aria-label="back-light" data-l10n-id="">Default</button>
</div>
<h2 class="bb-docs">Disabled</h2>
<div class="bb-docs skin-organic">
<button disabled>Default</button>
<a href="#" role="button" aria-disabled="true" class="recommend bb-button">Recommend</a>
<button disabled class="danger">Danger</button>
</div>
<h2 class="bb-docs">Skin dark</h2>
<div class="bb-docs skin-dark">
<button>Default</button>
<button class="recommend">Recommend</button>
<button class="danger">Danger</button>
</div>
<h2 class="bb-docs">Disabled</h2>
<div class="bb-docs skin-dark">
<button disabled>Default</button>
<a href="#" role="button" aria-disabled="true" class="recommend bb-button">Recommend</a>
<button disabled class="danger">Danger</button>
</div>
<h2 class="bb-docs">List buttons</h2>
<div class="bb-docs skin-organic">
<ul>
<li>
<button>Default</button>
</li>
<li>
<button disabled>Disabled</button>
</li>
<li>
<button class="icon icon-view">
Icon view button
</button>
</li>
<li>
<button class="icon icon-dialog">
Icon dialog button
</button>
</li>
<li>
<span class="button icon icon-dialog">
<select>
<option selected>Icon dialog with options</option>
<option>option 1</option>
<option>option 2</option>
<option>option 3</option>
</select>
</span>
</li>
<li>
<button class="icon icon-dialog" disabled>
Icon dialog button disabled
</button>
</li>
<li>
<button data-icon="forward" aria-label="forward" data-l10n-id="">
Side navigation
</button>
</li>
<li>
<button disabled data-icon="forward" aria-label="forward" data-l10n-id="">
Side navigation
</button>
</li>
<li>
<a role="button" data-icon="expand" class="bb-button" aria-label="expand" data-l10n-id="">
Tuesday Sept. 18, 2012
</a>
</li>
<li>
<a role="button" aria-disabled="true" data-icon="expand" class="bb-button" aria-label="expand" data-l10n-id="">
Tuesday Sept. 18, 2012
</a>
</li>
</ul>
</div>
<h2 class="bb-docs">Skin Dark</h2>
<div class="bb-docs skin-dark">
<ul>
<li>
<button>Default</button>
</li>
<li>
<button disabled>Disabled</button>
</li>
<li>
<button class="icon icon-dialog">
Dark icon dialog button
</button>
</li>
<li>
<button data-icon="forward" aria-label="forward" data-l10n-id="">
Side navigation
</button>
</li>
<li>
<button disabled data-icon="forward" aria-label="forward" data-l10n-id="">
Side navigation
</button>
</li>
<li>
<a role="button" data-icon="expand" class="bb-button" aria-label="expand" data-l10n-id="">
Tuesday Sept. 18, 2012
</a>
</li>
<li>
<a role="button" aria-disabled="true" data-icon="expand" class="bb-button" aria-label="expand" data-l10n-id="">
Tuesday Sept. 18, 2012
</a>
</li>
</ul>
</div>
<h2 class="bb-docs">Select</h2>
<div class="bb-docs">
<select>
<option>option 1</option>
<option>option 2</option>
</select>
</div>
<h2 class="bb-docs">Fake select</h2>
<div class="bb-docs">
<ul>
<li>
<span class="bb-button" data-icon="expand" aria-label="expand" data-l10n-id="">
<select>
<option>option 1</option>
<option>option 2</option>
</select>
</span>
</li>
</ul>
</div>
<h2 class="bb-docs">Disabled select</h2>
<div class="bb-docs">
<ul>
<li>
<span aria-disabled="true" class="bb-button" data-icon="expand" aria-label="expand" data-l10n-id="">
<select>
<option>option 1</option>
<option>option 2</option>
</select>
</span>
</li>
</ul>
</div>
<h2 class="bb-docs">Skin dark</h2>
<div class="bb-docs skin-dark">
<select>
<option>option 1</option>
<option>option 2</option>
</select>
</div>
<h2 class="bb-docs">Fake select</h2>
<div class="bb-docs skin-dark">
<ul>
<li>
<span class="bb-button" data-icon="expand" aria-label="expand" data-l10n-id="">
<select>
<option>option 1</option>
<option>option 2</option>
</select>
</span>
</li>
<li>
<span aria-disabled="true" class="bb-button" data-icon="expand" aria-label="expand" data-l10n-id="">
<select>
<option>option 1</option>
<option>option 2</option>
</select>
</span>
</li>
</ul>
</div>
</body>
</html>

View file

@ -0,0 +1,575 @@
/* ----------------------------------
* Input areas
* ---------------------------------- */
/**
* BiDi notes:
*
* - as the <input>, <textarea> and [contentEditable] elements are filled by
* the user, they need a `unicode-bidi: -moz-plaintext` attribute for their
* content to be displayed properly (e.g. LTR content in an RTL document,
* or vice-versa);
*
* - consequence: *-inline-start/end properties can still be used on such
* elements content direction doesn't impact element's direction
*
* - however, the one-line inputs must still be aligned along with the
* *document* direction: to the left in LTR, to the right in RTL
* -- no matter what the *content* direction is.
*/
html[dir="ltr"] input { text-align: left; }
html[dir="rtl"] input { text-align: right; }
input[type="text"],
input[type="password"],
input[type="email"],
input[type="tel"],
input[type="search"],
input[type="url"],
input[type="number"],
textarea {
font-family: sans-serif;
box-sizing: border-box;
display: block;
width: 100%;
height: 4rem;
line-height: 4rem;
resize: none;
padding: 0 1.5rem;
font-size: 1.6rem;
border: 0.1rem solid #c7c7c7;
border-radius: 0;
box-shadow: none; /* override the box-shadow from the system (performance issue) */
color: #333;
background: #fff;
margin: 0 0 1rem;
unicode-bidi: -moz-plaintext;
}
label:active {
background-color: transparent;
}
textarea {
height: 10rem;
max-height: 10rem;
line-height: 2rem;
}
input::-moz-placeholder,
textarea::-moz-placeholder {
color: #a9a9a9;
opacity: 1;
font-style: italic;
font-weight: 400;
}
input[disabled],
textarea[disabled] {
opacity: 0.5;
}
.skin-dark input[type="text"],
.skin-dark input[type="password"],
.skin-dark input[type="email"],
.skin-dark input[type="tel"],
.skin-dark input[type="search"],
.skin-dark input[type="url"],
.skin-dark input[type="number"],
.skin-dark textarea {
color: #fff;
background-color: transparent;
border-color: #454545;
}
form p {
position: relative;
margin: 0;
}
form p input + button[type="reset"],
form p textarea + button[type="reset"] {
position: absolute;
top: 0;
width: 4rem;
height: 4rem;
padding: 0;
border: none;
font-size: 0;
opacity: 0;
pointer-events: none;
background: url(input_areas/images/clear.png) no-repeat 50% 50% / 2.4rem auto;
}
html[dir="ltr"] form p input + button[type="reset"],
html[dir="ltr"] form p textarea + button[type="reset"] {
right: -0.3rem;
}
html[dir="rtl"] form p input + button[type="reset"],
html[dir="rtl"] form p textarea + button[type="reset"] {
left: -0.3rem;
}
.skin-dark p input + button[type="reset"],
.skin-dark p textarea + button[type="reset"] {
background-image: url(input_areas/images/clear_dark.png);
}
/* To avoid colission with BB butons */
li input + button[type="reset"]:after,
li textarea + button[type="reset"]:after {
background: none;
}
textarea {
padding: 1.2rem;
}
html[dir="ltr"] form p input:focus { padding-right: 3rem; }
html[dir="rtl"] form p input:focus { padding-left: 3rem; }
form p input:focus + button[type="reset"],
form p textarea:focus + button[type="reset"] {
opacity: 1;
pointer-events: all;
}
/* Fieldset */
fieldset {
position: relative;
overflow: hidden;
margin: 1.5rem 0 0;
padding: 0;
font-size: 2rem;
line-height: 1em;
background: none;
border: 0;
}
fieldset legend,
input[type="date"],
input[type="time"],
input[type="datetime"],
input[type="datetime-local"] {
border: 0.1rem solid #c7c7c7;
width: 100%;
box-sizing: border-box;
font-family: sans-serif;
font-style: italic;
font-size: 1.6rem;
margin: 0 0 1.5rem;
padding: 0 1.5rem;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
height: 4rem;
color: #333;
border-radius: 0;
background-color: #fff;
background-repeat: no-repeat;
}
html[dir="ltr"] fieldset legend,
html[dir="ltr"] input[type="date"],
html[dir="ltr"] input[type="time"],
html[dir="ltr"] input[type="datetime"],
html[dir="ltr"] input[type="datetime-local"] {
background-image: url(input_areas/images/dialog.svg);
background-position: right 1rem bottom 1rem;
}
html[dir="rtl"] fieldset legend,
html[dir="rtl"] input[type="date"],
html[dir="rtl"] input[type="time"],
html[dir="rtl"] input[type="datetime"],
html[dir="rtl"] input[type="datetime-local"] {
background-image: url(input_areas/images/dialog_rtl.svg);
background-position: left 1rem bottom 1rem;
}
fieldset legend {
margin: 0 0 1rem;
padding: 1rem 1.5rem 0;
background-color: rgba(0,0,0,0.05);
}
fieldset[disabled] {
opacity: 0.5;
pointer-events: none;
}
.skin-dark fieldset legend,
.skin-dark input[type="date"],
.skin-dark input[type="time"],
.skin-dark input[type="datetime"],
.skin-dark input[type="datetime-local"] {
color: #fff;
background-color: transparent;
border-color: #454545;
}
fieldset[disabled] legend,
input[type="date"][disabled],
input[type="time"][disabled],
input[type="datetime"][disabled],
input[type="datetime-local"][disabled] {
background-color: transparent;
}
html[dir="ltr"] fieldset[disabled] legend,
html[dir="ltr"] input[type="date"][disabled],
html[dir="ltr"] input[type="time"][disabled],
html[dir="ltr"] input[type="datetime"][disabled],
html[dir="ltr"] input[type="datetime-local"][disabled] {
background-image: url(input_areas/images/dialog_disabled.svg);
}
html[dir="rtl"] fieldset[disabled] legend,
html[dir="rtl"] input[type="date"][disabled],
html[dir="rtl"] input[type="time"][disabled],
html[dir="rtl"] input[type="datetime"][disabled],
html[dir="rtl"] input[type="datetime-local"][disabled] {
background-image: url(input_areas/images/dialog_disabled_rtl.svg);
}
input[type="date"]:focus,
input[type="time"]:focus,
input[type="datetime"]:focus,
input[type="datetime-local"]:focus {
box-shadow: none;
border-bottom-color: #c7c7c7;
}
.skin-dark input[type="date"]:focus,
.skin-dark input[type="time"]:focus,
.skin-dark input[type="datetime"]:focus,
.skin-dark input[type="datetime-local"]:focus {
box-shadow: none;
border-bottom-color: #454545;
}
input[type="date"]:active,
input[type="time"]:active,
input[type="datetime"]:active,
input[type="datetime-local"]:active {
background-color: #b2f2ff;
color: #fff;
}
.skin-dark input[type="date"]:active,
.skin-dark input[type="time"]:active,
.skin-dark input[type="datetime"]:active,
.skin-dark input[type="datetime-local"]:active {
background-color: #00aacc;
background-image: url(input_areas/images/dialog_active.svg);
}
/* fix for required inputs with wrong or empty value e.g. [type=email] */
input:invalid,
textarea:invalid,
.skin-dark input:invalid,
.skin-dark textarea:invalid {
color: #b90000;
}
input:focus,
textarea:focus,
.skin-dark input:focus,
.skin-dark textarea:focus {
box-shadow: inset 0 -0.1rem 0 #00caf2;
border-bottom-color: #00caf2;
}
input:invalid:focus,
textarea:invalid:focus,
.skin-dark input:invalid:focus,
.skin-dark textarea:invalid:focus {
box-shadow: inset 0 -0.1rem 0 #820000;
border-bottom-color: #820000;
}
input[type="range"]:focus,
.skin-dark input[type="range"]:focus {
box-shadow: none;
border-bottom: 0;
}
/* Tidy (search/submit) */
form[role="search"] {
position: relative;
height: 3.7rem;
background: #f4f4f4;
}
form[role="search"].skin-dark {
background: #202020;
}
form[role="search"] p {
padding: 0 1.5rem;
padding-inline-start: 3rem;
overflow: hidden;
position: relative;
}
form[role="search"] p input,
form[role="search"] p textarea {
height: 3.7rem;
border: none;
background: none;
padding: 0;
margin: 0;
box-shadow: none;
}
form[role="search"] p textarea {
padding: 1rem 0 0;
}
form[role="search"] p input::-moz-placeholder {
background-image: url(input_areas/images/search.svg);
background-repeat: no-repeat;
background-size: 3rem;
}
html[dir="ltr"] form[role="search"] p input::-moz-placeholder {
background-position: right -0.5rem center;
}
html[dir="rtl"] form[role="search"] p input::-moz-placeholder {
background-position: left -0.5rem center;
}
form[role="search"].skin-dark p input::-moz-placeholder {
background-image: url(input_areas/images/search_dark.svg);
}
form[role="search"] p input:focus::-moz-placeholder {
background: none;
}
form[role="search"] p input:invalid,
form[role="search"] p textarea:invalid,
form[role="search"] p input:focus,
form[role="search"] p textarea:focus {
border: none;
box-shadow: none;
}
form[role="search"].skin-dark p input,
form[role="search"].skin-dark p textarea {
color: #fff;
background: none;
box-shadow: none;
}
form[role="search"] button[type="submit"] {
min-width: 6rem;
height: 3.7rem;
padding: 0 1.5rem;
border: none;
color: #00aac5;
font-weight: normal;
font-size: 1.6rem;
line-height: 3.7rem;
width: auto;
border-radius: 0;
margin: 0;
position: relative;
background-image: none;
background-color: unset;
font-style: italic;
overflow: visible;
}
html[dir="ltr"] form[role="search"] button[type="submit"] { float: right; }
html[dir="rtl"] form[role="search"] button[type="submit"] { float: left; }
form[role="search"] button[type="submit"]:after {
content: "";
position: absolute;
offset-inline-start: -0.1rem;
top: 0.7rem;
bottom: 0.7rem;
width: 0.1rem;
background: #c7c7c7;
}
form[role="search"] button[type="submit"].icon {
font-size: 0;
background-repeat: no-repeat;
background-position: center;
background-size: 3rem;
}
form[role="search"] p input + button[type="reset"]{
height: 3.7rem;
offset-inline-end: 0.5rem;
}
form[role="search"].skin-dark button[type="submit"]:after {
background-color: #575757;
}
form[role="search"] button[type="submit"] + p > textarea {
height: 3.7rem;
line-height: 2rem;
}
form[role="search"] button[type="submit"] + p button {
height: 3.7rem;
}
form[role="search"].full button[type="submit"] {
display: none;
}
form[role="search"] button.icon:active,
form[role="search"] button[type="submit"]:active {
background-color: #b2f2ff;
color: #fff;
}
form[role="search"].skin-dark button.icon:active,
form[role="search"].skin-dark button[type="submit"]:active {
background: #00aacc;
}
form[role="search"] button[type="submit"][disabled] {
color: #adadad;
pointer-events: none;
}
form button::-moz-focus-inner {
border: none;
outline: none;
}
/* .bb-editable - e.g email & SMS recipients */
.bb-editable [contenteditable] {
display: inline-block;
box-sizing: border-box;
position: relative;
max-width: calc(100% - 1.4rem);
overflow: hidden;
padding: 0 1.5rem;
margin: 0 0 0.6rem;
line-height: 2.8rem;
border-radius: 1.4rem;
text-overflow: ellipsis;
font-size: 1.6rem;
font-style: italic;
white-space: nowrap;
color: #333;
background: #fff;
}
html[dir="ltr"] .bb-editable [contenteditable].icon {
padding-right: 3.7rem;
}
html[dir="rtl"] .bb-editable [contenteditable].icon {
padding-left: 3.7rem;
}
.bb-editable [contenteditable="false"].invalid {
background-color: #fedcdc;
padding: 0 1.5rem;
}
html[dir="ltr"] .bb-editable [contenteditable="false"].invalid { padding-left: 3.3rem; }
html[dir="rtl"] .bb-editable [contenteditable="false"].invalid { padding-right: 3.3rem; }
.bb-editable [contenteditable="false"].invalid:before {
content: "!";
position: absolute;
top: 0.6rem;
display: block;
width: 1.7rem;
height: 1.6rem;
padding: 0;
border-radius: 50%;
line-height: 1.6rem;
font-size: 1.2rem;
text-align: center;
text-indent: -0.1rem;
font-style: normal;
font-weight: 600;
color: #fff;
background-color: #b80404;
}
html[dir="ltr"] .bb-editable [contenteditable="false"].invalid:before { left: 0.8rem; }
html[dir="rtl"] .bb-editable [contenteditable="false"].invalid:before { right: 0.8rem; }
.bb-editable [contenteditable="false"].invalid:hover:before,
.bb-editable [contenteditable="false"].invalid:active:before {
color: #b2f2ff;
background-color: #fff;
}
.bb-editable [contenteditable]:hover,
.bb-editable [contenteditable]:active,
.bb-editable [contenteditable].invalid:hover,
.bb-editable [contenteditable].invalid:active {
color: #fff;
background-color: #b2f2ff;
}
.bb-editable [contenteditable].icon:after {
content: "";
position: absolute;
top: 0.7rem;
width: 3rem;
height: 3rem;
background-repeat: no-repeat;
background-size: 3rem;
}
html[dir="ltr"] .bb-editable [contenteditable].icon:after {
right: 0;
background-position: -0.7rem -3.8rem;
}
html[dir="rtl"] .bb-editable [contenteditable].icon:after {
left: 0;
background-position: 0.7rem -3.8rem;
}
html[dir="ltr"] .bb-editable [contenteditable].icon:hover:after,
html[dir="ltr"] .bb-editable [contenteditable].icon:active:after {
background-position: -0.7rem -0.8rem;
}
html[dir="rtl"] .bb-editable [contenteditable].icon:hover:after,
html[dir="rtl"] .bb-editable [contenteditable].icon:active:after {
background-position: 0.7rem -0.8rem;
}
html[dir="ltr"] .bb-editable [contenteditable].invalid.icon {
padding-right: 3.7rem;
}
html[dir="rtl"] .bb-editable [contenteditable].invalid.icon {
padding-left: 3.7rem;
}
html[dir="ltr"] .bb-editable [contenteditable].invalid.icon:after {
background-position: -0.7rem -6.8rem;
}
html[dir="rtl"] .bb-editable [contenteditable].invalid.icon:after {
background-position: 0.7rem -6.8rem;
}
html[dir="ltr"] .bb-editable [contenteditable].invalid.icon:hover:after,
html[dir="ltr"] .bb-editable [contenteditable].invalid.icon:active:after {
background-position: -0.7rem -0.8rem;
}
html[dir="rtl"] .bb-editable [contenteditable].invalid.icon:hover:after,
html[dir="rtl"] .bb-editable [contenteditable].invalid.icon:active:after {
background-position: 0.7rem -0.8rem;
}
.bb-editable [contenteditable="true"],
.bb-editable [contenteditable="true"]:hover,
.skin-comms .bb-editable [contenteditable="true"]:hover {
padding: 0 0.2rem;
white-space: normal;
font-size: 1.8rem;
font-style: normal;
color: #000;
background: none repeat scroll 0 0 transparent;
}
.skin-comms .bb-editable [contenteditable]:hover,
.skin-comms .bb-editable [contenteditable]:active {
color: #fff;
background-color: #29bbb5;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="9" height="9" viewBox="0 0 9 9"><path fill="#00AAC5" enable-background="new" d="M9 0L9 9 0 9"/></svg>

After

Width:  |  Height:  |  Size: 148 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="9" height="9" viewBox="0 0 9 9"><path fill="#B2F2FF" d="M9 0v9h-9"/></svg>

After

Width:  |  Height:  |  Size: 121 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="9" height="9" viewBox="0 0 9 9"><path opacity=".5" fill="#A9A9A9" d="M9 0L9 9 0 9"/></svg>

After

Width:  |  Height:  |  Size: 137 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="9" height="9" viewBox="0 0 9 9"><path opacity=".5" fill="#A9A9A9" enable-background="new" d="M0 0L0 9 9 9"/></svg>

After

Width:  |  Height:  |  Size: 161 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="9" height="9" viewBox="0 0 9 9"><path fill="#00AAC5" enable-background="new" d="M0 0L0 9 9 9"/></svg>

After

Width:  |  Height:  |  Size: 148 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="30" height="30" viewBox="0 0 30 30"><path fill-rule="evenodd" clip-rule="evenodd" fill="#424242" d="M23.385 21.494c.266.266.057.903-.465 1.426s-1.16.729-1.426.465l-4.041-4.039c-1.01.656-2.21 1.047-3.505 1.047-3.561 0-6.448-2.885-6.448-6.446 0-3.56 2.887-6.446 6.448-6.446 3.562 0 6.449 2.887 6.449 6.446 0 1.297-.393 2.499-1.051 3.509l4.041 4.039h-.002zm-9.437-12.459c-2.712 0-4.914 2.198-4.914 4.912s2.202 4.913 4.914 4.913c2.714 0 4.914-2.199 4.914-4.913-.001-2.714-2.2-4.912-4.914-4.912z"/></svg>

After

Width:  |  Height:  |  Size: 546 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="30" height="30" viewBox="0 0 30 30"><path fill-rule="evenodd" clip-rule="evenodd" fill="#8F9091" d="M23.385 21.494c.266.266.057.903-.465 1.426s-1.16.729-1.426.465l-4.041-4.039c-1.01.656-2.21 1.047-3.505 1.047-3.561 0-6.448-2.885-6.448-6.446 0-3.56 2.887-6.446 6.448-6.446 3.562 0 6.449 2.887 6.449 6.446 0 1.297-.393 2.499-1.051 3.509l4.041 4.039h-.002zm-9.437-12.459c-2.712 0-4.914 2.198-4.914 4.912s2.202 4.913 4.914 4.913c2.714 0 4.914-2.199 4.914-4.913-.001-2.714-2.2-4.912-4.914-4.912z"/></svg>

After

Width:  |  Height:  |  Size: 546 B

View file

@ -0,0 +1,236 @@
<!DOCTYPE html>
<html dir="ltr">
<head>
<meta charset="utf-8">
<title>Input areas</title>
<link rel="stylesheet" href="../input_areas.css">
<link rel="stylesheet" href="../../elements/gaia-icons/gaia-icons-embedded.css">
<link rel="stylesheet" href="../../elements/gaia-icons/bidi-helper.css">
<!--
- These <style> and <script> elements are only used for the example code,
- they are not needed for the real use case.
-->
<style>
html, body {
margin: 0;
padding: 0;
font-size: 10px;
background-color: #f4f4f4;
}
label.bb-docs {
font-family: sans-serif;
font-size: 1.2rem;
margin-left: 1.5rem;
}
h2.bb-docs {
font-family: sans-serif;
padding: 0.4rem 3rem;
margin: -0.1rem 0 0;
border: solid 0.1rem #e8e8e8;
color: #666;
font-size: 1.8rem;
font-weight: 300;
background-color: #fff;
}
form { margin: 1rem 1.5rem; }
form[role="search"] { margin: 1rem 0; }
div.skin-dark {
background: #000;
}
div.skin-dark.margins {
margin: 1rem -1.5rem;
padding: 1.5rem 1.5rem 0.5rem;
}
/**
* .bb-editable
* Added properties for this example, don't add on app.
*/
.bb-editable {
padding: 1.5rem 1rem 0.5rem;
background: #bbb;
}
/**
* .icon-email:before
* Define the background-image with custom icon for App.
* Attention, the pseudo-element is required for compatibility with Building Blocks.
*
* Example:
* .icon-mms:before { background-image: url(...); }
*/
.icon-email:after {
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAABaCAYAAACv+ebYAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAASRJREFUeNrsl8ENgjAUhqlhAEfxQDyzgXdOjoCT0ClYg54Nu+gGtcRiAlJ5fQ9CDP+XFJOW+hlo6/uTBADw76hfg9ba0n0cBd//VErp6FlO3FgZDevn9pO50u4iEXPkn/ul4hj54L4lxBT51/hS4odrVUA62b+UOCSvfP+q4rE8KF1DTF5w3JPLio9Gx1T/Yauzen/idGb8Jv13QgEAAAAAyNJi27bitJhlmeYUAhfXcoHYuKY5pU/uJ3OluaTm4sjN3JOiFnsxckN5PTFVJkVuqGsitrw9hRaL7ycvxFhxt7WuE3Lt+1ct6MfyXhq131PmVukkpX+n5RYRhn24IC0iLQIAAABIi2/uRSFOi+e6RlpEWkRaRFpEWkRaRFoEYG+8BBgAvaeWTjgXDaoAAAAASUVORK5CYII=);
}
</style>
<script>
window.onload = function() {
document.getElementById('dir').checked = false;
};
</script>
</head>
<body role="application">
<p dir="ltr">
<label class="bb-docs">
<input id="dir" type="checkbox" checked="false"
onchange="document.documentElement.dir = this.checked ? 'rtl' : 'ltr'">
RTL mode
</label>
</p>
<h2 class="bb-docs">Default inputs</h2>
<form>
<p>
<input dir="auto" type="text" placeholder="Placeholder">
<button type="reset">Clear</button>
</p>
<p>
<textarea placeholder="Placeholder in textarea"></textarea>
</p>
<p>
<input dir="auto" type="text" placeholder="Placeholder" value="Some written text" required>
<button type="reset">Clear</button>
</p>
<p>
<input dir="auto" type="text" placeholder="Disabled" disabled required>
<button type="reset">Clear</button>
</p>
<div class="skin-dark margins">
<p>
<input dir="auto" type="text" placeholder="Placeholder" value="Some written text">
<button type="reset">Clear</button>
</p>
</div>
</form>
<h2 class="bb-docs">Required inputs</h2>
<form>
<p>
<input dir="auto" type="text" placeholder="Placeholder" required>
<button type="reset">Clear</button>
</p>
<p>
<textarea placeholder="Placeholder in textarea" required></textarea>
</p>
</form>
<h2 class="bb-docs">Date/time inputs</h2>
<form>
<p>
<input dir="auto" disabled type="date" placeholder="YYYY-MM-DD">
</p>
<p>
<input dir="auto" type="time" placeholder="hh:mm:ss">
</p>
</form>
<h2 class="bb-docs">Fieldset</h2>
<form>
<fieldset>
<legend class="action">Mobile</legend>
<section>
<p>
<input dir="ltr" type="tel" placeholder="Phone number">
<button type="reset">Clear</button>
</p>
<p>
<input dir="auto" type="text" placeholder="Name" value="Jessy James">
<button type="reset">Clear</button>
</p>
</section>
</fieldset>
<fieldset disabled>
<legend>Work</legend>
<section>
<p>
<input dir="ltr" type="tel" placeholder="Email">
<button type="reset">Clear</button>
</p>
</section>
</fieldset>
</form>
<h2 class="bb-docs">Forms: Input text</h2>
<form role="search">
<p>
<input dir="auto" type="text" placeholder="Search…">
<button type="reset">Clear</button>
</p>
</form>
<form role="search">
<button type="submit">Cancel</button>
<p>
<input dir="auto" type="text" placeholder="Search…" value="Some text here">
<button type="reset">Clear</button>
</p>
</form>
<div class="skin-dark">
<h2 class="bb-docs">Skin dark</h2>
<form role="search" class="skin-dark">
<p>
<input dir="auto" type="text" placeholder="Search…">
<button type="reset">Clear</button>
</p>
</form>
<form role="search" class="skin-dark">
<button type="submit">Cancel</button>
<p>
<input dir="auto" type="text" placeholder="Search…" value="Some text here">
<button type="reset">Clear</button>
</p>
</form>
</div>
<h2 class="bb-docs">Forms: Text area</h2>
<form role="search" class="bottom">
<!-- this button should be enabled after start typing in the input field-->
<button type="submit" disabled>Send</button>
<p>
<textarea></textarea>
</p>
</form>
<form role="search" class="bottom">
<button type="submit">Send</button>
<p>
<textarea>Some text here</textarea>
</p>
</form>
<form role="search" class="bottom">
<button type="submit" class="icon" data-icon="send" aria-hidden="true">Send</button>
<p>
<textarea>Some text here</textarea>
</p>
</form>
<h2 class="bb-docs">Content Editable</h2>
<ul class="bb-editable">
<li dir="auto" contenteditable="false">Travis Gray Travis Gray</li>
<li dir="auto" contenteditable="false" class="invalid">Lucy B.</li>
<li dir="auto" contenteditable="false" class="icon icon-email">Mary</li>
<li dir="auto" contenteditable="false" class="invalid icon icon-email">Paul C.</li>
<li dir="auto" contenteditable="true">John</li>
</ul>
</body>
</html>

View file

@ -0,0 +1,242 @@
/* ----------------------------------
* Lists
* ---------------------------------- */
[data-type="list"] {
font-weight: 400;
padding: 0 1.5rem;
}
[data-type="list"] ul {
list-style: none;
margin: 0;
padding: 0;
overflow: hidden;
}
[data-type="list"] strong {
font-weight: 500;
}
/* Title divisors */
[data-type="list"] header {
padding: 0 1.5rem;
margin: 0;
font-size: 1.6rem;
height: 3rem;
line-height: 4rem;
font-weight: normal;
color: #ff4e00;
text-transform: uppercase;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
html[dir="ltr"] [data-type="list"] header { text-align: left; }
html[dir="rtl"] [data-type="list"] header { text-align: right; }
/* List items */
[data-type="list"] li {
width: 100%;
height: 6rem;
box-sizing: border-box;
border-bottom: solid 0.1rem #e7e7e7;
color: #333;
margin: 0;
display: block;
position: relative;
}
[data-type="list"] li:last-child {
border: none;
}
/* Pressed State */
li a:active {
background-color: transparent; /* remove default color for active state */
}
[data-type="list"] li > a {
text-decoration: none;
color: #333;
display: block;
height: 6rem;
position: relative;
border: none;
outline: none;
}
[data-type="list"].active li:after,
[data-type="list"] li > a:after {
content: "";
pointer-events: none;
position: absolute;
left: -1.5rem;
top: 0;
right: -1.5rem;
bottom: 0;
}
[data-type="list"].active li:active:after,
[data-type="list"] li > a:active:after {
background-color: rgba(0,213,255,0.3);
}
/* Disabled */
[data-type="list"] li[aria-disabled="true"] {
pointer-events: none;
opacity: 0.2;
border-color: #888;
}
/* Graphic content */
[data-type="list"] aside {
margin: 0;
margin-inline-end: 0.5rem;
height: 100%;
position: relative;
}
html[dir="ltr"] [data-type="list"] aside { float: left; }
html[dir="rtl"] [data-type="list"] aside { float: right; }
[data-type="list"] li > a aside,
[data-type="list"] li > a aside.icon {
background-color: transparent;
}
[data-type="list"] aside.pack-end {
margin: 0;
margin-inline-start: 0.5rem;
text-align: end;
}
html[dir="ltr"] [data-type="list"] aside.pack-end { float: right; }
html[dir="rtl"] [data-type="list"] aside.pack-end { float: left; }
[data-type="list"] aside.icon {
width: 3rem;
height: 6rem;
background-color: #fff;
background-repeat: no-repeat;
background-size: 100% auto;
font-size: 0;
display: block;
overflow: hidden;
}
html[dir="ltr"] [data-type="list"] aside.icon {
background-position: left center;
}
html[dir="rtl"] [data-type="list"] aside.icon {
background-position: right center;
}
[data-type="list"] aside span[data-type=img],
[data-type="list"] aside img {
display: block;
overflow: hidden;
width: 5rem;
height: 5rem;
border-radius: 2.5rem;
margin-top: 0.5rem;
box-sizing: border-box;
background: transparent center center / cover;
position: relative;
}
[data-type="list"] [data-icon] {
font-size: 0;
}
[data-type="list"] [data-icon]:before {
line-height: 6rem;
text-align: center;
width: 3rem;
color: #333;
}
/* Text content */
[data-type="list"] li p {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
border: none;
display: block;
margin: 0;
color: #858585;
font-size: 1.5rem;
line-height: 2rem;
}
[data-type="list"] li p:first-of-type {
font-size: 1.8rem;
line-height: 2.2rem;
color: #333;
padding: 1rem 0 0;
}
[data-type="list"] li p:only-child,
[data-type="list"] li p:first-of-type:last-of-type {
line-height: 4rem;
}
[data-type="list"] li p em {
font-size: 1.5rem;
font-style: normal;
}
[data-type="list"] li p time {
margin-inline-end: 0.3rem;
text-transform: uppercase;
}
/* Checkable content */
[data-type="list"] li > .pack-checkbox,
[data-type="list"] li > .pack-switch {
pointer-events: none;
position: absolute;
top: -0.1rem;
bottom: 0;
/* Intentionally adding a conflict between right/left here: this will be
* resolved differently whether we're in LTR or RTL mode. */
right: -4rem;
left: -4rem;
width: calc(100% + 4rem);
height: auto;
}
[data-type="list"] li > .pack-checkbox input ~ span:after,
[data-type="list"] li > .pack-switch input ~ span:after {
/* because width is set in switches.css, the conflict between left/right will
* be resolved in favor of "left" in LTR and "right" in RTL */
left: 0;
right: 0;
margin-inline-start: 0;
}
/* Edit mode */
[data-type="list"] [data-type="edit"] li {
padding-inline-start: 4rem;
}
[data-type="list"] [data-type="edit"] li > .pack-checkbox,
[data-type="list"] [data-type="edit"] li > .pack-switch {
pointer-events: auto;
left: 0;
right: 0;
}
[data-type="list"] [data-type="edit"] li > a {
pointer-events: none;
}
[data-type="list"] [data-type="edit"] li > a:active:after,
[data-type="list"] [data-type="edit"] li > a:hover:after {
display: none;
}
[data-type="list"] [data-type="edit"] aside.pack-end {
display: none;
}
/* Themes */
.skin-comms [data-type="list"] header {
color: #4997a7;
}

View file

@ -0,0 +1,209 @@
<!DOCTYPE html>
<html dir="ltr">
<head>
<meta charset="utf-8">
<title>Lists</title>
<meta name="description" content="Displaying an enumeration of a set of items ">
<link rel="stylesheet" href="../lists.css">
<!--
- These <style>, <script> and <link> elements are only used for the example
- code, they are not needed for the real case.
-->
<link rel="stylesheet" href="../../style/headers.css">
<link rel="stylesheet" href="../../style/switches.css">
<link rel="stylesheet" href="../../style/buttons.css">
<link rel="stylesheet" href="../../elements/gaia-icons/gaia-icons-embedded.css">
<link rel="stylesheet" href="../../elements/gaia-icons/bidi-helper.css">
<style>
html, body {
margin: 0;
padding: 0;
font-size: 10px;
background-color: #fff;
}
body {
background: none;
}
h2.pack-docs {
font-size: 1.8rem;
font-weight: 300;
color: #666;
margin: -1px 0 0;
background-color: #f5f5f5;
padding: 0.4rem 0.4rem 0.4rem 3rem;
border: solid 1px #e8e8e8;
}
h2.pack-docs:first-child {
margin-top: 0;
}
[data-type="list"] li img[alt="placeholder"] {
background: #ccc url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjwhLS0gR2VuZXJhdG9yOiBBZG9iZSBJbGx1c3RyYXRvciAxNS4wLjIsIFNWRyBFeHBvcnQgUGx1Zy1JbiAuIFNWRyBWZXJzaW9uOiA2LjAwIEJ1aWxkIDApICAtLT4NCjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+DQo8c3ZnIHZlcnNpb249IjEuMSIgaWQ9IkNhcGFfMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgeD0iMHB4IiB5PSIwcHgiDQoJIHdpZHRoPSI1MHB4IiBoZWlnaHQ9IjUwcHgiIHZpZXdCb3g9IjAgMCA1MCA1MCIgZW5hYmxlLWJhY2tncm91bmQ9Im5ldyAwIDAgNTAgNTAiIHhtbDpzcGFjZT0icHJlc2VydmUiPg0KPGxpbmUgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjMDAwMDAwIiBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiIHgxPSIwIiB5MT0iMCIgeDI9IjUwIiB5Mj0iNTAiLz4NCjxsaW5lIGZpbGw9Im5vbmUiIHN0cm9rZT0iIzAwMDAwMCIgc3Ryb2tlLW1pdGVybGltaXQ9IjEwIiB4MT0iNTAiIHkxPSIwIiB4Mj0iMC4wODIiIHkyPSI0OS45MTkiLz4NCjwvc3ZnPg0K);
background-size: 100% 100%;
font-size: 0;
}
[data-icon]:before {
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
[data-type="list"] [data-icon="forward"]:before {
color: #686f72;
}
#settings-list { background-color: #eff0eb; }
#settings-list li { border-color: #dcdcdc; }
#settings-list button { max-width: 8rem; margin: 1rem 1rem 0 0; }
#settings-list aside label { margin: 0 1rem 0 0; height: 100%; }
#settings-list > header h2 { margin: 0 1.5rem; }
</style>
<script>
window.onload = function() {
var dirInput = document.getElementById('dir');
dirInput.checked = false;
dirInput.onchange = function() {
document.documentElement.dir = this.checked ? "rtl" : "ltr";
};
};
</script>
</head>
<body role="application">
<p dir="ltr">
<label class="bb-docs">
<input id="dir" type="checkbox"> RTL mode
</label>
</p>
<section data-type="list">
<header>Title</header>
<ul>
<li>
<p>Margherita <strong>Gomez</strong></p>
</li>
<li>
<a href="#">
<aside class="pack-end" data-icon="forward" data-l10n-id="" aria-label="forward"></aside>
<p>Margherita Clickable <strong>Gomez</strong></p>
</a>
</li>
<li>
<aside class="pack-end">
<img alt="placeholder" src="myimage.jpg">
</aside>
<a href="#">
<p>Margherita <strong>Gomez</strong></p>
<p>Family</p>
</a>
</li>
</ul>
<header>Another title</header>
<ul>
<li aria-disabled="true">
<aside class="pack-end">
<img alt="placeholder" src="myimage.jpg">
</aside>
<a href="#">
<p>Margherita <strong>Gomez</strong></p>
<p>Family</p>
</a>
</li>
<li>
<aside class="pack-end icon icon-nopic"></aside>
<a href="#">
<aside data-icon="call-outgoing" data-l10n-id="" aria-label="outgoing call">
asidecall
</aside>
<p dir="auto">
Margherita <strong>Gomez</strong>
<em>(2)</em>
</p>
<p>
<time datetime="17:43">5:43PM</time>
Mobile, Vivo
</p>
</a>
</li>
</ul>
</section>
<section data-type="list">
<header>Edit mode</header>
<ul data-type="edit">
<li>
<label class="pack-checkbox danger">
<input type="checkbox">
<span></span>
</label>
<aside class="pack-end">
<img alt="placeholder" src="myimage.jpg">
</aside>
<a href="#">
<p>Margherita <strong>Gomez</strong></p>
<p>Family</p>
</a>
</li>
<li>
<label class="pack-checkbox danger">
<input type="checkbox">
<span></span>
</label>
<a href="#">
<aside data-icon="call-outgoing" data-l10n-id="" aria-label="outgoing call"></aside>
<p dir="auto">
Margherita <strong>Gomez</strong>
<em>(2)</em>
</p>
<p>
<time datetime="17:43">5:43PM</time>
Mobile, Vivo
</p>
</a>
</li>
</ul>
</section>
<section id="settings-list" role="region" class="skin-organic">
<section data-type="list">
<ul>
<li>
<a href="#">
<aside data-icon="call-outgoing" data-l10n-id="" aria-label="outgoing call"></aside>
<p>Phone lock</p>
</a>
</li>
</ul>
</section>
<header>
<h2>Settings style</h2>
</header>
<section data-type="list">
<ul>
<li>
<aside class="pack-end">
<button class="icon icon-dialog">Spain</button>
</aside>
<p>Time zone</p>
<p>Choose your country</p>
</li>
<li>
<aside class="pack-end">
<label class="pack-switch">
<input type="checkbox" checked>
<span></span>
</label>
</aside>
<p>Phone lock</p>
</li>
</ul>
</section>
</section>
</body>
</html>

View file

@ -0,0 +1,114 @@
/* ----------------------------------
* CHECKBOXES / RADIOS
* ---------------------------------- */
label.pack-checkbox,
label.pack-switch {
display: inline-block;
vertical-align: middle;
width: 5rem;
height: 5rem;
position: relative;
background: none;
}
label.pack-checkbox input,
label.pack-switch input {
margin: 0;
opacity: 0;
position: absolute;
top: 0;
/* the conflict between left/right/width is resolved differently in LTR and
* RTL. The goal here is to keep "input" invisible */
left: 0;
right: 0;
width: 0;
}
label.pack-checkbox input ~ span:after {
content: '';
position: absolute;
/* CSS will resolve the conflict among left, right, and width "intelligently"
and pick left+width for LTR and right+width for RTL. */
left: 50%;
right: 50%;
top: 50%;
margin: 0;
margin-top: -1.1rem;
margin-inline-start: -1.1rem;
width: 2.2rem;
height: 2.2rem;
pointer-events: none;
}
label.pack-checkbox input ~ span:after {
background: url(switches/images/check/default.png) no-repeat center top / 2.2rem auto;
}
/* checkbox & radio: disabled state */
label.pack-checkbox input:disabled ~ span:after {
opacity: 0.4;
}
label.pack-checkbox input:checked ~ span:after,
label.pack-switch input:checked ~ span:after {
background-position: center bottom;
}
/* 'Dangerous' switches */
label.pack-checkbox.danger input ~ span:after {
background-image: url(switches/images/check/danger.png);
}
/* ----------------------------------
* ON/OFF SWITCHES
* ---------------------------------- */
label.pack-switch input ~ span:after {
content: '';
position: absolute;
offset-inline-end: 0;
top: 50%;
width: 6rem;
margin: -1.4rem 0 0;
height: 2.7rem;
pointer-events: none;
border-radius: 1.35rem;
overflow: hidden;
background-color: #e6e6e6;
background-image: url(switches/images/switch/background_off.png);
background-repeat: no-repeat;
background-size: 9.2rem 2.7rem;
transition: background 0.2s ease;
}
html[dir="ltr"] label.pack-switch input ~ span:after {
background-position: -3.2rem 0;
}
html[dir="rtl"] label.pack-switch input ~ span:after {
background-position: 0;
}
/* switch: 'ON' state */
label.pack-switch input:checked ~ span:after {
background-color: #e6e6e6;
background-repeat: no-repeat;
background-size: 9.2rem 2.7rem;
}
html[dir="ltr"] label.pack-switch input:checked ~ span:after {
background-image: url(switches/images/switch/background.png);
background-position: 0 0;
}
html[dir="rtl"] label.pack-switch input:checked ~ span:after {
background-image: url(switches/images/switch/background_rtl.png);
background-position: -3.2rem 0;
}
/* switch: disabled state */
label.pack-switch input:disabled ~ span:after {
opacity: 0.4;
}
label.pack-switch input.uninit ~ span:after {
transition: none;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 583 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 583 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 755 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 475 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 748 B

View file

@ -0,0 +1,83 @@
<!DOCTYPE html>
<html dir="ltr">
<head>
<meta charset="utf-8">
<title>Switches</title>
<meta name="description" content="Activates/Deactivates a given item. It's also used to select an element within a list">
<link rel="stylesheet" href="../switches.css">
<!--
- These <style> elements are only used for the example code;
- they are not required for the real use case.
-->
<style type="text/css">
html, body {
margin: 0;
padding: 0;
font-size: 10px;
background-color: #fff;
}
body {
background: none;
}
h2.bb-docs {
font-size: 1.8rem;
font-weight: 300;
color: #666;
margin: -1px 0 0;
background-color: #f5f5f5;
padding: 0.4rem;
padding-inline-start: 3rem;
border: solid 1px #e8e8e8;
}
label {
margin-inline-start: 2rem;
}
</style>
<script>
window.onload = function() {
document.getElementById('dir').checked = false;
};
</script>
</head>
<body role="application">
<p dir="ltr">
<label>
<input type="checkbox"
onchange="document.documentElement.dir = this.checked ? 'rtl' : 'ltr'">
RTL mode
</label>
</p>
<h2 class="bb-docs">Checkbox, commonly used in edit mode</h2>
<label class="pack-checkbox">
<input type="checkbox" checked>
<span></span>
</label>
<label class="pack-checkbox">
<input type="checkbox">
<span></span>
</label>
<label class="pack-checkbox danger">
<input type="checkbox" checked>
<span></span>
</label>
<label class="pack-checkbox danger">
<input type="checkbox">
<span></span>
</label>
<h2 class="bb-docs">Switch, commonly used in settings</h2>
<label class="pack-switch">
<input type="checkbox" checked>
<span></span>
</label>
<label class="pack-switch">
<input type="checkbox">
<span></span>
</label>
</body>
</html>