import { randomId, getSearchParamsByKeys, getUrlSearchParams, } from '@seamly/web-ui' import { FC } from 'preact/compat' import { useCallback, useEffect, useState } from 'preact/hooks' import { getStateObj } from '../states' import StyleGuideLinks from './links' import StyleGuideView from './view' type HProps = { tag: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' id: string } const H: FC = ({ tag, id, children }) => { const Component = tag return {children} } const StyleGuideApp = ({ config, styleGuideConfig, translations, headingLevel = 2, participants, }) => { const [staticState, setStaticState] = useState | null>( null, ) const [selectedStateDescription, setSelectedStateDescription] = useState('') const [showStyleGuide, setShowStyleGuide] = useState(true) const [headingId] = useState(randomId()) const { stateUpdateCallback, customMessageEventBodies } = styleGuideConfig const [mainState] = useState(() => { const mainStateObj = getStateObj( styleGuideConfig.showLayoutModes || ['inline', 'window'], customMessageEventBodies, ) return Object.keys(mainStateObj).reduce( (acc, key) => ({ ...acc, [key]: { ...mainStateObj[key], descriptionId: randomId(), }, }), {}, ) }) const updateState = useCallback( (state) => { if (stateUpdateCallback) { setStaticState(stateUpdateCallback(state)) } else { setStaticState(state) } }, [setStaticState, stateUpdateCallback], ) const getState = useCallback( (layoutMode, feature) => { const keys = getSearchParamsByKeys('apiKey', 'locale') const combinedParams = new URLSearchParams({ ...keys, layoutMode, feature, }) window.history.replaceState(null, '', `?${combinedParams}`) const { headingText } = mainState[feature] setSelectedStateDescription(headingText) const bareState = mainState[feature][layoutMode] const { overrideMessages, showSuggestions } = bareState.config const agent = participants.find( (participant) => participant.id === 'agent', ) const updatedState = { ...bareState, config: { ...bareState.config, ...config, showDisclaimer: bareState.config.showDisclaimer || config.showDisclaimer, context: { ...config.context, ...bareState.config?.context, }, layoutMode: bareState.config.layoutMode, ...(overrideMessages ? { messages: overrideMessages } : {}), showSuggestions, }, headerTitles: { ...bareState.headerTitles, subTitle: agent?.name || bareState.headerTitles.subTitle, }, } return updatedState }, [mainState, participants, config], ) useEffect(() => { // As the modal focus trap disables clicking on elements outside the modal container, // we need to hide the modal manually on clicking the overlay as the internal // close action is not functional. if (showStyleGuide) { const overlay = document.querySelector('[aria-modal="true"]') if (overlay) { overlay.addEventListener('click', () => { setStaticState((s) => { if (!s) return s return { ...s, visibility: { ...s.visibility, visible: 'minimized' }, } }) }) } } }, [staticState, setStaticState, showStyleGuide]) const loadFromUrl = useCallback(() => { const params = getUrlSearchParams() const { layoutMode, feature } = { layoutMode: 'inline', feature: 'base', ...params, } updateState(getState(layoutMode, feature)) window.document.title = `Style` }, [getState, updateState]) useEffect(() => { loadFromUrl() }, [loadFromUrl]) useEffect(() => { window.onpopstate = () => { loadFromUrl() } }, [loadFromUrl]) const setSelectedState = ({ layoutMode, feature }) => { const renderState = getState(layoutMode, feature) updateState(renderState) if (renderState.styleGuideReset) { // This is required for changes where a previously rendered component may cause the // state not to resolve properly. For example: File uploads. // So we render the base state briefly to reset everything. setShowStyleGuide(false) setTimeout(() => { setShowStyleGuide(true) }) } } return ( <>
Current selected state: {`${selectedStateDescription}`}
{showStyleGuide && ( )}
) } export default StyleGuideApp