Merge branch 'feature-sidebar' into feature

# Conflicts:
#	netbox/project-static/dist/netbox.js
#	netbox/project-static/dist/netbox.js.map
This commit is contained in:
checktheroads
2021-07-29 17:39:07 -07:00
24 changed files with 2191 additions and 230 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -41,6 +41,32 @@
"eslint-plugin-prettier": "^3.3.1",
"prettier": "^2.2.1",
"prettier-eslint": "^12.0.0",
"stylelint": "^13.13.1",
"stylelint-config-twbs-bootstrap": "^2.2.3",
"typescript": "^4.2.3"
},
"stylelint": {
"extends": "stylelint-config-twbs-bootstrap/scss",
"rules": {
"selector-max-class": 16,
"selector-max-compound-selectors": 16,
"selector-no-qualifying-type": [
true,
{
"ignore": [
"attribute",
"class"
]
}
],
"number-leading-zero": "always",
"string-quotes": "single",
"selector-pseudo-element-colon-notation": "single",
"declaration-property-value-disallowed-list": {
"border": "none",
"outline": "none"
},
"scss/selector-no-union-class-name": true
}
}
}

View File

@@ -1,22 +1,246 @@
import { getElement, getElements } from './util';
import { StateManager } from './state';
import { getElements, isElement } from './util';
const breakpoints = {
sm: 540,
md: 720,
lg: 960,
xl: 1140,
};
type NavState = { pinned: boolean };
type BodyAttr = 'show' | 'hide' | 'hidden' | 'pinned';
function toggleBodyPosition(position: HTMLBodyElement['style']['position']): void {
for (const element of getElements('body')) {
element.style.position = position;
class SideNav {
/**
* Sidenav container element.
*/
private base: HTMLDivElement;
/**
* SideNav internal state manager.
*/
private state: StateManager<NavState>;
constructor(base: HTMLDivElement) {
this.base = base;
this.state = new StateManager<NavState>({ pinned: true }, { persist: true });
this.init();
this.initLinks();
}
/**
* Determine if `document.body` has a sidenav attribute.
*/
private bodyHas(attr: BodyAttr): boolean {
return document.body.hasAttribute(`data-sidenav-${attr}`);
}
/**
* Remove sidenav attributes from `document.body`.
*/
private bodyRemove(...attrs: BodyAttr[]): void {
for (const attr of attrs) {
document.body.removeAttribute(`data-sidenav-${attr}`);
}
}
/**
* Add sidenav attributes to `document.body`.
*/
private bodyAdd(...attrs: BodyAttr[]): void {
for (const attr of attrs) {
document.body.setAttribute(`data-sidenav-${attr}`, '');
}
}
/**
* Set initial values & add event listeners.
*/
private init() {
for (const toggler of this.base.querySelectorAll('.sidenav-toggle')) {
toggler.addEventListener('click', event => this.onToggle(event));
}
for (const toggler of getElements<HTMLButtonElement>('.sidenav-toggle-mobile')) {
toggler.addEventListener('click', event => this.onMobileToggle(event));
}
if (window.innerWidth > 1200) {
if (this.state.get('pinned')) {
this.pin();
}
if (!this.state.get('pinned')) {
this.unpin();
}
window.addEventListener('resize', () => this.onResize());
}
if (window.innerWidth < 1200) {
this.bodyRemove('hide');
this.bodyAdd('hidden');
window.addEventListener('resize', () => this.onResize());
}
this.base.addEventListener('mouseenter', () => this.onEnter());
this.base.addEventListener('mouseleave', () => this.onLeave());
}
/**
* If the sidenav is shown, expand active nav links. Otherwise, collapse them.
*/
private initLinks(): void {
for (const link of this.getActiveLinks()) {
if (this.bodyHas('show')) {
this.activateLink(link, 'expand');
} else if (this.bodyHas('hidden')) {
this.activateLink(link, 'collapse');
}
}
}
private show(): void {
this.bodyAdd('show');
this.bodyRemove('hidden', 'hide');
}
private hide(): void {
this.bodyAdd('hidden');
this.bodyRemove('pinned', 'show');
for (const collapse of this.base.querySelectorAll('.collapse')) {
collapse.classList.remove('show');
}
}
/**
* Pin the sidenav.
*/
private pin(): void {
this.bodyAdd('show', 'pinned');
this.bodyRemove('hidden');
this.state.set('pinned', true);
}
/**
* Unpin the sidenav.
*/
private unpin(): void {
this.bodyRemove('pinned', 'show');
this.bodyAdd('hidden');
for (const collapse of this.base.querySelectorAll('.collapse')) {
collapse.classList.remove('show');
}
this.state.set('pinned', false);
}
/**
* Starting from the bottom-most active link in the element tree, work backwards to determine the
* link's containing `.collapse` element and the `.collapse` element's containing `.nav-link`
* element. Once found, expand (or collapse) the `.collapse` element and add (or remove) the
* `.active` class to the the parent `.nav-link` element.
*
* @param link Active nav link
* @param action Expand or Collapse
*/
private activateLink(link: HTMLAnchorElement, action: 'expand' | 'collapse'): void {
// Find the closest .collapse element, which should contain `link`.
const collapse = link.closest('.collapse') as Nullable<HTMLDivElement>;
if (isElement(collapse)) {
// Find the closest `.nav-link`, which should be adjacent to the `.collapse` element.
const groupLink = collapse.parentElement?.querySelector('.nav-link');
if (isElement(groupLink)) {
groupLink.classList.add('active');
switch (action) {
case 'expand':
groupLink.setAttribute('aria-expanded', 'true');
collapse.classList.add('show');
link.classList.add('active');
break;
case 'collapse':
groupLink.setAttribute('aria-expanded', 'false');
collapse.classList.remove('show');
link.classList.remove('active');
break;
}
}
}
}
/**
* Find any nav links with `href` attributes matching the current path, to determine which nav
* link should be considered active.
*/
private *getActiveLinks(): Generator<HTMLAnchorElement> {
for (const link of this.base.querySelectorAll<HTMLAnchorElement>(
'.navbar-nav .nav .nav-item a.nav-link',
)) {
const href = new RegExp(link.href, 'gi');
if (Boolean(window.location.href.match(href))) {
yield link;
}
}
}
/**
* Show the sidenav and expand any active sections.
*/
private onEnter(): void {
if (!this.bodyHas('pinned')) {
this.bodyRemove('hide', 'hidden');
this.bodyAdd('show');
for (const link of this.getActiveLinks()) {
this.activateLink(link, 'expand');
}
}
}
/**
* Hide the sidenav and collapse any active sections.
*/
private onLeave(): void {
if (!this.bodyHas('pinned')) {
this.bodyRemove('show');
this.bodyAdd('hide');
for (const link of this.getActiveLinks()) {
this.activateLink(link, 'collapse');
}
setTimeout(() => {
this.bodyRemove('hide');
this.bodyAdd('hidden');
}, 300);
}
}
/**
* Close the (unpinned) sidenav when the window is resized.
*/
private onResize(): void {
if (this.bodyHas('show') && !this.bodyHas('pinned')) {
this.bodyRemove('show');
this.bodyAdd('hidden');
}
}
/**
* Pin & unpin the sidenav when the pin button is toggled.
*/
private onToggle(event: Event): void {
event.preventDefault();
if (this.state.get('pinned')) {
this.unpin();
} else {
this.pin();
}
}
private onMobileToggle(event: Event): void {
event.preventDefault();
if (this.bodyHas('hidden')) {
this.show();
} else {
this.hide();
}
}
}
export function initSideNav() {
const element = getElement<HTMLAnchorElement>('sidebarMenu');
if (element !== null && document.body.clientWidth < breakpoints.lg) {
element.addEventListener('shown.bs.collapse', () => toggleBodyPosition('fixed'));
element.addEventListener('hidden.bs.collapse', () => toggleBodyPosition('relative'));
for (const sidenav of getElements<HTMLDivElement>('.sidenav')) {
new SideNav(sidenav);
}
}

View File

@@ -56,6 +56,13 @@ export function isTruthy<V extends string | number | boolean | null | undefined>
return false;
}
/**
* Type guard to determine if a value is an `Element`.
*/
export function isElement(obj: Element | null | undefined): obj is Element {
return typeof obj !== null && typeof obj !== 'undefined';
}
/**
* Retrieve the CSRF token from cookie storage.
*/
@@ -152,6 +159,22 @@ export function getElement<E extends HTMLElement>(id: string): Nullable<E> {
return document.getElementById(id) as Nullable<E>;
}
export function removeElements(...selectors: string[]): void {
for (const element of getElements(...selectors)) {
element.remove();
}
}
export function elementWidth<E extends HTMLElement>(element: Nullable<E>): number {
let width = 0;
if (element !== null) {
const style = getComputedStyle(element);
const pre = style.width.replace('px', '');
width = parseFloat(pre);
}
return width;
}
/**
* scrollTo() wrapper that calculates a Y offset relative to `element`, but also factors in an
* offset relative to div#content-title. This ensures we scroll to the element, but leave enough

View File

@@ -2,3 +2,4 @@
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap');
@import '../node_modules/@mdi/font/css/materialdesignicons.min.css';
@import '../node_modules/flatpickr/dist/flatpickr.css';
@import '../node_modules/simplebar/dist/simplebar.css';

View File

@@ -1,9 +1,11 @@
// Netbox-specific Styles and Overrides.
@use 'sass:map';
@import './sidenav.scss';
:root {
--nbx-sidebar-bg: #{$gray-200};
--nbx-sidebar-scroll: #{$gray-500};
--nbx-sidebar-link-color: #{$gray-800};
--nbx-sidebar-link-hover-bg: #{$blue-100};
--nbx-sidebar-title-color: #{$text-muted};
@@ -21,9 +23,11 @@
--nbx-cable-termination-border-color: #{$gray-300};
--nbx-search-filter-border-left-color: #{$gray-300};
--nbx-color-mode-toggle-color: #{$primary};
--nbx-sidenav-pin-color: #{$orange};
&[data-netbox-color-mode='dark'] {
--nbx-sidebar-bg: #{$gray-900};
--nbx-sidebar-scroll: #{$gray-700};
--nbx-sidebar-link-color: #{$gray-100};
--nbx-sidebar-link-hover-bg: #{rgba($blue-300, 0.15)};
--nbx-sidebar-title-color: #{$gray-600};
@@ -41,6 +45,7 @@
--nbx-cable-termination-border-color: #{$gray-700};
--nbx-search-filter-border-left-color: #{$gray-600};
--nbx-color-mode-toggle-color: #{$yellow-300};
--nbx-sidenav-pin-color: #{$yellow};
}
}
@@ -119,6 +124,13 @@ small {
background: transparent escape-svg($btn-close-bg) center / $btn-close-width auto no-repeat;
}
.btn.btn-ghost-#{$color} {
color: $value;
&:hover {
background-color: rgba($value, 0.12);
}
}
// Use Bootstrap's method of coloring the .alert-link class automatically.
// See: https://github.com/twbs/bootstrap/blob/2bdbb42dcf6bfb99b5e9e5444d9e64589eb8c08f/scss/_alert.scss#L50-L52
.toast.bg-#{$color},
@@ -359,6 +371,8 @@ div.title-container {
nav.search {
background-color: var(--nbx-body-bg);
// Don't overtake dropdowns
z-index: 999;
form button.dropdown-toggle {
border-color: $input-border-color;
font-weight: $input-group-addon-font-weight;
@@ -374,6 +388,16 @@ nav.search {
}
}
main.layout {
display: flex;
flex-wrap: nowrap;
height: 100vh;
height: -webkit-fill-available;
max-height: 100vh;
overflow-x: auto;
overflow-y: hidden;
}
main.login-container {
display: flex;
height: calc(100vh - 4rem);
@@ -425,7 +449,6 @@ h3.accordion-item-title,
h4.accordion-item-title,
h5.accordion-item-title,
h6.accordion-item-title {
// padding: 0 0.5rem;
padding: 0.25rem 0.5rem;
font-weight: $font-weight-bold;
text-transform: uppercase;
@@ -474,7 +497,7 @@ li.dropdown-item.dropdown-item-btns {
height: calc(100vh - 48px);
padding-top: 0.5rem;
overflow-x: hidden;
overflow-y: auto; /* Scrollable contents if viewport is shorter than content. */
overflow-y: auto; // Scrollable contents if viewport is shorter than content.
}
.navbar-brand {
@@ -498,13 +521,16 @@ nav.nav.nav-pills {
// Ensure the content container is full-height, and that the content block is also full height so
// that the footer is always at the bottom.
div.content-container {
position: relative;
min-height: 100vh;
display: flex;
flex-direction: column;
overflow: hidden;
width: calc(100% - 4.5rem);
overflow-x: hidden;
overflow-y: auto;
@include media-breakpoint-up(md) {
margin-left: $sidebar-width;
@include media-breakpoint-down(lg) {
width: 100%;
}
div.content {
@@ -527,7 +553,7 @@ div.content-container {
top: 0;
bottom: 0;
left: 0;
z-index: 100; /* Behind the navbar */
z-index: 100; // Behind the navbar
border-right: 1px solid $border-color;
background-color: var(--nbx-sidebar-bg);
max-height: 100%;
@@ -963,12 +989,12 @@ html {
&[data-netbox-path='/'] {
.content-container,
.search {
background-color: $gray-100;
background-color: $gray-100 !important;
}
&[data-netbox-color-mode='dark'] {
.content-container,
.search {
background-color: $darkest;
background-color: $darkest !important;
}
}
}

View File

@@ -0,0 +1,407 @@
@use 'sass:map';
@mixin parent-link {
.navbar-nav .nav-item .nav-link[data-bs-toggle] {
@content;
}
}
@mixin child-link {
.collapse .nav .nav-item .nav-link {
@content;
}
}
@mixin sidenav-open {
body[data-sidenav-show],
body[data-sidenav-pinned] {
.sidenav {
@content;
}
}
}
@mixin sidenav-closed {
body[data-sidenav-hide],
body[data-sidenav-hidden] {
.sidenav {
@content;
}
}
}
@mixin sidenav-pinned {
body[data-sidenav-pinned] {
.sidenav {
@content;
}
}
}
@mixin sidenav-show {
body[data-sidenav-show] {
.sidenav {
@content;
}
}
}
@mixin sidenav-hide {
body[data-sidenav-hide] {
.sidenav {
@content;
}
}
}
@mixin sidenav-peek {
.g-sidenav-show:not(.g-sidenav-pinned) {
.sidenav {
@content;
}
}
}
$transition-100ms-ease-in-out: all 0.1s ease-in-out;
.sidenav {
position: fixed;
top: 0;
bottom: 0;
left: 0;
z-index: 1050;
display: block;
width: 100%;
max-width: $sidenav-width-closed;
padding-top: 0;
padding-right: 0;
padding-left: 0;
background-color: var(--nbx-sidebar-bg);
border-right: 1px solid $border-color;
transition: $transition-100ms-ease-in-out;
// Media fixes for iPhone 5 like resolutions
@include media-breakpoint-down(lg) {
transform: translateX(-$sidenav-width-closed);
+ .content-container[class] {
margin-left: 0;
}
}
+ .content-container {
margin-left: $sidenav-width-closed;
transition: $transition-100ms-ease-in-out;
}
// Navbar brand
.sidenav-brand {
margin-right: 0;
}
.sidenav-inner {
padding-right: $sidenav-spacing-x;
padding-left: $sidenav-spacing-x;
@include media-breakpoint-up(md) {
padding-right: 0;
padding-left: 0;
}
}
.sidenav-brand-img,
.sidenav-brand > img {
max-width: 100%;
max-height: calc(#{$sidenav-width-open} - 1rem);
}
.navbar-heading {
padding-top: $nav-link-padding-y;
padding-bottom: $nav-link-padding-y;
font-size: $font-size-xs;
text-transform: uppercase;
letter-spacing: 0.04em;
}
.sidenav-header {
position: relative;
display: flex;
align-items: center;
justify-content: space-between;
height: 78px;
padding: $spacer;
transition: $transition-100ms-ease-in-out;
}
.sidenav-toggle {
display: none;
}
.sidenav-collapse {
display: flex;
flex: 1;
flex-direction: column;
align-items: stretch;
padding-right: $sidenav-spacing-x;
padding-left: $sidenav-spacing-x;
margin-right: -$sidenav-spacing-x;
margin-left: -$sidenav-spacing-x;
> * {
min-width: 100%;
}
@include media-breakpoint-up(md) {
margin-right: 0;
margin-left: 0;
}
}
// Child Link nav-item
.nav .nav-item {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
}
@include child-link() {
width: 100%;
padding-top: $sidenav-link-spacing-y / 2.675;
padding-right: map.get($spacers, 1);
padding-bottom: $sidenav-link-spacing-y / 2.675;
/* stylelint-disable */
padding-left: $sidenav-link-spacing-x + $sidenav-icon-width + $sidenav-link-spacing-x / 4;
/* stylelint-enable */
margin-top: $sidenav-link-spacing-y / 3.3;
margin-bottom: $sidenav-link-spacing-y / 3.3;
font-size: $font-size-xs;
border-top-right-radius: $border-radius;
border-bottom-right-radius: $border-radius;
.sidenav-normal {
color: $text-muted;
&:hover {
opacity: 0.8;
}
}
.sidenav-mini-icon {
width: $sidenav-link-spacing-x;
text-align: center;
transition: $transition-100ms-ease-in-out;
}
}
@include parent-link() {
width: unset;
height: 100%;
font-weight: $font-weight-bold;
&.active {
color: $accordion-button-active-color;
background: $accordion-button-active-bg;
}
&:after {
display: inline-block;
margin-left: auto;
/* stylelint-disable */
font-family: 'Material Design Icons';
/* stylelint-enable */
font-style: normal;
font-weight: 700;
font-variant: normal;
color: $text-muted;
text-rendering: auto;
-webkit-font-smoothing: antialiased;
content: '\f0142';
transition: $transition-100ms-ease-in-out;
}
// Expanded
&[aria-expanded='true'] {
&.active:after {
color: $accordion-button-active-color;
}
&:after {
color: $primary;
transform: rotate(90deg);
}
}
.nav-link-text {
padding-left: 0.25rem;
transition: $transition-100ms-ease-in-out;
}
}
.navbar-nav {
flex-direction: column;
margin-right: -$sidenav-spacing-x;
margin-left: -$sidenav-spacing-x;
.nav-item {
margin-top: 2px;
&.disabled {
cursor: not-allowed;
opacity: 0.8;
}
// All Links
.nav-link {
display: flex;
align-items: center;
width: 100%;
padding: $sidenav-link-spacing-y $sidenav-link-spacing-x;
font-size: $font-size-sm;
white-space: nowrap;
transition: $transition-100ms-ease-in-out;
// &.disabled {
// opacity: 0.8;
// }
&.active {
position: relative;
color: var(--nbx-sidebar-link-hover-bg);
background-color: var(--nbx-sidebar-link-hover-bg);
}
// Icon
> i {
min-width: $sidenav-icon-width;
font-size: calc(45px / 2);
text-align: center;
}
}
}
.nav-group-label {
display: block;
font-size: $font-size-xs;
font-weight: $font-weight-bold;
color: $primary;
text-transform: uppercase;
white-space: nowrap;
}
}
}
@include sidenav-pinned() {
.sidenav-toggle-icon {
color: var(--nbx-sidenav-pin-color);
transform: rotate(90deg);
}
}
@include sidenav-peek() {
.sidenav-toggle-icon {
transform: rotate(0deg);
// transform: rotate(90deg);
}
}
@include sidenav-open() {
max-width: $sidenav-width-open;
.sidenav-brand,
.navbar-heading {
display: block;
}
.sidenav-brand {
opacity: 1;
transform: translateX(0);
}
.sidenav-brand-icon {
position: absolute;
opacity: 0;
}
@include media-breakpoint-down(lg) {
transform: translateX(0);
}
@include media-breakpoint-up(xl) {
+ .content-container {
margin-left: $sidenav-width-open;
}
}
}
@include sidenav-closed() {
.sidenav-header {
padding: $spacer * 0.5;
}
.sidenav-brand {
position: absolute;
opacity: 0;
transform: translateX(-150%);
}
.sidenav-brand-icon {
opacity: 1;
}
.navbar-nav > .nav-item {
> .nav-link {
&:after {
content: '';
}
}
}
.nav-item .collapse {
display: none;
}
.nav-link-text {
opacity: 0;
}
@include parent-link() {
&.active {
margin-right: 0;
margin-left: 0;
border-radius: unset;
}
}
}
@include sidenav-show() {
.sidenav-brand {
display: block;
}
.nav-item .collapse {
height: auto;
transition: $transition-100ms-ease-in-out;
}
.nav-item .nav-link .nav-link-text {
opacity: 1;
}
.nav-item .sidenav-mini-icon {
opacity: 0;
}
@include media-breakpoint-up(lg) {
.sidenav-toggle {
display: inline-block;
}
}
}
.simplebar-track.simplebar-vertical {
right: 0;
width: 6px;
background-color: transparent;
.simplebar-scrollbar:before {
right: 0;
width: 3px;
background: var(--nbx-sidebar-scroll);
border-radius: $border-radius;
}
&.simplebar-hover .simplebar-scrollbar:before {
width: 5px;
}
}

View File

@@ -122,11 +122,13 @@ $theme-color-addons: (
'pink-900': $pink-900,
);
/* stylelint-disable */
$font-family-sans-serif: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont,
'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji',
'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
$font-family-monospace: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono',
'Courier New', monospace;
/* stylelint-enable */
// This is the same value as the default from Bootstrap, but it needs to be in scope prior to
// importing _variables.scss from Bootstrap.
@@ -137,3 +139,12 @@ $accordion-padding-x: 0.8125rem;
$sidebar-width: 280px;
$sidebar-bottom-height: 4rem;
// Sidebar/Sidenav
$sidenav-width-closed: 4rem;
$sidenav-width-open: 16rem;
$sidenav-icon-width: 2rem;
$sidenav-link-px: 1rem;
$sidenav-spacing-x: 1.5rem;
$sidenav-link-spacing-x: 1rem;
$sidenav-link-spacing-y: 0.675rem;

View File

@@ -1,7 +1,7 @@
// Dark Mode Theme Variables and Overrides.
@use 'sass:map';
@import './theme-base.scss';
@import './theme-base';
$primary: $blue-300;
$secondary: $gray-500;
@@ -175,13 +175,11 @@ $accordion-bg: transparent;
$accordion-border-color: $border-color;
$accordion-button-color: $accordion-color;
$accordion-button-bg: $accordion-bg;
$accordion-body-active-bg: rgba($blue-300, 0.2);
$accordion-button-active-bg: rgba($blue-300, 0.25);
$accordion-button-active-color: $gray-300;
$accordion-button-active-bg: shade-color($blue-300, 10%);
$accordion-button-active-color: color-contrast($accordion-button-active-bg);
$accordion-button-focus-border-color: $input-focus-border-color;
$accordion-icon-color: $accordion-color;
$accordion-icon-active-color: $accordion-button-active-color;
$accordion-button-icon: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='#{$accordion-icon-color}'><path fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/></svg>");
$accordion-button-active-icon: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='#{$accordion-icon-active-color}'><path fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/></svg>");

File diff suppressed because it is too large Load Diff