Compare commits

..

3 Commits

17 changed files with 321 additions and 149 deletions

View File

@@ -36,7 +36,7 @@ export const CollapsedResourceTree: FunctionComponent<CollapsedResourceTreeProps
id="collapseToggleLeftPaneButton" id="collapseToggleLeftPaneButton"
role="button" role="button"
tabIndex={0} tabIndex={0}
aria-label={getApiShortDisplayName() + `Expand tree`} aria-label="Expand Tree"
onClick={toggleLeftPaneExpanded} onClick={toggleLeftPaneExpanded}
onKeyPress={onKeyPressToggleLeftPaneExpanded} onKeyPress={onKeyPressToggleLeftPaneExpanded}
ref={focusButton} ref={focusButton}

View File

@@ -55,6 +55,7 @@ export const EntityValue: FunctionComponent<TableEntityProps> = ({
label={entityValueLabel && entityValueLabel} label={entityValueLabel && entityValueLabel}
className="addEntityTextField" className="addEntityTextField"
id="entityValueId" id="entityValueId"
autoFocus
disabled={isEntityValueDisable} disabled={isEntityValueDisable}
type={entityValueType} type={entityValueType}
placeholder={entityValuePlaceholder} placeholder={entityValuePlaceholder}

View File

@@ -51,7 +51,7 @@ export const ResourceTreeContainer: FunctionComponent<ResourceTreeContainerProps
role="button" role="button"
data-bind="click: onRefreshResourcesClick, clickBubble: false, event: { keypress: onRefreshDatabasesKeyPress }" data-bind="click: onRefreshResourcesClick, clickBubble: false, event: { keypress: onRefreshDatabasesKeyPress }"
tabIndex={0} tabIndex={0}
aria-label={getApiShortDisplayName() + `Refresh tree`} aria-label="Refresh tree"
title="Refresh tree" title="Refresh tree"
> >
<img className="refreshcol" src={refreshImg} alt="Refresh Tree" /> <img className="refreshcol" src={refreshImg} alt="Refresh Tree" />
@@ -63,7 +63,7 @@ export const ResourceTreeContainer: FunctionComponent<ResourceTreeContainerProps
onClick={toggleLeftPaneExpanded} onClick={toggleLeftPaneExpanded}
onKeyPress={onKeyPressToggleLeftPaneExpanded} onKeyPress={onKeyPressToggleLeftPaneExpanded}
tabIndex={0} tabIndex={0}
aria-label={getApiShortDisplayName() + `Collapse Tree`} aria-label="Collapse Tree"
title="Collapse Tree" title="Collapse Tree"
ref={focusButton} ref={focusButton}
> >

View File

@@ -142,6 +142,7 @@ export const TableEntity: FunctionComponent<TableEntityProps> = ({
{...imageProps} {...imageProps}
src={EditIcon} src={EditIcon}
alt="editEntity" alt="editEntity"
id="editEntity"
onClick={onEditEntity} onClick={onEditEntity}
tabIndex={0} tabIndex={0}
onKeyPress={handleKeyPress} onKeyPress={handleKeyPress}

View File

@@ -73,7 +73,7 @@ export class AccordionItemComponent extends React.Component<AccordionItemCompone
<img <img
className="expandCollapseIcon" className="expandCollapseIcon"
src={this.state.isExpanded ? TriangleDownIcon : TriangleRightIcon} src={this.state.isExpanded ? TriangleDownIcon : TriangleRightIcon}
alt={this.state.isExpanded ? `${this.props.title} hide` : `${this.props.title} expand`} alt="Hide"
tabIndex={0} tabIndex={0}
role="button" role="button"
/> />

View File

@@ -15,13 +15,13 @@ import {
import { import {
ChangeFeedPolicyState, ChangeFeedPolicyState,
GeospatialConfigType, GeospatialConfigType,
getSanitizedInputValue,
IsComponentDirtyResult, IsComponentDirtyResult,
isDirty,
TtlOff, TtlOff,
TtlOn, TtlOn,
TtlOnNoDefault, TtlOnNoDefault,
TtlType, TtlType,
getSanitizedInputValue,
isDirty,
} from "../SettingsUtils"; } from "../SettingsUtils";
import { ToolTipLabelComponent } from "./ToolTipLabelComponent"; import { ToolTipLabelComponent } from "./ToolTipLabelComponent";
@@ -55,6 +55,18 @@ export interface SubSettingsComponentProps {
onSubSettingsSaveableChange: (isSubSettingsSaveable: boolean) => void; onSubSettingsSaveableChange: (isSubSettingsSaveable: boolean) => void;
onSubSettingsDiscardableChange: (isSubSettingsDiscardable: boolean) => void; onSubSettingsDiscardableChange: (isSubSettingsDiscardable: boolean) => void;
} }
const stylingforvisuallyhiddenspan = {
position: "absolute",
width: "1px",
height: "1px",
margin: "-1px",
border: 0,
padding: 0,
clip: "rect(0 0 0 0)",
overflow: "hidden",
};
export class SubSettingsComponent extends React.Component<SubSettingsComponentProps> { export class SubSettingsComponent extends React.Component<SubSettingsComponentProps> {
private shouldCheckComponentIsDirty = true; private shouldCheckComponentIsDirty = true;
@@ -197,17 +209,26 @@ export class SubSettingsComponent extends React.Component<SubSettingsComponentPr
</MessageBar> </MessageBar>
)} )}
{this.props.timeToLive === TtlType.On && ( {this.props.timeToLive === TtlType.On && (
<TextField <div>
id="timeToLiveSeconds" <span id="timetoliveon" style={stylingforvisuallyhiddenspan}>
styles={getTextFieldStyles(this.props.timeToLiveSeconds, this.props.timeToLiveSecondsBaseline)} Time to live on
type="number" </span>
required <TextField
min={1} id="timeToLiveSeconds"
max={Int32.Max} styles={getTextFieldStyles(this.props.timeToLiveSeconds, this.props.timeToLiveSecondsBaseline)}
value={this.props.timeToLiveSeconds?.toString()} type="number"
onChange={this.onTimeToLiveSecondsChange} required
suffix="second(s)" min={1}
/> max={Int32.Max}
value={this.props.timeToLiveSeconds?.toString()}
onChange={this.onTimeToLiveSecondsChange}
suffix="second(s)"
aria-labelledby="timetoliveon secondssuffixforscreenreader"
/>
<span id="secondssuffixforscreenreader" style={stylingforvisuallyhiddenspan}>
Second(s)
</span>
</div>
)} )}
</Stack> </Stack>
); );

