Skip to content

Commit 10d8ea0

Browse files
committed
AboutDialog spacing improvements for phetsims/special-ops#318
1 parent 97c8fdc commit 10d8ea0

3 files changed

Lines changed: 73 additions & 39 deletions

File tree

js/AboutDialog.ts

Lines changed: 42 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ import VoicingText from '../../scenery/js/accessibility/voicing/nodes/VoicingTex
2121
import VBox from '../../scenery/js/layout/nodes/VBox.js';
2222
import Node from '../../scenery/js/nodes/Node.js';
2323
import RichText from '../../scenery/js/nodes/RichText.js';
24-
import VStrut from '../../scenery/js/nodes/VStrut.js';
2524
import allowLinksProperty from '../../scenery/js/util/allowLinksProperty.js';
2625
import Dialog, { type DialogOptions } from '../../sun/js/Dialog.js';
2726
import Tandem from '../../tandem/js/Tandem.js';
@@ -33,6 +32,7 @@ import packageJSON from './packageJSON.js';
3332
import updateCheck from './updateCheck.js';
3433
import UpdateNodes from './UpdateNodes.js';
3534
import UpdateState from './UpdateState.js';
35+
import { ABOUT_DIALOG_LARGE_GAP_DELTA, ABOUT_DIALOG_MEDIUM_GAP_DELTA, ABOUT_DIALOG_SMALL_GAP, ABOUT_DIALOG_TEXT_LINE_SPACING } from './AboutDialogConstants.js';
3636

3737
// constants
3838
const MAX_WIDTH = 550; // Maximum width of elements in the dialog
@@ -74,16 +74,18 @@ export default class AboutDialog extends Dialog {
7474
const Brand: TBrand = phet.brand.Brand;
7575
assert && assert( Brand, 'Brand should exist by now' );
7676

77-
let children = [];
78-
79-
const strutSize = 15 - 6; // 15 is the desired spacing, but we are including a spacing of 6 already.
77+
const children = [];
8078

8179
const titleText = new VoicingText( nameStringProperty, {
8280
font: new PhetFont( 2 * NOMINAL_FONT_SIZE ),
8381
maxWidth: MAX_WIDTH,
8482

8583
// No accessibleParagraph because the content is already the accessibleName of the Dialog.
86-
accessibleParagraph: null
84+
accessibleParagraph: null,
85+
86+
layoutOptions: {
87+
bottomMargin: ABOUT_DIALOG_MEDIUM_GAP_DELTA
88+
}
8789
} );
8890
children.push( titleText );
8991

@@ -99,7 +101,10 @@ export default class AboutDialog extends Dialog {
99101
if ( phet.chipper.buildTimestamp ) {
100102
children.push( new VoicingText( phet.chipper.buildTimestamp, {
101103
font: new PhetFont( 0.65 * NOMINAL_FONT_SIZE ),
102-
maxWidth: MAX_WIDTH
104+
maxWidth: MAX_WIDTH,
105+
layoutOptions: {
106+
topMargin: ABOUT_DIALOG_MEDIUM_GAP_DELTA
107+
}
103108
} ) );
104109
}
105110

@@ -140,17 +145,18 @@ export default class AboutDialog extends Dialog {
140145
offlineNode
141146
],
142147
maxWidth: MAX_WIDTH,
143-
visibleProperty: allowLinksProperty
148+
visibleProperty: allowLinksProperty,
149+
layoutOptions: {
150+
topMargin: ABOUT_DIALOG_MEDIUM_GAP_DELTA
151+
}
144152
} );
145153

146154
children.push( updatePanel );
147155
}
148156

