carbon-design-system / carbon-web-components

Carbon Design System variant on top of Web Components
https://web-components.carbondesignsystem.com/
Apache License 2.0
478 stars 80 forks source link

Header with actions #633

Closed paoloproni closed 3 years ago

paoloproni commented 3 years ago

Hi, how can I put some actions in the UI Shell header? I would like the icons at the right part of the header, as it is shown in the first live demo here: https://www.carbondesignsystem.com/components/UI-shell-header/usage/

kennylam commented 3 years ago

Hi @paoloproni, the app switcher is a part of Carbon core. Here is an example, and here are the docs (starts at the Switcher tab)

paoloproni commented 3 years ago

Hello, thank you for your answer. I wonder if I can do it with the web components. It seems to me that the example is in React.

ljcarot commented 3 years ago

Are you building a site for IBM.com or are you building a product? @paoloproni

paoloproni commented 3 years ago

I am building a personal project. I can call it a product. I would like to use Web components. Actually I wrap them with some custom Vue components, as I do not feel very comfortable with the supplied Vue components, but I do not know React, unfortunately.

RobertaJHahn commented 3 years ago

Closing, based on comments above.

ljcarot commented 3 years ago

As this page shows, the code for App Switcher is avail in React, Vue and Angular. It isn't avail in Web components at this time https://www.carbondesignsystem.com/components/UI-shell-header/code

ejez commented 3 years ago

Here is an example layout component made with lit 2:

Click to expand! ```ts import 'carbon-web-components/es/components/button/button.js'; import 'carbon-web-components/es/components/skip-to-content/skip-to-content.js'; import 'carbon-web-components/es/components/ui-shell/header-menu-button.js'; import 'carbon-web-components/es/components/ui-shell/header-name.js'; import 'carbon-web-components/es/components/ui-shell/header-nav-item.js'; import 'carbon-web-components/es/components/ui-shell/header-nav.js'; import 'carbon-web-components/es/components/ui-shell/header.js'; import 'carbon-web-components/es/components/ui-shell/side-nav-items.js'; import 'carbon-web-components/es/components/ui-shell/side-nav-link.js'; import 'carbon-web-components/es/components/ui-shell/side-nav.js'; import AppSwitcher20 from 'carbon-web-components/es/icons/app-switcher/20.js'; import UserAvatar20 from 'carbon-web-components/es/icons/user--avatar/20.js'; import type { TemplateResult } from 'lit'; import { css, html, LitElement } from 'lit'; import { customElement } from 'lit/decorators/custom-element.js'; import { property } from 'lit/decorators/property.js'; import { query } from 'lit/decorators/query.js'; import { unsafeHTML } from 'lit/directives/unsafe-html.js'; import { I18nController } from 'src/i18n/i18n-controller.js'; import { p, tc } from 'src/i18n/utils.js'; import { BreakpointController } from 'src/responsive-system/breakpoint-controller.js'; import { responsive } from 'src/responsive-system/responsive-system.js'; import { BP } from 'src/responsive-system/responsive.js'; import { router } from 'src/router/router.js'; import { handleLink } from 'src/router/utils.js'; @customElement('cc-layout') export class CcLayout extends LitElement { static styles = css` :host { min-height: 100vh; overflow: hidden; display: grid; grid-template-columns: auto 1fr; grid-template-rows: auto 1fr auto; } #header { grid-column: 1 / -1; grid-row: 1 / 2; } #side-nav { grid-column: 1 / 2; grid-row: 2 / -1; } #main { grid-column: 2 / -1; grid-row: 2 / 3; } #footer { grid-column: 2 / -1; grid-row: 3 / -1; } bx-header, bx-side-nav { position: unset; } #header-global { margin-inline-start: auto; display: flex; } #header-global bx-btn svg { fill: var(--cds-icon-03); } `; private i18nController = new I18nController(this); private breakpointController = new BreakpointController(this); @query('#main') mainElement: HTMLElement | undefined; private skipToContent = () => { this.mainElement?.focus(); }; private defaultHeader = () => { const brandName = tc('brand-name'); return html` ${tc('skip-to-content')} ${brandName} ${tc('docs')} ${tc('tutorial')} ${tc('blog')}
${unsafeHTML(UserAvatar20().strings.join(''))} ${unsafeHTML(AppSwitcher20().strings.join(''))}
`; }; private defaultSideNav = () => responsive.breakpoint < BP.M ? html` ${tc('docs')} ${tc('tutorial')} ${tc('blog')} ` : html``; private defaultMain = html`
Main...
`; private defaultFooter = html`
Footer...
`; @property({ attribute: false }) header?: TemplateResult | (() => TemplateResult); @property({ attribute: false }) sideNav?: TemplateResult | (() => TemplateResult); @property({ attribute: false }) main?: TemplateResult | (() => TemplateResult); @property({ attribute: false }) footer?: TemplateResult | (() => TemplateResult); render() { let header = typeof this.header === 'function' ? this.header() : this.header; if (header === undefined) header = this.defaultHeader(); let sideNav = typeof this.sideNav === 'function' ? this.sideNav() : this.sideNav; if (sideNav === undefined) sideNav = this.defaultSideNav(); let main = typeof this.main === 'function' ? this.main() : this.main; if (main === undefined) main = this.defaultMain; let footer = typeof this.footer === 'function' ? this.footer() : this.footer; if (footer === undefined) footer = this.defaultFooter; return html`${header}${sideNav}${main}${footer}`; } private handleRouteChange = async () => { const pathname = window.location.href.replace(window.location.origin, ''); const detail = { id: `resolve route: ${pathname}` }; window.dispatchEvent(new CustomEvent('np:progressstart', { detail })); const data = await router.resolve({ pathname }); window.dispatchEvent(new CustomEvent('np:progressend', { detail })); this.header = data?.header; this.sideNav = data?.sideNav; this.main = data?.main; this.footer = data?.footer; }; constructor() { super(); this.handleRouteChange(); } connectedCallback() { super.connectedCallback(); window.addEventListener('popstate', this.handleRouteChange); } disconnectedCallback() { super.disconnectedCallback(); window.removeEventListener('popstate', this.handleRouteChange); } } declare global { interface HTMLElementTagNameMap { 'cc-layout': CcLayout; } } ```

Things relevant to your question:

      <div id="header-global">
        <bx-btn kind="ghost"
          >${unsafeHTML(UserAvatar20().strings.join(''))}</bx-btn
        >
        <bx-btn kind="ghost"
          >${unsafeHTML(AppSwitcher20().strings.join(''))}</bx-btn
        >
      </div>
    #header-global {
      margin-inline-start: auto;
      display: flex;
    }

    #header-global bx-btn svg {
      fill: var(--cds-icon-03);
    }

Notice the use of unsafeHTML( UserAvatar20().strings.join('') ). This is because the return type of the icon functions (UserAvatar() in this example) is made with the old lit-html version svg tag function, which is not compatible with the newer lit-html version. Hence the conversion to string first.

If you don't use lit 2, you can instead just use UserAvatar20().