- <%= render "sage/examples/objects/card/markup",
+ <%= render "examples/objects/card/markup",
is_compact: false,
is_spaced: false,
title: "Card w/ Image",
@@ -9,12 +9,12 @@
action_style_primary: true,
action_style_tertiary: false,
has_img: true,
- img_path: "sage/card/card-placeholder-lg.png",
+ img_path: "docs/card/card-placeholder-lg.png",
img_alt: "Alternative text for image"
%>
- <%= render "sage/examples/objects/card/markup",
+ <%= render "examples/objects/card/markup",
is_compact: false,
is_spaced: false,
title: "Card w/ Image",
@@ -23,12 +23,12 @@
action_style_primary: false,
action_style_tertiary: true,
has_img: true,
- img_path: "sage/card/card-placeholder-lg.png",
+ img_path: "docs/card/card-placeholder-lg.png",
img_alt: "Alternative text for image"
%>
- <%= render "sage/examples/objects/card/markup",
+ <%= render "examples/objects/card/markup",
is_compact: true,
is_spaced: false,
title: "Compact Card w/ Image",
@@ -37,10 +37,10 @@
action_style_primary: true,
action_style_tertiary: false,
has_img: true,
- img_path: "sage/card/card-placeholder-sm.png",
+ img_path: "docs/card/card-placeholder-sm.png",
img_alt: "Alternative text for image"
%>
- <%= render "sage/examples/objects/card/markup",
+ <%= render "examples/objects/card/markup",
is_compact: true,
is_spaced: false,
title: "Compact Card w/ Image",
@@ -49,12 +49,12 @@
action_style_primary: false,
action_style_tertiary: true,
has_img: true,
- img_path: "sage/card/card-placeholder-sm.png",
+ img_path: "docs/card/card-placeholder-sm.png",
img_alt: "Alternative text for image"
%>
- <%= render "sage/examples/objects/card/markup",
+ <%= render "examples/objects/card/markup",
is_compact: false,
is_spaced: true,
title: "Spaced Card w/ Image",
@@ -63,12 +63,12 @@
action_style_primary: true,
action_style_tertiary: false,
has_img: true,
- img_path: "sage/card/card-placeholder-lg.png",
+ img_path: "docs/card/card-placeholder-lg.png",
img_alt: "Alternative text for image"
%>
- <%= render "sage/examples/objects/card/markup",
+ <%= render "examples/objects/card/markup",
is_compact: false,
is_spaced: true,
title: "Spaced Card w/ Image",
@@ -77,12 +77,12 @@
action_style_primary: false,
action_style_tertiary: true,
has_img: true,
- img_path: "sage/card/card-placeholder-lg.png",
+ img_path: "docs/card/card-placeholder-lg.png",
img_alt: "Alternative text for image"
%>
- <%= render "sage/examples/objects/card/markup",
+ <%= render "examples/objects/card/markup",
is_compact: true,
is_spaced: true,
title: "Spaced & Compact w/ Image",
@@ -91,10 +91,10 @@
action_style_primary: true,
action_style_tertiary: false,
has_img: true,
- img_path: "sage/card/card-placeholder-sm.png",
+ img_path: "docs/card/card-placeholder-sm.png",
img_alt: "Alternative text for image"
%>
- <%= render "sage/examples/objects/card/markup",
+ <%= render "examples/objects/card/markup",
is_compact: true,
is_spaced: true,
title: "Spaced & Compact w/ Image",
@@ -103,12 +103,12 @@
action_style_primary: false,
action_style_tertiary: true,
has_img: true,
- img_path: "sage/card/card-placeholder-sm.png",
+ img_path: "docs/card/card-placeholder-sm.png",
img_alt: "Alternative text for image"
%>
- <%= render "sage/examples/objects/card/markup",
+ <%= render "examples/objects/card/markup",
is_compact: false,
is_spaced: false,
title: "Card w/ No Image",
@@ -122,7 +122,7 @@
%>
- <%= render "sage/examples/objects/card/markup",
+ <%= render "examples/objects/card/markup",
is_compact: false,
is_spaced: false,
title: "Card w/ No Image",
diff --git a/app/views/examples/objects/form_section/_markup.html.erb b/app/views/examples/objects/form_section/_markup.html.erb
index eff3df1db..5294ffa86 100644
--- a/app/views/examples/objects/form_section/_markup.html.erb
+++ b/app/views/examples/objects/form_section/_markup.html.erb
@@ -6,7 +6,7 @@
\ No newline at end of file
+
diff --git a/app/views/examples/objects/live_avatar/_preview.html.erb b/app/views/examples/objects/live_avatar/_preview.html.erb
index a4e9aab67..3db1833ee 100644
--- a/app/views/examples/objects/live_avatar/_preview.html.erb
+++ b/app/views/examples/objects/live_avatar/_preview.html.erb
@@ -24,7 +24,7 @@
initials: "LJ",
color: "",
hasRing: false,
- url: "sage/avatar/jay.png",
+ url: "docs/avatar/jay.png",
scale: false,
custom_class: ""
%>
@@ -54,7 +54,7 @@
initials: "CJ",
color: "sage",
hasRing: true,
- url: "sage/avatar/cj.png",
+ url: "docs/avatar/cj.png",
scale: false,
custom_class: ""
%>
diff --git a/app/views/examples/objects/live_profile_card/_preview.html.erb b/app/views/examples/objects/live_profile_card/_preview.html.erb
index e184d318d..df80446d4 100644
--- a/app/views/examples/objects/live_profile_card/_preview.html.erb
+++ b/app/views/examples/objects/live_profile_card/_preview.html.erb
@@ -7,7 +7,7 @@
initials: "JC",
color: "",
hasRing: false,
- url: "sage/avatar/jay.png",
+ url: "docs/avatar/jay.png",
iconsActive: true
%>
@@ -20,7 +20,7 @@
initials: "JC",
color: "sage",
hasRing: false,
- url: "sage/avatar/cj.png",
+ url: "docs/avatar/cj.png",
iconsActive: false
%>
diff --git a/app/views/examples/objects/live_stream_video_grid/_preview.html.erb b/app/views/examples/objects/live_stream_video_grid/_preview.html.erb
index 73a90482d..011c077ed 100644
--- a/app/views/examples/objects/live_stream_video_grid/_preview.html.erb
+++ b/app/views/examples/objects/live_stream_video_grid/_preview.html.erb
@@ -14,12 +14,12 @@
},
{
initials: 'CM',
- avatarURL: 'sage/avatar/court.png',
+ avatarURL: 'docs/avatar/court.png',
color: '',
},
{
initials: 'PJ',
- avatarURL: 'sage/avatar/phil.png',
+ avatarURL: 'docs/avatar/phil.png',
color: 'red',
},
{
diff --git a/app/views/examples/objects/nav/_preview.html.erb b/app/views/examples/objects/nav/_preview.html.erb
index 4a442057d..e6c7d9aec 100644
--- a/app/views/examples/objects/nav/_preview.html.erb
+++ b/app/views/examples/objects/nav/_preview.html.erb
@@ -1,4 +1,4 @@
-<%= render "sage/examples/objects/nav/markup",
+<%= render "examples/objects/nav/markup",
nav_links: [
{
text: "Nav Link 1",
@@ -49,4 +49,4 @@
url: "#"
}
]
-%>
\ No newline at end of file
+%>
diff --git a/app/views/examples/objects/page_heading/_preview.html.erb b/app/views/examples/objects/page_heading/_preview.html.erb
index 3af94cd0e..20ae003d4 100644
--- a/app/views/examples/objects/page_heading/_preview.html.erb
+++ b/app/views/examples/objects/page_heading/_preview.html.erb
@@ -1,6 +1,6 @@
-<%= render "sage/examples/objects/page_heading/markup",
+<%= render "examples/objects/page_heading/markup",
page_title: "Page Title",
back_icon: "sage-icon-caret-left",
back_text: "Back to Something",
back_url: "#"
-%>
\ No newline at end of file
+%>
diff --git a/app/views/examples/objects/pagination/_preview.html.erb b/app/views/examples/objects/pagination/_preview.html.erb
index 305297120..1b88e4fff 100644
--- a/app/views/examples/objects/pagination/_preview.html.erb
+++ b/app/views/examples/objects/pagination/_preview.html.erb
@@ -1,4 +1,4 @@
-<%= render "sage/examples/objects/pagination/markup",
+<%= render "examples/objects/pagination/markup",
total_count: "32",
pagination_items: [
{
@@ -95,4 +95,4 @@
is_current: false
},
]
-%>
\ No newline at end of file
+%>
diff --git a/app/views/examples/objects/panel/_preview.html.erb b/app/views/examples/objects/panel/_preview.html.erb
index ffdc5b4eb..7d42fb77b 100644
--- a/app/views/examples/objects/panel/_preview.html.erb
+++ b/app/views/examples/objects/panel/_preview.html.erb
@@ -1,5 +1,5 @@
-<%= render "sage/examples/objects/panel/markup",
+<%= render "examples/objects/panel/markup",
header: "Header Content",
body: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.",
footer: "Footer Content"
-%>
\ No newline at end of file
+%>
diff --git a/app/views/examples/objects/sidebar/_preview.html.erb b/app/views/examples/objects/sidebar/_preview.html.erb
index aa8af4220..3de6eb9c7 100644
--- a/app/views/examples/objects/sidebar/_preview.html.erb
+++ b/app/views/examples/objects/sidebar/_preview.html.erb
@@ -1,4 +1,4 @@
-<%= render "sage/examples/objects/sidebar/markup",
- body_content: "sage/examples/objects/nav/preview",
+<%= render "examples/objects/sidebar/markup",
+ body_content: "examples/objects/nav/preview",
footer_content: ""
-%>
\ No newline at end of file
+%>
diff --git a/app/views/examples/objects/tabs/_preview.html.erb b/app/views/examples/objects/tabs/_preview.html.erb
index 9492796c2..938a5b5e6 100644
--- a/app/views/examples/objects/tabs/_preview.html.erb
+++ b/app/views/examples/objects/tabs/_preview.html.erb
@@ -1,4 +1,4 @@
-<%= render "sage/examples/objects/tabs/markup",
+<%= render "examples/objects/tabs/markup",
options: [
{
text: "Active",
@@ -17,4 +17,4 @@
is_active: false
},
]
-%>
\ No newline at end of file
+%>
diff --git a/app/views/pages/index.html.erb b/app/views/pages/index.html.erb
index 1a626006f..f358535de 100644
--- a/app/views/pages/index.html.erb
+++ b/app/views/pages/index.html.erb
@@ -3,11 +3,11 @@
The Sage Design System (SDS) is our single source of truth, providing everything you need to build great products for our customers. It is the culmination of designers and developers working together to give teams the ability to ship high-quality products faster.
<% end %>
- <%= image_pack_tag("media/images/docs/sage_illustration.png", class: "sage-panel__img")%>
+ <%= image_pack_tag("docs/sage_illustration.png", class: "sage-panel__img")%>
How it works
The UI of the Kajabi Core application is a combination of Rails Components, React Components and a custom CSS Framework called Sage that applies a uniform style to both.
We think of our approach to UI at Kajabi in this way: We default to using Rails Components and a classic Rails approach to most problems and move to React where it counts.
Rails does many things very well and gives us the ability to move quickly and solve complex problems with very simple, tried and true, code solutions. With that said, sometimes we come across a problem that we want to solve that the standard rails approach will just not be enough ( Think complex interactions with many versions of state ). In this case we move to React as our default approach to solving complex, Javascript heavy problems.
Because our system contains two different approaches to UI creation, we utilize a SCSS Design System to provide the styles to both types of components (Think Bootstrap, but customized for our products). Our Design System provides the styles for the core components that make up the UI of our product.
- <%= image_pack_tag("media/images/docs/sage_structure.png", class: "sage-panel__img", style: "width: 500px; margin: 0 auto")%>
+ <%= image_pack_tag("docs/sage_structure.png", class: "sage-panel__img", style: "width: 500px; margin: 0 auto")%>
diff --git a/config/webpacker.yml b/config/webpacker.yml
index c5436d176..11452c784 100644
--- a/config/webpacker.yml
+++ b/config/webpacker.yml
@@ -17,7 +17,7 @@ default: &default
cache_manifest: false
# Extract and emit a css file
- extract_css: false
+ extract_css: true
static_assets_extensions:
- .jpg
diff --git a/lib/sage-frontend/images/docs/favicon.ico b/lib/sage-frontend/images/docs/favicon.ico
deleted file mode 100644
index f0a67c47e..000000000
Binary files a/lib/sage-frontend/images/docs/favicon.ico and /dev/null differ
diff --git a/lib/sage-frontend/javascript/define.js b/lib/sage-frontend/javascript/define.js
index 4c37f3bf6..73574bb25 100644
--- a/lib/sage-frontend/javascript/define.js
+++ b/lib/sage-frontend/javascript/define.js
@@ -1 +1,2 @@
window.Sage = window.Sage || {};
+window.Sage.docs = window.Sage.docs || {};
diff --git a/lib/sage-frontend/javascript/docs/banner.js b/lib/sage-frontend/javascript/docs/banner.js
new file mode 100644
index 000000000..75a5f709a
--- /dev/null
+++ b/lib/sage-frontend/javascript/docs/banner.js
@@ -0,0 +1,16 @@
+Sage.docs.banner = (function() {
+
+ // ==================================================
+ // Functions
+ // ==================================================
+
+ function init() {
+ Sage.banner.init();
+ }
+
+
+ return {
+ init: init
+ };
+
+})();
diff --git a/lib/sage-frontend/javascript/docs/example.js b/lib/sage-frontend/javascript/docs/example.js
new file mode 100644
index 000000000..83d741d57
--- /dev/null
+++ b/lib/sage-frontend/javascript/docs/example.js
@@ -0,0 +1,46 @@
+Sage.docs.example = (function() {
+
+ var expandedText = "Collapse this code snippet";
+ var collapsedText = "Expand this code snippet";
+
+
+ // ==================================================
+ // Functions
+ // ==================================================
+
+
+ // Note: assumes we won't have multiple code snippets per page
+ function showHideCodeSample() {
+ var codeBtn = document.querySelector('.example__expand-btn'),
+ codeSnippet = codeBtn.closest('.sage-code-snippet');
+
+ codeBtn.addEventListener('click', function(e) {
+ updateButtonState(e.target, this);
+ e.target.parentElement.classList.toggle('example__code--expanded');
+ e.target.nextElementSibling.focus();
+ });
+ }
+
+
+ // toggle code example button state
+ function updateButtonState(evt) {
+ if (evt.getAttribute('aria-expanded') === 'false') {
+ evt.setAttribute('aria-expanded', 'true');
+ evt.innerText = expandedText;
+ } else {
+ evt.setAttribute('aria-expanded', 'false');
+ evt.innerText = collapsedText;
+ }
+ }
+
+
+ function init() {
+ showHideCodeSample();
+ }
+
+
+ return {
+ init: init
+ };
+
+})();
diff --git a/lib/sage-frontend/javascript/docs/index.js b/lib/sage-frontend/javascript/docs/index.js
index 8c0a92838..1b0a3aee4 100644
--- a/lib/sage-frontend/javascript/docs/index.js
+++ b/lib/sage-frontend/javascript/docs/index.js
@@ -1,3 +1,34 @@
-require("../define")
+require('../define')
-console.warn('JS DOCS!');
+require('./live-option-menu')
+require('./banner')
+require('./example')
+require('./meter')
+require('./inputhelper')
+require('./example')
+
+// Conditional routing
+// NOTE: modules must be imported above to be initialized below
+if (document.querySelector('.sage-docs') !== null) {
+
+ if (document.querySelector('.sage-live-option-menu-anchor') !== null) {
+ Sage.docs.liveOptionMenu.init();
+ }
+
+ if (document.getElementById('pw-meter-example') !== null) {
+ Sage.docs.meter.init();
+ }
+
+ if (document.querySelector('.sage-banner--active') !== null && document.querySelector('.example__preview--page') !== null) {
+ Sage.docs.banner.init();
+ }
+
+ if (document.querySelector('.example__code') !== null && document.querySelector('.example__expand-btn') !== null) {
+ Sage.docs.example.init();
+ }
+
+ if (document.querySelector('.sage-input-helper') !== null && document.querySelector('[data-js-example="input-helper"]') !== null) {
+ Sage.docs.inputhelper.init();
+ }
+
+}
diff --git a/lib/sage-frontend/javascript/docs/inputhelper.js b/lib/sage-frontend/javascript/docs/inputhelper.js
new file mode 100644
index 000000000..ade0c53fb
--- /dev/null
+++ b/lib/sage-frontend/javascript/docs/inputhelper.js
@@ -0,0 +1,20 @@
+Sage.docs.inputhelper = (function() {
+
+ // ==================================================
+ // Functions
+ // ==================================================
+
+ function init() {
+ var helperFieldset = document.querySelector('[data-js-example="input-helper"]');
+ var helperExample = helperFieldset.querySelector('.sage-input-helper');
+
+ helperExample.classList.add('sage-input-helper--visible');
+ helperExample.style.position = 'relative';
+ }
+
+
+ return {
+ init: init
+ };
+
+})();
diff --git a/lib/sage-frontend/javascript/docs/live-option-menu.js b/lib/sage-frontend/javascript/docs/live-option-menu.js
new file mode 100644
index 000000000..34dfb9e3c
--- /dev/null
+++ b/lib/sage-frontend/javascript/docs/live-option-menu.js
@@ -0,0 +1,30 @@
+Sage.docs.liveOptionMenu = (function() {
+ // ==================================================
+ // Variables
+ // ==================================================
+ var sageLiveOptionMenu = document.querySelectorAll('.sage-live-option-menu-anchor');
+
+
+ // ==================================================
+ // Functions
+ // ==================================================
+
+ function init() {
+
+ // Simulate contextual menu
+ sageLiveOptionMenu.forEach(function(anchor) {
+ anchor.addEventListener('click', function(e) {
+ var target = e.currentTarget;
+ if (!target) return;
+ var isExpanded = target.getAttribute('aria-expanded') == 'true';
+ target.setAttribute('aria-expanded', !isExpanded);
+ });
+ });
+ }
+
+
+ return {
+ init: init
+ }
+
+})();
diff --git a/lib/sage-frontend/javascript/docs/meter.js b/lib/sage-frontend/javascript/docs/meter.js
new file mode 100644
index 000000000..d38495fb6
--- /dev/null
+++ b/lib/sage-frontend/javascript/docs/meter.js
@@ -0,0 +1,23 @@
+Sage.docs.meter = (function() {
+
+ // ==================================================
+ // Functions
+ // ==================================================
+
+
+ function init() {
+ var meterUpdate = document.getElementById('pw-meter-example'),
+ meterBar = document.getElementById('pw-hint-meter');
+
+ meterUpdate.addEventListener('input', function(e) {
+ meterBar.value = e.target.value;
+ Sage.meter.updateMeter('[js-meter-type="password"]');
+ });
+ }
+
+
+ return {
+ init: init
+ };
+
+})();
diff --git a/lib/sage-frontend/javascript/system/alert.js b/lib/sage-frontend/javascript/system/alert.js
new file mode 100644
index 000000000..f0bcf0d1c
--- /dev/null
+++ b/lib/sage-frontend/javascript/system/alert.js
@@ -0,0 +1,24 @@
+Sage.alert = (function () {
+ // ==================================================
+ // Variables
+ // ==================================================
+
+ var alertCloseBtns = document.querySelectorAll(".sage-alert__close");
+
+ // ==================================================
+ // Functions
+ // ==================================================
+
+ alertCloseBtns.forEach(function (btn) {
+ var alert = btn.closest(".sage-alert");
+ btn.addEventListener("click", function () {
+ alert.style.display = "none";
+ });
+ });
+
+ function init() {}
+
+ return {
+ init: init,
+ };
+})();
diff --git a/lib/sage-frontend/javascript/system/banner.js b/lib/sage-frontend/javascript/system/banner.js
new file mode 100644
index 000000000..f9666a151
--- /dev/null
+++ b/lib/sage-frontend/javascript/system/banner.js
@@ -0,0 +1,46 @@
+Sage.banner = (function() {
+
+ // ==================================================
+ // Variables
+ // ==================================================
+
+ var activeBanner, bannerCloseBtn;
+
+ var bodyClass = "banner-active";
+ var bannerClass = ".sage-banner--active";
+
+
+ // ==================================================
+ // Functions
+ // ==================================================
+
+ // check to see if an active banner exists
+ function bannerIsEnabled() {
+ return document.querySelector(bannerClass) !== null;
+ }
+
+ function bindEvents() {
+ bannerCloseBtn.addEventListener('click', function(e) {
+ e.target.parentElement.classList.toggle('sage-banner--active');
+ document.querySelector('body').classList.toggle(bodyClass);
+ });
+ }
+
+
+ function init() {
+ if (bannerIsEnabled()) {
+ document.body.classList.add(bodyClass);
+ activeBanner = document.querySelector(bannerClass);
+ bannerCloseBtn = activeBanner.querySelector('.sage-banner__close');
+
+ bindEvents();
+ }
+ }
+
+
+ return {
+ init: init,
+ bannerIsEnabled: bannerIsEnabled
+ };
+
+})();
diff --git a/lib/sage-frontend/javascript/system/define.js b/lib/sage-frontend/javascript/system/define.js
new file mode 100644
index 000000000..f7ba27d2c
--- /dev/null
+++ b/lib/sage-frontend/javascript/system/define.js
@@ -0,0 +1,2 @@
+var Sage = Sage || {};
+Sage.docs = Sage.docs || {};
diff --git a/lib/sage-frontend/javascript/system/example.js b/lib/sage-frontend/javascript/system/example.js
new file mode 100644
index 000000000..d9b2b4496
--- /dev/null
+++ b/lib/sage-frontend/javascript/system/example.js
@@ -0,0 +1,38 @@
+Sage.module = (function() {
+ /*
+ This is an example template. To create a module:
+ 1. Duplicate or copy this structure
+ 2. Rename `Sage.module` above to your module name
+ 3. Import (require) the module in `sage_system.js`
+ 4. When applicable, initialize in `init.js`
+ */
+
+ // ==================================================
+ // Variables
+ // ==================================================
+
+
+ // ==================================================
+ // Functions
+ // ==================================================
+
+ /*
+ Add functions here that should run when this module
+ is initialized in `init.js`: event handlers, etc.
+ */
+ function init() {
+
+ }
+
+
+ /*
+ Functions declared above are considered "private" and
+ not exposed to the global `window` object. To allow a
+ function to be "public" and usable in other modules,
+ return them below. See `util.js` for examples.
+ */
+ return {
+ init: init
+ };
+
+})();
diff --git a/lib/sage-frontend/javascript/system/index.js b/lib/sage-frontend/javascript/system/index.js
index f77c456a7..af5f7464f 100644
--- a/lib/sage-frontend/javascript/system/index.js
+++ b/lib/sage-frontend/javascript/system/index.js
@@ -1,12 +1,17 @@
-require("../define")
-// require("./util")
+//= require lib/zxcvbn
-// require("./tooltip")
-// require("./sidebar")
-// require("./overlay")
-// require("./banner")
-// require("./alert")
+require('../define')
+require('./util')
-// require("./init")
+require('./table')
+require('./tooltip')
+require('./sidebar')
+require('./overlay')
+require('./banner')
+require('./alert')
+require('./select')
+require('./meter')
+require('./inputgroup')
+require('./inputhelper')
-console.warn('JS System!');
+require('./init')
diff --git a/lib/sage-frontend/javascript/system/init.js b/lib/sage-frontend/javascript/system/init.js
new file mode 100644
index 000000000..235765c22
--- /dev/null
+++ b/lib/sage-frontend/javascript/system/init.js
@@ -0,0 +1,56 @@
+Sage.init = function(elementNamesToInit) {
+ var shouldInit = function(elementName, selector) {
+ return elementNamesToInit.includes(elementName) && document.querySelector(selector) !== null;
+ };
+
+ // Initialize Table
+ if ( shouldInit('table', '.sage-table') ) {
+ Sage.table.init();
+ }
+
+ // Initialize Tooltip
+ if ( shouldInit('tooltip', '[data-tooltip]') ) {
+ Sage.tooltip();
+ }
+
+ // Initialize Sidebar
+ if ( shouldInit('sidebar', '.sage-sidebar') ) {
+ Sage.sidebar.init();
+ }
+
+ // Initialize Overlay
+ if ( shouldInit('overlay', '.sage-overlay') ) {
+ Sage.overlay.init();
+ }
+
+ // Initialize Alert
+ if ( shouldInit('alert', '.sage-alert') ) {
+ Sage.alert.init();
+ }
+
+ // Initialize Select
+ if ( shouldInit('select', '.sage-select') ) {
+ Sage.select.init();
+ }
+
+ // Initialize Input groups
+ if ( shouldInit('inputgroup', '.sage-input-group') ) {
+ Sage.inputgroup.init();
+ }
+
+ // Initialize Input groups
+ if ( shouldInit('inputhelper', '.sage-input-helper') ) {
+ Sage.inputhelper.init();
+ }
+
+ // Initialize Meter
+ if ( shouldInit('meter', '.sage-meter') ) {
+ Sage.meter.init();
+ }
+
+ // Initialize Banner
+ if ( shouldInit('banner', '.sage-banner--active') && !document.querySelector('.sage-docs') ) {
+ Sage.banner.init();
+ }
+
+}
diff --git a/lib/sage-frontend/javascript/system/inputgroup.js b/lib/sage-frontend/javascript/system/inputgroup.js
new file mode 100644
index 000000000..583b70821
--- /dev/null
+++ b/lib/sage-frontend/javascript/system/inputgroup.js
@@ -0,0 +1,47 @@
+Sage.inputgroup = (function() {
+
+ // ==================================================
+ // Functions
+ // ==================================================
+
+ function togglePasswordDisplay(evt) {
+ var parentEle = evt.target.parentElement,
+ field = parentEle.querySelector(".sage-input__field"),
+ activeClassName = "sage-input-group--visible";
+
+ if (field.type === "password") {
+ field.type = "text";
+ parentEle.classList.add(activeClassName);
+ } else {
+ field.type = "password";
+ parentEle.classList.remove(activeClassName);
+ }
+
+ field.focus();
+ }
+
+
+ function bindPWEvents() {
+ var pwShowBtn = Sage.util.nodelistToArray(document.querySelectorAll("[data-js-mask='password']"));
+
+ // show/hide password text; assumes multiple password fields
+ pwShowBtn.forEach(function(btn) {
+ btn.addEventListener("click", function(e) {
+ togglePasswordDisplay(e);
+ });
+ });
+ }
+
+
+ function init() {
+ if (document.querySelector(".sage-input-group__toggle").length !== null) {
+ bindPWEvents();
+ }
+ }
+
+
+ return {
+ init: init
+ };
+
+})();
diff --git a/lib/sage-frontend/javascript/system/inputhelper.js b/lib/sage-frontend/javascript/system/inputhelper.js
new file mode 100644
index 000000000..b773bf0e1
--- /dev/null
+++ b/lib/sage-frontend/javascript/system/inputhelper.js
@@ -0,0 +1,153 @@
+Sage.inputhelper = (function() {
+
+ // ==================================================
+ // Variables
+ // ==================================================
+ var spcValues = /(?=.*[~`!@#$%^&*|(){}/=:;.,<>+-])/g;
+ var numValues = /(?=[0-9])/g;
+ var minLengthPW = 8;
+
+ var visibleHintClass = "sage-input-helper--visible";
+ var passingClass = "sage-hint__list-item--success";
+ var inputErrorClass = "sage-input--error";
+ var sageMeterBar = ".sage-meter__bar";
+
+
+ // ==================================================
+ // Functions
+ // ==================================================
+
+ // update strength level of password
+ function updateStrengthMeter(inputValue, meter, reqsMet) {
+ var trueScore, revisedScore;
+
+ if (typeof zxcvbn !== "undefined") {
+ trueScore = zxcvbn(inputValue).score;
+ // artificially reduce score if requirements are not met
+ revisedScore = (reqsMet === true ? trueScore : trueScore - 1);
+ // update meter value
+ meter.value = revisedScore;
+ // append corresponding classes
+ Sage.meter.updateMeter(sageMeterBar);
+ } else {
+ // hides meter if zxcvbn library is not loaded
+ meter.closest(".sage-meter").style.display = "none";
+ }
+ }
+
+ // test value against regex string
+ function regexTest(str, testMatch) {
+ return testMatch.test(str);
+ }
+
+ // check for password length
+ function pwLength(str) {
+ return str.length >= minLengthPW;
+ }
+
+ function checkRequirements(ele, reqs, meter) {
+ var val = ele.value;
+ var metChar = false,
+ metSym = false,
+ metNum = false,
+ metAllReqs = false;
+
+ reqs.forEach(function(item) {
+ if (item.type === "characters") {
+ metChar = updateCriteria(pwLength(val), item.id);
+ } else if (item.type === "symbols") {
+ metSym = updateCriteria(regexTest(val, spcValues), item.id);
+ } else if (item.type === "numbers") {
+ metNum = updateCriteria(regexTest(val, numValues), item.id);
+ }
+ });
+
+ // all requirements are met
+ if (metChar && metSym && metNum) {
+ metAllReqs = true;
+ ele.parentElement.classList.remove(inputErrorClass);
+ }
+
+ updateStrengthMeter(val, meter, metAllReqs);
+ }
+
+ // toggles item criteria
+ function updateCriteria(bool, itemID) {
+ var criteria = document.getElementById(itemID);
+
+ if (bool === true ) {
+ criteria.classList.add(passingClass);
+ } else {
+ criteria.classList.remove(passingClass);
+ }
+ return bool;
+ }
+
+
+ // toggles visibility of helper window
+ function toggleHintIE(ele, className) {
+ if (ele.classList.contains(className)) {
+ ele.classList.remove(className);
+ } else {
+ ele.classList.add(className);
+ }
+ }
+
+
+ // trigger classes for active state in IE
+ function focusBlurIE(field) {
+ field.addEventListener("focus", function(e) {
+ toggleHintIE(helper, visibleHintClass);
+ });
+ field.addEventListener("blur", function(e) {
+ toggleHintIE(helper, visibleHintClass);
+ });
+ }
+
+
+ // bind events related to each input helper for passwords
+ function bindPWEvents(helper) {
+ var fieldID = helper.getAttribute("data-js-helper-target"),
+ field = document.getElementById(fieldID),
+ meter = helper.querySelector(".sage-meter__bar");
+
+ var helperList = Sage.util.nodelistToArray(helper.querySelectorAll(".sage-hint__list-item")),
+ helperReqItems = helperList.map(function(ele) {
+ return {
+ id: ele.id,
+ type: ele.getAttribute("data-js-hint-type") || null
+ }
+ });
+
+ if (Sage.util.isIE()) {
+ focusBlurIE(field);
+ }
+
+ field.addEventListener("input", function(e) {
+ var targetField = e.target,
+ fieldParent = e.target.parentElement;
+
+ // add error state
+ fieldParent.classList.add(inputErrorClass);
+ checkRequirements(targetField, helperReqItems, meter);
+ });
+ }
+
+
+ function init() {
+ if (document.querySelector("[data-js-helper-target]") !== null) {
+ var helperTargets = Sage.util.nodelistToArray(document.querySelectorAll("[data-js-helper-target]"));
+
+ helperTargets.forEach(function(helper) {
+ bindPWEvents(helper);
+ });
+ }
+ }
+
+
+ return {
+ init: init,
+ updateStrengthMeter: updateStrengthMeter
+ };
+
+})();
diff --git a/lib/sage-frontend/javascript/system/meter.js b/lib/sage-frontend/javascript/system/meter.js
new file mode 100644
index 000000000..507679783
--- /dev/null
+++ b/lib/sage-frontend/javascript/system/meter.js
@@ -0,0 +1,60 @@
+Sage.meter = (function() {
+
+ // ==================================================
+ // Functions
+ // ==================================================
+
+ // builds an object from meter data
+ function getMeterValues(ele) {
+ var meter = document.querySelector(ele);
+
+ return {
+ current: meter.value,
+ optimum: attributeValue(meter, "optimum"),
+ min: attributeValue(meter, "min"),
+ max: attributeValue(meter, "max")
+ };
+ }
+
+
+ // appends classes to meter for styling of current state
+ function updateMeter(ele) {
+ var meter = document.querySelector(ele),
+ values = getMeterValues(ele),
+ optimumThreshold = 0.5;
+
+ // reset any meter classes
+ meter.className = "sage-meter__bar";
+
+ // assign new class based on current value
+ if (values.current === values.max) {
+ meter.classList.add('sage-meter__bar--max');
+ } else if ((values.optimum !== 0) && values.current >= values.optimum) {
+ meter.classList.add('sage-meter__bar--optimum');
+ } else if ((values.optimum !== 0) && ((values.current / values.optimum) > optimumThreshold)) {
+ meter.classList.add('sage-meter__bar--med');
+ } else if (values.current <= values.max) {
+ meter.classList.add('sage-meter__bar--low');
+ }
+ }
+
+
+ // retrieve attribute value or default to 0
+ function attributeValue(ele, attributeName) {
+ return ele.hasAttribute(attributeName) ? parseInt(ele.getAttribute(attributeName)) : 0;
+ }
+
+
+ function init() {
+ if (document.querySelector('[js-meter-type="password"]') !== null) {
+ updateMeter('[js-meter-type="password"]');
+ }
+ }
+
+
+ return {
+ init: init,
+ updateMeter: updateMeter
+ };
+
+})();
diff --git a/lib/sage-frontend/javascript/system/overlay.js b/lib/sage-frontend/javascript/system/overlay.js
new file mode 100644
index 000000000..c895c70af
--- /dev/null
+++ b/lib/sage-frontend/javascript/system/overlay.js
@@ -0,0 +1,40 @@
+Sage.overlay = (function() {
+
+ // ==================================================
+ // Variables
+ // ==================================================
+ var sageNavOverlay = document.querySelector('.sage-overlay');
+ var bodyClassName = 'sage-overlay--open';
+
+
+ // ==================================================
+ // Functions
+ // ==================================================
+
+ // open or close overlay
+ function toggleOverlay(open) {
+ return open === 'open' ? sageNavOverlay.classList.add(bodyClassName) : sageNavOverlay.classList.remove(bodyClassName)
+ }
+
+
+ function init() {
+ // Close overlay and sidebar menu on click
+ sageNavOverlay.addEventListener('click', function(e) {
+ if (document.querySelector('.sage-overlay--open') !== null) {
+ toggleOverlay('close');
+ Sage.sidebar.resetSideNav();
+ }
+ });
+
+ }
+
+
+
+
+
+ return {
+ init: init,
+ toggleOverlay: toggleOverlay
+ };
+
+})();
diff --git a/lib/sage-frontend/javascript/system/select.js b/lib/sage-frontend/javascript/system/select.js
new file mode 100644
index 000000000..3c6291ad7
--- /dev/null
+++ b/lib/sage-frontend/javascript/system/select.js
@@ -0,0 +1,47 @@
+Sage.select = (function() {
+
+ // ==================================================
+ // Variables
+ // ==================================================
+ var elSelectParentList = document.querySelectorAll('.sage-select'),
+ classActive = 'sage-select--value-selected',
+ htmlArrow = '
';
+
+ // ==================================================
+ // Functions
+ // ==================================================
+
+ function updateValueSelectedState(value, elSelectParent) {
+ var cL = elSelectParent.classList;
+ Sage.util.isEmptyString(value) ? cL.remove(classActive) : cL.add(classActive);
+ }
+
+ function disableSelectPromptOptions(elSelect) {
+ [].forEach.call(elSelect.options, function(elOption) {
+ if (elOption.text.startsWith('--')) elOption.setAttribute('disabled', true);
+ });
+ }
+
+ function bindEvents(elSelect, elSelectParent) {
+ elSelect.addEventListener('change', function(evt) {
+ updateValueSelectedState(evt.target.value, elSelectParent)
+ });
+ }
+
+ function init() {
+ elSelectParentList.forEach(function(elSelectParent) {
+ var elSelect = elSelectParent.querySelector('select');
+
+ elSelectParent.insertAdjacentHTML('beforeEnd', htmlArrow);
+ disableSelectPromptOptions(elSelect);
+ updateValueSelectedState(elSelect.value, elSelectParent);
+
+ bindEvents(elSelect, elSelectParent);
+ });
+ }
+
+ return {
+ init: init,
+ };
+
+})();
diff --git a/lib/sage-frontend/javascript/system/sidebar.js b/lib/sage-frontend/javascript/system/sidebar.js
new file mode 100644
index 000000000..5ee33a334
--- /dev/null
+++ b/lib/sage-frontend/javascript/system/sidebar.js
@@ -0,0 +1,76 @@
+Sage.sidebar = (function() {
+
+ // ==================================================
+ // Variables
+ // ==================================================
+ var sageToggleBtns = Sage.util.nodelistToArray(document.querySelectorAll('[data-js-target-type="sidebar"]'));
+
+
+ // ==================================================
+ // Functions
+ // ==================================================
+
+ // open or close sidenav
+ function toggleSidebar(ele, evt) {
+ var buttonTarget = document.getElementById(Sage.util.getBtnTarget(evt));
+
+ buttonTarget.classList.toggle('sage-sidebar--open');
+
+ if (buttonTarget.classList.contains('sage-sidebar--open')) {
+ ele.setAttribute('aria-expanded', true);
+ Sage.overlay.toggleOverlay('open');
+ } else {
+ ele.setAttribute('aria-expanded', false);
+ Sage.overlay.toggleOverlay('closed');
+ }
+ }
+
+
+ // reset sidenav state to closed/default
+ function resetSideNav() {
+ if (document.querySelector('.sage-sidebar--open') !== null) {
+ var openSidebar = document.querySelector('.sage-sidebar--open');
+
+ openSidebar.classList.remove('sage-sidebar--open');
+ sageToggleBtns.forEach(function(btn) {
+ btn.setAttribute('aria-expanded', false);
+ });
+ Sage.overlay.toggleOverlay('close');
+ }
+ }
+
+
+ // ==================================================
+ // Event handlers
+ // ==================================================
+
+ function init() {
+
+ // Toggle sidebar on menu button click
+ sageToggleBtns.forEach(function(btn) {
+ if (btn.dataset.jsBtnTarget.includes('example-')) {
+ return;
+ }
+
+ btn.addEventListener('click', function(e) {
+ toggleSidebar(this, e);
+ });
+ });
+
+ // Close overlay and menu on esc keypress
+ document.addEventListener('keyup', function(e) {
+ var keyNum = 'which' in e ? e.which : e.keyCode;
+
+ if (keyNum === 27 && document.querySelector('.sage-sidebar--open') !== null) { // esc key
+ resetSideNav();
+ }
+ });
+ }
+
+
+ return {
+ init: init,
+ resetSideNav: resetSideNav
+ };
+
+})();
diff --git a/lib/sage-frontend/javascript/system/table.js b/lib/sage-frontend/javascript/system/table.js
new file mode 100644
index 000000000..f864d201a
--- /dev/null
+++ b/lib/sage-frontend/javascript/system/table.js
@@ -0,0 +1,47 @@
+Sage.table = (function() {
+
+ // ==================================================
+ // Functions
+ // ==================================================
+
+ // column sort functions
+ function sortEvents() {
+ var sortableCols = Sage.util.nodelistToArray(document.querySelectorAll('.sage-table__sort'));
+
+ // update sorted column
+ sortableCols.forEach(function(column) {
+ column.addEventListener('click', function(e) {
+ if (e.target.classList.contains('sage-table__sort--selected')) {
+ this.classList.toggle('sage-table__sort--ascending');
+ } else {
+ // NOTE: IE doesn't support multiple args with classList, so we have to run this twice
+ removeActiveStyle(sortableCols, 'sage-table__sort--selected');
+ removeActiveStyle(sortableCols, 'sage-table__sort--ascending');
+
+ this.classList.add('sage-table__sort--selected');
+ }
+ });
+ });
+ }
+
+
+ // reset classes on elements
+ function removeActiveStyle(arr, className) {
+ arr.forEach(function(ele) {
+ ele.classList.remove(className);
+ });
+ }
+
+
+ function init() {
+ if (document.querySelector('.sage-table--sortable') !== null) {
+ sortEvents();
+ }
+ }
+
+
+ return {
+ init: init
+ };
+
+})();
diff --git a/lib/sage-frontend/javascript/system/tooltip.js b/lib/sage-frontend/javascript/system/tooltip.js
new file mode 100644
index 000000000..c2113e933
--- /dev/null
+++ b/lib/sage-frontend/javascript/system/tooltip.js
@@ -0,0 +1,100 @@
+Sage.tooltip = function() {
+
+ // ==================================================
+ // Variables
+ // ==================================================
+ var toolTips = Sage.util.nodelistToArray(document.querySelectorAll("[data-tooltip]"));
+ var toolTipClassname = ".sage-tooltip";
+
+
+ // ==================================================
+ // Functions
+ // ==================================================
+
+ toolTips.forEach(function(item) {
+ item.addEventListener("mouseover", function(e) {
+ buildToolTip(e);
+ });
+
+ item.addEventListener("mouseout", function(e) {
+ if (e.target.hasAttribute("data-tooltip")) {
+ window.requestAnimationFrame(removeTooltip);
+ }
+ });
+ });
+
+
+ // tooltip template
+ function buildToolTip(e) {
+ if (!e.target.hasAttribute("data-tooltip")) return;
+
+ var pos = e.target.getAttribute("data-position") || "top";
+ var tooltip = document.createElement("div");
+ tooltip.className = "sage-tooltip";
+ tooltip.innerHTML = e.target.getAttribute("data-tooltip");
+ tooltip.position = e.target.getAttribute("data-position");
+ tooltip.dataItems = ["data-tooltip-theme", "data-tooltip-size"];
+
+ if (!tooltip.innerHTML.length > 0) return;
+ generateClasses(tooltip, e);
+
+ document.body.appendChild(tooltip);
+ positionTooltip(e.target, tooltip, pos);
+ }
+
+
+ // Removes tooltip from DOM
+ function removeTooltip() {
+ if (document.querySelector(toolTipClassname) !== null) {
+ document.body.removeChild(document.querySelector(toolTipClassname));
+ }
+ }
+
+
+ // Builds list of modifier classes from array of data-attributes
+ function generateClasses(ele, evt) {
+ ele.dataItems.forEach(function(item) {
+ var tgt = evt.target;
+ if (tgt.hasAttribute(item)) {
+ ele.classList.add("sage-tooltip--" + tgt.getAttribute(item));
+ }
+ });
+ }
+
+
+ function positionTooltip(parent, tooltip, position) {
+ var parentCoords = parent.getBoundingClientRect(),
+ dist = 8,
+ left,
+ top;
+
+ switch (position) {
+ case "left":
+ top = (parseInt(parentCoords.top) + parseInt(parentCoords.bottom)) / 2 - tooltip.offsetHeight / 2;
+ left = parseInt(parentCoords.left) - dist - tooltip.offsetWidth;
+ if (parseInt(parentCoords.left) - tooltip.offsetWidth < 0) {
+ left = dist;
+ }
+ break;
+ case "right":
+ top = (parseInt(parentCoords.top) + parseInt(parentCoords.bottom)) / 2 - tooltip.offsetHeight / 2;
+ left = parentCoords.right + dist;
+ if (parseInt(parentCoords.right) + tooltip.offsetWidth > document.documentElement.clientWidth) {
+ left = document.documentElement.clientWidth - tooltip.offsetWidth - dist;
+ }
+ break;
+ case "bottom":
+ top = parseInt(parentCoords.bottom) + dist;
+ left = parseInt(parentCoords.left) + (parent.offsetWidth - tooltip.offsetWidth) / 2;
+ break;
+ default:
+ case "top":
+ top = parseInt(parentCoords.top) - tooltip.offsetHeight - dist;
+ left = parseInt(parentCoords.left) + (parent.offsetWidth - tooltip.offsetWidth) / 2;
+ }
+
+ tooltip.style.left = left + "px";
+ tooltip.style.top = top + pageYOffset + "px";
+ tooltip.classList.add("sage-tooltip-" + position);
+ }
+};
diff --git a/lib/sage-frontend/javascript/system/util.js b/lib/sage-frontend/javascript/system/util.js
new file mode 100644
index 000000000..7f0ff2e1b
--- /dev/null
+++ b/lib/sage-frontend/javascript/system/util.js
@@ -0,0 +1,29 @@
+Sage.util = (function(Sage) {
+
+ // retrieve button target from aria-controls or data attribute
+ function getBtnTarget(e) {
+ return e.target.getAttribute('aria-controls') ? e.target.getAttribute('aria-controls') : e.target.getAttribute('data-js-menu');
+ }
+
+
+ // convert nodelist to array for iteration
+ function nodelistToArray(selection) {
+ return Array.prototype.slice.apply(selection);
+ }
+
+ function isEmptyString(str) {
+ return (!str || 0 === str.length);
+ }
+
+ function isIE() {
+ return document.documentMode ? true : false;
+ }
+
+ return {
+ getBtnTarget: getBtnTarget,
+ nodelistToArray: nodelistToArray,
+ isEmptyString: isEmptyString,
+ isIE: isIE
+ };
+
+})(Sage);
diff --git a/lib/sage-frontend/packs/docs.js b/lib/sage-frontend/packs/docs.js
index db58774e9..107a67c91 100644
--- a/lib/sage-frontend/packs/docs.js
+++ b/lib/sage-frontend/packs/docs.js
@@ -6,5 +6,3 @@ import 'regenerator-runtime/runtime'
require.context('../images/docs', true)
import '../stylesheets/docs/index.scss'
import '../javascript/docs/index'
-
-console.warn('WEBPACKER DOCS HAS LOADED');
diff --git a/lib/sage-frontend/packs/system.js b/lib/sage-frontend/packs/system.js
index a74b02875..52249798c 100644
--- a/lib/sage-frontend/packs/system.js
+++ b/lib/sage-frontend/packs/system.js
@@ -6,5 +6,3 @@ import 'regenerator-runtime/runtime'
require.context('../images/system', true)
import '../stylesheets/system/index.scss'
import '../javascript/system/index'
-
-console.warn('WEBPACKER SYSTEM HAS LOADED');
diff --git a/lib/sage-frontend/stylesheets/system/core/_icons.scss b/lib/sage-frontend/stylesheets/system/core/_icons.scss
index c92e98a1d..6823e590c 100644
--- a/lib/sage-frontend/stylesheets/system/core/_icons.scss
+++ b/lib/sage-frontend/stylesheets/system/core/_icons.scss
@@ -15,6 +15,7 @@ $sage-icon-li-margin-right: 0.4em !default;
font-family: "Sage";
src: url("#{$sage-icon-font-path}/Sage.eot");
src: url("#{$sage-icon-font-path}/Sage.eot") format("embedded-opentype"),
+ url("#{$sage-icon-font-path}/Sage.woff2") format("woff2"),
url("#{$sage-icon-font-path}/Sage.woff") format("woff"),
url("#{$sage-icon-font-path}/Sage.ttf") format("truetype"),
url("#{$sage-icon-font-path}/Sage.svg") format("svg");
diff --git a/lib/sage-frontend/stylesheets/system/core/_mixins.scss b/lib/sage-frontend/stylesheets/system/core/_mixins.scss
index eb7d45810..aeed8bd65 100644
--- a/lib/sage-frontend/stylesheets/system/core/_mixins.scss
+++ b/lib/sage-frontend/stylesheets/system/core/_mixins.scss
@@ -78,6 +78,7 @@
arguments: $content
================================================== */
+ /* stylelint-disable */
@mixin placeholder {
&::-webkit-input-placeholder {
@content;
@@ -91,7 +92,8 @@
&:-ms-input-placeholder {
@content;
}
-} /* stylelint-enable */
+}
+/* stylelint-enable */
/* ==================================================
diff --git a/lib/sage-frontend/stylesheets/system/index.scss b/lib/sage-frontend/stylesheets/system/index.scss
index 880d3cb46..b0577a30a 100644
--- a/lib/sage-frontend/stylesheets/system/index.scss
+++ b/lib/sage-frontend/stylesheets/system/index.scss
@@ -1,13 +1,3 @@
-body::before {
- content: "WEBPACKER IS LOADED";
- color: pink;
- display: fixed;
- background: blue;
- padding: 40px;
- top: 0;
- right: 0;
-}
-
// Reset
@import "vendor/reboot";
@@ -55,6 +45,7 @@ body::before {
@import "utilities/hidden";
// Elements
+@import "patterns/elements/meter";
@import "patterns/elements/live_stream_wrapper";
@import "patterns/elements/description";
@import "patterns/elements/label";
@@ -72,6 +63,8 @@ body::before {
@import "patterns/elements/overlay";
// Objects
+@import "patterns/objects/input_helper";
+@import "patterns/objects/input_group";
@import "patterns/objects/billboard";
@import "patterns/objects/pagination";
@import "patterns/objects/banner";
diff --git a/lib/sage-frontend/images/docs/apple-touch-icon.png b/public/apple-touch-icon.png
similarity index 100%
rename from lib/sage-frontend/images/docs/apple-touch-icon.png
rename to public/apple-touch-icon.png
diff --git a/lib/sage-frontend/images/docs/favicon-16x16.png b/public/favicon-16x16.png
similarity index 100%
rename from lib/sage-frontend/images/docs/favicon-16x16.png
rename to public/favicon-16x16.png
diff --git a/lib/sage-frontend/images/docs/favicon-32x32.png b/public/favicon-32x32.png
similarity index 100%
rename from lib/sage-frontend/images/docs/favicon-32x32.png
rename to public/favicon-32x32.png
diff --git a/public/favicon.ico b/public/favicon.ico
index e69de29bb..f0a67c47e 100644
Binary files a/public/favicon.ico and b/public/favicon.ico differ