diff --git a/src/components/captions-control/captions-control.tsx b/src/components/captions-control/captions-control.tsx
new file mode 100644
index 000000000..9c86aa66b
--- /dev/null
+++ b/src/components/captions-control/captions-control.tsx
@@ -0,0 +1,120 @@
+import {h} from 'preact';
+import {connect, useSelector} from 'react-redux';
+import {ClosedCaptions} from '../closed-captions';
+import {CaptionsMenu} from '../captions-menu';
+import {ButtonControl} from '../button-control';
+import {Tooltip} from '../tooltip';
+import {Button} from '../button';
+import style from '../../styles/style.scss';
+import {Icon, IconType} from '../icon';
+import {SmartContainer} from '../smart-container';
+import {Text, withText} from 'preact-i18n';
+import {useRef, useState, useEffect, useCallback} from 'preact/hooks';
+import {focusElement} from '../../utils';
+import {createPortal} from 'preact/compat';
+import {CVAAOverlay} from '../cvaa-overlay';
+
+/**
+ * mapping state to props
+ * @param {*} state - redux store state
+ * @returns {Object} - mapped state to this component
+ */
+const mapStateToProps = state => ({
+ textTracks: state.engine.textTracks,
+ showCCButton: state.config.showCCButton,
+ openMenuFromCCCButton: state.config.openMenuFromCCButton,
+ isMobile: state.shell.isMobile,
+ isSmallSize: state.shell.isSmallSize,
+ isCVAAOverlayOpen: state.shell.isCVAAOverlayOpen
+});
+
+const COMPONENT_NAME = 'CaptionsControl';
+
+/**
+ * CaptionsControl component
+ *
+ * @class CaptionsControl
+ * @example
+ * @extends {Component}
+ */
+const CaptionsControl = connect(mapStateToProps)(
+ withText({
+ captionsLabelText: 'captions.captions',
+ advancedCaptionsSettingsText: 'captions.advanced_captions_settings'
+ })((props, context) => {
+ const [smartContainerOpen, setSmartContainerOpen] = useState(false);
+ const [cvaaOverlay, setCVAAOverlay] = useState(false);
+ const [ccOn, setCCOn] = useState(false);
+ const buttonRef = useRef(null);
+ const controlCaptionsElement = useRef(null);
+
+ const {player} = context;
+ const {isSmallSize, isMobile, textTracks} = props;
+ const activeTextTrack = textTracks.find(textTrack => textTrack.active);
+
+ const onControlButtonClick = (e?: KeyboardEvent, byKeyboard?: boolean): void => {
+ setSmartContainerOpen(smartContainerOpen => !smartContainerOpen);
+ if (byKeyboard && smartContainerOpen) {
+ focusElement(buttonRef.current);
+ }
+ };
+
+ const toggleCVAAOverlay = (): void => {
+ setCVAAOverlay(cvaaOverlay => !cvaaOverlay);
+ };
+
+ const onCVAAOverlayClose = (e?: KeyboardEvent, byKeyboard?: boolean): void => {
+ toggleCVAAOverlay();
+ onControlButtonClick(e, byKeyboard);
+ };
+
+ const handleClickOutside = (e: any) => {
+ if (!isMobile && !isSmallSize && !!controlCaptionsElement.current && !controlCaptionsElement.current.contains(e.target)) {
+ setSmartContainerOpen(false);
+ }
+ };
+
+ useEffect(() => {
+ document.addEventListener('click', handleClickOutside);
+ return () => document.removeEventListener('click', handleClickOutside);
+ }, [isSmallSize, isMobile]);
+
+ useEffect(() => {
+ setCCOn(activeTextTrack?.language !== 'off');
+ }, [activeTextTrack]);
+
+ const shouldRender = !!textTracks?.length && props.showCCButton;
+ props.onToggle(COMPONENT_NAME, shouldRender);
+ if (!shouldRender) return undefined;
+
+ const targetId: HTMLDivElement | Document = (document.getElementById(player.config.targetId) as HTMLDivElement) || document;
+ const portalSelector = `.overlay-portal`;
+
+ return props.openMenuFromCCCButton ? (
+
+
+
+
+ {smartContainerOpen && !cvaaOverlay && (
+ setSmartContainerOpen(false)} title={}>
+
+
+ )}
+ {cvaaOverlay ? createPortal(, targetId.querySelector(portalSelector)!) : }
+
+ ) : (
+
+ );
+ })
+);
+
+CaptionsControl.displayName = COMPONENT_NAME;
+export {CaptionsControl};
diff --git a/src/components/captions-control/index.ts b/src/components/captions-control/index.ts
new file mode 100644
index 000000000..de9a877af
--- /dev/null
+++ b/src/components/captions-control/index.ts
@@ -0,0 +1 @@
+export {CaptionsControl} from './captions-control';
diff --git a/src/components/captions-menu/captions-menu.tsx b/src/components/captions-menu/captions-menu.tsx
index 7bde5c866..abc5c6253 100644
--- a/src/components/captions-menu/captions-menu.tsx
+++ b/src/components/captions-menu/captions-menu.tsx
@@ -10,6 +10,8 @@ import {withEventManager} from '../../event';
import {withLogger} from '../logger';
import {withEventDispatcher} from '../event-dispatcher';
import {withKeyboardEvent} from '../../components/keyboard';
+import {KeyboardEventHandlers} from '../../types';
+import {Menu} from '../menu';
/**
* mapping state to props
@@ -86,17 +88,21 @@ class CaptionsMenu extends Component {
textOptions.push({label: props.advancedCaptionsSettingsText, value: props.advancedCaptionsSettingsText, active: false});
}
- return (
- {
- props.pushRef(el);
- }}
- icon={IconType.Captions}
- label={this.props.captionsLabelText}
- options={textOptions}
- onMenuChosen={textTrack => this.onCaptionsChange(textTrack)}
- />
- );
+ if (this.props.asDropdown) {
+ return (
+ {
+ props.pushRef(el);
+ }}
+ icon={IconType.Captions}
+ label={this.props.captionsLabelText}
+ options={textOptions}
+ onMenuChosen={textTrack => this.onCaptionsChange(textTrack)}
+ />
+ );
+ } else {
+ return