$default-menu-levels: 3 !default; @function _menu_get($config, $prefix, $level, $state, $key, $default: null) { // TODO no state fallback for folders! @return ( map-get-nested($config, $prefix $level $state $key, null) or map-get-nested($config, $prefix $level $key, null) or map-get-nested($config, $prefix 0 $state $key, null) or map-get-nested($config, $prefix 0 $key, null) or map-get-nested($DEFAULT-MENU-CONFIG, $prefix $level $state $key, null) or map-get-nested($DEFAULT-MENU-CONFIG, $prefix $level $key, null) or map-get-nested($DEFAULT-MENU-CONFIG, $prefix 0 $state $key, null) or map-get-nested($DEFAULT-MENU-CONFIG, $prefix 0 $key, null) or $default); } @mixin _menu__props($config, $prefix, $level, $state, $props) { @each $prop in $props { $value: _menu_get($config, $prefix, $level, $state, $prop); @if $value != null { @if type-of($value) == color and alpha($value) < 1 { // Show an old-IE-friendly version of the color, with no alpha #{$prop}: rgb($value); } #{$prop}: $value; @if $prop == border-width { border-style: solid; } } } } @mixin _menu__props_map($config, $prefix, $level, $state, $props) { @each $prop, $key in $props { $value: _menu_get($config, $prefix, $level, $state, $key); @if $value != null { #{$prop}: $value; } } } @mixin _menu__separator($config, $level) { $-width: _menu_get($config, i, $level, natural, separator-width); $-color: _menu_get($config, i, $level, natural, separator-color); @if $-width and $-color { border-top: $-width solid $-color; // mimic separator } } @mixin _menu__common_height_from_border($config, $level, $state, $default-delta: null) { $-border-width: _menu_get($config, i, $level, $state, border-width); @if $-border-width or $default-delta { @if $-border-width { $-delta: nth($-border-width, 1) + nth($-border-width, 3); } @else { $-delta: $default-delta; } $-height: _menu_get($config, i, $level, natural, height); @if $-height { height: $-height - $-delta; } $-line-height: _menu_get($config, i, $level, natural, line-height); @if $-line-height { line-height: $-line-height - $-delta; } } } @mixin _menu__common_folder_icon($config, $level, $state) { $arrow-color: _menu_get($config, i, $level, $state, arrow-color); @if $arrow-color { $-type: map-get-nested($config, u ($level + 1) type); @if $-type == dropup { $-dir: up; } @else if $-type == dropright { $-dir: right; } @else if $-type == dropleft { $-dir: left; } @else { $-dir: down; } background: image-url('menus/arrow_#{$-dir}.png', $dst-color: $arrow-color, $cache-buster: false) no-repeat right center; } } @mixin _menu__common_state($config, $level, $state) { &::before, &::after { background-clip: padding-box; // NOTE: this is really not part of the structure (put here as optimization) @include _menu__props($config, i, $level, $state, (background, background-color, background-image)); } a.l#{$level}::before, .a.l#{$level}::before, a.l#{$level}::after, .a.l#{$level}::after { // NOTE: this is really not part of the structure (put here as optimization) @include _menu__props_map($config, i, $level, $state, (background: roundness-background, background-image: roundness-background-image)); } a.l#{$level}, .a.l#{$level} { .label { @include _menu__props($config, i, $level, $state, (color, text-shadow)); @include _menu__common_height_from_border($config, $level, $state); } &.folder .tag { padding-right: 12px; @include _menu__common_folder_icon($config, $level, $state); } @include _menu__props($config, i, $level, $state, (background, background-color, background-image, box-shadow, border-width, border-color, border-radius)); } } @mixin _menu-horizontal($config, $level) { $l: $level; ul.l#{$l} { margin: 0; padding: 0; list-style: none; background-clip: padding-box; @include _menu__props($config, u, $level, natural, (padding, margin, background, background-color, background-image, border-width, border-color, border-radius)); // ...box-shadow // &.focused, &.hover, &:hover { @include _menu__props($config, u, $level, hover, (background, background-color, background-image)); } // Special case for nesting (depending on current behavior): @if _menu_get($config, u, $level, null, keep-current) { $_current: nest('&, &:hover', 'li.l#{$l}.current'); // Makes current remain selected on hover } @else { $_current: 'li.l#{$l}.current'; // Makes current get unselected on hover } $_selectors: append(nest('&, &:hover', 'li.l#{$l}:hover'), $_current); #{$_selectors} { /****** Structure Starts ******/ $max-roundness: max( _menu_get($config, i, $level, natural, roundness, 0), _menu_get($config, i, $level, folder, roundness, 0), ); @if $max-roundness > 0 { &, a, .a { &::before, &::after { content: ""; display: block; position: absolute; top: 0; bottom: 0; width: $max-roundness; } &::before { left: -$max-roundness; } &::after { right: -$max-roundness; } } &::before, &::after { z-index: -1; } a.l#{$l}::before, .a.l#{$l}::before, a.l#{$l}::after, .a.l#{$l}::after { z-index: 1; } } /****** Structure Ends ******/ a.l#{$l}, .a.l#{$l} { $roundness: _menu_get($config, i, $level, natural, roundness); @if $roundness { &::before { border-top-right-radius: $roundness; } &::after { border-top-left-radius: $roundness; } } } } #{$_current} { @include _menu__common_state($config, $l, current); } // &.focused, &.hover, &:hover, & { // li.l#{$l}.focused, li.l#{$l}.hover, li.l#{$l}:hover { @include _menu__common_state($config, $l, hover); } /* Natural state: */ li.l#{$l} { /****** Structure Starts ******/ position: relative; display: inline-block; *vertical-align: middle; // IE fix /****** Structure Ends ******/ z-index: 50; &:hover { z-index: 100; // This pushes down the currently focused/hovered item, so the rounded sides go below the menus on its sides. } &::before, a.l#{$l}::before, .a.l#{$l}::before, &::after, a.l#{$l}::after, .a.l#{$l}::after { display: none; } /****** Structure Ends ******/ a.l#{$l}, .a.l#{$l} { /****** Structure Starts ******/ display: inline-block; img.icon { vertical-align: middle; $icon-size: _menu_get($config, i, $level, natural, icon-size); @if $icon-size { width: $icon-size; height: $icon-size; } } .tag { vertical-align: middle; } /****** Structure Ends ******/ // XXX this is basically copy/pasted from the top level?? no padding though (bug?) background-clip: padding-box; @include _menu__props($config, i, $level, natural, (margin, background, background-color, background-image, box-shadow, border-width, border-color, border-radius)); .label { /****** Structure Starts ******/ @if not _menu_get($config, i, $level, null, word-wrap) { white-space: nowrap; } display: inline-block; /****** Structure Ends ******/ $-margin: _menu_get($config, i, $level, natural, margin); $-border-width: _menu_get($config, i, $level, natural, border-width); @if $-margin and $-border-width { margin: -($-margin + $-border-width); } @else if $-margin { margin: -($margin); } @else if $-border-width { margin: -($-border-width); } text-decoration: none; @include _menu__props($config, i, $level, natural, (font-size, color, text-shadow, padding)); @include _menu__common_height_from_border($config, $level, natural, $default-delta: 0); } // &:active .label, // &:focus .label { // text-decoration: underline; // } &.center .label { text-align: center; } &.folder { .label { @include _menu__common_height_from_border($config, $level, folder); } .tag { padding-right: 12px; @include _menu__common_folder_icon($config, $level, natural); } } } // &.folder.focused, &.folder.hover, &.folder:hover { a.l#{$l}, .a.l#{$l} { @include _menu__props($config, i, $level, folder, margin); $-margin: _menu_get($config, i, $level, folder, margin); $-border-width: _menu_get($config, i, $level, folder, border-width); .label { @if $-margin { margin: -($-margin + ( $-border-width or _menu_get($config, i, $level, natural, border-width); or 0 )); } @else if $-border-width { margin: -($-border-width); } } @include _menu__props($config, i, $level, folder, (box-shadow, border-width, border-color, border-radius)); $-roundness: _menu_get($config, i, $level, folder, roundness); @if $-roundness { $-border-radius: _menu_get($config, i, $level, folder, border-radius); &::before { @if $-border-radius { border-radius: 0; } border-bottom-right-radius: $-roundness; background-position: center 0; } &::after { @if $-border-radius { border-radius: 0; } border-bottom-left-radius: $-roundness; background-position: center 0; } } } } } } } } @mixin _menu-vertical($config, $level) { $l: $level; ul.l#{$l} { margin: 0; padding: 0; list-style: none; // XXX this is repeated again @include _menu__props($config, u, $level, natural, (padding, margin)); background-clip: padding-box; @include _menu__props($config, u, $level, natural, (background, background-color, background-image, box-shadow, border-width, border-color, border-radius)); transition: background 1s; // &.focused, &.hover, &:hover { @include _menu__props($config, u, $level, hover, (background, background-color, background-image)); } // &.focused, &.hover, &:hover, & { /* Hovered/selected state: */ // li.l#{$l}.focused, li.l#{$l}.hover, li.l#{$l}:hover { a.l#{$l}, .a.l#{$l} { @include _menu__props($config, i, $level, hover, (background, background-color, background-image, box-shadow, border-width, border-color, border-radius)); .label { @include _menu__props($config, i, $level, hover, (color, text-shadow)); } } } li.l#{$l}.current { a.l#{$l}, .a.l#{$l} { // XXX identical to the above, but with current! @include _menu__props($config, i, $level, current, (background, background-color, background-image, box-shadow, border-width, border-color, border-radius)); .label { @include _menu__props($config, i, $level, current, (color, text-shadow)); } } } /* Natural state: */ li.l#{$l} { /****** Structure Starts ******/ position: relative; /****** Structure Ends ******/ z-index: 50; &:hover { z-index: 100; // This pushes down the currently focused/hovered item, so the rounded sides go below the menus on its sides. } @if $l > 1 { padding: 0 6px; } margin: 2px 0; &.first { margin-top: 6px; } &.last { margin-bottom: 6px; } &.separator { padding: 0; font-size: 1px; /* IE */ @include _menu__separator($config, $level); } a.l#{$l}, .a.l#{$l} { /****** Structure Starts ******/ display: inline-block; img.icon { vertical-align: middle; // XXX c/p $icon-size: _menu_get($config, i, $level, natural, icon-size); @if $icon-size { width: $icon-size; height: $icon-size; } } .tag { vertical-align: middle; } /****** Structure Ends ******/ // XXX HMMM background-clip: padding-box; @include _menu__props($config, i, $level, natural, (background, background-color, background-image, box-shadow, border-width, border-color, border-radius)); width: 100%; .label { /****** Structure Starts ******/ @if not _menu_get($config, i, $level, null, word-wrap) { white-space: nowrap; } display: inline-block; /****** Structure Ends ******/ text-decoration: none; @include _menu__props($config, i, $level, natural, (font-size, color, text-shadow, padding)); @include _menu__common_height_from_border($config, $level, natural, $default-delta: 0); } // &:active .label, // &:focus .label { // text-decoration: underline; // } &.center .label { text-align: center; } } } } } } @mixin _menu-inplace($config, $level) { $l: $level; $config: map-merge-deep( ( u: ( 0: ( natural: ( padding: 2px 10px, margin: 2px 0 0 0, )))), $config); @include _menu-vertical($config, $level); .built-menu.l#{$l} { display: block; ul.l#{$l} { @include _menu__separator($config, $level); } } } @mixin _menu-accordion($config, $level) { $l: $level; $config: map-merge-deep( ( u: ( $level: ( natural: ( padding: 0 0, margin: 2px 10px 0, )))), $config); @include _menu-vertical($config, $level); .built-menu.l#{$l} { ul.l#{$l} { @include _menu__separator($config, $level); } } } @mixin _menu-dropdown($config, $level) { $l: $level; $config: map-merge-deep( ( u: ( $level: ( natural: ( box-shadow: rgba(black, 12%) 0 2px 4px, background: white, )))), $config); @include _menu-vertical($config, $level); .built-menu.l#{$l} { position: absolute; padding: 5px 30px 30px; margin: -5px -30px -30px; @if $i#{$l - 1}-height-natural != undefined { top: $i#{$l}-height-natural - nth($i#{$l}-margin-folder, 1); } ul.l#{$l} { li.l#{$l}.first { margin-top: 4px !important; } li.l#{$l}.last { margin-bottom: 4px !important; } } } } @mixin _menu-dropup($config, $level) { $l: $level; $config: map-merge-deep( ( u: ( 0: ( natural: ( box-shadow: rgba(black, 12%) 0 2px 4px, background: white, )))), $config); @include _menu-vertical($config, $level); .built-menu.l#{$l} { position: absolute; padding: 30px 30px 5px; margin: -30px -30px -5px; bottom: _menu_get($config, i, $level - 1, natural, height, $default: 1em); ul.l#{$l} { li.l#{$l}.first { margin-top: 4px !important; } li.l#{$l}.last { margin-bottom: 4px !important; } } } } @mixin _menu-dropright($config, $level) { $l: $level; $config: map-merge-deep( ( u: ( $level: ( natural: ( box-shadow: rgba(black, 12%) 0 2px 4px, background: white, )))), $config); @include _menu-vertical($config, $level); .built-menu.l#{$l} { position: absolute; padding: 30px 30px 30px 5px; margin: -30px -30px -30px -5px; top: 0; left: 90%; ul.l#{$l} { li.l#{$l}.first { margin-top: 4px !important; } li.l#{$l}.last { margin-bottom: 4px !important; } } } } @mixin _menu-dropleft($config, $level) { $l: $level; $config: map-merge-deep( ( u: ( $level: ( natural: ( box-shadow: rgba(black, 12%) 0 2px 4px, background: white, )))), $config); @include _menu-vertical($config, $level); .built-menu.l#{$l} { position: absolute; padding: 30px 30px 30px 5px; margin: -30px -30px -30px -5px; top: 0; right: 90%; ul.l#{$l} { li.l#{$l}.first { margin-top: 4px !important; } li.l#{$l}.last { margin-bottom: 4px !important; } } } } @mixin menu($config) { /****** Structure Starts ******/ // Multilevel menu show/hide: .built-menu .built-menu { display: none; } @for $l from 1 through $default-menu-levels { // li.l#{$l}.focused .built-menu.l#{$l + 1}, li.l#{$l}.hover .built-menu.l#{$l + 1}, li.l#{$l}:hover .built-menu.l#{$l + 1} { display: block; } } // Hide pre/post items by default: li.pre_items, li.post_items { display: none !important; } /****** Structure Ends ******/ @for $l from 1 through $default-menu-levels { $type: '_menu-' + _menu_get($config, u, $l, null, type); @include #{$type}($config, $level: $l); } } // Default values: $DEFAULT-MENU-CONFIG: ( u: ( 1: ( type: horizontal, ), 2: ( type: dropdown, ), 3: ( type: inplace, ), ), i: ( 0: ( natural: ( separator-width: 1px, color: blue, arrow-color: blue, icon-size: 20px, ), hover: ( color: red, arrow-color: red, ), current: ( color: red, arrow-color: red, ), folder: ( color: red, arrow-color: red, roundness: null, ), word-wrap: false, ), 1: ( natural: ( padding: 0 12px, ), ), 2: ( natural: ( padding: 2px 6px, ), ), 3: ( natural: ( padding: 2px 6px, ), ), ), );