View File

@@ -59,31 +59,68 @@ exports[`SubSettingsComponent analyticalTimeToLive hidden 1`] = `
} }
} }
/> />
<StyledTextFieldBase <div>
id="timeToLiveSeconds" <span
max={2147483647} id="timetoliveon"
min={1} style={
onChange={[Function]} Object {
required={true} "border": 0,
styles={ "clip": "rect(0 0 0 0)",
Object { "height": "1px",
"fieldGroup": Object { "margin": "-1px",
"borderColor": "", "overflow": "hidden",
"height": 25, "padding": 0,
"selectors": Object { "position": "absolute",
":disabled": Object { "width": "1px",
"backgroundColor": undefined, }
"borderColor": undefined,
},
},
"width": 300,
},
} }
} >
suffix="second(s)" Time to live on
type="number" </span>
value="1000" <StyledTextFieldBase
/> aria-labelledby="timetoliveon secondssuffixforscreenreader"
id="timeToLiveSeconds"
max={2147483647}
min={1}
onChange={[Function]}
required={true}
styles={
Object {
"fieldGroup": Object {
"borderColor": "",
"height": 25,
"selectors": Object {
":disabled": Object {
"backgroundColor": undefined,
"borderColor": undefined,
},
},
"width": 300,
},
}
}
suffix="second(s)"
type="number"
value="1000"
/>
<span
id="secondssuffixforscreenreader"
style={
Object {
"border": 0,
"clip": "rect(0 0 0 0)",
"height": "1px",
"margin": "-1px",
"overflow": "hidden",
"padding": 0,
"position": "absolute",
"width": "1px",
}
}
>
Second(s)
</span>
</div>
</Stack> </Stack>
<StyledChoiceGroup <StyledChoiceGroup
id="geoSpatialConfig" id="geoSpatialConfig"
@@ -283,31 +320,68 @@ exports[`SubSettingsComponent analyticalTimeToLiveSeconds hidden 1`] = `
} }
} }
/> />
<StyledTextFieldBase <div>
id="timeToLiveSeconds" <span
max={2147483647} id="timetoliveon"
min={1} style={
onChange={[Function]} Object {
required={true} "border": 0,
styles={ "clip": "rect(0 0 0 0)",
Object { "height": "1px",
"fieldGroup": Object { "margin": "-1px",
"borderColor": "", "overflow": "hidden",
"height": 25, "padding": 0,
"selectors": Object { "position": "absolute",
":disabled": Object { "width": "1px",
"backgroundColor": undefined, }
"borderColor": undefined,
},
},
"width": 300,
},
} }
} >
suffix="second(s)" Time to live on
type="number" </span>
value="1000" <StyledTextFieldBase
/> aria-labelledby="timetoliveon secondssuffixforscreenreader"
id="timeToLiveSeconds"
max={2147483647}
min={1}
onChange={[Function]}
required={true}
styles={
Object {
"fieldGroup": Object {
"borderColor": "",
"height": 25,
"selectors": Object {
":disabled": Object {
"backgroundColor": undefined,
"borderColor": undefined,
},
},
"width": 300,
},
}
}
suffix="second(s)"
type="number"
value="1000"
/>
<span
id="secondssuffixforscreenreader"
style={
Object {
"border": 0,
"clip": "rect(0 0 0 0)",
"height": "1px",
"margin": "-1px",
"overflow": "hidden",
"padding": 0,
"position": "absolute",
"width": "1px",
}
}
>
Second(s)
</span>
</div>
</Stack> </Stack>
<StyledChoiceGroup <StyledChoiceGroup
id="geoSpatialConfig" id="geoSpatialConfig"
@@ -560,31 +634,68 @@ exports[`SubSettingsComponent changeFeedPolicy hidden 1`] = `
} }
} }
/> />
<StyledTextFieldBase <div>
id="timeToLiveSeconds" <span
max={2147483647} id="timetoliveon"
min={1} style={
onChange={[Function]} Object {
required={true} "border": 0,
styles={ "clip": "rect(0 0 0 0)",
Object { "height": "1px",
"fieldGroup": Object { "margin": "-1px",
"borderColor": "", "overflow": "hidden",
"height": 25, "padding": 0,
"selectors": Object { "position": "absolute",
":disabled": Object { "width": "1px",
"backgroundColor": undefined, }
"borderColor": undefined,
},
},
"width": 300,
},
} }
} >
suffix="second(s)" Time to live on
type="number" </span>
value="1000" <StyledTextFieldBase
/> aria-labelledby="timetoliveon secondssuffixforscreenreader"
id="timeToLiveSeconds"
max={2147483647}
min={1}
onChange={[Function]}
required={true}
styles={
Object {
"fieldGroup": Object {
"borderColor": "",
"height": 25,
"selectors": Object {
":disabled": Object {
"backgroundColor": undefined,
"borderColor": undefined,
},
},
"width": 300,
},
}
}
suffix="second(s)"
type="number"
value="1000"
/>
<span
id="secondssuffixforscreenreader"
style={
Object {
"border": 0,
"clip": "rect(0 0 0 0)",
"height": "1px",
"margin": "-1px",
"overflow": "hidden",
"padding": 0,
"position": "absolute",
"width": "1px",
}
}
>
Second(s)
</span>
</div>
</Stack> </Stack>
<StyledChoiceGroup <StyledChoiceGroup
id="geoSpatialConfig" id="geoSpatialConfig"
@@ -799,31 +910,68 @@ exports[`SubSettingsComponent renders 1`] = `
} }
} }
/> />
<StyledTextFieldBase <div>
id="timeToLiveSeconds" <span
max={2147483647} id="timetoliveon"
min={1} style={
onChange={[Function]} Object {
required={true} "border": 0,
styles={ "clip": "rect(0 0 0 0)",
Object { "height": "1px",
"fieldGroup": Object { "margin": "-1px",
"borderColor": "", "overflow": "hidden",
"height": 25, "padding": 0,
"selectors": Object { "position": "absolute",
":disabled": Object { "width": "1px",
"backgroundColor": undefined, }
"borderColor": undefined,
},
},
"width": 300,
},
} }
} >
suffix="second(s)" Time to live on
type="number" </span>
value="1000" <StyledTextFieldBase
/> aria-labelledby="timetoliveon secondssuffixforscreenreader"
id="timeToLiveSeconds"
max={2147483647}
min={1}
onChange={[Function]}
required={true}
styles={
Object {
"fieldGroup": Object {
"borderColor": "",
"height": 25,
"selectors": Object {
":disabled": Object {
"backgroundColor": undefined,
"borderColor": undefined,
},
},
"width": 300,
},
}
}
suffix="second(s)"
type="number"
value="1000"
/>
<span
id="secondssuffixforscreenreader"
style={
Object {
"border": 0,
"clip": "rect(0 0 0 0)",
"height": "1px",
"margin": "-1px",
"overflow": "hidden",
"padding": 0,
"position": "absolute",
"width": "1px",
}
}
>
Second(s)
</span>
</div>
</Stack> </Stack>
<StyledChoiceGroup <StyledChoiceGroup
id="geoSpatialConfig" id="geoSpatialConfig"

