feat(uishell): make component based

This commit is contained in:
Marcus Feitoza 2020-03-31 00:10:03 -03:00
commit e24de4e797
24 changed files with 172 additions and 1261 deletions

View file

@ -12,9 +12,7 @@
<nav
class={cx('--side-nav__navigation', '--side-nav', '--side-nav--ux', isOpen && '--side-nav--expanded')}
aria-label={ariaLabel}>
<ul class={cx('--side-nav__items')}>
<slot />
</ul>
<slot />
<footer class={cx('--side-nav__footer')}>
<button class={cx('--side-nav__toggle')} type="button" title="Open">
<div class={cx('--side-nav__icon')}>

View file

@ -0,0 +1,7 @@
<script>
import { cx } from '../../../lib';
</script>
<ul class={cx('--side-nav__items')}>
<slot />
</ul>

View file

@ -0,0 +1,24 @@
<script>
export let href = undefined;
export let text = undefined;
export let icon = undefined;
export let isSelected = false;
import { cx } from '../../../lib';
import Icon from '../../Icon/Icon.svelte';
</script>
<li class={cx('--side-nav__item')}>
<a
{href}
class={cx('--side-nav__link', isSelected && '--side-nav__link--current')}
aria-current={isSelected ? 'page' : ''}
on:click>
<div class={cx('--side-nav__icon', '--side-nav__icon--small')}>
{#if icon}
<Icon {...icon} />
{/if}
</div>
<span class={cx('--side-nav__link-text')}>{text}</span>
</a>
</li>

View file

@ -0,0 +1,43 @@
<script>
export let text = undefined;
export let icon = undefined;
export let expanded = false;
import { cx } from '../../../lib';
import Icon from '../../Icon/Icon.svelte';
import ChevronDown16 from 'carbon-icons-svelte/lib/ChevronDown16';
let iconProps = {
class: undefined,
skeleton: false,
render: ChevronDown16,
title: 'Open Menu',
tabindex: '0',
focusable: false,
style: undefined
};
</script>
<li class={cx('--side-nav__item', '--side-nav__item--icon')}>
<button
aria-haspopup="true"
aria-expanded={expanded}
class={cx('--side-nav__submenu')}
type="button"
on:click={() => {
expanded = !expanded;
}}>
<div class={cx('--side-nav__icon')}>
{#if icon}
<Icon {...icon} />
{/if}
</div>
<span class={cx('--side-nav__submenu-title')}>{text}</span>
<div class={cx('--side-nav__icon', '--side-nav__icon--small', '--side-nav__submenu-chevron')}>
<Icon {...iconProps} />
</div>
</button>
<ul class={cx('--side-nav__menu')} role="menu">
<slot />
</ul>
</li>

View file

@ -0,0 +1,18 @@
<script>
export let href = undefined;
export let text = undefined;
export let isSelected = undefined;
import { cx } from '../../../lib';
</script>
<li class={cx('--side-nav__menu-item')} role="none">
<a
{href}
class={cx('--side-nav__link')}
role="menuitem"
aria-current={isSelected ? 'page' : ''}
on:click>
<span class={cx('--side-nav__link-text')}>{text}</span>
</a>
</li>

View file

@ -2,312 +2,69 @@
export let story = undefined;
import UIShell from './UIShell.svelte';
import FormTest from './FormTest.svelte';
import SettingsAdjust20 from 'carbon-icons-svelte/lib/SettingsAdjust20';
import ChangeCatalog16 from 'carbon-icons-svelte/lib/ChangeCatalog16';
import ManageProtection16 from 'carbon-icons-svelte/lib/ManageProtection16';
import Recommend16 from 'carbon-icons-svelte/lib/Recommend16';
import Settings16 from 'carbon-icons-svelte/lib/Settings16';
import { leftPanelActions, leftPanelTypes } from './constants';
import searchStore from './searchStore';
import isNavItemSelectedStore from './UIShellSideNav/isNavItemSelectedStore';
const navMenu = [
{
href: '#',
text: 'Link 1',
subMenu: undefined
},
{
href: '#',
text: 'Link 2',
subMenu: undefined
},
{
href: '#',
text: 'Link 3',
subMenu: undefined
},
{
href: undefined,
text: 'Link 4',
subMenu: [
{
href: '#',
text: 'Sub-link 1'
},
{
href: '#',
text: 'Sub-link 2'
},
{
href: '#',
text: 'Sub-link 3'
}
]
}
];
import SideNav from './SideNav/SideNav.svelte';
import SideNavItems from './SideNav/SideNavItems.svelte';
import SideNavMenu from './SideNav/SideNavMenu.svelte';
import SideNavMenuItem from './SideNav/SideNavMenuItem.svelte';
import SideNavLink from './SideNav/SideNavLink.svelte';
/*
Format:
let iCatalog = {
class: undefined,
skeleton: false,
render: ChangeCatalog16,
title: 'Catalog',
tabindex: '0',
focusable: false,
style: undefined
};
action: will be place on the aria-label, use to identify the action from the others. Predifined actions: 'search', 'help', 'notification', 'account', 'switcher'
let iAdjust = {
class: undefined,
skeleton: false,
render: SettingsAdjust20,
title: 'Catalog',
tabindex: '0',
focusable: false,
style: undefined
};
type: search-> will open an inputText to start a search.
component-> when specifying this property, content will expect a .svelte file.
link-> when specifying this property, content will expect for just 1 href and text
links-> when specifying this property, content will expect for an array of objects with property subject and another array of objects with href and text
icon: you need to specify the properties specified in carbon-icons-svelte components as shown in the example below.
content: what will be shown on the component, could vary the information depending on the type property.
*** actions 'search' and 'switcher' doesnt need to specify type and icon because those are predefined actions in the component.
*/
const rightPanel = [
{
action: leftPanelActions.search.actionString,
type: leftPanelTypes.search,
isVisible: true
},
{
action: 'customsettings',
type: leftPanelTypes.component,
icon: [
{
class: undefined,
skeleton: false,
render: SettingsAdjust20,
title: 'Settings',
tabindex: '0',
focusable: false,
style: undefined
}
],
content: FormTest,
isVisible: true
},
{
action: leftPanelActions.help.actionString,
type: leftPanelTypes.link,
content: {
href: '#',
text: leftPanelActions.help.actionString
},
isVisible: true
},
{
action: leftPanelActions.notifications.actionString,
type: leftPanelTypes.links,
content: [
{
subject: 'Notification subject 1',
items: [
{
href: '#',
text: 'Notification item 1'
}
]
},
{
subject: 'Notification subject 2',
items: [
{
href: '#',
text: 'Notification item 1'
},
{
href: '#',
text: 'Notification item 2'
},
{
href: '#',
text: 'Notification item 3'
},
{
href: '#',
text: 'Notification item 4'
}
]
}
],
isVisible: true
},
{
action: leftPanelActions.switcher.actionString,
type: leftPanelTypes.links,
content: [
{
subject: 'Switcher subject 1',
items: [
{
href: '#',
text: 'Switcher item 1'
}
]
},
{
subject: 'Switcher subject 2',
items: [
{
href: '#',
text: 'Switcher item 1'
},
{
href: '#',
text: 'Switcher item 2'
},
{
href: '#',
text: 'Switcher item 3'
},
{
href: '#',
text: 'Switcher item 4'
}
]
},
{
subject: 'Switcher subject 3',
items: [
{
href: '#',
text: 'Switcher item 1'
},
{
href: '#',
text: 'Switcher item 2'
}
]
}
],
isVisible: true
},
{
action: leftPanelActions.account.actionString,
type: leftPanelTypes.link,
content: {
href: '#',
text: leftPanelActions.account.actionString
},
isVisible: true
}
];
const sideNavMenu = [
{
href: undefined,
text: 'Category 1',
subMenu: [
{
href: '#',
text: 'Cat 1 Sub-link 1'
},
{
href: '#',
text: 'Cat 1 Sub-link 2'
},
{
href: '#',
text: 'Cat 1 Sub-link 3'
}
],
icon: [
{
class: undefined,
skeleton: false,
render: ChangeCatalog16,
title: 'Catalog',
tabindex: '0',
focusable: false,
style: undefined
}
]
},
{
href: '#',
text: 'Link 2',
subMenu: undefined,
icon: [
{
class: undefined,
skeleton: false,
render: ManageProtection16,
title: 'Protection',
tabindex: '0',
focusable: false,
style: undefined
}
]
},
{
href: '#',
text: 'Link 3',
subMenu: undefined,
icon: [
{
class: undefined,
skeleton: false,
render: Recommend16,
title: 'Recommend',
tabindex: '0',
focusable: false,
style: undefined
}
]
},
{
href: undefined,
text: 'Category 2',
subMenu: [
{
href: '#',
text: 'Cat 2 Sub-link 1'
},
{
href: '#',
text: 'Cat 2 Sub-link 2'
},
{
href: '#',
text: 'Cat 2 Sub-link 3'
}
],
icon: [
{
class: undefined,
skeleton: false,
render: Settings16,
title: 'Settings',
tabindex: '0',
focusable: false,
style: undefined
}
]
}
];
isNavItemSelectedStore.setStoreValues(sideNavMenu);
function searchInStore(event) {
if (event.detail.action === leftPanelActions.search.actionString) {
searchStore.search(event.detail.textInput);
}
}
let iProtection = {
class: undefined,
skeleton: false,
render: ManageProtection16,
title: 'Catalog',
tabindex: '0',
focusable: false,
style: undefined
};
</script>
{#if story === 'with-nav'}
<UIShell {...$$props} {navMenu} />
{:else if story === 'with-actions'}
<UIShell {...$$props} {rightPanel} on:inputSearch={searchInStore} />
{:else if story === 'with-actions-nav'}
<UIShell {...$$props} {navMenu} {rightPanel} on:inputSearch={searchInStore} />
{:else if story === 'with-actions-sidenav'}
<UIShell {...$$props} {sideNavMenu} {rightPanel} on:inputSearch={searchInStore} />
{#if story === 'with-actions-sidenav'}
<UIShell {...$$props}>
<div slot="SideNav">
<SideNav>
<SideNavItems>
<SideNavMenu text="Menu 01" icon={iCatalog}>
<SideNavMenuItem text="Sub 01" href="/" />
<SideNavMenuItem text="Sub 02" href="/" />
<SideNavMenuItem text="Sub 03" href="/" />
<SideNavMenuItem text="Sub 04" href="/" />
</SideNavMenu>
<SideNavMenu text="Menu 02" icon={iAdjust}>
<SideNavMenuItem text="Sub 01" href="/" />
<SideNavMenuItem text="Sub 02" href="/" />
<SideNavMenuItem text="Sub 03" href="/" />
<SideNavMenuItem text="Sub 04" href="/" />
</SideNavMenu>
<SideNavLink text="Menu 03" href="/" icon={iProtection} />
</SideNavItems>
</SideNav>
</div>
</UIShell>
{:else}
<UIShell {...$$props} />
{/if}

View file

@ -3,46 +3,6 @@ import Component from './UIShell.Story.svelte';
export default { title: 'UIShell', decorators: [withKnobs] };
export const Default = () => ({
Component,
props: {
story: 'default',
href: text('The link href (href)', '#'),
company: text('Company name', 'IBM'),
platformName: text('Platform name', 'Platform Name')
}
});
export const WithNav = () => ({
Component,
props: {
story: 'with-nav',
href: text('The link href (href)', '#'),
company: text('Company name', 'IBM'),
platformName: text('Platform name', 'Platform Name')
}
});
export const WithActions = () => ({
Component,
props: {
story: 'with-actions',
href: text('The link href (href)', '#'),
company: text('Company name', 'IBM'),
platformName: text('Platform name', 'Platform Name')
}
});
export const WithActionsAndNav = () => ({
Component,
props: {
story: 'with-actions-nav',
href: text('The link href (href)', '#'),
company: text('Company name', 'IBM'),
platformName: text('Platform name', 'Platform Name')
}
});
export const WithActionsAndSidenav = () => ({
Component,
props: {

View file

@ -3,21 +3,13 @@
export let href = undefined;
export let company = undefined;
export let platformName = undefined;
export let navMenu = undefined;
export let rightPanel = undefined;
export let sideNavMenu = undefined;
import { cx } from '../../lib';
import UIShellNavWrapper from './UIShellNav/UIShellNavWrapper.svelte';
import UIShellNavItem from './UIShellNav/UIShellNavItem.svelte';
import UIShellRightPanel from './UIShellRightPanel/UIShellRightPanel.svelte';
import UIShellSideNavWrapper from './UIShellSideNav/UIShellSideNavWrapper.svelte';
import UIShellSideNavItem from './UIShellSideNav/UIShellSideNavItem.svelte';
import HamburgerMenu from './UIShellSideNav/HamburgerMenu.svelte';
import HamburgerMenu from './SideNav/HamburgerMenu.svelte';
let isSideNavOpen = undefined;
let winWidth = undefined;
$: isSideNavOpen = winWidth >= 1056
$: isSideNavOpen = winWidth >= 1056;
$: ariaLabel = company + (uiShellAriaLabel || $$props['aria-label'] || platformName);
</script>
@ -32,21 +24,5 @@
<span class={cx('--header__name--prefix')}>{company}</span>
&nbsp;{platformName}
</a>
{#if navMenu}
<UIShellNavWrapper ariaLabel>
{#each navMenu as itemMenu}
<UIShellNavItem {...itemMenu} />
{/each}
</UIShellNavWrapper>
{/if}
{#if rightPanel}
<UIShellRightPanel {rightPanel} on:inputSearch />
{/if}
{#if sideNavMenu}
<UIShellSideNavWrapper bind:isOpen={isSideNavOpen} bind:winWidth>
{#each sideNavMenu as itemSideMenu}
<UIShellSideNavItem {...itemSideMenu} />
{/each}
</UIShellSideNavWrapper>
{/if}
<slot name="SideNav" />
</header>

View file

@ -1,41 +0,0 @@
<script>
export let href = undefined;
export let text = undefined;
export let subMenu = undefined;
import { cx } from '../../../lib';
import UIShellSubmenu from './UIShellNavSubmenu.svelte';
</script>
{#if href}
<li>
<a
class={cx('--header__menu-item')}
role="menuitem"
tabindex="0"
on:click
on:mouseover
on:mouseenter
on:mouseleave
on:keyup
on:keydown
on:focus
on:blur
{href}>
<span class={cx('--text-truncate--end')}>{text}</span>
</a>
</li>
{:else}
<UIShellSubmenu
{href}
{text}
{subMenu}
on:click
on:mouseover
on:mouseenter
on:mouseleave
on:keyup
on:keydown
on:focus
on:blur />
{/if}

View file

@ -1,73 +0,0 @@
<script>
export let href = undefined;
export let text = undefined;
export let subMenu = undefined;
export let iconDescription = 'Expand/Collapse';
export let expanded = false;
import ChevronDown16 from 'carbon-icons-svelte/lib/ChevronDown16';
import { cx } from '../../../lib';
let listItemSubMenu = undefined;
window.addEventListener('mouseup', ({ target }) => {
if (listItemSubMenu) {
if (listItemSubMenu.contains(target) || target === listItemSubMenu) {
expanded = !expanded;
} else {
if (expanded) {
expanded = false;
}
}
}
});
</script>
<li class={cx('--header__submenu')} title={iconDescription}>
<a
bind:this={listItemSubMenu}
aria-haspopup="menu"
aria-expanded={expanded}
class={cx('--header__menu-item', '--header__menu-title')}
role="menuitem"
tabindex="0"
aria-label={text}
href="javascript:void(0)"
on:keydown
on:keydown={({ key }) => {
if (key === 'Enter') {
expanded = !expanded;
}
}}
on:click
on:mouseover
on:mouseenter
on:mouseleave
on:keyup
on:focus
on:blur>
{text}
<ChevronDown16 class={cx('--header__menu-arrow')} aria-label={iconDescription} />
</a>
<ul aria-label={href} class={cx('--header__menu')} role="menu">
{#each subMenu as item}
<li role="none">
<a
href={item.href}
class={cx('--header__menu-item')}
role="menuitem"
tabindex="0"
on:click
on:mouseover
on:mouseenter
on:mouseleave
on:keyup
on:keydown
on:focus
on:blur>
<span class={cx('--text-truncate--end')}>{item.text}</span>
</a>
</li>
{/each}
</ul>
</li>

View file

@ -1,11 +0,0 @@
<script>
export let ariaLabel = undefined;
import { cx } from '../../../lib';
</script>
<nav aria-label={ariaLabel} class={cx('--header__nav')}>
<ul aria-label={ariaLabel} class={cx('--header__menu-bar')} role="menubar">
<slot />
</ul>
</nav>

View file

@ -1,40 +0,0 @@
<script>
export let type = undefined;
export let icon = undefined;
export let content = undefined;
export let componentIsActive = undefined;
import { cx } from '../../../lib';
import Icon from '../../Icon/Icon.svelte';
import { slide } from 'svelte/transition';
</script>
<style>
.component-form {
margin: 1rem;
}
</style>
<div id="right-panel-action-component">
<button
aria-label={type}
class={cx('--header__action', componentIsActive && '--header__action--active')}
type="button"
on:keydown={({ key }) => {
if (key === 'Enter') {
componentIsActive = !componentIsActive;
}
}}>
<Icon {...icon[0]} render={icon[0].render} />
</button>
{#if componentIsActive}
<div
id="right-panel-action-component-form"
class={cx('--header-panel', '--header-panel--expanded')}
transition:slide={{ duration: 200 }}>
<div class="component-form">
<svelte:component this={content} />
</div>
</div>
{/if}
</div>

View file

@ -1,138 +0,0 @@
<script>
export let action = undefined;
import ActionLink from './ActionLink.svelte';
import ActionComponent from './ActionComponent.svelte';
import ActionSearch from './ActionSearch.svelte';
import { leftPanelTypes } from '../constants';
import { isChildOf } from '../helpers';
let typeComponent = undefined;
let componentIsActive = false;
let typeSearch = undefined;
let searchIsActive = false;
let typeLink = undefined;
let linkIsActive = false;
let isSearchFocus = false;
window.addEventListener('mouseup', ({ target }) => {
checkForClicksTypeComponent(target, typeComponent);
checkForClicksTypeSearch(target, typeSearch);
checkForClicksTypeLink(target, typeLink);
});
function checkForClicksTypeComponent(target, component) {
if (component && target) {
if (
!isChildOf(target, 'right-panel-action-component') ||
!isChildOf(target, 'right-panel-action-component-form')
) {
if (component.contains(target) || target === component) {
componentIsActive = !componentIsActive;
} else {
if (componentIsActive) {
componentIsActive = false;
}
}
}
}
}
function checkForClicksTypeSearch(target, component) {
if (component && target) {
if (!isChildOf(target, 'right-panel-action-search')) {
if (component.contains(target) || target === component) {
searchIsActive = !searchIsActive;
} else {
if (searchIsActive) {
searchIsActive = false;
}
}
} else {
if (!searchIsActive && target.id !== 'right-panel-close-search') {
searchIsActive = true;
} else if (searchIsActive && isChildOf(target, 'right-panel-close-search')) {
searchIsActive = false;
}
}
}
}
function checkForClicksTypeLink(target, component) {
if (component && target) {
if (component.contains(target) || target === component) {
linkIsActive = !linkIsActive;
} else {
if (linkIsActive) {
linkIsActive = false;
}
}
}
}
</script>
<style>
.search-wrapper {
position: relative;
display: flex;
max-width: 28rem;
width: 100%;
margin-left: 0.5rem;
height: 3rem;
background-color: #393939;
color: #fff;
transition: max-width 0.11s cubic-bezier(0.2, 0, 0.38, 0.9),
background 0.11s cubic-bezier(0.2, 0, 0.38, 0.9);
}
.search-wrapper-hidden {
max-width: 3rem;
background-color: #161616;
}
.search-focus {
outline: 2px solid #fff;
outline-offset: -2px;
}
</style>
{#if action.isVisible}
{#if action.type === leftPanelTypes.component}
<div bind:this={typeComponent}>
<ActionComponent
icon={action.icon ? action.icon : undefined}
content={action.content}
bind:componentIsActive />
</div>
{:else if action.type === leftPanelTypes.search}
<div
bind:this={typeSearch}
class="search-wrapper"
class:search-wrapper-hidden={!searchIsActive}
class:search-focus={isSearchFocus || searchIsActive}
role="search">
<ActionSearch
action={action.action}
icon={action.icon ? action.icon : undefined}
content={action.content}
bind:searchIsActive
focusInputSearch={() => {
isSearchFocus = true;
}}
focusOutInputSearch={() => {
isSearchFocus = false;
}}
on:inputSearch />
</div>
{:else if action.type === leftPanelTypes.link || action.type === leftPanelTypes.links}
<div bind:this={typeLink}>
<ActionLink
action={action.action}
type={action.type}
icon={action.icon ? action.icon : undefined}
content={action.content}
bind:linkIsActive />
</div>
{/if}
{/if}

View file

@ -1,99 +0,0 @@
<script>
export let action = undefined;
export let type = undefined;
export let icon = undefined;
export let content = undefined;
export let linkIsActive = undefined;
import { cx } from '../../../lib';
import Icon from '../../Icon/Icon.svelte';
import { leftPanelActions, leftPanelTypes } from '../constants';
import { slide } from 'svelte/transition';
let href = undefined;
if (type === leftPanelTypes.link) {
href = content.href;
}
if (!icon) {
const actionsArray = Object.entries(leftPanelActions);
for (const definedAction of actionsArray) {
for (const content of definedAction) {
if (typeof content === 'object') {
if (content.actionString === action) {
icon = content.iconProps;
}
}
}
}
}
</script>
<style>
.action-link {
text-align: center;
align-items: center;
vertical-align: middle;
justify-content: center;
padding-top: 10px;
}
.subject-divider {
color: #525252;
padding-bottom: 4px;
border-bottom: 1px solid #525252;
margin: 32px 1rem 8px;
}
.subject-divider span {
font-size: 0.75rem;
font-weight: 400;
line-height: 1rem;
letter-spacing: 0.32px;
color: #c6c6c6;
}
</style>
{#if type === leftPanelTypes.link}
<a
aria-label={type}
class={cx('--header__action', linkIsActive && '--header__action--active')}
class:action-link={true}
{href}>
<Icon {...icon} />
</a>
{:else}
<button
aria-label={type}
class={cx('--header__action', linkIsActive && '--header__action--active')}
type="button"
on:keydown={({ key }) => {
if (key === 'Enter') {
linkIsActive = !linkIsActive;
}
}}>
<Icon {...icon} />
</button>
{#if linkIsActive && type === leftPanelTypes.links}
<div
class={cx('--header-panel', '--header-panel--expanded')}
transition:slide={{ duration: 200 }}>
<ul class={cx('--switcher__item')}>
{#each content as subject}
{#if subject.subject}
<li class="subject-divider">
<span>{subject.subject}</span>
</li>
{/if}
{#each subject.items as link}
<li class={cx('--switcher__item')}>
<a class={cx('--switcher__item-link')} href={link.href}>{link.text}</a>
</li>
{/each}
{/each}
</ul>
</div>
{/if}
{/if}

View file

@ -1,191 +0,0 @@
<script>
export let action = undefined;
export let icon = undefined;
export let searchIsActive = undefined;
import { createEventDispatcher } from 'svelte';
import { cx } from '../../../lib';
import Icon from '../../Icon/Icon.svelte';
import { leftPanelActions, closeIcon } from '../constants';
import searchStore from '../searchStore';
import ActionSearchResult from './ActionSearchResult.svelte';
let searchTabIndex = '0';
let closeTabIndex = '-1';
let inputSearchField = undefined;
const dispatch = createEventDispatcher();
if (!icon) {
const actionsArray = Object.entries(leftPanelActions);
for (const definedAction of actionsArray) {
for (const content of definedAction) {
if (typeof content === 'object') {
if (content.actionString === action) {
icon = content.iconProps;
}
}
}
}
}
function dispatchInputs(event) {
const params = {
action: action,
textInput: event.target.value
};
dispatch('inputSearch', params);
}
$: if (!searchIsActive) {
if (inputSearchField) {
inputSearchField.value = '';
}
searchStore.clear();
}
$: if (searchIsActive) {
searchTabIndex = '-1';
closeTabIndex = '0';
} else {
searchTabIndex = '0';
closeTabIndex = '-1';
}
$: showResults = $searchStore ? true : false;
</script>
<style>
.search-wrapper {
display: flex;
flex-grow: 1;
border-bottom: 1px solid #393939;
}
.btn-search {
width: 3rem;
height: 100%;
padding: 0;
flex-shrink: 0;
opacity: 1;
transition: background-color 0.11s cubic-bezier(0.2, 0, 0.38, 0.9),
opacity 0.11s cubic-bezier(0.2, 0, 0.38, 0.9);
}
.btn-search-disabled {
border: none;
pointer-events: none;
}
.input-search {
font-size: 1rem;
font-weight: 400;
line-height: 1.375rem;
letter-spacing: 0;
color: #fff;
caret-color: #fff;
background-color: initial;
border: none;
outline: none;
width: 100%;
height: 3rem;
padding: 0;
transition: opacity 0.11s cubic-bezier(0.2, 0, 0.38, 0.9);
}
.input-hidden {
opacity: 0;
pointer-events: none;
}
.btn-clear {
width: 3rem;
height: 100%;
padding: 0;
flex-shrink: 0;
opacity: 1;
display: block;
transition: background-color 0.11s cubic-bezier(0.2, 0, 0.38, 0.9),
opacity 0.11s cubic-bezier(0.2, 0, 0.38, 0.9);
}
.btn-clear:hover {
background-color: #4c4c4c;
}
.btn-clear-hidden {
opacity: 0;
display: none;
}
.search-list {
position: absolute;
z-index: 10000;
padding: 1rem 0;
left: 0;
right: 0;
top: 3rem;
background-color: #161616;
border: 1px solid #393939;
border-top: none;
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.5);
}
</style>
<div
id="right-panel-action-search"
class="search-wrapper"
role="combobox"
aria-expanded={searchIsActive}>
<button
tabindex={searchTabIndex}
aria-label={action}
class={cx('--header__action')}
class:btn-search={true}
class:btn-search-disabled={searchIsActive}
on:click={() => dispatch('focusInputSearch')}
type="button"
on:keydown={({ key }) => {
if (key === 'Enter') {
searchIsActive = !searchIsActive;
}
}}>
<Icon {...icon} />
</button>
<input
bind:this={inputSearchField}
id="input-search-field"
type="text"
autocomplete="off"
tabindex={closeTabIndex}
class="input-search"
class:input-hidden={!searchIsActive}
placeholder="Search"
on:focus={() => dispatch('focusInputSearch')}
on:focusout={() => dispatch('focusOutInputSearch')}
on:input={dispatchInputs} />
<button
id="right-panel-close-search"
tabindex={closeTabIndex}
class={cx('--header__action')}
class:btn-clear={true}
class:btn-clear-hidden={!searchIsActive}
type="button"
aria-label="Clear search"
on:click={() => searchStore.clear()}
on:keydown={({ key }) => {
if (key === 'Enter') {
searchIsActive = !searchIsActive;
}
}}>
<Icon {...closeIcon} />
</button>
</div>
{#if showResults}
<ul aria-labelledby="search-label" role="menu" id="search-menu" class="search-list">
{#each $searchStore as searchItem, index}
<ActionSearchResult {searchItem} {index} />
{/each}
</ul>
{/if}

View file

@ -1,72 +0,0 @@
<script>
export let searchItem = undefined;
export let index = undefined;
let onHover = false;
</script>
<style>
.search-list-item {
padding: 6px 1rem;
cursor: pointer;
font-size: 0.875rem;
font-weight: 600;
line-height: 1.125rem;
letter-spacing: 0.16px;
transition: all 70ms cubic-bezier(0.2, 0, 0.38, 0.9);
display: block;
text-decoration: none;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
color: #c6c6c6;
}
.search-list-item-menu {
text-transform: capitalize;
}
.search-list-item-description {
font-size: 0.75rem;
font-weight: 400;
line-height: 1rem;
letter-spacing: 0.32px;
text-transform: lowercase;
color: #c6c6c6;
}
.search-list-item-active {
background-color: #353535;
color: #f4f4f4;
outline: 0;
}
</style>
{#if index === 0}
<li role="none">
<a
tabindex="-1"
id={`menu-item-${index}`}
role="menuitem"
class="search-list-item search-list-item-active"
href={searchItem.href}>
{searchItem.title}&nbsp;
<span class="search-list-item-menu">{searchItem.menu}</span>
<span class="search-list-item-description">{searchItem.description}</span>
</a>
</li>
{:else}
<li role="none" on:mouseover={() => (onHover = true)} on:mouseleave={() => (onHover = false)}>
<a
tabindex="-1"
id={`menu-item-${index}`}
role="menuitem"
class="search-list-item"
class:search-list-item-active={onHover}
href={searchItem.href}>
{searchItem.title}&nbsp;
<span class="search-list-item-menu">{searchItem.menu}</span>
<span class="search-list-item-description">{searchItem.description}</span>
</a>
</li>
{/if}

View file

@ -1,50 +0,0 @@
<script>
export let rightPanel = undefined;
import { cx } from '../../../lib';
import ActionGeneric from './ActionGeneric.svelte';
import { leftPanelActions } from '../constants';
let orderedRightPanel = [];
let customActions = 1;
rightPanel.forEach((item, index) => {
orderedRightPanel[index] = undefined;
if (item.action) {
if (
Object.keys(leftPanelActions).indexOf(
item.action.charAt(0).toLowerCase() + item.action.slice(1)
) === -1
) {
orderedRightPanel[customActions] = rightPanel[index];
customActions += 1;
}
}
});
rightPanel.forEach((item, index) => {
if (item.action) {
if (item.action === leftPanelActions.search.actionString) {
orderedRightPanel[0] = rightPanel[index];
} else if (item.action === leftPanelActions.help.actionString) {
orderedRightPanel[customActions] = rightPanel[index];
} else if (item.action === leftPanelActions.notifications.actionString) {
orderedRightPanel[customActions + 1] = rightPanel[index];
} else if (item.action === leftPanelActions.account.actionString) {
orderedRightPanel[customActions + 2] = rightPanel[index];
} else if (item.action === leftPanelActions.switcher.actionString) {
orderedRightPanel[customActions + 3] = rightPanel[index];
}
}
});
orderedRightPanel = orderedRightPanel.filter(item => {
return item;
});
</script>
<div class={cx('--header__global')}>
{#each orderedRightPanel as action, index}
<ActionGeneric {action} on:inputSearch />
{/each}
</div>

View file

@ -1,81 +0,0 @@
<script>
export let href = undefined;
export let text = undefined;
export let subMenu = undefined;
export let icon = undefined;
export let expanded = false;
import { cx } from '../../../lib';
import { onDestroy } from 'svelte';
import Icon from '../../Icon/Icon.svelte';
import ChevronDown16 from 'carbon-icons-svelte/lib/ChevronDown16';
import isNavItemSelectedStore from './isNavItemSelectedStore';
import UIShellSideNavSubMenu from './UIShellSideNavSubMenu.svelte';
let isSelected = undefined;
const unsubscribe = isNavItemSelectedStore.subscribe(values => {
values.forEach(item => {
if (item.id === text) {
isSelected = item.isSelected;
}
});
});
let iconProps = {
class: undefined,
skeleton: false,
render: ChevronDown16,
title: 'Open Menu',
tabindex: '0',
focusable: false,
style: undefined
};
onDestroy(() => {
unsubscribe();
});
</script>
{#if href}
<li class={cx('--side-nav__item')}>
<a
{href}
class={cx('--side-nav__link', isSelected && '--side-nav__link--current')}
aria-current={isSelected ? 'page' : ''}
on:click={() => isNavItemSelectedStore.setSelected(text)}>
<div class={cx('--side-nav__icon', '--side-nav__icon--small')}>
{#if icon}
<Icon {...icon[0]} />
{/if}
</div>
<span class={cx('--side-nav__link-text')}>{text}</span>
</a>
</li>
{:else}
<li class={cx('--side-nav__item', '--side-nav__item--icon')}>
<button
aria-haspopup="true"
aria-expanded={expanded}
class={cx('--side-nav__submenu')}
type="button"
on:click={() => {
expanded = !expanded;
}}>
<div class={cx('--side-nav__icon')}>
<Icon {...icon[0]} />
</div>
<span class={cx('--side-nav__submenu-title')}>{text}</span>
<div class={cx('--side-nav__icon', '--side-nav__icon--small', '--side-nav__submenu-chevron')}>
{#if icon}
<Icon {...iconProps} />
{/if}
</div>
</button>
<ul class={cx('--side-nav__menu')} role="menu">
{#each subMenu as menuItem}
<UIShellSideNavSubMenu {...menuItem} />
{/each}
</ul>
</li>
{/if}

View file

@ -1,33 +0,0 @@
<script>
export let href = undefined;
export let text = undefined;
import { cx } from '../../../lib';
import { onDestroy } from 'svelte';
import isNavItemSelectedStore from './isNavItemSelectedStore';
let isSelected = undefined;
const unsubscribe = isNavItemSelectedStore.subscribe(values => {
values.forEach(item => {
if (item.id === text) {
isSelected = item.isSelected;
}
});
});
onDestroy(() => {
unsubscribe();
});
</script>
<li class={cx('--side-nav__menu-item')} role="none">
<a
{href}
class={cx('--side-nav__link')}
role="menuitem"
aria-current={isSelected ? 'page' : ''}
on:click={() => isNavItemSelectedStore.setSelected(text)}>
<span class={cx('--side-nav__link-text')}>{text}</span>
</a>
</li>

View file

@ -1,60 +0,0 @@
import { writable } from 'svelte/store';
const isSelectedStore = writable([undefined]);
const store = {
subscribe: isSelectedStore.subscribe,
setStoreValues: arrayOfObjs => {
let storeNewData = [];
arrayOfObjs.forEach(element => {
let newItem = {};
if (element.href) {
newItem = {
id: element.text,
isSelected: false
};
storeNewData.push(newItem);
} else {
element.subMenu.forEach(subelement => {
newItem = {
id: subelement.text,
isSelected: false
};
storeNewData.push(newItem);
});
}
});
isSelectedStore.set(storeNewData);
},
setSelected: id => {
isSelectedStore.update(values => {
const newValues = values.map(item => {
if (item.id === id) {
item.isSelected = true;
return item;
} else {
item.isSelected = false;
return item;
}
});
return newValues;
});
},
deselectAll: () => {
isSelectedStore.update(values => {
values.forEach(item => {
item.isSelected = false;
return item;
});
});
},
clear: () => {
isSelectedStore.set(undefined);
}
};
export default store;

View file

@ -1,3 +1,9 @@
import UIShell from './UIShell.svelte';
export default UIShell;
export default UIShell;
export { default as SideNav } from './SideNav/SideNav.svelte';
export { default as SideNavItems } from './SideNav/SideNavItems.svelte';
export { default as SideNavLink } from './SideNav/SideNavLink.svelte';
export { default as SideNavMenu } from './SideNav/SideNavMenu.svelte';
export { default as SideNavMenuItem } from './SideNav/SideNavMenuItem.svelte';

View file

@ -78,7 +78,13 @@ import ToggleSmall, { ToggleSmallSkeleton } from './components/ToggleSmall';
import Tooltip from './components/Tooltip';
import TooltipDefinition from './components/TooltipDefinition';
import TooltipIcon from './components/TooltipIcon';
import UIShell from './components/UIShell';
import UIShell, {
SideNav,
SideNavItems,
SideNavLink,
SideNavMenu,
SideNavMenuItem
} from './components/UIShell';
import UnorderedList from './components/UnorderedList';
export {
@ -194,5 +200,10 @@ export {
TooltipDefinition,
TooltipIcon,
UIShell,
SideNav,
SideNavItems,
SideNavLink,
SideNavMenu,
SideNavMenuItem,
UnorderedList
};

View file

@ -5148,9 +5148,9 @@ eslint-visitor-keys@^1.1.0:
resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.1.0.tgz#e2a82cea84ff246ad6fb57f9bde5b46621459ec2"
integrity sha512-8y9YjtM1JBJU/A9Kc+SbaOV4y29sSWckBwMHa+FGtVj5gN/sbnKDf6xJUl+8g7FAij9LVaP8C24DUiH/f/2Z9A==
eslint@^6.7.2:
eslint@^6.8.0:
version "6.8.0"
resolved "https://registry.npmjs.org/eslint/-/eslint-6.8.0.tgz#62262d6729739f9275723824302fb227c8c93ffb"
resolved "https://registry.yarnpkg.com/eslint/-/eslint-6.8.0.tgz#62262d6729739f9275723824302fb227c8c93ffb"
integrity sha512-K+Iayyo2LtyYhDSYwz5D5QdWw0hCacNzyq1Y821Xna2xSJj7cijoLLYmLxTQgcgZ9mC61nryMy9S7GRbYpI5Ig==
dependencies:
"@babel/code-frame" "^7.0.0"