feat:(client): show-workshop-independent-lower-jaw (#64137)

Co-authored-by: Oliver Eyton-Williams <ojeytonwilliams@gmail.com>
This commit is contained in:
Ahmad Abdolsaheb
2026-01-08 17:51:57 +03:00
committed by GitHub
parent 87cf2f2633
commit c3f0473cd0
13 changed files with 292 additions and 36 deletions

View File

@@ -14,6 +14,22 @@
flex-direction: column;
}
#learn-app-wrapper .editor-pane {
display: flex;
flex-direction: column;
min-height: 0;
width: 100%;
}
#learn-app-wrapper .editor-pane-code {
flex: 1 1 auto;
min-height: 0;
}
#learn-app-wrapper .editor-pane > .independent-lower-jaw {
flex: 0 0 auto;
margin-top: auto;
}
#learn-app-wrapper .reflex-container.vertical {
min-height: 0;
flex-grow: 1;

View File

@@ -314,9 +314,14 @@ const DesktopLayout = (props: DesktopLayoutProps): JSX.Element => {
name='editorPane'
{...resizeProps}
data-playwright-test-label='editor-pane'
className='editor-pane'
>
{!isEmpty(challengeFiles) && (
<ReflexContainer key='codePane' orientation='horizontal'>
<ReflexContainer
key='codePane'
orientation='horizontal'
className='editor-pane-code'
>
<ReflexElement
name='codePane'
{...(displayEditorConsole && { flex: codePane.flex })}

View File

@@ -314,13 +314,9 @@ function ShowClassic({
// AB testing Pre-fetch in the Spanish locale
const isPreFetchEnabled = useFeature('prefetch_ab_test').on;
const isIndependentLowerJawEnabled = useFeature('independent-lower-jaw').on;
// Independent lower jaw is only enabled for the urriculum outline workshop
const showIndependentLowerJaw =
block === 'workshop-curriculum-outline' &&
isIndependentLowerJawEnabled &&
!isMobile;
// Independent lower jaw is only enabled for desktop workshops.
const showIndependentLowerJaw = hasEditableBoundaries && !isMobile;
useEffect(() => {
if (isPreFetchEnabled && envData.clientLocale === 'espanol') {

View File

@@ -1,10 +1,8 @@
.independent-lower-jaw {
position: absolute;
left: 0;
right: 0;
bottom: 0;
width: 100%;
z-index: 10;
flex: 0 0 auto;
position: relative;
z-index: 101;
}
.independent-lower-jaw .hint-container {
@@ -13,7 +11,24 @@
flex-direction: row;
justify-content: space-between;
padding: 12px;
margin: 12px;
position: absolute;
bottom: 100%;
width: 95%;
left: 50%;
transform: translateX(-50%);
margin-bottom: 12px;
border: 1px solid var(--background-secondary);
opacity: 0;
animation: jaw-hint-fade-in 0.3s ease forwards;
}
@keyframes jaw-hint-fade-in {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
.independent-lower-jaw .btn-cta {

View File

@@ -51,6 +51,10 @@ export function IndependentLowerJaw({
const hint = firstFailedTest?.message;
const [showHint, setShowHint] = React.useState(false);
const [showSubmissionHint, setShowSubmissionHint] = React.useState(true);
const signInLinkRef = React.useRef<HTMLAnchorElement>(null);
const submitButtonRef = React.useRef<HTMLButtonElement>(null);
const [wasCheckButtonClicked, setWasCheckButtonClicked] =
React.useState(false);
const isChallengeComplete = tests.every(test => test.pass);
@@ -58,27 +62,60 @@ export function IndependentLowerJaw({
setShowHint(!!hint);
}, [hint]);
React.useEffect(() => {
if (!isChallengeComplete || !wasCheckButtonClicked) return;
const focusTarget = isSignedIn
? submitButtonRef.current
: signInLinkRef.current;
focusTarget?.focus();
setWasCheckButtonClicked(false);
}, [isChallengeComplete, isSignedIn, wasCheckButtonClicked]);
const handleCheckButtonClick = () => {
setWasCheckButtonClicked(true);
executeChallenge();
};
const isMacOS = navigator.userAgent.includes('Mac OS');
const checkButtonText = isMacOS ? t('command-enter') : t('ctrl-enter');
const checkButtonText = isMacOS
? t('buttons.command-enter')
: t('buttons.ctrl-enter');
return (
<div className='independent-lower-jaw' tabIndex={-1}>
<div
className='independent-lower-jaw'
data-playwright-test-label='independentLowerJaw-container'
tabIndex={-1}
>
{showHint && hint && (
<div className='hint-container'>
<div
className='hint-container'
data-playwright-test-label='independentLowerJaw-failing-hint'
>
<div dangerouslySetInnerHTML={{ __html: hint }} />
<button className={'tooltip'} onClick={() => setShowHint(false)}>
<button
className={'tooltip'}
data-playwright-test-label='independentLowerJaw-hint-close-button'
onClick={() => setShowHint(false)}
>
×<span className='tooltiptext'> {t('buttons.close')}</span>
</button>
</div>
)}
{isChallengeComplete && showSubmissionHint && (
<div className='hint-container'>
<div
className='hint-container'
data-playwright-test-label='independentLowerJaw-submission-hint'
>
<div>
<p>{t('learn.congratulations-code-passes')}</p>
{!isSignedIn && (
<a
href={`${apiLocation}/signin`}
className='btn-cta btn btn-block'
data-playwright-test-label='independentLowerJaw-signin-link'
ref={signInLinkRef}
onClick={() => {
callGA({
event: 'sign_in'
@@ -91,6 +128,7 @@ export function IndependentLowerJaw({
</div>
<button
className={'tooltip'}
data-playwright-test-label='independentLowerJaw-submission-hint-close-button'
onClick={() => setShowSubmissionHint(false)}
>
×<span className='tooltiptext'> {t('buttons.close')}</span>
@@ -104,7 +142,10 @@ export function IndependentLowerJaw({
<Button
block
className={`${isSignedIn && 'btn-cta'} tooltip`}
id='independent-lower-jaw-submit-button'
data-playwright-test-label='independentLowerJaw-submit-button'
onClick={() => submitChallenge()}
ref={submitButtonRef}
>
{t('buttons.submit-continue')}
<span className='tooltiptext left-tooltip '>
@@ -115,7 +156,8 @@ export function IndependentLowerJaw({
<button
type='button'
className='btn-cta tooltip'
onClick={() => executeChallenge()}
data-playwright-test-label='independentLowerJaw-check-button'
onClick={handleCheckButtonClick}
>
{t('buttons.check-code')}
<span className='tooltiptext left-tooltip '>
@@ -128,6 +170,7 @@ export function IndependentLowerJaw({
<button
type='button'
className='icon-botton tooltip'
data-playwright-test-label='independentLowerJaw-reset-button'
onClick={openResetModal}
>
<Reset />
@@ -136,6 +179,7 @@ export function IndependentLowerJaw({
<button
type='button'
className='icon-botton tooltip'
data-playwright-test-label='independentLowerJaw-help-button'
onClick={openHelpModal}
>
<Help />