View File

@@ -250,7 +250,7 @@ export const ThroughputInput: FunctionComponent<ThroughputInputProps> = ({
step={AutoPilotUtils.autoPilotIncrementStep} step={AutoPilotUtils.autoPilotIncrementStep}
min={AutoPilotUtils.autoPilotThroughput1K} min={AutoPilotUtils.autoPilotThroughput1K}
value={throughput.toString()} value={throughput.toString()}
ariaLabel={`${isDatabase ? "Database" : getCollectionName()} max RU/s`} aria-label="Max request units per second"
required={true} required={true}
errorMessage={throughputError} errorMessage={throughputError}
/> />

View File

@@ -1640,7 +1640,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
</div> </div>
</Stack> </Stack>
<StyledTextFieldBase <StyledTextFieldBase
ariaLabel="Container max RU/s" aria-label="Max request units per second"
errorMessage="" errorMessage=""
id="autoscaleRUValueField" id="autoscaleRUValueField"
key=".0:$.2" key=".0:$.2"
@@ -1663,7 +1663,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
value="4000" value="4000"
> >
<TextFieldBase <TextFieldBase
ariaLabel="Container max RU/s" aria-label="Max request units per second"
deferredValidationTime={200} deferredValidationTime={200}
errorMessage="" errorMessage=""
id="autoscaleRUValueField" id="autoscaleRUValueField"
@@ -1961,7 +1961,6 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
> >
<input <input
aria-invalid={false} aria-invalid={false}
aria-label="Container max RU/s"
className="ms-TextField-field field-64" className="ms-TextField-field field-64"
id="autoscaleRUValueField" id="autoscaleRUValueField"
min={1000} min={1000}

View File

@@ -729,7 +729,6 @@ export class D3ForceGraph implements GraphRenderer {
var iconGroup = newNodes var iconGroup = newNodes
.append("g") .append("g")
.attr("class", "iconContainer") .attr("class", "iconContainer")
.attr("role", "group")
.attr("tabindex", 0) .attr("tabindex", 0)
.attr("aria-label", (d: D3Node) => { .attr("aria-label", (d: D3Node) => {
return this.retrieveNodeCaption(d); return this.retrieveNodeCaption(d);

View File

@@ -23,9 +23,7 @@ export class MiddlePaneComponent extends React.Component<MiddlePaneComponentProp
onClick={this.props.toggleExpandGraph} onClick={this.props.toggleExpandGraph}
role="button" role="button"
aria-expanded={this.props.isTabsContentExpanded} aria-expanded={this.props.isTabsContentExpanded}
aria-label={ aria-name="View graph in full screen"
this.props.isTabsContentExpanded ? "Collapse graph to minimized view" : "View graph in full screen"
}
> >
<img <img
src={this.props.isTabsContentExpanded ? CollapseArrowIcon : ExpandIcon} src={this.props.isTabsContentExpanded ? CollapseArrowIcon : ExpandIcon}

View File

@@ -107,11 +107,11 @@ describe("New Vertex Component", () => {
it("should call onTypeChange method on type dropdown change", () => { it("should call onTypeChange method on type dropdown change", () => {
const DOWN_ARROW = { keyCode: 40 }; const DOWN_ARROW = { keyCode: 40 };
const onTypeChange = jest.fn(); const onTypeChange = jest.fn();
const dropdown = screen.getByRole("combobox"); const dropdown = screen.getByRole("listbox");
dropdown.onclick = onTypeChange(); dropdown.onclick = onTypeChange();
dropdown.onkeydown = onTypeChange(); dropdown.onkeydown = onTypeChange();
fireEvent.keyDown(screen.getByRole("combobox"), DOWN_ARROW); fireEvent.keyDown(screen.getByRole("listbox"), DOWN_ARROW);
fireEvent.click(screen.getByText(/number/)); fireEvent.click(screen.getByText(/number/));
expect(onTypeChange).toHaveBeenCalled(); expect(onTypeChange).toHaveBeenCalled();
}); });

View File

@@ -145,7 +145,6 @@ export const NewVertexComponent: FunctionComponent<INewVertexComponentProps> = (
type="text" type="text"
id="propertyKeyNewVertexPane" id="propertyKeyNewVertexPane"
componentRef={input} componentRef={input}
aria-required="true"
placeholder="Key" placeholder="Key"
autoComplete="off" autoComplete="off"
aria-label={`Enter value for propery ${index + 1}`} aria-label={`Enter value for propery ${index + 1}`}
@@ -153,8 +152,6 @@ export const NewVertexComponent: FunctionComponent<INewVertexComponentProps> = (
onChange={(event: React.ChangeEvent<HTMLInputElement>) => onKeyChange(event, index)} onChange={(event: React.ChangeEvent<HTMLInputElement>) => onKeyChange(event, index)}
/> />
</div> </div>
<span className="mandatoryStar">*&nbsp;</span>
<div className="valueCol"> <div className="valueCol">
<TextField <TextField
className="edgeInput" className="edgeInput"
@@ -168,7 +165,7 @@ export const NewVertexComponent: FunctionComponent<INewVertexComponentProps> = (
</div> </div>
<div> <div>
<Dropdown <Dropdown
role="combobox" role="listbox"
placeholder="Select an option" placeholder="Select an option"
defaultSelectedKey={data.values[0].type} defaultSelectedKey={data.values[0].type}
style={{ width: 100 }} style={{ width: 100 }}

View File

@@ -32,8 +32,14 @@ export const PanelInfoErrorComponent: React.FunctionComponent<PanelInfoErrorProp
return ( return (
<Stack className="panelInfoErrorContainer" horizontal verticalAlign="center"> <Stack className="panelInfoErrorContainer" horizontal verticalAlign="center">
{icon} {icon}
<span className="panelWarningErrorDetailsLinkContainer" role="alert" aria-live="assertive"> <span className="panelWarningErrorDetailsLinkContainer">
<Text aria-label={message} className="panelWarningErrorMessage" variant="small"> <Text
role="alert"
aria-live="assertive"
aria-label={message}
className="panelWarningErrorMessage"
variant="small"
>
{message} {message}
{link && linkText && ( {link && linkText && (
<Link target="_blank" href={link}> <Link target="_blank" href={link}>
@@ -42,7 +48,7 @@ export const PanelInfoErrorComponent: React.FunctionComponent<PanelInfoErrorProp
)} )}
</Text> </Text>
{showErrorDetails && ( {showErrorDetails && (
<a className="paneErrorLink" role="button" onClick={expandConsole} tabIndex={0} onKeyPress={expandConsole}> <a className="paneErrorLink" role="link" onClick={expandConsole} tabIndex={0} onKeyPress={expandConsole}>
More details More details
</a> </a>
)} )}

View File

@@ -1,10 +1,10 @@
import { DefaultButton, IconButton, Image, Modal, PrimaryButton, Stack, Text } from "@fluentui/react"; import { DefaultButton, IconButton, Image, Modal, PrimaryButton, Stack, Text } from "@fluentui/react";
import { useCarousel } from "hooks/useCarousel";
import React, { useState } from "react";
import Youtube from "react-youtube";
import { Action } from "Shared/Telemetry/TelemetryConstants"; import { Action } from "Shared/Telemetry/TelemetryConstants";
import { traceSuccess } from "Shared/Telemetry/TelemetryProcessor"; import { traceSuccess } from "Shared/Telemetry/TelemetryProcessor";
import { userContext } from "UserContext"; import { userContext } from "UserContext";
import { useCarousel } from "hooks/useCarousel";
import React, { useState } from "react";
import Youtube from "react-youtube";
import Image1 from "../../../images/CarouselImage1.svg"; import Image1 from "../../../images/CarouselImage1.svg";
import Image2 from "../../../images/CarouselImage2.svg"; import Image2 from "../../../images/CarouselImage2.svg";
@@ -25,7 +25,7 @@ export const QuickstartCarousel: React.FC<QuickstartCarouselProps> = ({
<Stack> <Stack>
<Stack horizontal horizontalAlign="space-between" style={{ padding: 16 }}> <Stack horizontal horizontalAlign="space-between" style={{ padding: 16 }}>
<Text variant="xLarge">{getHeaderText(page)}</Text> <Text variant="xLarge">{getHeaderText(page)}</Text>
<IconButton iconProps={{ iconName: "Cancel" }} onClick={() => setPage(4)} /> <IconButton iconProps={{ iconName: "Cancel" }} onClick={() => setPage(4)} ariaLabel="Close"/>
</Stack> </Stack>
{getContent(page)} {getContent(page)}
<Text variant="medium" style={{ padding: "0 16px" }}> <Text variant="medium" style={{ padding: "0 16px" }}>

View File

@@ -7,10 +7,10 @@ import { SplashScreen } from "Explorer/SplashScreen/SplashScreen";
import { ConnectTab } from "Explorer/Tabs/ConnectTab"; import { ConnectTab } from "Explorer/Tabs/ConnectTab";
import { PostgresConnectTab } from "Explorer/Tabs/PostgresConnectTab"; import { PostgresConnectTab } from "Explorer/Tabs/PostgresConnectTab";
import { QuickstartTab } from "Explorer/Tabs/QuickstartTab"; import { QuickstartTab } from "Explorer/Tabs/QuickstartTab";
import { userContext } from "UserContext";
import { useTeachingBubble } from "hooks/useTeachingBubble"; import { useTeachingBubble } from "hooks/useTeachingBubble";
import ko from "knockout"; import ko from "knockout";
import React, { MutableRefObject, useEffect, useRef, useState } from "react"; import React, { MutableRefObject, useEffect, useRef, useState } from "react";
import { userContext } from "UserContext";
import loadingIcon from "../../../images/circular_loader_black_16x16.gif"; import loadingIcon from "../../../images/circular_loader_black_16x16.gif";
import errorIcon from "../../../images/close-black.svg"; import errorIcon from "../../../images/close-black.svg";
import { useObservable } from "../../hooks/useObservable"; import { useObservable } from "../../hooks/useObservable";
@@ -102,20 +102,22 @@ function TabNav({ tab, active, tabKind }: { tab?: Tab; active: boolean; tabKind?
ref={focusTab} ref={focusTab}
> >
<span className="tabNavContentContainer"> <span className="tabNavContentContainer">
<div className="tab_Content"> <a data-toggle="tab" href={"#" + tabId} tabIndex={-1}>
<span className="statusIconContainer" style={{ width: tabKind === ReactTabKind.Home ? 0 : 18 }}> <div className="tab_Content">
{useObservable(tab?.isExecutionError || ko.observable(false)) && <ErrorIcon tab={tab} active={active} />} <span className="statusIconContainer" style={{ width: tabKind === ReactTabKind.Home ? 0 : 18 }}>
{useObservable(tab?.isExecuting || ko.observable(false)) && ( {useObservable(tab?.isExecutionError || ko.observable(false)) && <ErrorIcon tab={tab} active={active} />}
<img className="loadingIcon" title="Loading" src={loadingIcon} alt="Loading" /> {useObservable(tab?.isExecuting || ko.observable(false)) && (
)} <img className="loadingIcon" title="Loading" src={loadingIcon} alt="Loading" />
</span> )}
<span className="tabNavText">{useObservable(tab?.tabTitle || ko.observable(ReactTabKind[tabKind]))}</span>
{tabKind !== ReactTabKind.Home && (
<span className="tabIconSection">
<CloseButton tab={tab} active={active} hovering={hovering} tabKind={tabKind} />
</span> </span>
)} <span className="tabNavText">{useObservable(tab?.tabTitle || ko.observable(ReactTabKind[tabKind]))}</span>
</div> {tabKind !== ReactTabKind.Home && (
<span className="tabIconSection">
<CloseButton tab={tab} active={active} hovering={hovering} tabKind={tabKind} />
</span>
)}
</div>
</a>
</span> </span>
</li> </li>
); );