@wordpress/components 30.6.1-next.ff1cebbba.0 → 30.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (147) hide show
  1. package/CHANGELOG.md +26 -1
  2. package/build/color-palette/styles.js +2 -12
  3. package/build/color-palette/styles.js.map +2 -2
  4. package/build/combobox-control/index.js +1 -2
  5. package/build/combobox-control/index.js.map +2 -2
  6. package/build/custom-select-control-v2/custom-select.js +2 -2
  7. package/build/custom-select-control-v2/custom-select.js.map +2 -2
  8. package/build/date-time/date/styles.js +9 -9
  9. package/build/date-time/date/styles.js.map +2 -2
  10. package/build/focal-point-picker/index.js +21 -10
  11. package/build/focal-point-picker/index.js.map +3 -3
  12. package/build/focal-point-picker/styles/focal-point-picker-style.js +20 -11
  13. package/build/focal-point-picker/styles/focal-point-picker-style.js.map +2 -2
  14. package/build/font-size-picker/font-size-picker-select.js +20 -19
  15. package/build/font-size-picker/font-size-picker-select.js.map +3 -3
  16. package/build/font-size-picker/font-size-picker-toggle-group.js +27 -3
  17. package/build/font-size-picker/font-size-picker-toggle-group.js.map +2 -2
  18. package/build/font-size-picker/index.js +23 -11
  19. package/build/font-size-picker/index.js.map +2 -2
  20. package/build/font-size-picker/styles.js +30 -13
  21. package/build/font-size-picker/styles.js.map +3 -3
  22. package/build/font-size-picker/utils.js +11 -0
  23. package/build/font-size-picker/utils.js.map +2 -2
  24. package/build/palette-edit/styles.js +9 -9
  25. package/build/palette-edit/styles.js.map +2 -2
  26. package/build/popover/index.js +13 -2
  27. package/build/popover/index.js.map +2 -2
  28. package/build/tabs/styles.js +5 -5
  29. package/build/tabs/styles.js.map +1 -1
  30. package/build/tools-panel/styles.js +14 -22
  31. package/build/tools-panel/styles.js.map +2 -2
  32. package/build/utils/base-label.js +12 -12
  33. package/build/utils/base-label.js.map +3 -3
  34. package/build/utils/config-values.js +2 -0
  35. package/build/utils/config-values.js.map +2 -2
  36. package/build-module/color-palette/styles.js +2 -12
  37. package/build-module/color-palette/styles.js.map +2 -2
  38. package/build-module/combobox-control/index.js +1 -2
  39. package/build-module/combobox-control/index.js.map +2 -2
  40. package/build-module/custom-select-control-v2/custom-select.js +1 -1
  41. package/build-module/custom-select-control-v2/custom-select.js.map +1 -1
  42. package/build-module/date-time/date/styles.js +9 -9
  43. package/build-module/date-time/date/styles.js.map +2 -2
  44. package/build-module/focal-point-picker/index.js +23 -12
  45. package/build-module/focal-point-picker/index.js.map +2 -2
  46. package/build-module/focal-point-picker/styles/focal-point-picker-style.js +20 -12
  47. package/build-module/focal-point-picker/styles/focal-point-picker-style.js.map +2 -2
  48. package/build-module/font-size-picker/font-size-picker-select.js +21 -10
  49. package/build-module/font-size-picker/font-size-picker-select.js.map +2 -2
  50. package/build-module/font-size-picker/font-size-picker-toggle-group.js +27 -3
  51. package/build-module/font-size-picker/font-size-picker-toggle-group.js.map +2 -2
  52. package/build-module/font-size-picker/index.js +23 -11
  53. package/build-module/font-size-picker/index.js.map +2 -2
  54. package/build-module/font-size-picker/styles.js +28 -12
  55. package/build-module/font-size-picker/styles.js.map +2 -2
  56. package/build-module/font-size-picker/utils.js +10 -0
  57. package/build-module/font-size-picker/utils.js.map +2 -2
  58. package/build-module/palette-edit/styles.js +9 -9
  59. package/build-module/palette-edit/styles.js.map +2 -2
  60. package/build-module/popover/index.js +13 -2
  61. package/build-module/popover/index.js.map +2 -2
  62. package/build-module/tabs/styles.js +5 -5
  63. package/build-module/tabs/styles.js.map +1 -1
  64. package/build-module/tools-panel/styles.js +14 -22
  65. package/build-module/tools-panel/styles.js.map +2 -2
  66. package/build-module/utils/base-label.js +2 -12
  67. package/build-module/utils/base-label.js.map +2 -2
  68. package/build-module/utils/config-values.js +2 -0
  69. package/build-module/utils/config-values.js.map +2 -2
  70. package/build-style/style-rtl.css +11 -5
  71. package/build-style/style.css +11 -5
  72. package/build-types/card/card-body/component.d.ts.map +1 -1
  73. package/build-types/card/card-body/hook.d.ts.map +1 -1
  74. package/build-types/card/card-footer/component.d.ts +1 -3
  75. package/build-types/card/card-footer/component.d.ts.map +1 -1
  76. package/build-types/card/card-footer/hook.d.ts +6 -0
  77. package/build-types/card/card-footer/hook.d.ts.map +1 -1
  78. package/build-types/card/card-header/component.d.ts +1 -1
  79. package/build-types/card/card-header/component.d.ts.map +1 -1
  80. package/build-types/card/card-header/hook.d.ts +7 -0
  81. package/build-types/card/card-header/hook.d.ts.map +1 -1
  82. package/build-types/card/card-media/hook.d.ts.map +1 -1
  83. package/build-types/card/types.d.ts +3 -8
  84. package/build-types/card/types.d.ts.map +1 -1
  85. package/build-types/color-palette/styles.d.ts.map +1 -1
  86. package/build-types/combobox-control/index.d.ts.map +1 -1
  87. package/build-types/combobox-control/stories/index.story.d.ts.map +1 -1
  88. package/build-types/date-time/date/styles.d.ts.map +1 -1
  89. package/build-types/focal-point-picker/index.d.ts +1 -1
  90. package/build-types/focal-point-picker/index.d.ts.map +1 -1
  91. package/build-types/focal-point-picker/styles/focal-point-picker-style.d.ts +253 -0
  92. package/build-types/focal-point-picker/styles/focal-point-picker-style.d.ts.map +1 -1
  93. package/build-types/font-size-picker/font-size-picker-select.d.ts +3 -0
  94. package/build-types/font-size-picker/font-size-picker-select.d.ts.map +1 -1
  95. package/build-types/font-size-picker/font-size-picker-toggle-group.d.ts.map +1 -1
  96. package/build-types/font-size-picker/index.d.ts.map +1 -1
  97. package/build-types/font-size-picker/styles.d.ts +3 -0
  98. package/build-types/font-size-picker/styles.d.ts.map +1 -1
  99. package/build-types/font-size-picker/test/font-size-picker-select.d.ts +2 -0
  100. package/build-types/font-size-picker/test/font-size-picker-select.d.ts.map +1 -0
  101. package/build-types/font-size-picker/test/font-size-picker-toggle-group.d.ts +2 -0
  102. package/build-types/font-size-picker/test/font-size-picker-toggle-group.d.ts.map +1 -0
  103. package/build-types/font-size-picker/types.d.ts +18 -4
  104. package/build-types/font-size-picker/types.d.ts.map +1 -1
  105. package/build-types/font-size-picker/utils.d.ts +10 -1
  106. package/build-types/font-size-picker/utils.d.ts.map +1 -1
  107. package/build-types/popover/index.d.ts.map +1 -1
  108. package/build-types/utils/base-label.d.ts.map +1 -1
  109. package/build-types/utils/config-values.d.ts +1 -0
  110. package/package.json +20 -20
  111. package/src/badge/styles.scss +1 -0
  112. package/src/button/style.scss +4 -1
  113. package/src/card/types.ts +3 -9
  114. package/src/color-palette/styles.ts +2 -1
  115. package/src/combobox-control/index.tsx +1 -4
  116. package/src/combobox-control/stories/index.story.tsx +0 -1
  117. package/src/combobox-control/test/index.tsx +20 -7
  118. package/src/custom-select-control-v2/custom-select.tsx +1 -1
  119. package/src/date-time/date/styles.ts +1 -0
  120. package/src/dimension-control/test/__snapshots__/index.test.js.snap +4 -4
  121. package/src/dropdown-menu/style.scss +1 -0
  122. package/src/focal-point-picker/index.tsx +26 -16
  123. package/src/focal-point-picker/styles/focal-point-picker-style.ts +11 -1
  124. package/src/font-size-picker/README.md +10 -0
  125. package/src/font-size-picker/font-size-picker-select.tsx +44 -11
  126. package/src/font-size-picker/font-size-picker-toggle-group.tsx +58 -4
  127. package/src/font-size-picker/index.tsx +44 -19
  128. package/src/font-size-picker/styles.ts +9 -0
  129. package/src/font-size-picker/test/font-size-picker-select.tsx +221 -0
  130. package/src/font-size-picker/test/font-size-picker-toggle-group.tsx +275 -0
  131. package/src/font-size-picker/test/index.tsx +460 -2
  132. package/src/font-size-picker/types.ts +24 -4
  133. package/src/font-size-picker/utils.ts +23 -1
  134. package/src/menu-group/style.scss +1 -1
  135. package/src/menu-item/style.scss +1 -0
  136. package/src/modal/style.scss +1 -1
  137. package/src/palette-edit/styles.ts +1 -1
  138. package/src/panel/style.scss +1 -1
  139. package/src/popover/index.tsx +23 -2
  140. package/src/tab-panel/style.scss +1 -1
  141. package/src/tabs/styles.ts +1 -1
  142. package/src/toggle-group-control/test/__snapshots__/index.tsx.snap +4 -4
  143. package/src/tools-panel/styles.ts +2 -2
  144. package/src/utils/base-label.ts +6 -1
  145. package/src/utils/config-values.js +1 -0
  146. package/src/validated-form-controls/style.scss +1 -1
  147. package/tsconfig.tsbuildinfo +1 -1
