-
Notifications
You must be signed in to change notification settings - Fork 702
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
First version for the Header component using Clarity (#1831)
- Loading branch information
Andres Martinez Gotor
committed
Jul 1, 2020
1 parent
b5ed425
commit dc58023
Showing
13 changed files
with
439 additions
and
61 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
// Code from: https://stackblitz.com/edit/react-ts-wrapper-5w8nhf | ||
import { CdsButton as Button } from "@clr/core/button"; | ||
import { CdsIcon as Icon, ClarityIcons as ClrIcons } from "@clr/core/icon-shapes"; | ||
|
||
import "@clr/core/button"; | ||
import "@clr/core/icon"; | ||
import { createReactComponent } from "./converter/reactWrapper"; | ||
|
||
type CdsIconType = Icon; | ||
export const CdsIcon = createReactComponent<CdsIconType>("cds-icon"); | ||
export const ClarityIcons = ClrIcons; | ||
|
||
type CdsButtonType = Button & HTMLButtonElement; | ||
export const CdsButton = createReactComponent<CdsButtonType>("cds-button"); |
75 changes: 75 additions & 0 deletions
75
dashboard/src/components/Clarity/converter/reactWrapper.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
// Code from: https://stackblitz.com/edit/react-ts-wrapper-5w8nhf | ||
import React from "react"; | ||
|
||
export function createReactComponent<BaseComponent extends HTMLElement>(elementName: string) { | ||
return class ReactWrapperComponent extends React.Component< | ||
Partial<Omit<BaseComponent, "children"> & React.DOMAttributes<HTMLElement>> | ||
> { | ||
public ref: React.RefObject<BaseComponent>; | ||
|
||
get _customPropsList() { | ||
return Object.keys(this.props).filter(prop => !this._propIsReservedReactProp(prop)); | ||
} | ||
|
||
get nativeElement(): Promise<BaseComponent> { | ||
return (this.ref.current as any).updateComplete.then(() => this.ref.current); | ||
} | ||
|
||
constructor(props: any) { | ||
super(props); | ||
this.ref = React.createRef(); // need to document minimum version of react needed | ||
} | ||
|
||
public componentDidMount() { | ||
this._customPropsList.forEach(prop => { | ||
if (this._propIsFunction(prop)) { | ||
this._createCustomElementEvent(prop); | ||
} else { | ||
this._updateCustomElementProperty(prop); | ||
} | ||
}); | ||
|
||
if (this.ref.current) { | ||
this.ref.current.focus = this.ref.current.focus; | ||
} | ||
} | ||
|
||
public componentDidUpdate(prevProps: any) { | ||
this._customPropsList | ||
.filter(prop => !this._propIsFunction(prop) && prevProps[prop] !== this.props[prop]) | ||
.forEach(prop => this._updateCustomElementProperty(prop)); | ||
} | ||
|
||
public render() { | ||
return React.createElement(elementName, { ref: this.ref }, this.props.children); | ||
} | ||
|
||
public _propIsReservedReactProp(prop: any) { | ||
const reactProperties = ["children", "localName", "ref", "style", "className"]; | ||
return reactProperties.indexOf(prop) !== -1; | ||
} | ||
|
||
public _propIsFunction(prop: any) { | ||
return typeof this.props[prop] === "function"; | ||
} | ||
|
||
public _createCustomElementEvent(prop: any) { | ||
if (this.ref.current) { | ||
let eventName = prop.substring(2); | ||
eventName = eventName.charAt(0).toLowerCase() + eventName.slice(1); | ||
this.ref.current.addEventListener(eventName, e => this.props[prop](e)); | ||
} | ||
} | ||
|
||
public _updateCustomElementProperty(prop: any) { | ||
if (this.ref.current) { | ||
this.ref.current[prop] = this.props[prop]; | ||
|
||
// if prop value is a string we assume to set the attribute on custom element | ||
if (typeof this.props[prop] === "string") { | ||
this.ref.current.setAttribute(prop, this.props[prop]); | ||
} | ||
} | ||
} | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
.kubeapps__logo { | ||
max-width: 10em; | ||
} | ||
|
||
.kubeapps-nav-link { | ||
outline: none !important; | ||
background-color: transparent; | ||
border: none; | ||
cursor: pointer; | ||
} | ||
|
||
.kubeapps-align-center { | ||
display: flex; | ||
align-items: center; | ||
} | ||
|
||
.kubeapps-dropdown { | ||
opacity: 75%; | ||
} | ||
|
||
.kubeapps-dropdown-text { | ||
margin-left: 0.3em; | ||
margin-right: 0.3em; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
import { shallow } from "enzyme"; | ||
import * as React from "react"; | ||
import { IClustersState } from "../../reducers/cluster"; | ||
import { app } from "../../shared/url"; | ||
import Header from "./Header.v2"; | ||
|
||
const defaultProps = { | ||
authenticated: true, | ||
fetchNamespaces: jest.fn(), | ||
logout: jest.fn(), | ||
clusters: { | ||
currentCluster: "default", | ||
clusters: { | ||
default: { | ||
currentNamespace: "default", | ||
namespaces: ["default", "other"], | ||
}, | ||
}, | ||
} as IClustersState, | ||
defaultNamespace: "kubeapps-user", | ||
pathname: "", | ||
push: jest.fn(), | ||
setNamespace: jest.fn(), | ||
createNamespace: jest.fn(), | ||
getNamespace: jest.fn(), | ||
featureFlags: { operators: false, additionalClusters: [], ui: "hex" }, | ||
}; | ||
|
||
it("renders the header links and titles", () => { | ||
const wrapper = shallow(<Header {...defaultProps} />); | ||
const items = wrapper.find(".nav-link"); | ||
const expectedItems = [ | ||
{ children: "Applications", to: app.apps.list("default", "default") }, | ||
{ children: "Catalog", to: app.catalog("default") }, | ||
]; | ||
expect(items.length).toEqual(expectedItems.length); | ||
expectedItems.forEach((expectedItem, index) => { | ||
expect(expectedItem.children).toBe(items.at(index).text()); | ||
expect(expectedItem.to).toBe(items.at(index).prop("to")); | ||
}); | ||
}); | ||
|
||
it("should skip the links if it's not authenticated", () => { | ||
const wrapper = shallow(<Header {...defaultProps} authenticated={false} />); | ||
const items = wrapper.find(".nav-link"); | ||
expect(items).not.toExist(); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
import { | ||
angleIcon, | ||
applicationsIcon, | ||
ClarityIcons, | ||
clusterIcon, | ||
fileGroupIcon, | ||
} from "@clr/core/icon-shapes"; | ||
import * as React from "react"; | ||
import { NavLink } from "react-router-dom"; | ||
import { CdsIcon } from "../Clarity/clarity"; | ||
|
||
import logo from "../../logo.svg"; | ||
import { IClustersState } from "../../reducers/cluster"; | ||
import { app } from "../../shared/url"; | ||
import "./Header.v2.css"; | ||
|
||
ClarityIcons.addIcons(applicationsIcon, clusterIcon, fileGroupIcon, angleIcon); | ||
|
||
interface IHeaderProps { | ||
authenticated: boolean; | ||
fetchNamespaces: () => void; | ||
logout: () => void; | ||
clusters: IClustersState; | ||
defaultNamespace: string; | ||
push: (path: string) => void; | ||
setNamespace: (ns: string) => void; | ||
createNamespace: (ns: string) => Promise<boolean>; | ||
getNamespace: (ns: string) => void; | ||
} | ||
|
||
function Header(props: IHeaderProps) { | ||
const { clusters, authenticated: showNav } = props; | ||
const cluster = clusters.clusters[clusters.currentCluster]; | ||
|
||
const routesToRender = [ | ||
{ | ||
title: "Applications", | ||
path: app.apps.list(clusters.currentCluster, cluster.currentNamespace), | ||
external: false, | ||
}, | ||
{ title: "Catalog", path: app.catalog(cluster.currentNamespace), external: false }, | ||
]; | ||
return ( | ||
<section> | ||
<div className="container"> | ||
<header className="header header-7"> | ||
<div className="branding"> | ||
<NavLink to="/"> | ||
<img src={logo} alt="Kubeapps logo" className="kubeapps__logo" /> | ||
</NavLink> | ||
</div> | ||
{showNav && ( | ||
<nav className="header-nav"> | ||
{routesToRender.map(route => { | ||
const { path, title } = route; | ||
return ( | ||
<NavLink | ||
key={path} | ||
to={path} | ||
activeClassName="active" | ||
className="nav-link nav-text" | ||
> | ||
{title} | ||
</NavLink> | ||
); | ||
})} | ||
</nav> | ||
)} | ||
{showNav && ( | ||
<section className="header-actions"> | ||
<div className="dropdown bottom-right kubeapps-align-center kubeapps-nav-link kubeapps-dropdown"> | ||
<div className="clr-row"> | ||
<div className="clr-col-10"> | ||
<span>Current Context</span> | ||
<div> | ||
<CdsIcon size="sm" shape="cluster" inverse={true} /> | ||
<span className="kubeapps-dropdown-text">{clusters.currentCluster}</span> | ||
<CdsIcon size="sm" shape="file-group" inverse={true} /> | ||
<span className="kubeapps-dropdown-text">{cluster.currentNamespace}</span> | ||
</div> | ||
</div> | ||
<div className="clr-col-2 kubeapps-align-center"> | ||
<CdsIcon shape="angle" inverse={true} /> | ||
</div> | ||
</div> | ||
</div> | ||
<button | ||
className="kubeapps-nav-link nav-icon" | ||
aria-label="VMware Cloud services configuration" | ||
> | ||
<CdsIcon size="lg" shape="applications" solid={true} /> | ||
</button> | ||
</section> | ||
)} | ||
</header> | ||
</div> | ||
</section> | ||
); | ||
} | ||
|
||
export default Header; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,18 @@ | ||
import Header from "./Header"; | ||
import * as React from "react"; | ||
|
||
export default Header; | ||
import { IHeaderProps } from "./Header"; | ||
|
||
const Header = React.lazy(() => import("./Header")); | ||
const HeaderV2 = React.lazy(() => import("./Header.v2")); | ||
|
||
interface IHeaderSelectorProps extends IHeaderProps { | ||
UI: string; | ||
} | ||
|
||
const HeaderSelector: React.FC<IHeaderSelectorProps> = props => ( | ||
<React.Suspense fallback={null}> | ||
{props.UI === "clarity" ? <HeaderV2 {...props} /> : <Header {...props} />} | ||
</React.Suspense> | ||
); | ||
|
||
export default HeaderSelector; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
@import "node_modules/@clr/core/styles/global.min.css"; | ||
@import "node_modules/@clr/city/css/bundles/default.min.css"; | ||
|
||
body { | ||
font-family: "Clarity City", "Avenir Next", "Helvetica Neue", Arial, sans-serif; | ||
} |
Oops, something went wrong.