mirror of
https://github.com/carbon-design-system/carbon-components-svelte.git
synced 2025-09-15 10:21:05 +00:00
complete right panel
This commit is contained in:
parent
390f950ca5
commit
b6bc6aee87
9 changed files with 269 additions and 69 deletions
|
@ -4,7 +4,9 @@
|
||||||
import UIShell from './UIShell.svelte';
|
import UIShell from './UIShell.svelte';
|
||||||
import FormTest from './FormTest.svelte';
|
import FormTest from './FormTest.svelte';
|
||||||
import SettingsAdjust20 from 'carbon-icons-svelte/lib/SettingsAdjust20';
|
import SettingsAdjust20 from 'carbon-icons-svelte/lib/SettingsAdjust20';
|
||||||
|
// import Binoculars20 from 'carbon-icons-svelte/lib/Binoculars20'
|
||||||
import { leftPanelActions, leftPanelTypes } from './constants';
|
import { leftPanelActions, leftPanelTypes } from './constants';
|
||||||
|
import searchStore from './searchStore';
|
||||||
|
|
||||||
const navMenu = [
|
const navMenu = [
|
||||||
{
|
{
|
||||||
|
@ -61,6 +63,7 @@
|
||||||
|
|
||||||
*** actions 'search' and 'switcher' doesnt need to specify type and icon because those are predefined actions in the component.
|
*** actions 'search' and 'switcher' doesnt need to specify type and icon because those are predefined actions in the component.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const rightPanel = [
|
const rightPanel = [
|
||||||
{
|
{
|
||||||
action: leftPanelActions.search.actionString,
|
action: leftPanelActions.search.actionString,
|
||||||
|
@ -189,13 +192,35 @@
|
||||||
},
|
},
|
||||||
isVisible: true
|
isVisible: true
|
||||||
}
|
}
|
||||||
|
// {
|
||||||
|
// action: 'customsearch',
|
||||||
|
// type: leftPanelTypes.search,
|
||||||
|
// icon: [
|
||||||
|
// {
|
||||||
|
// class: undefined,
|
||||||
|
// skeleton: false,
|
||||||
|
// render: Binoculars20,
|
||||||
|
// title: 'binoculars',
|
||||||
|
// tabIndex: 0,
|
||||||
|
// focusable: false,
|
||||||
|
// style: undefined
|
||||||
|
// }
|
||||||
|
// ],
|
||||||
|
// isVisible: true
|
||||||
|
// }
|
||||||
];
|
];
|
||||||
|
|
||||||
|
function searchInStore(event) {
|
||||||
|
if (event.detail.action === leftPanelActions.search.actionString) {
|
||||||
|
searchStore.search(event.detail.textInput);
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if story === 'with-nav'}
|
{#if story === 'with-nav'}
|
||||||
<UIShell {...$$props} {navMenu} />
|
<UIShell {...$$props} {navMenu} />
|
||||||
{:else if story === 'with-actions'}
|
{:else if story === 'with-actions'}
|
||||||
<UIShell {...$$props} {rightPanel} />
|
<UIShell {...$$props} {rightPanel} on:inputSearch={searchInStore} />
|
||||||
{:else}
|
{:else}
|
||||||
<UIShell {...$$props} />
|
<UIShell {...$$props} />
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
@ -27,6 +27,6 @@
|
||||||
</UIShellNavWrapper>
|
</UIShellNavWrapper>
|
||||||
{/if}
|
{/if}
|
||||||
{#if rightPanel}
|
{#if rightPanel}
|
||||||
<UIShellRightPanel {rightPanel} />
|
<UIShellRightPanel {rightPanel} on:inputSearch />
|
||||||
{/if}
|
{/if}
|
||||||
</header>
|
</header>
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
<!-- <script>
|
<script>
|
||||||
export let action = undefined;
|
// export let action = undefined;
|
||||||
export let type = undefined;
|
export let type = undefined;
|
||||||
export let icon = undefined;
|
export let icon = undefined;
|
||||||
// export let content = undefined;
|
// export let content = undefined;
|
||||||
|
|
||||||
import { cx } from '../../../lib';
|
import { cx } from '../../../lib';
|
||||||
import Icon from '../../Icon/Icon.svelte';
|
import Icon from '../../Icon/Icon.svelte';
|
||||||
import { leftPanelActions } from '../constants';
|
// import { leftPanelActions } from '../constants';
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<button aria-label={type} class={cx('--header__action')} type="button">
|
<button aria-label={type} class={cx('--header__action')} type="button">
|
||||||
<Icon {...icon} render={icon[0].render} />
|
<Icon {...icon} render={icon[0].render} />
|
||||||
</button> -->
|
</button>
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
<script>
|
<script>
|
||||||
export let action = undefined;
|
export let action = undefined;
|
||||||
|
|
||||||
import { cx } from '../../../lib';
|
|
||||||
import ActionLink from './ActionLink.svelte';
|
import ActionLink from './ActionLink.svelte';
|
||||||
import ActionComponent from './ActionComponent.svelte';
|
import ActionComponent from './ActionComponent.svelte';
|
||||||
import ActionSearch from './ActionSearch.svelte';
|
import ActionSearch from './ActionSearch.svelte';
|
||||||
import { leftPanelTypes, actionSearchWhiteList } from '../constants';
|
import { leftPanelTypes } from '../constants';
|
||||||
|
|
||||||
let typeComponent = undefined;
|
let typeComponent = undefined;
|
||||||
let componentIsActive = false;
|
let componentIsActive = false;
|
||||||
|
@ -47,9 +46,6 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkForClicksTypeSearch(target, component, actionString) {
|
function checkForClicksTypeSearch(target, component, actionString) {
|
||||||
let parentFirstLine;
|
|
||||||
let parentSecondLine;
|
|
||||||
|
|
||||||
if (component && target) {
|
if (component && target) {
|
||||||
try {
|
try {
|
||||||
if (
|
if (
|
||||||
|
@ -158,6 +154,7 @@
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
{#if action.type === leftPanelTypes.component}
|
{#if action.type === leftPanelTypes.component}
|
||||||
|
{#if action.isVisible}
|
||||||
<div bind:this={typeComponent}>
|
<div bind:this={typeComponent}>
|
||||||
<ActionComponent
|
<ActionComponent
|
||||||
action={action.action}
|
action={action.action}
|
||||||
|
@ -165,7 +162,9 @@
|
||||||
content={action.content}
|
content={action.content}
|
||||||
bind:componentIsActive />
|
bind:componentIsActive />
|
||||||
</div>
|
</div>
|
||||||
|
{/if}
|
||||||
{:else if action.type === leftPanelTypes.search}
|
{:else if action.type === leftPanelTypes.search}
|
||||||
|
{#if action.isVisible}
|
||||||
<div
|
<div
|
||||||
bind:this={typeSearch}
|
bind:this={typeSearch}
|
||||||
class="search-wrapper"
|
class="search-wrapper"
|
||||||
|
@ -182,9 +181,12 @@
|
||||||
}}
|
}}
|
||||||
focusOutInputSearch={() => {
|
focusOutInputSearch={() => {
|
||||||
isSearchFocus = false;
|
isSearchFocus = false;
|
||||||
}} />
|
}}
|
||||||
|
on:inputSearch />
|
||||||
</div>
|
</div>
|
||||||
|
{/if}
|
||||||
{:else if action.type === leftPanelTypes.link || action.type === leftPanelTypes.links}
|
{:else if action.type === leftPanelTypes.link || action.type === leftPanelTypes.links}
|
||||||
|
{#if action.isVisible}
|
||||||
<div bind:this={typeLink}>
|
<div bind:this={typeLink}>
|
||||||
<ActionLink
|
<ActionLink
|
||||||
action={action.action}
|
action={action.action}
|
||||||
|
@ -193,3 +195,4 @@
|
||||||
bind:linkIsActive />
|
bind:linkIsActive />
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
{/if}
|
||||||
|
|
|
@ -10,8 +10,6 @@
|
||||||
import { leftPanelActions } from '../constants';
|
import { leftPanelActions } from '../constants';
|
||||||
import { slide } from 'svelte/transition';
|
import { slide } from 'svelte/transition';
|
||||||
|
|
||||||
let skeleton = icon ? icon.skeleton : false;
|
|
||||||
|
|
||||||
if (!icon) {
|
if (!icon) {
|
||||||
const actionsArray = Object.entries(leftPanelActions);
|
const actionsArray = Object.entries(leftPanelActions);
|
||||||
|
|
||||||
|
@ -27,23 +25,14 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if action === leftPanelActions.switcher.actionString}
|
|
||||||
<button
|
<button
|
||||||
aria-label={type}
|
aria-label={type}
|
||||||
class={cx('--header__action', linkIsActive && '--header__action--active')}
|
class={cx('--header__action', linkIsActive && '--header__action--active')}
|
||||||
type="button">
|
type="button">
|
||||||
<Icon {...icon} render={icon.render} />
|
<Icon {...icon} render={icon.render} />
|
||||||
</button>
|
</button>
|
||||||
{:else}
|
|
||||||
<button
|
|
||||||
aria-label={type}
|
|
||||||
class={cx('--header__action', linkIsActive && '--header__action--active')}
|
|
||||||
type="button">
|
|
||||||
<Icon {...icon} render={icon.render} />
|
|
||||||
</button>
|
|
||||||
{/if}
|
|
||||||
{#if linkIsActive}
|
{#if linkIsActive}
|
||||||
<div class="bx--header-panel bx--header-panel--expanded" transition:slide="{{duration: 200}}">
|
<div class="bx--header-panel bx--header-panel--expanded" transition:slide={{ duration: 200 }}>
|
||||||
<ul class="bx--switcher__item">
|
<ul class="bx--switcher__item">
|
||||||
<li class="bx--switcher__item">
|
<li class="bx--switcher__item">
|
||||||
<a class="bx--switcher__item-link bx--switcher__item-link--selected" href="/">Link</a>
|
<a class="bx--switcher__item-link bx--switcher__item-link--selected" href="/">Link</a>
|
||||||
|
|
|
@ -1,17 +1,18 @@
|
||||||
<script>
|
<script>
|
||||||
export let action = undefined;
|
export let action = undefined;
|
||||||
export let icon = undefined;
|
export let icon = undefined;
|
||||||
// export let content = undefined;
|
|
||||||
export let searchIsActive = undefined;
|
export let searchIsActive = undefined;
|
||||||
|
|
||||||
import { createEventDispatcher } from 'svelte';
|
import { createEventDispatcher } from 'svelte';
|
||||||
import { cx } from '../../../lib';
|
import { cx } from '../../../lib';
|
||||||
import Icon from '../../Icon/Icon.svelte';
|
import Icon from '../../Icon/Icon.svelte';
|
||||||
import { leftPanelActions, closeIcon } from '../constants';
|
import { leftPanelActions, closeIcon } from '../constants';
|
||||||
|
import searchStore from '../searchStore';
|
||||||
|
import ActionSearchResult from './ActionSearchResult.svelte'
|
||||||
|
|
||||||
// let searchIconProps = undefined;
|
|
||||||
let searchTabIndex = 0;
|
let searchTabIndex = 0;
|
||||||
let closeTabIndex = -1;
|
let closeTabIndex = -1;
|
||||||
|
let inputSearchField = undefined;
|
||||||
const dispatch = createEventDispatcher();
|
const dispatch = createEventDispatcher();
|
||||||
|
|
||||||
if (!icon) {
|
if (!icon) {
|
||||||
|
@ -28,6 +29,26 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function dispatchInputs(event) {
|
||||||
|
const params = {
|
||||||
|
action: action,
|
||||||
|
textInput: event.target.value
|
||||||
|
};
|
||||||
|
|
||||||
|
dispatch('inputSearch', params);
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearSearch() {
|
||||||
|
searchStore.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
$: if (!searchIsActive) {
|
||||||
|
try {
|
||||||
|
inputSearchField.value = '';
|
||||||
|
searchStore.clear();
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
|
||||||
$: if (searchIsActive) {
|
$: if (searchIsActive) {
|
||||||
searchTabIndex = -1;
|
searchTabIndex = -1;
|
||||||
closeTabIndex = 0;
|
closeTabIndex = 0;
|
||||||
|
@ -35,6 +56,8 @@
|
||||||
searchTabIndex = 0;
|
searchTabIndex = 0;
|
||||||
closeTabIndex = -1;
|
closeTabIndex = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$: showResults = $searchStore ? true : false;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
@ -86,6 +109,7 @@
|
||||||
padding: 0;
|
padding: 0;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
|
display: block;
|
||||||
transition: background-color 0.11s cubic-bezier(0.2, 0, 0.38, 0.9),
|
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);
|
opacity 0.11s cubic-bezier(0.2, 0, 0.38, 0.9);
|
||||||
}
|
}
|
||||||
|
@ -96,6 +120,20 @@
|
||||||
|
|
||||||
.btn-clear-hidden {
|
.btn-clear-hidden {
|
||||||
opacity: 0;
|
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>
|
</style>
|
||||||
|
|
||||||
|
@ -115,6 +153,8 @@
|
||||||
<Icon {...icon} render={icon.render} />
|
<Icon {...icon} render={icon.render} />
|
||||||
</button>
|
</button>
|
||||||
<input
|
<input
|
||||||
|
bind:this={inputSearchField}
|
||||||
|
id="input-search-field"
|
||||||
type="text"
|
type="text"
|
||||||
autocomplete="off"
|
autocomplete="off"
|
||||||
tabindex={closeTabIndex}
|
tabindex={closeTabIndex}
|
||||||
|
@ -122,7 +162,8 @@
|
||||||
class:input-hidden={!searchIsActive}
|
class:input-hidden={!searchIsActive}
|
||||||
placeholder="Search"
|
placeholder="Search"
|
||||||
on:focus={() => dispatch('focusInputSearch')}
|
on:focus={() => dispatch('focusInputSearch')}
|
||||||
on:focusout={() => dispatch('focusOutInputSearch')} />
|
on:focusout={() => dispatch('focusOutInputSearch')}
|
||||||
|
on:input={dispatchInputs} />
|
||||||
<button
|
<button
|
||||||
id="right-panel-close-search"
|
id="right-panel-close-search"
|
||||||
tabindex={closeTabIndex}
|
tabindex={closeTabIndex}
|
||||||
|
@ -130,7 +171,15 @@
|
||||||
class:btn-clear={true}
|
class:btn-clear={true}
|
||||||
class:btn-clear-hidden={!searchIsActive}
|
class:btn-clear-hidden={!searchIsActive}
|
||||||
type="button"
|
type="button"
|
||||||
aria-label="Clear search">
|
aria-label="Clear search"
|
||||||
|
on:click={clearSearch}>
|
||||||
<Icon {...closeIcon} render={closeIcon.render} />
|
<Icon {...closeIcon} render={closeIcon.render} />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</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}
|
||||||
|
|
|
@ -0,0 +1,72 @@
|
||||||
|
<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}
|
||||||
|
<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}
|
||||||
|
<span class="search-list-item-menu">→ {searchItem.menu}</span>
|
||||||
|
<span class="search-list-item-description">{searchItem.description}</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
{/if}
|
|
@ -11,7 +11,11 @@
|
||||||
rightPanel.forEach((item, index) => {
|
rightPanel.forEach((item, index) => {
|
||||||
orderedRightPanel[index] = undefined;
|
orderedRightPanel[index] = undefined;
|
||||||
if (item.action) {
|
if (item.action) {
|
||||||
if (Object.keys(leftPanelActions).indexOf(item.action.charAt(0).toLowerCase() + item.action.slice(1)) === -1) {
|
if (
|
||||||
|
Object.keys(leftPanelActions).indexOf(
|
||||||
|
item.action.charAt(0).toLowerCase() + item.action.slice(1)
|
||||||
|
) === -1
|
||||||
|
) {
|
||||||
orderedRightPanel[customActions] = rightPanel[index];
|
orderedRightPanel[customActions] = rightPanel[index];
|
||||||
customActions += 1;
|
customActions += 1;
|
||||||
}
|
}
|
||||||
|
@ -41,6 +45,6 @@
|
||||||
|
|
||||||
<div class={cx('--header__global')}>
|
<div class={cx('--header__global')}>
|
||||||
{#each orderedRightPanel as action, index}
|
{#each orderedRightPanel as action, index}
|
||||||
<ActionGeneric {action} />
|
<ActionGeneric {action} on:inputSearch />
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
|
|
58
src/components/UIShell/searchStore.js
Normal file
58
src/components/UIShell/searchStore.js
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
import { writable } from 'svelte/store';
|
||||||
|
|
||||||
|
const data = [
|
||||||
|
{
|
||||||
|
href: '#',
|
||||||
|
title: 'Test title search 1',
|
||||||
|
menu: 'Test menu 1',
|
||||||
|
description: 'This is a description for seach #1'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
href: '#',
|
||||||
|
title: 'Changing text to simulate search',
|
||||||
|
menu: 'Test menu 2',
|
||||||
|
description: 'This is a description for seach #2'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
href: '#',
|
||||||
|
title: 'More testing texts',
|
||||||
|
menu: 'Test menu 3',
|
||||||
|
description: 'This is a description for seach #3'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
href: '#',
|
||||||
|
title: 'We can find here another test text',
|
||||||
|
menu: 'Test menu 4',
|
||||||
|
description: 'This is a description for seach #4'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const globalStore = writable(undefined);
|
||||||
|
|
||||||
|
const store = {
|
||||||
|
subscribe: globalStore.subscribe,
|
||||||
|
search: searchString => {
|
||||||
|
if (searchString.length > 1) {
|
||||||
|
let resultSearch = [];
|
||||||
|
|
||||||
|
data.forEach(item => {
|
||||||
|
if (item.title.toLowerCase().includes(searchString.toLowerCase())) {
|
||||||
|
resultSearch.push(item);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (resultSearch.length > 0) {
|
||||||
|
globalStore.set(resultSearch);
|
||||||
|
} else {
|
||||||
|
globalStore.set(undefined);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
globalStore.set(undefined);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
clear: () => {
|
||||||
|
globalStore.set(undefined);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default store;
|
Loading…
Add table
Add a link
Reference in a new issue