import { Component, Prop, State, Watch, Event, EventEmitter, Listen, h, Element } from '@stencil/core';
import {
ApplicationHeaderInfo,
HeaderMenuToggleDetail,
LanguageToggleOptions,
OntarioMenuItems,
OntarioHeaderType,
} from './ontario-header.interface';
import OntarioIconClose from '../ontario-icon/assets/ontario-icon-close-header.svg';
import OntarioIconMenu from '../ontario-icon/assets/ontario-icon-menu-header.svg';
import OntarioIconSearch from '../ontario-icon/assets/ontario-icon-search.svg';
import OntarioIconSearchWhite from '../ontario-icon/assets/ontario-icon-search-white.svg';
import OntarioIconDropdownArrow from '../ontario-icon/assets/ontario-icon-dropdown-arrow.svg';
import OntarioHeaderDefaultData from './ontario-header-default-data.json';
import { Language } from '../../utils/common/language-types';
import { MenuItem } from '../../utils/common/common.interface';
import { DeviceTypes } from '../../utils/common/common.enum';
import { ScreenBreakpoints, standardFontSizePx } from '../../utils/common/common.variables';
import { isClientSideRendering } from '../../utils/common/environment';
import { Input } from '../../utils/common/input/input';
import { generateMenuItem } from '../../utils/components/header/header-menu-items';
import { ConsoleMessageClass } from '../../utils/console-message/console-message';
import { ConsoleType } from '../../utils/console-message/console-message.enum';
import { getImageAssetSrcPath } from '../../utils/helper/assets';
import { DeviceType } from '../../utils/helper/utils-types';
import { validateLanguage } from '../../utils/validation/validation-functions';
import translations from '../../translations/global.i18n.json';
import config from '../../config.json';
/**
* Ontario Header renders Ontario.ca, application, and ServiceOntario header variants.
*
* For component guidance, see:
* - https://designsystem.ontario.ca/components/detail/ontario-header.html
* - https://designsystem.ontario.ca/components/detail/application-header.html
* - https://designsystem.ontario.ca/components/detail/service-ontario-header.html
* - https://designsystem.ontario.ca/developer-docs/components/ontario-header/
*/
@Component({
tag: 'ontario-header',
styleUrls: ['ontario-header.scss', 'ontario-application-header.scss', 'service-ontario-header.scss'],
shadow: true,
assetsDirs: ['./assets'],
})
export class OntarioHeader {
@Element() el: HTMLElement;
/**
* The type of header.
*/
@Prop() type?: OntarioHeaderType = 'application';
/**
* Information pertaining to the application and ServiceOntario headers.
*
* For the 'application' header type, this includes the application name, URL and optional props for the number of links in the subheader for desktop, tablet, and mobile views.
*
* For the 'serviceOntario' header type, the 'title' property is used as the service name displayed in the subheader.
*
* @example
*
*
*
*
*
*/
@Prop() applicationHeaderInfo: ApplicationHeaderInfo | string;
/**
* The items that will go inside the menu dropdown.
*
* For the 'ontario' header type, these items are displayed in the overflow menu. If `disableDynamicMenu` is false, static items will be overridden by dynamically fetched items from the Ontario Header API.
*
* For the 'application' and 'serviceOntario' header types, these items are displayed in the subheader menu and overflow menu.
*/
@Prop() menuItems: MenuItem[] | string;
/**
* Information pertaining to the sign-in menu items for the Ontario header.
*/
@Prop() signInMenuItems?: MenuItem[] | string;
/**
* A custom function to pass to the sign-in button.
*/
@Prop() customSignInToggle?: (event: globalThis.Event) => void;
/**
* Option to disable fetching of the dynamic menu from the Ontario Header API.
*
* When set to true, the static `menuItems` prop will be used instead of fetching from the API.
* When set to false (default), menu items are fetched dynamically from the Ontario Header API endpoint.
*
* This property only applies to the 'ontario' header type. The 'application' and 'serviceOntario' types always use static menu items.
*
* @default false
*
* @example
*
*
*/
@Prop() disableDynamicMenu: boolean = false;
/**
* Information pertaining to the language toggle links.
*
* @example
*
*
*/
@Prop() languageToggleOptions?: LanguageToggleOptions | string;
/**
* A custom function to pass to the language toggle button.
*/
@Prop() customLanguageToggle?: (event: globalThis.Event) => void;
/**
* The language of the component.
* This is used for translations, and is by default set through event listeners checking for a language property from the header. If none is passed, it will default to English.
*/
@Prop({ mutable: true }) language?: Language = 'en';
/**
* The base path to an assets folder containing the Design System assets
*/
@Prop() assetBasePath: string;
/**
* The application header information is reassigned to applicationHeaderInfoState for parsing
*/
@State() private applicationHeaderInfoState: ApplicationHeaderInfo;
/**
* The menuItems is reassigned to itemState for parsing
*
* @example
*
*
*/
@State() private menuItemState: MenuItem[];
/**
* The parsed sign-in menu items state
*/
@State() private signInMenuItemsState: MenuItem[];
/**
* A boolean state to handle the toggling of the sign-in menu
*/
@State() signInToggled: boolean = false;
/**
* Check to see if menu is dynamic or static
*/
@State() private isDynamicMenu: boolean = false;
/**
* The languageToggleOptions is reassigned to languageState for parsing
*
* @example
*
*/
@State() private languageState: LanguageToggleOptions;
/**
* Toggler for the menu and the search button
*/
@State() menuToggled: boolean = false;
@State() searchToggle?: boolean = false;
@State() translations: any = translations;
@State() breakpointDeviceState: DeviceType;
/**
* Tracks the current value of the search input field.
*
* This state is updated as the user types and is used as the single
* source of truth for the search input content (instead of directly
* manipulating the DOM). Clearing the field via Escape now resets this
* state, which triggers the UI to update automatically.
*
* Also used as the value submitted when performing a header search,
* improving consistency and reliability of the search behaviour.
*/
@State() private searchBoxTextState: string = '';
private shouldFocusMenuOnOpen = false;
private pendingMenuToggleTrigger: HeaderMenuToggleDetail['trigger'] | null = null;
/**
* Header-specific device detection.
*/
private getHeaderDeviceType(): DeviceType {
const width = typeof window !== 'undefined' ? window.innerWidth : 1200;
const tablet = ScreenBreakpoints.Large * standardFontSizePx;
if (width < tablet - 1) return DeviceTypes.Mobile;
if (width === tablet - 1) return DeviceTypes.Tablet;
return DeviceTypes.Desktop;
}
/**
* Helper to check if current breakpoint is mobile or tablet (not desktop)
*/
private get isMobileOrTablet(): boolean {
return (this.breakpointDeviceState ?? DeviceTypes.Desktop) !== DeviceTypes.Desktop;
}
private createMenuToggleDetail(isOpen: boolean, trigger: HeaderMenuToggleDetail['trigger']): HeaderMenuToggleDetail {
return { isOpen, trigger };
}
private emitMenuToggle(isOpen: boolean, trigger: HeaderMenuToggleDetail['trigger']) {
this.menuButtonToggled.emit(this.createMenuToggleDetail(isOpen, trigger));
}
private consumePendingMenuToggleTrigger(defaultTrigger: HeaderMenuToggleDetail['trigger']) {
const trigger = this.pendingMenuToggleTrigger ?? defaultTrigger;
this.pendingMenuToggleTrigger = null;
return trigger;
}
private isMenuOpenButtonKey(key: string): boolean {
return key === 'Enter' || key === ' ' || key === 'Spacebar' || key === 'ArrowDown';
}
private moveFocusIntoOpenMenu() {
window.dispatchEvent(new CustomEvent('menuButtonTabPressed', { bubbles: true, composed: true }));
}
private focusDesktopMenuAfterKeyboardOpen() {
if (this.isMobileOrTablet) return;
requestAnimationFrame(() => {
if (this.menuToggled) {
this.moveFocusIntoOpenMenu();
}
});
}
private handleMenuButtonKeyDown = (event: KeyboardEvent) => {
if (!this.isMenuOpenButtonKey(event.key)) return;
this.pendingMenuToggleTrigger = 'keyboard';
if (event.key === 'ArrowDown') {
event.preventDefault();
if (this.menuToggled) {
this.moveFocusIntoOpenMenu();
return;
}
if (this.signInToggled) {
this.signInToggled = false;
}
this.menuToggled = true;
this.emitMenuToggle(this.menuToggled, 'keyboard');
this.searchToggle = undefined;
this.focusDesktopMenuAfterKeyboardOpen();
}
};
private handleMenuButtonClick = () => {
this.handlemenuToggled(this.consumePendingMenuToggleTrigger('click'));
};
private handleSignInButtonKeyDown = (event: KeyboardEvent) => {
if (!this.isMenuOpenButtonKey(event.key)) return;
this.pendingMenuToggleTrigger = 'keyboard';
if (event.key === 'ArrowDown') {
event.preventDefault();
if (this.signInToggled) {
this.moveFocusIntoOpenMenu();
return;
}
if (this.menuToggled) {
this.menuToggled = false;
}
this.signInToggled = true;
this.emitMenuToggle(this.signInToggled, 'keyboard');
}
};
private handleSignInButtonClick = () => {
this.handleSignInToggled(this.consumePendingMenuToggleTrigger('click'));
};
@Watch('applicationHeaderInfo')
private parseApplicationHeaderInfo() {
const applicationHeaderInfo = this.applicationHeaderInfo;
if (applicationHeaderInfo) {
try {
if (typeof applicationHeaderInfo === 'string')
this.applicationHeaderInfoState = JSON.parse(applicationHeaderInfo);
else this.applicationHeaderInfoState = applicationHeaderInfo;
} catch (error) {
const message = new ConsoleMessageClass();
message
.addDesignSystemTag()
.addRegularText(' failed to parse props for ')
.addMonospaceText('')
.addRegularText(' in ')
.addMonospaceText('parseApplicationHeaderInfo()')
.addRegularText(' method \n ')
.addMonospaceText(error.stack)
.printMessage(ConsoleType.Error);
this.applicationHeaderInfoState = {
title: '',
href: '/',
}; // fallback on error
}
}
}
@Watch('menuItems')
parseMenuItems() {
const isEnglish = this.language === 'en';
try {
if (!Array.isArray(this.menuItems) && typeof this.menuItems === 'string') {
this.menuItemState = JSON.parse(this.menuItems);
this.isDynamicMenu = false;
} else if (Array.isArray(this.menuItems) && this.type === 'application') {
this.menuItemState = this.menuItems;
this.isDynamicMenu = false;
} else {
this.menuItemState = isEnglish ? OntarioHeaderDefaultData.en : OntarioHeaderDefaultData.fr;
this.isDynamicMenu = false;
}
} catch (error) {
const message = new ConsoleMessageClass();
message
.addDesignSystemTag()
.addRegularText(' failed to parse props for ')
.addMonospaceText('')
.addRegularText(' in ')
.addMonospaceText('parseMenuItems()')
.addRegularText(' method \n ')
.addMonospaceText(error.stack)
.printMessage(ConsoleType.Error);
this.menuItemState = [];
}
}
@Watch('signInMenuItems')
parseSignInMenuItems() {
try {
if (!Array.isArray(this.signInMenuItems) && typeof this.signInMenuItems === 'string') {
this.signInMenuItemsState = JSON.parse(this.signInMenuItems);
} else if (Array.isArray(this.signInMenuItems)) {
this.signInMenuItemsState = this.signInMenuItems;
} else {
this.signInMenuItemsState = [];
}
} catch (error) {
const message = new ConsoleMessageClass();
message
.addDesignSystemTag()
.addRegularText(' failed to parse props for ')
.addMonospaceText('')
.addRegularText(' in ')
.addMonospaceText('parseSignInMenuItems()')
.addRegularText(' method \n ')
.addMonospaceText(error.stack)
.printMessage(ConsoleType.Error);
this.signInMenuItemsState = [];
}
}
@Watch('languageToggleOptions')
private parseLanguage() {
const languageToggleOptions = this.languageToggleOptions;
try {
if (languageToggleOptions) {
if (typeof languageToggleOptions === 'string') {
this.languageState = JSON.parse(languageToggleOptions);
} else {
this.languageState = languageToggleOptions;
}
}
} catch (error) {
const message = new ConsoleMessageClass();
message
.addDesignSystemTag()
.addRegularText(' failed to parse props for ')
.addMonospaceText('')
.addRegularText(' in ')
.addMonospaceText('parseLanguage()')
.addRegularText(' method \n ')
.addMonospaceText(error.stack)
.printMessage(ConsoleType.Error);
this.languageState = {
englishLink: '/en',
frenchLink: '/fr',
}; // fallback on error
}
}
/**
* Watch for changes to the disableDynamicMenu prop to reset the fetch state.
* This allows the menu to be fetched again if the prop is changed after initial load.
*/
@Watch('disableDynamicMenu')
// @ts-ignore - Watcher is called by Stencil framework on prop changes
private handleDisableDynamicMenuChange(newValue: boolean) {
// Only reset the dynamic menu flag if disableDynamicMenu is being changed to false (re-enabling)
// This allows the fetch to happen again when the user toggles dynamic menu back on
if (newValue === false) {
this.isDynamicMenu = false;
}
}
@Listen('keydown', { target: 'window' })
handleKeyDown(event: KeyboardEvent) {
// Handle Tab from menu button -> ask menu to take focus
if (event.key === 'Tab' && !event.shiftKey) {
if (document.activeElement === this.menuButton && this.menuToggled) {
event.preventDefault();
window.dispatchEvent(new CustomEvent('menuButtonTabPressed', { bubbles: true, composed: true }));
return;
}
if (document.activeElement === this.signInButton && this.signInToggled) {
event.preventDefault();
window.dispatchEvent(new CustomEvent('menuButtonTabPressed', { bubbles: true, composed: true }));
return;
}
}
if (event.key === 'Escape') {
if (this.menuToggled) {
this.menuToggled = false;
this.emitMenuToggle(this.menuToggled, 'programmatic');
this.focusMenuButton();
}
if (this.signInToggled) {
this.signInToggled = false;
this.emitMenuToggle(this.signInToggled, 'programmatic');
this.signInButton.focus();
}
}
}
/**
* Logic to close the menu when anything outside the menu is clicked
*/
@Listen('click', { capture: true, target: 'window' })
handleClick(event: any) {
// Check if clicking inside overflow menu
const overflowMenu = this.el.shadowRoot?.querySelector('ontario-header-overflow-menu');
if (overflowMenu && event.composedPath().includes(overflowMenu)) {
return;
}
// Check if clicking inside tabs component (NEW!)
const tabsMenu = this.el.shadowRoot?.querySelector('ontario-header-menu-tabs');
if (tabsMenu && event.composedPath().includes(tabsMenu)) {
return;
}
// if the sign-in button is clicked, return
if (this.signInButton && event.composedPath().includes(this.signInButton)) {
return;
}
// if the menu button is clicked, return
if (event.composedPath().includes(this.menuButton)) {
return;
}
// Close both menus when clicking outside
if (this.menuToggled || this.signInToggled) {
this.menuToggled = false;
this.signInToggled = false;
this.emitMenuToggle(false, 'programmatic');
}
}
/**
* Logic to close the menu when the focus leaves the menu
*/
@Listen('focusout', { target: 'window' })
handleFocusOut(event: FocusEvent) {
if (this.menuToggled && !this.el.contains(event.relatedTarget as Node)) {
this.menuToggled = false;
this.emitMenuToggle(this.menuToggled, 'programmatic');
}
}
/**
* Logic to set breakpointDeviceState to the appropriate device when the screen resizes
*/
@Listen('resize', { target: 'window' })
handleResize() {
const previousBreakpoint = this.breakpointDeviceState;
// Use header-specific device detection here so the header's UI logic stays consistent
this.breakpointDeviceState = isClientSideRendering() ? this.getHeaderDeviceType() : DeviceTypes.Desktop;
// Close all menus when breakpoint changes
if (previousBreakpoint && previousBreakpoint !== this.breakpointDeviceState) {
this.menuToggled = false;
this.signInToggled = false;
this.emitMenuToggle(false, 'programmatic');
}
// If we enter tabbed mode, signInToggled is no longer used.
// Reset it so the internal state doesn’t stay "stuck" when returning to desktop.
const nowTabbed = this.isMobileOrTablet && this.signInMenuItemsState?.length > 0;
if (nowTabbed && this.signInToggled) {
this.signInToggled = false;
}
}
/**
* This listens for the `setAppLanguage` event sent from the language toggle when it is is connected to the DOM.
* It is used for the initial language when the input component loads.
*/
@Listen('setAppLanguage', { target: 'window' })
handleSetAppLanguage(event: CustomEvent | Language) {
this.language = validateLanguage(event);
this.parseMenuItems();
}
/**
* This listens for the `headerLanguageToggled` event sent from the language toggle when it is is connected to the DOM.
* It is used for changing the component language after the language toggle has been activated.
*/
@Listen('headerLanguageToggled', { target: 'window' })
handleLanguageToggle(event: CustomEvent<{ oldLanguage: Language; newLanguage: Language }>) {
this.handleSetAppLanguage(event.detail.newLanguage);
}
/**
* Listen for menu closed event from overflow menu
*/
@Listen('menuClosed', { target: 'window' })
handleMenuClosed() {
this.menuToggled = false;
this.signInToggled = false;
}
/**
* Listen for overflow menu requesting menu button focus.
* Happens when user presses Shift+Tab from first menu item.
*/
@Listen('focusMenuButton', { target: 'window' })
handleFocusMenuButton() {
if (this.signInToggled && this.signInButton) {
this.signInButton.focus();
// Emit event so menu knows button is focused (prevents auto-close)
window.dispatchEvent(new CustomEvent('menuButtonFocused', { bubbles: true, composed: true }));
return;
}
if (this.menuToggled && this.menuButton) {
this.menuButton.focus();
window.dispatchEvent(new CustomEvent('menuButtonFocused', { bubbles: true, composed: true }));
}
}
/**
* This event is toggled when the menu button is pressed.
* The `` sub-component listens for this event
* To trigger the showing and hiding of the overflow menu.
*/
@Event() menuButtonToggled: EventEmitter;
/**
* Logic to handle the menu toggling
*/
handlemenuToggled = (trigger: HeaderMenuToggleDetail['trigger'] = 'keyboard') => {
// Close sign-in menu if it's open
if (this.signInToggled) {
this.signInToggled = false;
}
this.menuToggled = !this.menuToggled;
this.emitMenuToggle(this.menuToggled, trigger);
this.searchToggle = undefined;
if (this.menuToggled && trigger === 'keyboard') {
this.focusDesktopMenuAfterKeyboardOpen();
}
};
/**
* Logic to handle the search toggling
*/
handleSearchToggle = () => {
this.searchToggle = !this.searchToggle;
};
/**
* Logic to handle the sign-in toggling
*/
handleSignInToggled = (trigger: HeaderMenuToggleDetail['trigger'] = 'keyboard') => {
// Close main menu if it's open
if (this.menuToggled) {
this.menuToggled = false;
}
this.signInToggled = !this.signInToggled;
// Emit the menuButtonToggled event for either dropdown
this.emitMenuToggle(this.signInToggled, trigger);
};
/**
* event.preventDefault(): https://developer.mozilla.org/en-US/docs/Web/API/Event/preventDefault
* location.href: https://developer.mozilla.org/en-US/docs/Web/API/Location/href
*/
handleSubmit = (event: any) => {
event.preventDefault();
const query = this.searchBoxTextState.trim();
if (!query) return;
const baseUrl = this.translations.header.ontarioSearchURL[this.language ? this.language : 'en'];
location.href = `${baseUrl}${encodeURIComponent(query)}`;
};
/**
* Logic to make the focus go back to the menu button when the list ends
*/
@Listen('endOfMenuReached', { target: 'window' })
focusMenuButton() {
this.menuButton.focus();
}
/**
* Call to Ontario Menu API to fetch linksets to populate header component
*/
async fetchOntarioMenu() {
const isEnglish = this.language === 'en';
// If menu has already been fetched and contains dynamic menu items, do not run fetch again
if (!this.isDynamicMenu) {
const apiUrl = isEnglish
? (config.ONTARIO_HEADER_API_URL_EN as string)
: (config.ONTARIO_HEADER_API_URL_FR as string);
const response = await fetch(apiUrl)
.then((response) => response.json())
.then((json) => json.linkset[0].item as OntarioMenuItems[])
.catch(() => {
console.error('Unable to retrieve data from Ontario Menu API');
return [];
});
if (response.length > 0) {
const externalMenuItems = response.map((item) => {
return { href: item.href, title: item.title };
});
this.menuItemState = externalMenuItems;
this.isDynamicMenu = true;
}
}
return;
}
/**
* Hydration Guard Flag
*
* This flag is used to determine if the component has been hydrated in the browser.
* It prevents certain browser-only operations, like fetching from APIs, from running
* during Server-Side Rendering (SSR), where `window` and `fetch` are not available.
*
* The `isHydrated` flag is set to true in `componentDidLoad()` and checked before
* triggering logic that should only run in the browser (e.g., `fetchOntarioMenu()`).
*
* Not reactive - should not be stored in State.
*/
private isHydrated = false;
/**
* Generate the full path to an image asset based on the base asset path.
*
* - If `assetBasePath` is provided, it is used as the base path.
* - If not, attempts to use Stencil's `getAssetPath` (for Stencil/Angular builds).
* - If that fails (e.g., in React), falls back to `/assets/`, assuming assets are in the public folder.
*
* This allows the component to work across multiple environments (Stencil, Angular, React).
*
* @param imageName - The name of the image file.
* @returns The full image path as a string.
*/
private getImageAssetSrcPath(imageName: string): string {
return getImageAssetSrcPath(imageName, this.assetBasePath);
}
/**
* This function generates the menu dropdown button for the ontario header component.
* It now derives viewport from component state (this.breakpointDeviceState) instead
* of relying on a caller-provided string.
*/
private renderMenuButton() {
const viewportSize = this.breakpointDeviceState as string;
if (!this.isMenuVisible(viewportSize)) {
return;
}
const isApplicationOrServiceHeader = this.type === 'application' || this.type === 'serviceOntario';
const isOntarioHeader = this.type === 'ontario';
const shouldShowOutline = isApplicationOrServiceHeader || this.isMobileOrTablet;
const useMenuCloseToggle = isApplicationOrServiceHeader || this.isMobileOrTablet;
const buttonClasses = [
'ontario-header__menu-toggle',
'ontario-header-button',
isOntarioHeader && 'ontario-header-button--desktop',
shouldShowOutline && 'ontario-header-button--with-outline',
this.menuToggled && 'ontario-header-button--toggled-open',
]
.filter(Boolean)
.join(' ');
const getButtonContent = () => {
if (useMenuCloseToggle) {
return (
);
}
return (
);
};
return (
);
}
/**
* This function generates the sign-in button for the ontario header component.
*/
private renderSignInButton() {
if (!this.signInMenuItemsState || this.signInMenuItemsState.length === 0) {
return;
}
// Only render sign-in button on desktop
if (this.breakpointDeviceState !== 'desktop') {
return;
}
return (
);
}
/**
* The onEscapePressed function clears the searchbar form when Escape is pressed
*/
private onEscapePressed = (event: KeyboardEvent) => {
if (event.key === 'Escape') {
this.searchBoxTextState = '';
}
};
private isMenuVisible(viewportSize: string) {
if (this.type !== 'ontario') {
const { menuItemState, applicationHeaderInfoState } = this;
const { mobile = 0, tablet = 0, desktop = 0 } = applicationHeaderInfoState?.maxSubheaderLinks ?? {};
const numOfMenuItems = menuItemState?.length ?? 0;
if (numOfMenuItems <= 0) {
return false;
}
if (viewportSize === DeviceTypes.Mobile) {
return numOfMenuItems - mobile > 0;
}
if (viewportSize === DeviceTypes.Tablet) {
return numOfMenuItems - tablet > 0;
}
if (viewportSize === DeviceTypes.Desktop) {
return numOfMenuItems - desktop > 0;
}
}
return true;
}
componentWillLoad() {
this.parseApplicationHeaderInfo();
this.parseMenuItems();
this.parseSignInMenuItems();
this.parseLanguage();
}
componentDidLoad() {
this.isHydrated = true;
}
componentDidRender() {
if (this.isHydrated && this.disableDynamicMenu === false && this.type === 'ontario') {
this.fetchOntarioMenu();
}
this.handleResize();
}
/**
* Handles the search focus when the search toggle button is clicked.
* When search button is clicked, the search bar is in focus,
* when the closed button is clicked, the search button is back into focus.
*/
componentDidUpdate() {
if (this.type == 'ontario') {
if (this.searchToggle === true) this.searchBar.focus();
if (this.searchToggle === false) this.searchButton.focus();
}
}
/**
* Assigning values to elements to use them as ref
*/
header!: HTMLElement;
menuButton!: HTMLElement;
signInButton!: HTMLElement;
searchBar!: HTMLInputElement;
searchButton!: HTMLInputElement;
render() {
const isServiceOntarioType = this.type === 'serviceOntario';
if (this.type == 'ontario') {
// Check if we should show tabbed interface
const shouldShowTabs = this.isMobileOrTablet && this.signInMenuItemsState && this.signInMenuItemsState.length > 0;
return (
);
} else {
return (
);
}
}
}