@@ -145,7 +145,7 @@ describe( 'FontSizePicker', () => {
145
145
  {
146
146
  option: 'Default',
147
147
  value: '8px',
148
- expectedArguments: [ undefined ],
148
+ expectedArguments: [ undefined, undefined ],
149
149
  },
150
150
  {
151
151
  option: 'Tiny 8px',
@@ -249,7 +249,7 @@ describe( 'FontSizePicker', () => {
249
249
  {
250
250
  option: 'Default',
251
251
  value: '8px',
252
- expectedArguments: [ undefined ],
252
+ expectedArguments: [ undefined, undefined ],
253
253
  },
254
254
  {
255
255
  option: 'Tiny 8px',
@@ -358,6 +358,98 @@ describe( 'FontSizePicker', () => {
358
358
 
359
359
  commonToggleGroupTests( fontSizes );
360
360
  commonTests( fontSizes );
361
+
362
+ describe( 'valueMode prop for toggle group', () => {
363
+ it( 'should find font size by size value when valueMode is literal', async () => {
364
+ await render(
365
+ <FontSizePicker
366
+ fontSizes={ fontSizes }
367
+ value="16px"
368
+ valueMode="literal"
369
+ />
370
+ );
371
+ // Should select the medium option (16px)
372
+ expect(
373
+ screen.getByRole( 'radio', { checked: true } )
374
+ ).toHaveAccessibleName( 'Medium' );
375
+ } );
376
+
377
+ it( 'should find font size by slug when valueMode is slug', async () => {
378
+ await render(
379
+ <FontSizePicker
380
+ fontSizes={ fontSizes }
381
+ value="medium"
382
+ valueMode="slug"
383
+ />
384
+ );
385
+ // Should select the medium option
386
+ expect(
387
+ screen.getByRole( 'radio', { checked: true } )
388
+ ).toHaveAccessibleName( 'Medium' );
389
+ } );
390
+
391
+ it( 'should handle multiple font sizes with same value in literal mode', async () => {
392
+ const fontSizesWithDuplicates = [
393
+ {
394
+ slug: 'small-1',
395
+ name: 'Small 1',
396
+ size: '12px',
397
+ },
398
+ {
399
+ slug: 'small-2',
400
+ name: 'Small 2',
401
+ size: '12px',
402
+ },
403
+ {
404
+ slug: 'medium',
405
+ name: 'Medium',
406
+ size: '16px',
407
+ },
408
+ ];
409
+ await render(
410
+ <FontSizePicker
411
+ fontSizes={ fontSizesWithDuplicates }
412
+ value="12px"
413
+ valueMode="literal"
414
+ />
415
+ );
416
+ // Should have no selection when there are multiple matches
417
+ expect(
418
+ screen.queryByRole( 'radio', { checked: true } )
419
+ ).not.toBeInTheDocument();
420
+ } );
421
+
422
+ it( 'should handle multiple font sizes with same value in slug mode', async () => {
423
+ const fontSizesWithDuplicates = [
424
+ {
425
+ slug: 'small-1',
426
+ name: 'Small 1',
427
+ size: '12px',
428
+ },
429
+ {
430
+ slug: 'small-2',
431
+ name: 'Small 2',
432
+ size: '12px',
433
+ },
434
+ {
435
+ slug: 'medium',
436
+ name: 'Medium',
437
+ size: '16px',
438
+ },
439
+ ];
440
+ await render(
441
+ <FontSizePicker
442
+ fontSizes={ fontSizesWithDuplicates }
443
+ value="small-1"
444
+ valueMode="slug"
445
+ />
446
+ );
447
+ // Should select the specific font size by slug
448
+ expect(
449
+ screen.getByRole( 'radio', { checked: true } )
450
+ ).toHaveAccessibleName( 'Small 1' );
451
+ } );
452
+ } );
361
453
  } );
362
454
 
363
455
  describe( 'with ≤ 5 heterogeneous font sizes', () => {
@@ -430,6 +522,64 @@ describe( 'FontSizePicker', () => {
430
522
 
431
523
  commonToggleGroupTests( fontSizes );
432
524
  commonTests( fontSizes );
525
+
526
+ describe( 'valueMode prop for heterogeneous toggle group', () => {
527
+ it( 'should find font size by size value when valueMode is literal', async () => {
528
+ await render(
529
+ <FontSizePicker
530
+ fontSizes={ fontSizes }
531
+ value="1em"
532
+ valueMode="literal"
533
+ />
534
+ );
535
+ // Should select the medium option (1em)
536
+ expect(
537
+ screen.getByRole( 'radio', { checked: true } )
538
+ ).toHaveAccessibleName( 'Medium' );
539
+ } );
540
+
541
+ it( 'should find font size by slug when valueMode is slug', async () => {
542
+ await render(
543
+ <FontSizePicker
544
+ fontSizes={ fontSizes }
545
+ value="medium"
546
+ valueMode="slug"
547
+ />
548
+ );
549
+ // Should select the medium option
550
+ expect(
551
+ screen.getByRole( 'radio', { checked: true } )
552
+ ).toHaveAccessibleName( 'Medium' );
553
+ } );
554
+
555
+ it( 'should handle complex font size values in literal mode', async () => {
556
+ await render(
557
+ <FontSizePicker
558
+ fontSizes={ fontSizes }
559
+ value="clamp(1.75rem, 3vw, 2.25rem)"
560
+ valueMode="literal"
561
+ />
562
+ );
563
+ // Should select the extra large option
564
+ expect(
565
+ screen.getByRole( 'radio', { checked: true } )
566
+ ).toHaveAccessibleName( 'Extra Large' );
567
+ } );
568
+
569
+ it( 'should handle complex font size values in slug mode', async () => {
570
+ await render(
571
+ <FontSizePicker
572
+ fontSizes={ fontSizes }
573
+ value="x-large"
574
+ valueMode="slug"
575
+ />
576
+ );
577
+ // Should select the extra large option
578
+ expect(
579
+ screen.getByRole( 'radio', { checked: true } )
580
+ ).toHaveAccessibleName( 'Extra Large' );
581
+ } );
582
+ } );
433
583
  } );
434
584
 
435
585
  function commonToggleGroupTests( fontSizes: FontSize[] ) {
@@ -653,4 +803,312 @@ describe( 'FontSizePicker', () => {
653
803
  expect( units[ 2 ] ).toHaveAccessibleName( 'ex' );
654
804
  } );
655
805
  }
806
+
807
+ describe( 'valueMode prop', () => {
808
+ // Use 6 font sizes to trigger select control (> 5)
809
+ const fontSizes = [
810
+ {
811
+ slug: 'small',
812
+ name: 'Small',
813
+ size: '12px',
814
+ },
815
+ {
816
+ slug: 'medium',
817
+ name: 'Medium',
818
+ size: '16px',
819
+ },
820
+ {
821
+ slug: 'large',
822
+ name: 'Large',
823
+ size: '20px',
824
+ },
825
+ {
826
+ slug: 'x-large',
827
+ name: 'Extra Large',
828
+ size: '24px',
829
+ },
830
+ {
831
+ slug: 'xx-large',
832
+ name: 'XX Large',
833
+ size: '28px',
834
+ },
835
+ {
836
+ slug: 'huge',
837
+ name: 'Huge',
838
+ size: '32px',
839
+ },
840
+ ];
841
+
842
+ describe( 'valueMode="literal" (default)', () => {
843
+ it( 'should find font size by size value when valueMode is literal', async () => {
844
+ await render(
845
+ <FontSizePicker
846
+ fontSizes={ fontSizes }
847
+ value="16px"
848
+ valueMode="literal"
849
+ />
850
+ );
851
+ // Should select the medium option (16px)
852
+ expect(
853
+ screen.getByRole( 'combobox', { name: 'Font size' } )
854
+ ).toHaveTextContent( 'Medium' );
855
+ } );
856
+
857
+ it( 'should call onChange with size value and FontSize object when valueMode is literal', async () => {
858
+ const user = userEvent.setup();
859
+ const onChange = jest.fn();
860
+ await render(
861
+ <FontSizePicker
862
+ fontSizes={ fontSizes }
863
+ onChange={ onChange }
864
+ valueMode="literal"
865
+ />
866
+ );
867
+ await user.click(
868
+ screen.getByRole( 'combobox', { name: 'Font size' } )
869
+ );
870
+ await user.click(
871
+ screen.getByRole( 'option', { name: 'Medium 16px' } )
872
+ );
873
+ expect( onChange ).toHaveBeenCalledWith(
874
+ '16px',
875
+ fontSizes[ 1 ]
876
+ );
877
+ } );
878
+ } );
879
+
880
+ describe( 'valueMode="slug"', () => {
881
+ it( 'should find font size by slug when valueMode is slug', async () => {
882
+ await render(
883
+ <FontSizePicker
884
+ fontSizes={ fontSizes }
885
+ value="medium"
886
+ valueMode="slug"
887
+ />
888
+ );
889
+ // Should select the medium option
890
+ expect(
891
+ screen.getByRole( 'combobox', { name: 'Font size' } )
892
+ ).toHaveTextContent( 'Medium' );
893
+ } );
894
+
895
+ it( 'should call onChange with size value and FontSize object when valueMode is slug', async () => {
896
+ const user = userEvent.setup();
897
+ const onChange = jest.fn();
898
+ await render(
899
+ <FontSizePicker
900
+ fontSizes={ fontSizes }
901
+ onChange={ onChange }
902
+ valueMode="slug"
903
+ />
904
+ );
905
+ await user.click(
906
+ screen.getByRole( 'combobox', { name: 'Font size' } )
907
+ );
908
+ await user.click(
909
+ screen.getByRole( 'option', { name: 'Large 20px' } )
910
+ );
911
+ expect( onChange ).toHaveBeenCalledWith(
912
+ '20px',
913
+ fontSizes[ 2 ]
914
+ );
915
+ } );
916
+
917
+ it( 'should handle undefined value when valueMode is slug', async () => {
918
+ await render(
919
+ <FontSizePicker
920
+ fontSizes={ fontSizes }
921
+ value={ undefined }
922
+ valueMode="slug"
923
+ />
924
+ );
925
+ // Should show default option
926
+ expect(
927
+ screen.getByRole( 'combobox', { name: 'Font size' } )
928
+ ).toHaveTextContent( 'Default' );
929
+ } );
930
+
931
+ it( 'should handle empty string value when valueMode is slug', async () => {
932
+ await render(
933
+ <FontSizePicker
934
+ fontSizes={ fontSizes }
935
+ value=""
936
+ valueMode="slug"
937
+ />
938
+ );
939
+ // Should show default option
940
+ expect(
941
+ screen.getByRole( 'combobox', { name: 'Font size' } )
942
+ ).toHaveTextContent( 'Default' );
943
+ } );
944
+ } );
945
+
946
+ describe( 'edge cases with valueMode', () => {
947
+ // Use 6 font sizes to trigger select control (> 5)
948
+ const fontSizesWithDuplicates = [
949
+ {
950
+ slug: 'small-1',
951
+ name: 'Small 1',
952
+ size: '12px',
953
+ },
954
+ {
955
+ slug: 'small-2',
956
+ name: 'Small 2',
957
+ size: '12px',
958
+ },
959
+ {
960
+ slug: 'medium',
961
+ name: 'Medium',
962
+ size: '16px',
963
+ },
964
+ {
965
+ slug: 'large',
966
+ name: 'Large',
967
+ size: '20px',
968
+ },
969
+ {
970
+ slug: 'x-large',
971
+ name: 'Extra Large',
972
+ size: '24px',
973
+ },
974
+ {
975
+ slug: 'huge',
976
+ name: 'Huge',
977
+ size: '28px',
978
+ },
979
+ ];
980
+
981
+ it( 'should handle multiple font sizes with same value in literal mode', async () => {
982
+ await render(
983
+ <FontSizePicker
984
+ fontSizes={ fontSizesWithDuplicates }
985
+ value="12px"
986
+ valueMode="literal"
987
+ />
988
+ );
989
+ // Should show the first matching font size when there are multiple matches
990
+ expect(
991
+ screen.getByRole( 'combobox', { name: 'Font size' } )
992
+ ).toHaveTextContent( 'Small 1' );
993
+ } );
994
+
995
+ it( 'should handle multiple font sizes with same value in slug mode', async () => {
996
+ await render(
997
+ <FontSizePicker
998
+ fontSizes={ fontSizesWithDuplicates }
999
+ value="small-1"
1000
+ valueMode="slug"
1001
+ />
1002
+ );
1003
+ // Should select the specific font size by slug
1004
+ expect(
1005
+ screen.getByRole( 'combobox', { name: 'Font size' } )
1006
+ ).toHaveTextContent( 'Small 1' );
1007
+ } );
1008
+ } );
1009
+ } );
1010
+
1011
+ describe( 'onChange callback signature', () => {
1012
+ // Use 6 font sizes to trigger select control (> 5)
1013
+ const fontSizes = [
1014
+ {
1015
+ slug: 'small',
1016
+ name: 'Small',
1017
+ size: '12px',
1018
+ },
1019
+ {
1020
+ slug: 'medium',
1021
+ name: 'Medium',
1022
+ size: '16px',
1023
+ },
1024
+ {
1025
+ slug: 'large',
1026
+ name: 'Large',
1027
+ size: '20px',
1028
+ },
1029
+ {
1030
+ slug: 'x-large',
1031
+ name: 'Extra Large',
1032
+ size: '24px',
1033
+ },
1034
+ {
1035
+ slug: 'xx-large',
1036
+ name: 'XX Large',
1037
+ size: '28px',
1038
+ },
1039
+ {
1040
+ slug: 'huge',
1041
+ name: 'Huge',
1042
+ size: '32px',
1043
+ },
1044
+ ];
1045
+
1046
+ it( 'should call onChange with FontSize object as second parameter for select control', async () => {
1047
+ const user = userEvent.setup();
1048
+ const onChange = jest.fn();
1049
+ await render(
1050
+ <FontSizePicker fontSizes={ fontSizes } onChange={ onChange } />
1051
+ );
1052
+ await user.click(
1053
+ screen.getByRole( 'combobox', { name: 'Font size' } )
1054
+ );
1055
+ await user.click(
1056
+ screen.getByRole( 'option', { name: 'Small 12px' } )
1057
+ );
1058
+ expect( onChange ).toHaveBeenCalledWith( '12px', fontSizes[ 0 ] );
1059
+ } );
1060
+
1061
+ it( 'should call onChange with undefined as second parameter for default option', async () => {
1062
+ const user = userEvent.setup();
1063
+ const onChange = jest.fn();
1064
+ await render(
1065
+ <FontSizePicker
1066
+ fontSizes={ fontSizes }
1067
+ value="16px" // Start with a selected value
1068
+ onChange={ onChange }
1069
+ />
1070
+ );
1071
+ await user.click(
1072
+ screen.getByRole( 'combobox', { name: 'Font size' } )
1073
+ );
1074
+ await user.click(
1075
+ screen.getByRole( 'option', { name: 'Default' } )
1076
+ );
1077
+ expect( onChange ).toHaveBeenCalledWith( undefined, undefined );
1078
+ } );
1079
+
1080
+ it( 'should call onChange with FontSize object as second parameter for toggle group control', async () => {
1081
+ const user = userEvent.setup();
1082
+ const onChange = jest.fn();
1083
+ // Use fewer font sizes to trigger toggle group (≤ 5)
1084
+ const toggleGroupFontSizes = [
1085
+ {
1086
+ slug: 'small',
1087
+ name: 'Small',
1088
+ size: '12px',
1089
+ },
1090
+ {
1091
+ slug: 'medium',
1092
+ name: 'Medium',
1093
+ size: '16px',
1094
+ },
1095
+ {
1096
+ slug: 'large',
1097
+ name: 'Large',
1098
+ size: '20px',
1099
+ },
1100
+ ];
1101
+ await render(
1102
+ <FontSizePicker
1103
+ fontSizes={ toggleGroupFontSizes }
1104
+ onChange={ onChange }
1105
+ />
1106
+ );
1107
+ await user.click( screen.getByRole( 'radio', { name: 'Small' } ) );
1108
+ expect( onChange ).toHaveBeenCalledWith(
1109
+ '12px',
1110
+ toggleGroupFontSizes[ 0 ]
1111
+ );
1112
+ } );
1113
+ } );
656
1114
  } );
@@ -40,6 +40,14 @@ export type FontSizePickerProps = {
40
40
  * as a number, the component operates in "unitless mode" where the `units` property has no effect.
41
41
  */
42
42
  value?: number | string;
43
+ /**
44
+ * Determines how the `value` prop should be interpreted.
45
+ * - `'literal'`: The `value` prop contains the actual font size value (number or string)
46
+ * - `'slug'`: The `value` prop contains the slug of the selected font size
47
+ *
48
+ * @default 'literal'
49
+ */
50
+ valueMode?: 'literal' | 'slug';
43
51
  /**
44
52
  * If `true`, a slider will be displayed alongside the input field when a
45
53
  * custom font size is active. Has no effect when `disableCustomFontSizes`
@@ -96,17 +104,26 @@ export type FontSize = {
96
104
  * size. Used for the class generation process.
97
105
  */
98
106
  slug: string;
107
+ /**
108
+ * The `hint` property is an optional string that provides additional information
109
+ * about the font size, such as fluid typography ranges or custom descriptions.
110
+ * Consumers can use this to provide their own preferred hints.
111
+ */
112
+ hint?: string;
99
113
  };
100
114
 
101
115
  export type FontSizePickerSelectProps = Pick<
102
116
  FontSizePickerProps,
103
- 'value' | 'size'
117
+ 'value' | 'size' | 'valueMode'
104
118
  > & {
105
119
  fontSizes: NonNullable< FontSizePickerProps[ 'fontSizes' ] >;
106
120
  disableCustomFontSizes: NonNullable<
107
121
  FontSizePickerProps[ 'disableCustomFontSizes' ]
108
122
  >;
109
- onChange: NonNullable< FontSizePickerProps[ 'onChange' ] >;
123
+ onChange: (
124
+ value: number | string | undefined,
125
+ selectedItem?: FontSize
126
+ ) => void;
110
127
  onSelectCustom: () => void;
111
128
  __next40pxDefaultSize: boolean;
112
129
  };
@@ -120,8 +137,11 @@ export type FontSizePickerSelectOption = {
120
137
 
121
138
  export type FontSizePickerToggleGroupProps = Pick<
122
139
  FontSizePickerProps,
123
- 'value' | 'size' | '__next40pxDefaultSize'
140
+ 'value' | 'size' | '__next40pxDefaultSize' | 'valueMode'
124
141
  > & {
125
142
  fontSizes: NonNullable< FontSizePickerProps[ 'fontSizes' ] >;
126
- onChange: NonNullable< FontSizePickerProps[ 'onChange' ] >;
143
+ onChange: (
144
+ value: number | string | undefined,
145
+ selectedItem?: FontSize
146
+ ) => void;
127
147
  };
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Internal dependencies
3
3
  */
4
- import type { FontSizePickerProps } from './types';
4
+ import type { FontSizePickerProps, FontSize } from './types';
5
5
 
6
6
  /**
7
7
  * Some themes use css vars for their font sizes, so until we
@@ -17,3 +17,25 @@ export function isSimpleCssValue(
17
17
  /^[\d\.]+(px|em|rem|vw|vh|%|svw|lvw|dvw|svh|lvh|dvh|vi|svi|lvi|dvi|vb|svb|lvb|dvb|vmin|svmin|lvmin|dvmin|vmax|svmax|lvmax|dvmax)?$/i;
18
18
  return sizeRegex.test( String( value ) );
19
19
  }
20
+
21
+ /**
22
+ * Generates hint text for a font size.
23
+ * This function returns the hint provided by the consumer, if any.
24
+ * If no hint is provided, it falls back to showing the size value for simple CSS values.
25
+ *
26
+ * @param fontSize The font size object to generate hint text for.
27
+ * @return The hint text provided by the consumer, or the size value for simple CSS values, or undefined.
28
+ */
29
+ export function generateFontSizeHint( fontSize: FontSize ): string | undefined {
30
+ // If the font size already has a hint, use it
31
+ if ( fontSize.hint ) {
32
+ return fontSize.hint;
33
+ }
34
+
35
+ // Fallback to showing the size value if it's a simple CSS value
36
+ if ( isSimpleCssValue( fontSize.size ) ) {
37
+ return String( fontSize.size );
38
+ }
39
+
40
+ return undefined;
41
+ }
@@ -23,6 +23,6 @@
23
23
  color: $gray-700;
24
24
  text-transform: uppercase;
25
25
  font-size: 11px;
26
- font-weight: 500;
26
+ font-weight: $font-weight-medium;
27
27
  white-space: nowrap;
28
28
  }
@@ -6,6 +6,7 @@
6
6
  .components-menu-item__button,
7
7
  .components-menu-item__button.components-button {
8
8
  width: 100%;
9
+ font-weight: $font-weight-regular;
9
10
 
10
11
  &[role="menuitemradio"],
11
12
  &[role="menuitemcheckbox"] {
@@ -151,7 +151,7 @@
151
151
  left: 0;
152
152
 
153
153
  .components-modal__header-heading {
154
- font-size: 1.2rem;
154
+ font-size: $font-size-x-large;
155
155
  font-weight: 600;
156
156
  }
157
157
 
@@ -52,7 +52,7 @@ export const NameContainer = styled.div`
52
52
  export const PaletteHeading = styled( Heading )`
53
53
  text-transform: uppercase;
54
54
  line-height: ${ space( 6 ) };
55
- font-weight: 500;
55
+ font-weight: ${ CONFIG.fontWeightMedium };
56
56
  &&& {
57
57
  font-size: 11px;
58
58
  margin-bottom: 0;
@@ -89,7 +89,7 @@
89
89
  padding: $grid-unit-20 $grid-unit-60 $grid-unit-20 $grid-unit-20;
90
90
  outline: none;
91
91
  width: 100%;
92
- font-weight: 500;
92
+ font-weight: $font-weight-medium;
93
93
  text-align: left;
94
94
  color: $gray-900;
95
95
  border: none;
@@ -268,9 +268,30 @@ const UnforwardedPopover = (
268
268
  onDialogClose = ( type: string | undefined, event: SyntheticEvent ) => {
269
269
  // Ideally the popover should have just a single onClose prop and
270
270
  // not three props that potentially do the same thing.
271
- if ( type === 'focus-outside' && onFocusOutside ) {
272
- onFocusOutside( event );
271
+ if ( type === 'focus-outside' ) {
272
+ // Check if this blur event is actually relevant to this popover
273
+ const blurTarget = event?.target as Element;
274
+ const referenceElement = refs.reference.current;
275
+ const floatingElement = refs.floating.current;
276
+
277
+ // Check if blur is from this popover's reference element or its floating content
278
+ const isBlurFromThisPopover =
279
+ ( referenceElement &&
280
+ 'contains' in referenceElement &&
281
+ referenceElement.contains( blurTarget ) ) ||
282
+ floatingElement?.contains( blurTarget );
283
+ // Only proceed if the blur is actually from this popover
284
+ if ( ! isBlurFromThisPopover ) {
285
+ return;
286
+ }
287
+ // Call onFocusOutside if defined or call onClose.
288
+ if ( onFocusOutside ) {
289
+ onFocusOutside( event );
290
+ } else if ( onClose ) {
291
+ onClose();
292
+ }
273
293
  } else if ( onClose ) {
294
+ // onClose should be called for other event types if it exists.
274
295
  onClose();
275
296
  }
276
297
  };
@@ -21,7 +21,7 @@
21
21
  cursor: pointer;
22
22
  padding: 3px $grid-unit-20; // Use padding to offset the is-active border, this benefits Windows High Contrast mode
23
23
  margin-left: 0;
24
- font-weight: 500;
24
+ font-weight: $font-weight-regular;
25
25
 
26
26
  &:focus:not(:disabled) {
27
27
  position: relative;
@@ -58,7 +58,7 @@ export const StyledTabList = styled( Ariakit.TabList )`
58
58
  when scaling in the transform, see: https://stackoverflow.com/a/52159123 */
59
59
  --antialiasing-factor: 100;
60
60
  &[aria-orientation='horizontal'] {
61
- --fade-width: 4rem;
61
+ --fade-width: 64px;
62
62
  --fade-gradient-base: transparent 0%, black var( --fade-width );
63
63
  --fade-gradient-composed: var( --fade-gradient-base ), black 60%,
64
64
  transparent 50%;