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 FormTest from './FormTest.svelte';
|
||||
import SettingsAdjust20 from 'carbon-icons-svelte/lib/SettingsAdjust20';
|
||||
// import Binoculars20 from 'carbon-icons-svelte/lib/Binoculars20'
|
||||
import { leftPanelActions, leftPanelTypes } from './constants';
|
||||
import searchStore from './searchStore';
|
||||
|
||||
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.
|
||||
*/
|
||||
|
||||
const rightPanel = [
|
||||
{
|
||||
action: leftPanelActions.search.actionString,
|
||||
|
@ -189,13 +192,35 @@
|
|||
},
|
||||
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>
|
||||
|
||||
{#if story === 'with-nav'}
|
||||
<UIShell {...$$props} {navMenu} />
|
||||
{:else if story === 'with-actions'}
|
||||
<UIShell {...$$props} {rightPanel} />
|
||||
<UIShell {...$$props} {rightPanel} on:inputSearch={searchInStore} />
|
||||
{:else}
|
||||
<UIShell {...$$props} />
|
||||
{/if}
|
||||
|
|
|
@ -27,6 +27,6 @@
|
|||
</UIShellNavWrapper>
|
||||
{/if}
|
||||
{#if rightPanel}
|
||||
<UIShellRightPanel {rightPanel} />
|
||||
<UIShellRightPanel {rightPanel} on:inputSearch />
|
||||
{/if}
|
||||
</header>
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
<!-- <script>
|
||||
export let action = undefined;
|
||||
<script>
|
||||
// export let action = undefined;
|
||||
export let type = undefined;
|
||||
export let icon = undefined;
|
||||
// export let content = undefined;
|
||||
|
||||
import { cx } from '../../../lib';
|
||||
import Icon from '../../Icon/Icon.svelte';
|
||||
import { leftPanelActions } from '../constants';
|
||||
// import { leftPanelActions } from '../constants';
|
||||
</script>
|
||||
|
||||
<button aria-label={type} class={cx('--header__action')} type="button">
|
||||
<Icon {...icon} render={icon[0].render} />
|
||||
</button> -->
|
||||
</button>
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
<script>
|
||||
export let action = undefined;
|
||||
|
||||
import { cx } from '../../../lib';
|
||||
import ActionLink from './ActionLink.svelte';
|
||||
import ActionComponent from './ActionComponent.svelte';
|
||||
import ActionSearch from './ActionSearch.svelte';
|
||||
import { leftPanelTypes, actionSearchWhiteList } from '../constants';
|
||||
import { leftPanelTypes } from '../constants';
|
||||
|
||||
let typeComponent = undefined;
|
||||
let componentIsActive = false;
|
||||
|
@ -47,9 +46,6 @@
|
|||
}
|
||||
|
||||
function checkForClicksTypeSearch(target, component, actionString) {
|
||||
let parentFirstLine;
|
||||
let parentSecondLine;
|
||||
|
||||
if (component && target) {
|
||||
try {
|
||||
if (
|
||||
|
@ -158,6 +154,7 @@
|
|||
</style>
|
||||
|
||||
{#if action.type === leftPanelTypes.component}
|
||||
{#if action.isVisible}
|
||||
<div bind:this={typeComponent}>
|
||||
<ActionComponent
|
||||
action={action.action}
|
||||
|
@ -165,7 +162,9 @@
|
|||
content={action.content}
|
||||
bind:componentIsActive />
|
||||
</div>
|
||||
{/if}
|
||||
{:else if action.type === leftPanelTypes.search}
|
||||
{#if action.isVisible}
|
||||
<div
|
||||
bind:this={typeSearch}
|
||||
class="search-wrapper"
|
||||
|
@ -182,9 +181,12 @@
|
|||
}}
|
||||
focusOutInputSearch={() => {
|
||||
isSearchFocus = false;
|
||||
}} />
|
||||
}}
|
||||
on:inputSearch />
|
||||
</div>
|
||||
{/if}
|
||||
{:else if action.type === leftPanelTypes.link || action.type === leftPanelTypes.links}
|
||||
{#if action.isVisible}
|
||||
<div bind:this={typeLink}>
|
||||
<ActionLink
|
||||
action={action.action}
|
||||
|
@ -192,4 +194,5 @@
|
|||
content={action.content}
|
||||
bind:linkIsActive />
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
|
|
|
@ -10,8 +10,6 @@
|
|||
import { leftPanelActions } from '../constants';
|
||||
import { slide } from 'svelte/transition';
|
||||
|
||||
let skeleton = icon ? icon.skeleton : false;
|
||||
|
||||
if (!icon) {
|
||||
const actionsArray = Object.entries(leftPanelActions);
|
||||
|
||||
|
@ -27,23 +25,14 @@
|
|||
}
|
||||
</script>
|
||||
|
||||
{#if action === leftPanelActions.switcher.actionString}
|
||||
<button
|
||||
<button
|
||||
aria-label={type}
|
||||
class={cx('--header__action', linkIsActive && '--header__action--active')}
|
||||
type="button">
|
||||
<Icon {...icon} render={icon.render} />
|
||||
</button>
|
||||
{:else}
|
||||
<button
|
||||
aria-label={type}
|
||||
class={cx('--header__action', linkIsActive && '--header__action--active')}
|
||||
type="button">
|
||||
<Icon {...icon} render={icon.render} />
|
||||
</button>
|
||||
{/if}
|
||||
</button>
|
||||
{#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">
|
||||
<li class="bx--switcher__item">
|
||||
<a class="bx--switcher__item-link bx--switcher__item-link--selected" href="/">Link</a>
|
||||
|
|
|
@ -1,17 +1,18 @@
|
|||
<script>
|
||||
export let action = undefined;
|
||||
export let icon = undefined;
|
||||
// export let content = 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 searchIconProps = undefined;
|
||||
let searchTabIndex = 0;
|
||||
let closeTabIndex = -1;
|
||||
let inputSearchField = undefined;
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
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) {
|
||||
searchTabIndex = -1;
|
||||
closeTabIndex = 0;
|
||||
|
@ -35,6 +56,8 @@
|
|||
searchTabIndex = 0;
|
||||
closeTabIndex = -1;
|
||||
}
|
||||
|
||||
$: showResults = $searchStore ? true : false;
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
@ -86,6 +109,7 @@
|
|||
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);
|
||||
}
|
||||
|
@ -96,6 +120,20 @@
|
|||
|
||||
.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>
|
||||
|
||||
|
@ -115,6 +153,8 @@
|
|||
<Icon {...icon} render={icon.render} />
|
||||
</button>
|
||||
<input
|
||||
bind:this={inputSearchField}
|
||||
id="input-search-field"
|
||||
type="text"
|
||||
autocomplete="off"
|
||||
tabindex={closeTabIndex}
|
||||
|
@ -122,7 +162,8 @@
|
|||
class:input-hidden={!searchIsActive}
|
||||
placeholder="Search"
|
||||
on:focus={() => dispatch('focusInputSearch')}
|
||||
on:focusout={() => dispatch('focusOutInputSearch')} />
|
||||
on:focusout={() => dispatch('focusOutInputSearch')}
|
||||
on:input={dispatchInputs} />
|
||||
<button
|
||||
id="right-panel-close-search"
|
||||
tabindex={closeTabIndex}
|
||||
|
@ -130,7 +171,15 @@
|
|||
class:btn-clear={true}
|
||||
class:btn-clear-hidden={!searchIsActive}
|
||||
type="button"
|
||||
aria-label="Clear search">
|
||||
aria-label="Clear search"
|
||||
on:click={clearSearch}>
|
||||
<Icon {...closeIcon} render={closeIcon.render} />
|
||||
</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}
|
||||
|
|
|
@ -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) => {
|
||||
orderedRightPanel[index] = undefined;
|
||||
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];
|
||||
customActions += 1;
|
||||
}
|
||||
|
@ -41,6 +45,6 @@
|
|||
|
||||
<div class={cx('--header__global')}>
|
||||
{#each orderedRightPanel as action, index}
|
||||
<ActionGeneric {action} />
|
||||
<ActionGeneric {action} on:inputSearch />
|
||||
{/each}
|
||||
</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