diff --git a/packages/web-components/src/components/cbp-app-header/api-docs.mdx b/packages/web-components/src/components/cbp-app-header/api-docs.mdx index 83aea124..32acaaf2 100644 --- a/packages/web-components/src/components/cbp-app-header/api-docs.mdx +++ b/packages/web-components/src/components/cbp-app-header/api-docs.mdx @@ -1,6 +1,9 @@ import { Meta, Markdown } from "@storybook/blocks"; import Docs from './readme.md?raw'; +import NavItemDocs from '../cbp-nav-item/readme.md?raw'; {Docs} + +{NavItemDocs} \ No newline at end of file diff --git a/packages/web-components/src/components/cbp-app-header/cbp-app-header.scss b/packages/web-components/src/components/cbp-app-header/cbp-app-header.scss index 717bc2c9..90b5b05b 100644 --- a/packages/web-components/src/components/cbp-app-header/cbp-app-header.scss +++ b/packages/web-components/src/components/cbp-app-header/cbp-app-header.scss @@ -2,97 +2,16 @@ /*** Base variables *** * @Prop --cbp-app-header-color-background: var(--cbp-color-white) * @Prop --cbp-app-header-color-background-dark: var(--cbp-color-base-neutral-dark); - - /*** Child variables *** - * @Prop --cbp-app-header-child-color: var(--cbp-color-interactive-secondary-darker); - * @Prop --cbp-app-header-child-color-dark: var(--cbp-color-interactive-secondary-lighter); - * @Prop --cbp-app-header-child-color-background: transparent; - * @Prop --cbp-app-header-child-color-background-dark: transparent; - * @Prop --cbp-app-header-child-color-border: transparent; - * @Prop --cbp-app-header-child-color-border-dark: transparent; - * @Prop --cbp-app-header-child-color-outline: var(--cbp-color-white); - * @Prop --cbp-app-header-child-color-outline-dark: transparent; - - /*** Hover variables *** - * @Prop --cbp-app-header-child-color-text-hover: var(--cbp-color-interactive-secondary-darker); - * @Prop --cbp-app-header-child-color-text-hover-dark: var(--cbp-color-interactive-secondary-lighter); - * @Prop --cbp-app-header-child-color-background-hover: var(--cbp-color-interactive-secondary-lighter); - * @Prop --cbp-app-header-child-color-background-hover-dark: var(--cbp-color-interactive-secondary-darker); - * @Prop --cbp-app-header-child-color-border-hover: var(--cbp-color-interactive-secondary-darker); - * @Prop --cbp-app-header-child-color-border-hover-dark: var(--cbp-color-interactive-secondary-lighter); - - /*** Focus variables *** - * @Prop --cbp-app-header-child-color-text-focus: var(--cbp-color-text-lightest); - * @Prop --cbp-app-header-child-color-text-focus-dark: var(--cbp-color-text-darkest); - * @Prop --cbp-app-header-child-color-background-focus: var(--cbp-color-interactive-focus-dark); - * @Prop --cbp-app-header-child-color-background-focus-dark: var(--cbp-color-interactive-focus-light); - * @Prop --cbp-app-header-child-color-border-focus: var(--cbp-color-interactive-focus-dark); - * @Prop --cbp-app-header-child-color-border-focus-dark: transparent; - * @Prop --cbp-app-header-child-color-outline-focus: var(--cbp-color-white); - * @Prop --cbp-app-header-child-color-outline-focus-dark: var(--cbp-color-black); - - /*** Active variables *** - * @Prop --cbp-app-header-child-color-background-active: var(--cbp-color-interactive-active-dark); - * @Prop --cbp-app-header-child-color-background-active-dark: var(--cbp-color-interactive-focus-light); - * @Prop --cbp-app-header-child-color-border-active: var(--cbp-color-interactive-active-dark); - * @Prop --cbp-app-header-child-color-border-active-dark: var(--cbp-color-text-darkest); - * */ +*/ :root { --cbp-app-header-color-background: var(--cbp-color-white); --cbp-app-header-color-background-dark: var(--cbp-color-base-neutral-dark); - - --cbp-app-header-child-color: var(--cbp-color-interactive-secondary-darker); - --cbp-app-header-child-color-dark: var(--cbp-color-interactive-secondary-lighter); - --cbp-app-header-child-color-background: transparent; - --cbp-app-header-child-color-background-dark: transparent; - --cbp-app-header-child-color-border: transparent; - --cbp-app-header-child-color-border-dark: transparent; - --cbp-app-header-child-color-outline: var(--cbp-color-white); - --cbp-app-header-child-color-outline-dark: transparent; - - --cbp-app-header-child-color-text-hover: var(--cbp-color-interactive-secondary-darker); - --cbp-app-header-child-color-text-hover-dark: var(--cbp-color-interactive-secondary-lighter); - --cbp-app-header-child-color-background-hover: var(--cbp-color-interactive-secondary-lighter); - --cbp-app-header-child-color-background-hover-dark: var(--cbp-color-interactive-secondary-darker); - --cbp-app-header-child-color-border-hover: var(--cbp-color-interactive-secondary-darker); - --cbp-app-header-child-color-border-hover-dark: var(--cbp-color-interactive-secondary-lighter); - - --cbp-app-header-child-color-text-focus: var(--cbp-color-text-lightest); - --cbp-app-header-child-color-text-focus-dark: var(--cbp-color-text-darkest); - --cbp-app-header-child-color-background-focus: var(--cbp-color-interactive-focus-dark); - --cbp-app-header-child-color-background-focus-dark: var(--cbp-color-interactive-focus-light); - --cbp-app-header-child-color-border-focus: var(--cbp-color-interactive-focus-dark); - --cbp-app-header-child-color-border-focus-dark: transparent; - --cbp-app-header-child-color-outline-focus: var(--cbp-color-white); - --cbp-app-header-child-color-outline-focus-dark: var(--cbp-color-black); - - --cbp-app-header-child-color-background-active: var(--cbp-color-interactive-active-dark); - --cbp-app-header-child-color-background-active-dark: var(--cbp-color-interactive-focus-light); - --cbp-app-header-child-color-border-active: var(--cbp-color-interactive-active-dark); - --cbp-app-header-child-color-border-active-dark: var(--cbp-color-text-darkest); } [data-cbp-theme=light] cbp-app-header[context*=dark]:not([context=light-always]), [data-cbp-theme=dark] cbp-app-header:not([context=dark-inverts]):not([context=light-always]) { --cbp-app-header-color-background: var(--cbp-app-header-color-background-dark); - - --cbp-app-header-child-color: var(--cbp-app-header-child-color-dark); - --cbp-app-header-child-color-background: var(--cbp-app-header-child-color-background-dark); - --cbp-app-header-child-color-border: var(--cbp-app-header-child-color-border-dark); - --cbp-app-header-child-color-outline: var(--cbp-app-header-child-color-outline-dark); - - --cbp-app-header-child-color-text-hover: var(--cbp-app-header-child-color-text-hover-dark); - --cbp-app-header-child-color-background-hover: var(--cbp-app-header-child-color-background-hover-dark); - --cbp-app-header-child-color-border-hover: var(--cbp-app-header-child-color-border-hover-dark); - - --cbp-app-header-child-color-text-focus: var(--cbp-app-header-child-color-text-focus-dark); - --cbp-app-header-child-color-background-focus: var(--cbp-app-header-child-color-background-focus-dark); - --cbp-app-header-child-color-border-focus: var(--cbp-app-header-child-color-border-focus-dark); - --cbp-app-header-child-color-outline-focus: var(--cbp-app-header-child-color-outline-focus-dark); - - --cbp-app-header-child-color-background-active: var(--cbp-app-header-child-color-background-active-dark); - --cbp-app-header-child-color-border-active: var(--cbp-app-header-child-color-border-active-dark); } cbp-app-header { @@ -108,60 +27,4 @@ cbp-app-header { box-shadow: var(--cbp-shadow-level-3-down); z-index: var(--cbp-z-index-level-1); - // Style all nav items consistently, whether links or button controls for menu/dropdown. - // This may need to be refactored or pulled out and placed into a new component, e.g., cbp-nav-item. - // Actually rendering them as cbp-button components would cut down on the redundant CSS and support most cases as drawer controls and links. - //.nav-home, - //.cbp-nav-item, - //.cbp-menu-dropdown, - button, a { - display: flex; - align-items: center; - height: 100%; - min-height: var(--cbp-space-14x); - padding: 0 var(--cbp-space-3x); - color: var(--cbp-app-header-child-color); - background-color: var(--cbp-app-header-child-color-background); - font-size: var(--cbp-font-size-heading-xs); - font-weight: var(--cbp-font-weight-medium); - text-decoration: none; - white-space: nowrap; - border-color: var(--cbp-app-header-child-color-border); - border-style: solid; - border-width: 0 0 var(--cbp-border-size-xl) 0; - outline-color: var(--cbp-app-header-child-color-outline); - outline-style: solid; - outline-width: 0; - outline-offset: calc(-1 * var(--cbp-space-1x)); - cursor: pointer; - - &:hover { - color: var(--cbp-app-header-child-color-text-hover); - background-color: var(--cbp-app-header-child-color-background-hover); - border-color: var(--cbp-app-header-child-color-border-hover); - } - - &:focus { - color: var(--cbp-app-header-child-color-text-focus); - background-color: var(--cbp-app-header-child-color-background-focus); - border-color: var(--cbp-app-header-child-color-border-focus); - outline-width: var(--cbp-border-size-md); - outline-color: var(--cbp-app-header-child-color-outline-focus); - - } - - &:active { - background-color: var(--cbp-app-header-child-color-background-active); - border: var(--cbp-app-header-child-color-border-active); - } - } - - [slot=cbp-home] { - font-size: var(--cbp-font-size-heading-sm); - font-weight: var(--cbp-font-weight-bold); - padding-left: var(--cbp-space-5x); - padding-right: var(--cbp-space-5x); - margin-left: calc(-1 * var(--cbp-space-5x)); - } - } diff --git a/packages/web-components/src/components/cbp-app-header/cbp-app-header.stories.tsx b/packages/web-components/src/components/cbp-app-header/cbp-app-header.stories.tsx index f50a1054..6f14efe0 100644 --- a/packages/web-components/src/components/cbp-app-header/cbp-app-header.stories.tsx +++ b/packages/web-components/src/components/cbp-app-header/cbp-app-header.stories.tsx @@ -5,10 +5,6 @@ export default { layout: 'fullscreen', }, argTypes: { - context : { - control: 'select', - options: [ "light-inverts", "light-always", "dark-inverts", "dark-always"] - }, sx: { description: 'Supports adding inline styles as an object of key-value pairs comprised of CSS properties and values. Values should reference design tokens when possible.', control: 'object', @@ -16,15 +12,60 @@ export default { }, }; -const Template = ({ context, sx }) => { +function generateNavItems(navItems){ + const html = navItems.map(({html, selected}) => { + return ` ${html}`; + } + ); + return html.join(''); +} + + +const Template = ({ navItems, sx }) => { return ` - Application Name + ${generateNavItems(navItems)} `; }; export const ApplicationHeader = Template.bind({}); + +ApplicationHeader.args = { + navItems: [ + { + html: ` + Application Name + `, + selected: true + },{ + html: ` + Single Nav Item 1 + `, + selected: false, + }, + { + html: ` + Single Nav Item 2 + `, + selected: false + }, + ] +}; \ No newline at end of file diff --git a/packages/web-components/src/components/cbp-app-header/cbp-app-header.tsx b/packages/web-components/src/components/cbp-app-header/cbp-app-header.tsx index 7b56918e..851c15b6 100644 --- a/packages/web-components/src/components/cbp-app-header/cbp-app-header.tsx +++ b/packages/web-components/src/components/cbp-app-header/cbp-app-header.tsx @@ -1,4 +1,4 @@ -import { Component, Host, h, Prop } from '@stencil/core'; +import { Component, Element, Host, h } from '@stencil/core'; @Component({ tag: 'cbp-app-header', @@ -7,8 +7,44 @@ import { Component, Host, h, Prop } from '@stencil/core'; export class CbpAppHeader { - /** Specifies the context of the component as it applies to the visual design and whether it inverts when light/dark mode is toggled. Default behavior is "light-inverts" and does not have to be specified. */ - @Prop({ reflect: true }) context: "light-inverts" | "light-always" | "dark-inverts" | "dark-always"; + private navItems: HTMLCbpNavItemElement[] = []; + + @Element() host: HTMLElement; + + initNavItemset() { + // check for a default navItem, otherwise set the first one active + let activeNavItem; + activeNavItem = this.host.querySelector('cbp-nav-item[selected]'); + + this.setActiveNav(activeNavItem); + } + + setActiveNav(activatedNav) { + this.navItems.forEach((navItem: HTMLCbpNavItemElement) => { + let link = navItem.querySelector('a, button'); + + if (activatedNav == navItem){ + navItem.selected = true; + link.setAttribute('aria-current', 'true'); + } else { + navItem.selected = false; + link.removeAttribute('aria-current'); + } + }) + } + + componentWillLoad() { + this.navItems = Array.from(this.host.querySelectorAll('cbp-nav-item')); + + // Attach event listeners to the child navItem + this.navItems.forEach(navItem => { + navItem.addEventListener('navClicked', e => this.setActiveNav(e.detail.host)); + }); + } + + componentDidLoad() { + this.initNavItemset(); + } render() { return ( @@ -19,3 +55,4 @@ export class CbpAppHeader { ); } } + diff --git a/packages/web-components/src/components/cbp-nav-item/cbp-nav-item.scss b/packages/web-components/src/components/cbp-nav-item/cbp-nav-item.scss new file mode 100644 index 00000000..fdd632df --- /dev/null +++ b/packages/web-components/src/components/cbp-nav-item/cbp-nav-item.scss @@ -0,0 +1,126 @@ +/** + * @Prop --cbp-nav-item-color: var(--cbp-color-interactive-secondary-darker); + * @Prop --cbp-nav-item-color-dark: var(--cbp-color-interactive-secondary-lighter); + + * @Prop --cbp-nav-item-color-hover: var(--cbp-color-interactive-secondary-darker); + * @Prop --cbp-nav-item-color-hover-dark: var(--cbp-color-interactive-secondary-lighter); + * @Prop --cbp-nav-item-color-bg-hover: var(--cbp-color-interactive-secondary-lighter); + * @Prop --cbp-nav-item-color-bg-hover-dark: var(--cbp-color-interactive-secondary-darker); + * @Prop --cbp-nav-item-border-bottom-hover: var(--cbp-color-interactive-secondary-darker); + * @Prop --cbp-nav-item-border-bottom-hover-dark: var(--cbp-color-interactive-secondary-lighter); + + * @Prop --cbp-nav-item-color-active: var(--cbp-color-text-lightest); + * @Prop --cbp-nav-item-color-active-dark: var(--cbp-color-text-darkest); + * @Prop --cbp-nav-item-color-bg-active: var(--cbp-color-interactive-active-dark); + * @Prop --cbp-nav-item-color-bg-active-dark: var(--cbp-color-interactive-active-light); + + * @Prop --cbp-nav-item-color-selected: var(--cbp-color-interactive-active-dark); + * @Prop --cbp-nav-item-color-selected-dark: var(--cbp-color-interactive-active-light); + * @Prop --cbp-nav-item-color-bg-selected: var(transparent); + * @Prop --cbp-nav-item-border-bottom-selected: var(--cbp-color-interactive-active-dark); + * @Prop --cbp-nav-item-border-bottom-selected-dark: var(--cbp-color-interactive-active-light); + + */ + + :root { + --cbp-nav-item-color: var(--cbp-color-interactive-secondary-darker); + --cbp-nav-item-color-dark: var(--cbp-color-interactive-secondary-lighter); + + --cbp-nav-item-color-hover: var(--cbp-color-interactive-secondary-darker); + --cbp-nav-item-color-hover-dark: var(--cbp-color-interactive-secondary-lighter); + --cbp-nav-item-color-bg-hover: var(--cbp-color-interactive-secondary-lighter); + --cbp-nav-item-color-bg-hover-dark: var(--cbp-color-interactive-secondary-darker); + --cbp-nav-item-border-bottom-hover: var(--cbp-color-interactive-secondary-darker); + --cbp-nav-item-border-bottom-hover-dark: var(--cbp-color-interactive-secondary-lighter); + + --cbp-nav-item-color-active: var(--cbp-color-text-lightest); + --cbp-nav-item-color-active-dark: var(--cbp-color-text-darkest); + --cbp-nav-item-color-bg-active: var(--cbp-color-interactive-active-dark); + --cbp-nav-item-color-bg-active-dark: var(--cbp-color-interactive-active-light); + + --cbp-nav-item-color-selected: var(--cbp-color-interactive-active-dark); + --cbp-nav-item-color-selected-dark: var(--cbp-color-interactive-active-light); + --cbp-nav-item-color-bg-selected: var(transparent); + --cbp-nav-item-border-bottom-selected: var(--cbp-color-interactive-active-dark); + --cbp-nav-item-border-bottom-selected-dark: var(--cbp-color-interactive-active-light); +} + +/* +* Dark Mode - display dark design based on mode or context. +*/ + +[data-cbp-theme=light] cbp-app-header:not(#fakeId), +[data-cbp-theme=dark] cbp-app-header:not(#fakeId) { + +--cbp-nav-item-color: var(--cbp-nav-item-color-dark); +--cbp-nav-item-color-hover: var(--cbp-nav-item-color-hover-dark); + +--cbp-nav-item-color-bg-hover: var(--cbp-nav-item-color-bg-hover-dark); +--cbp-nav-item-border-bottom-hover: var(--cbp-nav-item-border-bottom-hover-dark); + +--cbp-nav-item-color-active: var(--cbp-nav-item-color-active-dark); +--cbp-nav-item-color-bg-active: var(--cbp-nav-item-color-bg-active-dark); + +--cbp-nav-item-color-selected: var(--cbp-nav-item-color-selected-dark); +--cbp-nav-item-border-bottom-selected: var(--cbp-nav-item-border-bottom-selected-dark); +} + + +cbp-nav-item { + +/* remap cbp-button vars */ +cbp-button[fill=ghost][color=secondary] { + --cbp-button-min-height: 3.5rem; + --cbp-button-color: var(--cbp-nav-item-color); + --cbp-button-border-width: 0 0 var(--cbp-border-size-xl) 0; + --cbp-button-border-radius: var(--cbp-border-radius-sharp); + + font-size: var(--cbp-font-weight-bold); + + button, a{ + text-transform: unset; + } + + &:hover{ + --cbp-button-color-hover: var(--cbp-nav-item-color-hover); + --cbp-button-color-bg-hover: var(--cbp-nav-item-color-bg-hover); + + //tech debt: cbp-button dark overrides causing issues due to specificity, assigned the nav-item border colors to the --cbp-button-color-border-hover & --cbp-button-color-border-hover-dark seperately to circumvent issue + --cbp-button-color-border-hover: var(--cbp-nav-item-border-bottom-hover); + --cbp-button-color-border-hover-dark: var(--cbp-nav-item-border-bottom-hover-dark); + } + + &:active{ + --cbp-button-color: var(--cbp-nav-item-color-active); + --cbp-button-color-bg: var(--cbp-nav-item-color-bg-active); + + //tech debt: cbp-button dark overrides causing issues due to specificity, assigned the nav-item border colors to the --cbp-button-color-border-active & --cbp-button-color-border-active-dark seperately to circumvent issue + --cbp-button-color-dark: var(--cbp-nav-item-color-active-dark); + --cbp-button-color-bg-dark: var(--cbp-nav-item-color-bg-active-dark); + } +} + +//Selected/Active state +&[selected] cbp-button[fill=ghost][color=secondary]{ + --cbp-button-color: var(--cbp-nav-item-color-selected); + --cbp-button-color-bg: var(--cbp-nav-item-color-bg-selected); + --cbp-button-color-border: var(--cbp-nav-item-border-bottom-selected); + //TODO: need to set text to italics (confirm with Jer) + font-style: italic; + //tech debt: cbp-button dark overrides causing issues due to specificity, same issue as with hover & active above + --cbp-button-color-dark: var(--cbp-nav-item-color-selected-dark); + --cbp-button-color-bg-dark: var(--cbp-nav-item-color-bg-selected-dark); + --cbp-button-color-border-dark: var(--cbp-nav-item-border-bottom-selected-dark); + +} + +//styling for home/application title nav item +&:first-of-type a{ + font-size: var(--cbp-font-size-heading-sm); + font-weight: var(--cbp-font-weight-bold); + padding-left: var(--cbp-space-5x); + padding-right: var(--cbp-space-5x); + margin-left: calc(-1 * var(--cbp-space-5x)); + letter-spacing: unset; +} +} \ No newline at end of file diff --git a/packages/web-components/src/components/cbp-nav-item/cbp-nav-item.tsx b/packages/web-components/src/components/cbp-nav-item/cbp-nav-item.tsx new file mode 100644 index 00000000..c14b88dc --- /dev/null +++ b/packages/web-components/src/components/cbp-nav-item/cbp-nav-item.tsx @@ -0,0 +1,44 @@ +import { Component, Element, Event, EventEmitter, Host, Prop, h } from '@stencil/core'; +import { setCSSProps } from '../../utils/utils'; +@Component({ + tag: 'cbp-nav-item', + styleUrl: 'cbp-nav-item.scss', +}) + +export class CbpNavItem { + + @Element() host: HTMLElement; + + /** Specifies whether this is the selected nav-item. Only one item per set should be marked as selected.*/ + @Prop({ reflect: true }) selected: boolean; + + /** Supports adding inline styles as an object */ + @Prop() sx: any = {}; + + @Event() navClicked: EventEmitter; + handleNavClick() { + this.selected=true; + this.navClicked.emit({ + host: this.host, + }) + } + + componentWillLoad() { + if (typeof this.sx == 'string') { + this.sx = JSON.parse(this.sx) || {}; + } + setCSSProps(this.host, { + ...this.sx, + }); + } + + render() { + return ( + this.handleNavClick()} + > + + + ); + } +}