149-
const brandChildren = [];
150-
151157
// Show the brand name, if it exists
152158
if ( Brand.name ) {
153-
brandChildren.push( new VoicingRichText( Brand.name, {
159+
children.push( new VoicingRichText( Brand.name, {
154160
font: new PhetFont( NOMINAL_FONT_SIZE ),
155161
supScale: 0.5,
156162
supYOffset: 3,
@@ -159,7 +165,11 @@ export default class AboutDialog extends Dialog {
159165
// Default behavior is to create an accessibleParagraph for this element
160166
// but it needs to be a heading instead.
161167
accessibleHeading: Brand.name,
162-
accessibleParagraph: null
168+
accessibleParagraph: null,
169+
170+
layoutOptions: {
171+
topMargin: ABOUT_DIALOG_LARGE_GAP_DELTA
172+
}
163173
} ) );
164174
}
165175

@@ -171,59 +181,54 @@ export default class AboutDialog extends Dialog {
171181

172182
const copyright = StringUtils.fillIn( Brand.copyright, { year: year } );
173183

174-
brandChildren.push( new VoicingText( copyright, {
184+
children.push( new VoicingText( copyright, {
175185
font: new PhetFont( 0.75 * NOMINAL_FONT_SIZE ),
176186
maxWidth: MAX_WIDTH
177187
} ) );
178188
}
179189

180-
if ( brandChildren.length > 0 ) {
181-
children.push( new VStrut( strutSize ) );
182-
children = children.concat( brandChildren );
183-
}
184-
185-
const licenseChildren = [];
186-
187190
if ( Brand.license ) {
188191
const licenseStringProperty = new DerivedProperty( [ allowLinksProperty ], allowLinks => {
189192
return allowLinks ? Brand.license! : Brand.licenseWithoutLinks ?? Brand.license!;
190193
} );
191194

192-
licenseChildren.push( new VoicingText( JoistFluent.license.titleStringProperty, {
195+
children.push( new VoicingText( JoistFluent.license.titleStringProperty, {
193196
font: new PhetFont( { size: NOMINAL_FONT_SIZE, weight: 'bold' } ),
194197

195198
accessibleHeading: JoistFluent.license.titleStringProperty,
196-
accessibleParagraph: null
199+
accessibleParagraph: null,
200+
201+
layoutOptions: {
202+
topMargin: ABOUT_DIALOG_LARGE_GAP_DELTA
203+
}
197204
} ) );
198205

199206
// innerContent cannot embed links (xss vulnerability), so the accessible content uses the form of the
200207
// strings without any links
201208
const accessibleLicenseContent = Brand.licenseWithoutLinks ?? '';
202-
licenseChildren.push( new VoicingRichText( licenseStringProperty, {
209+
children.push( new VoicingRichText( licenseStringProperty, {
203210
font: new PhetFont( 0.75 * NOMINAL_FONT_SIZE ),
204211
align: 'left' as const,
205212
lineWrap: MAX_WIDTH,
206-
leading: 1, // to match the spacing in the CreditsNode between paragraphs
213+
leading: ABOUT_DIALOG_TEXT_LINE_SPACING,
207214
accessibleParagraph: accessibleLicenseContent,
208215
readingBlockNameResponse: RichText.getAccessibleStringProperty( new TinyProperty( accessibleLicenseContent ), false ),
209216
links: true // allow the embedded links, because they are from a controlled source
210217
} ) );
211218
}
212219

213-
if ( licenseChildren.length > 0 ) {
214-
children.push( new VStrut( strutSize ) );
215-
children = children.concat( licenseChildren );
216-
}
217-
218220
let creditsNode: Node | null = null;
219221

220222
// Add credits for specific brands
221223
if ( ( Brand.id === 'phet' || Brand.id === 'phet-io' ) ) {
222-
children.push( new VStrut( strutSize ) );
223224
creditsNode = new CreditsNode( credits, {
224225
titleFont: new PhetFont( { size: NOMINAL_FONT_SIZE, weight: 'bold' } ),
225226
textFont: new PhetFont( 0.75 * NOMINAL_FONT_SIZE ),
226-
maxWidth: MAX_WIDTH
227+
maxWidth: MAX_WIDTH,
228+
229+
layoutOptions: {
230+
topMargin: ABOUT_DIALOG_LARGE_GAP_DELTA
231+
}
227232
} );
228233
children.push( creditsNode );
229234
}
@@ -236,7 +241,10 @@ export default class AboutDialog extends Dialog {
236241
const linksParent = new VBox( {
237242
spacing: 6,
238243
align: 'left',
239-
maxWidth: MAX_WIDTH
244+
maxWidth: MAX_WIDTH,
245+
layoutOptions: {
246+
topMargin: ABOUT_DIALOG_LARGE_GAP_DELTA
247+
}
240248
} );
241249
children.push( linksParent );
242250

@@ -247,8 +255,6 @@ export default class AboutDialog extends Dialog {
247255
const links = Brand.getLinks( packageJSON.name, locale );
248256
const linksChildren: Node[] = [];
249257

250-
linksChildren.push( new VStrut( strutSize ) );
251-
252258
for ( let i = 0; i < links.length; i++ ) {
253259
const link = links[ i ];
254260

@@ -260,7 +266,8 @@ export default class AboutDialog extends Dialog {
260266
// This is PhET-iO instrumented because it is a keyboard navigation focusable element.
261267
const richText = new RichText( stringProperty, {
262268
links: { url: link.url }, // RichText must fill in URL for link
263-
font: new PhetFont( NOMINAL_FONT_SIZE )
269+
font: new PhetFont( NOMINAL_FONT_SIZE ),
270+
leading: ABOUT_DIALOG_TEXT_LINE_SPACING
264271
} );
265272
richText.disposeEmitter.addListener( () => {
266273
stringProperty.dispose();
@@ -274,7 +281,7 @@ export default class AboutDialog extends Dialog {
274281

275282
const content = new VBox( {
276283
align: 'left',
277-
spacing: 6,
284+
spacing: ABOUT_DIALOG_SMALL_GAP,
278285
children: children,
279286

280287
// pdom - accessible container for all AboutDialog content

js/AboutDialogConstants.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// Copyright 2026, University of Colorado Boulder
2+
3+
/**
4+
* Constants used for consistent styling and content in the About dialog.
5+
*
6+
* @author Jonathan Olson (PhET Interactive Simulations)
7+
*/
8+
9+
// Space between lines (needed for leading in RichText, or between other lines)
10+
export const ABOUT_DIALOG_TEXT_LINE_SPACING = 1;
11+
12+
// Typical gap between sections
13+
export const ABOUT_DIALOG_LARGE_GAP = 15;
14+
15+
// Gap between
16+
export const ABOUT_DIALOG_MEDIUM_GAP = 6;
17+
18+
// Typical gap within sections
19+
export const ABOUT_DIALOG_SMALL_GAP = 1;
20+
21+
// Deltas (if the small gap is used as spacing):
22+
export const ABOUT_DIALOG_LARGE_GAP_DELTA = ABOUT_DIALOG_LARGE_GAP - ABOUT_DIALOG_SMALL_GAP;
23+
export const ABOUT_DIALOG_MEDIUM_GAP_DELTA = ABOUT_DIALOG_MEDIUM_GAP - ABOUT_DIALOG_SMALL_GAP;

js/CreditsNode.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,9 @@ import VoicingRichText from '../../scenery/js/accessibility/voicing/nodes/Voicin
1818
import VoicingText from '../../scenery/js/accessibility/voicing/nodes/VoicingText.js';
1919
import VBox, { type VBoxOptions } from '../../scenery/js/layout/nodes/VBox.js';
2020
import type Node from '../../scenery/js/nodes/Node.js';
21-
import VStrut from '../../scenery/js/nodes/VStrut.js';
2221
import joist from './joist.js';
2322
import JoistFluent from './JoistFluent.js';
23+
import { ABOUT_DIALOG_LARGE_GAP_DELTA, ABOUT_DIALOG_SMALL_GAP, ABOUT_DIALOG_TEXT_LINE_SPACING } from './AboutDialogConstants.js';
2424

2525
type SelfOptions = {
2626
titleFont?: PhetFont;
@@ -48,14 +48,15 @@ export default class CreditsNode extends VBox {
4848
titleFont: new PhetFont( { size: 18, weight: 'bold' } ),
4949
textFont: new PhetFont( 16 ),
5050
align: 'left',
51-
spacing: 1,
51+
spacing: ABOUT_DIALOG_SMALL_GAP,
5252
maxWidth: 550
5353
}, options );
5454

5555
const richTextOptions = {
5656
font: options.textFont,
5757
align: 'left' as const,
5858
lineWrap: options.maxWidth,
59+
leading: ABOUT_DIALOG_TEXT_LINE_SPACING,
5960
tagName: 'p'
6061
};
6162

@@ -119,12 +120,15 @@ export default class CreditsNode extends VBox {
119120

120121
// Thanks
121122
if ( credits.thanks ) {
122-
if ( children.length > 0 ) { children.push( new VStrut( 13 ) ); }
123123
children.push( new VoicingText( JoistFluent.credits.thanksStringProperty, {
124124
font: options.titleFont,
125125

126126
accessibleHeading: JoistFluent.credits.thanksStringProperty,
127-
accessibleParagraph: null
127+
accessibleParagraph: null,
128+
layoutOptions: {
129+
// If needed, create a large gap
130+
topMargin: children.length > 0 ? ABOUT_DIALOG_LARGE_GAP_DELTA : 0
131+
}
128132
} ) );
129133

130134
const thanksText = new VoicingRichText( credits.thanks, richTextOptions );

0 commit comments

Comments
 (0)