diff --git a/packages/edit-site/src/components/global-styles/font-sizes/confirm-delete-font-size-dialog.js b/packages/edit-site/src/components/global-styles/font-sizes/confirm-delete-font-size-dialog.js
new file mode 100644
index 00000000000000..69d561ee0232a8
--- /dev/null
+++ b/packages/edit-site/src/components/global-styles/font-sizes/confirm-delete-font-size-dialog.js
@@ -0,0 +1,43 @@
+/**
+ * WordPress dependencies
+ */
+import { __experimentalConfirmDialog as ConfirmDialog } from '@wordpress/components';
+import { __, sprintf } from '@wordpress/i18n';
+
+function ConfirmDeleteFontSizeDialog( {
+ fontSize,
+ isOpen,
+ toggleOpen,
+ handleRemoveFontSize,
+} ) {
+ const handleConfirm = async () => {
+ toggleOpen();
+ handleRemoveFontSize( fontSize );
+ };
+
+ const handleCancel = () => {
+ toggleOpen();
+ };
+
+ return (
+
+ { fontSize &&
+ sprintf(
+ /* translators: %s: Name of the font size preset. */
+ __(
+ 'Are you sure you want to delete "%s" font size preset?'
+ ),
+ fontSize.name
+ ) }
+
+ );
+}
+
+export default ConfirmDeleteFontSizeDialog;
diff --git a/packages/edit-site/src/components/global-styles/font-sizes/confirm-reset-font-sizes-dialog.js b/packages/edit-site/src/components/global-styles/font-sizes/confirm-reset-font-sizes-dialog.js
new file mode 100644
index 00000000000000..fb1bd51ba1479a
--- /dev/null
+++ b/packages/edit-site/src/components/global-styles/font-sizes/confirm-reset-font-sizes-dialog.js
@@ -0,0 +1,37 @@
+/**
+ * WordPress dependencies
+ */
+import { __experimentalConfirmDialog as ConfirmDialog } from '@wordpress/components';
+import { __ } from '@wordpress/i18n';
+
+function ConfirmResetFontSizesDialog( {
+ text,
+ confirmButtonText,
+ isOpen,
+ toggleOpen,
+ onConfirm,
+} ) {
+ const handleConfirm = async () => {
+ toggleOpen();
+ onConfirm();
+ };
+
+ const handleCancel = () => {
+ toggleOpen();
+ };
+
+ return (
+
+ { text }
+
+ );
+}
+
+export default ConfirmResetFontSizesDialog;
diff --git a/packages/edit-site/src/components/global-styles/font-sizes/font-size-preview.js b/packages/edit-site/src/components/global-styles/font-sizes/font-size-preview.js
new file mode 100644
index 00000000000000..e49ed0249e65a4
--- /dev/null
+++ b/packages/edit-site/src/components/global-styles/font-sizes/font-size-preview.js
@@ -0,0 +1,43 @@
+/**
+ * WordPress dependencies
+ */
+import {
+ getComputedFluidTypographyValue,
+ privateApis as blockEditorPrivateApis,
+} from '@wordpress/block-editor';
+import { __ } from '@wordpress/i18n';
+
+/**
+ * Internal dependencies
+ */
+import { unlock } from '../../../lock-unlock';
+const { useGlobalStyle } = unlock( blockEditorPrivateApis );
+
+function FontSizePreview( { fontSize } ) {
+ const [ font ] = useGlobalStyle( 'typography' );
+
+ const input =
+ fontSize?.fluid?.min && fontSize?.fluid?.max
+ ? {
+ minimumFontSize: fontSize.fluid.min,
+ maximumFontSize: fontSize.fluid.max,
+ }
+ : {
+ fontSize: fontSize.size,
+ };
+
+ const computedFontSize = getComputedFluidTypographyValue( input );
+ return (
+
+ { __( 'Aa' ) }
+
+ );
+}
+
+export default FontSizePreview;
diff --git a/packages/edit-site/src/components/global-styles/font-sizes/font-size.js b/packages/edit-site/src/components/global-styles/font-sizes/font-size.js
new file mode 100644
index 00000000000000..8eb6bc20bd407d
--- /dev/null
+++ b/packages/edit-site/src/components/global-styles/font-sizes/font-size.js
@@ -0,0 +1,250 @@
+/**
+ * WordPress dependencies
+ */
+import { privateApis as blockEditorPrivateApis } from '@wordpress/block-editor';
+import { __, sprintf } from '@wordpress/i18n';
+import {
+ __experimentalSpacer as Spacer,
+ __experimentalUseNavigator as useNavigator,
+ __experimentalView as View,
+ __experimentalHStack as HStack,
+ __experimentalVStack as VStack,
+ privateApis as componentsPrivateApis,
+ Button,
+ FlexItem,
+ ToggleControl,
+} from '@wordpress/components';
+import { moreVertical } from '@wordpress/icons';
+import { useState } from '@wordpress/element';
+
+/**
+ * Internal dependencies
+ */
+import { unlock } from '../../../lock-unlock';
+const {
+ DropdownMenuV2: DropdownMenu,
+ DropdownMenuItemV2: DropdownMenuItem,
+ DropdownMenuItemLabelV2: DropdownMenuItemLabel,
+} = unlock( componentsPrivateApis );
+const { useGlobalSetting } = unlock( blockEditorPrivateApis );
+import ScreenHeader from '../header';
+import FontSizePreview from './font-size-preview';
+import ConfirmDeleteFontSizeDialog from './confirm-delete-font-size-dialog';
+import RenameFontSizeDialog from './rename-font-size-dialog';
+import SizeControl from '../size-control';
+
+function FontSize() {
+ const [ isDeleteConfirmOpen, setIsDeleteConfirmOpen ] = useState( false );
+ const [ isRenameDialogOpen, setIsRenameDialogOpen ] = useState( false );
+
+ const {
+ params: { origin, slug },
+ goBack,
+ goTo,
+ } = useNavigator();
+
+ const [ fontSizes, setFontSizes ] = useGlobalSetting(
+ 'typography.fontSizes'
+ );
+
+ // Get the font sizes from the origin, default to empty array.
+ const sizes = fontSizes[ origin ] ?? [];
+
+ // Get the font size by slug.
+ const fontSize = sizes.find( ( size ) => size.slug === slug );
+
+ // Whether fluid is true or an object, set it to true, otherwise false.
+ const isFluid = !! fontSize.fluid ?? false;
+
+ // Whether custom fluid values are used.
+ const isCustomFluid = typeof fontSize.fluid === 'object';
+
+ const handleNameChange = ( value ) => {
+ updateFontSize( 'name', value );
+ };
+
+ const handleFontSizeChange = ( value ) => {
+ updateFontSize( 'size', value );
+ };
+
+ const handleFluidChange = ( value ) => {
+ updateFontSize( 'fluid', value );
+ };
+
+ const handleCustomFluidValues = ( value ) => {
+ if ( value ) {
+ // If custom values are used, init the values with the current ones.
+ updateFontSize( 'fluid', {
+ min: fontSize.size,
+ max: fontSize.size,
+ } );
+ } else {
+ // If custom fluid values are disabled, set fluid to true.
+ updateFontSize( 'fluid', true );
+ }
+ };
+
+ const handleMinChange = ( value ) => {
+ updateFontSize( 'fluid', { ...fontSize.fluid, min: value } );
+ };
+
+ const handleMaxChange = ( value ) => {
+ updateFontSize( 'fluid', { ...fontSize.fluid, max: value } );
+ };
+
+ const updateFontSize = ( key, value ) => {
+ const newFontSizes = sizes.map( ( size ) => {
+ if ( size.slug === slug ) {
+ return { ...size, [ key ]: value }; // Create a new object with updated key
+ }
+ return size;
+ } );
+
+ setFontSizes( {
+ ...fontSizes,
+ [ origin ]: newFontSizes,
+ } );
+ };
+
+ const handleRemoveFontSize = () => {
+ // Navigate to the font sizes list.
+ goBack();
+
+ const newFontSizes = sizes.filter( ( size ) => size.slug !== slug );
+ setFontSizes( {
+ ...fontSizes,
+ [ origin ]: newFontSizes,
+ } );
+ };
+
+ const toggleDeleteConfirm = () => {
+ setIsDeleteConfirmOpen( ! isDeleteConfirmOpen );
+ };
+
+ const toggleRenameDialog = () => {
+ setIsRenameDialogOpen( ! isRenameDialogOpen );
+ };
+
+ return (
+ <>
+
+
+ { isRenameDialogOpen && (
+
+ ) }
+
+
+
+ goTo( '/typography/font-sizes/' ) }
+ />
+ { origin === 'custom' && (
+
+
+
+ }
+ >
+
+
+ { __( 'Rename' ) }
+
+
+
+
+ { __( 'Delete' ) }
+
+
+
+
+
+ ) }
+
+
+
+
+
+
+
+
+
+
+
+
+
+ { isFluid && (
+
+ ) }
+
+ { isCustomFluid && (
+ <>
+
+
+ >
+ ) }
+
+
+
+
+ >
+ );
+}
+
+export default FontSize;
diff --git a/packages/edit-site/src/components/global-styles/font-sizes/font-sizes-count.js b/packages/edit-site/src/components/global-styles/font-sizes/font-sizes-count.js
new file mode 100644
index 00000000000000..8c8c79772ecbf3
--- /dev/null
+++ b/packages/edit-site/src/components/global-styles/font-sizes/font-sizes-count.js
@@ -0,0 +1,40 @@
+/**
+ * WordPress dependencies
+ */
+import { __, isRTL } from '@wordpress/i18n';
+import {
+ __experimentalItemGroup as ItemGroup,
+ __experimentalVStack as VStack,
+ __experimentalHStack as HStack,
+ FlexItem,
+} from '@wordpress/components';
+import { Icon, chevronLeft, chevronRight } from '@wordpress/icons';
+
+/**
+ * Internal dependencies
+ */
+import Subtitle from '../subtitle';
+import { NavigationButtonAsItem } from '../navigation-button';
+
+function FontSizes() {
+ return (
+
+
+ { __( 'Font Sizes' ) }
+
+
+
+
+ { __( 'Font size presets' ) }
+
+
+
+
+
+ );
+}
+
+export default FontSizes;
diff --git a/packages/edit-site/src/components/global-styles/font-sizes/font-sizes.js b/packages/edit-site/src/components/global-styles/font-sizes/font-sizes.js
new file mode 100644
index 00000000000000..450d0797139f85
--- /dev/null
+++ b/packages/edit-site/src/components/global-styles/font-sizes/font-sizes.js
@@ -0,0 +1,263 @@
+/**
+ * WordPress dependencies
+ */
+import { privateApis as blockEditorPrivateApis } from '@wordpress/block-editor';
+import { __, sprintf, isRTL } from '@wordpress/i18n';
+import {
+ privateApis as componentsPrivateApis,
+ __experimentalSpacer as Spacer,
+ __experimentalView as View,
+ __experimentalItemGroup as ItemGroup,
+ __experimentalVStack as VStack,
+ __experimentalHStack as HStack,
+ FlexItem,
+ FlexBlock,
+ Button,
+} from '@wordpress/components';
+import {
+ Icon,
+ plus,
+ moreVertical,
+ chevronLeft,
+ chevronRight,
+} from '@wordpress/icons';
+import { useState } from '@wordpress/element';
+
+/**
+ * Internal dependencies
+ */
+import { unlock } from '../../../lock-unlock';
+const {
+ DropdownMenuV2: DropdownMenu,
+ DropdownMenuItemV2: DropdownMenuItem,
+ DropdownMenuItemLabelV2: DropdownMenuItemLabel,
+} = unlock( componentsPrivateApis );
+const { useGlobalSetting } = unlock( blockEditorPrivateApis );
+import Subtitle from '../subtitle';
+import { NavigationButtonAsItem } from '../navigation-button';
+import { getNewIndexFromPresets } from '../utils';
+import ScreenHeader from '../header';
+import ConfirmResetFontSizesDialog from './confirm-reset-font-sizes-dialog';
+
+function FontSizeGroup( {
+ label,
+ origin,
+ sizes,
+ handleAddFontSize,
+ handleResetFontSizes,
+} ) {
+ const [ isResetDialogOpen, setIsResetDialogOpen ] = useState( false );
+
+ const toggleResetDialog = () => setIsResetDialogOpen( ! isResetDialogOpen );
+
+ const resetDialogText =
+ origin === 'custom'
+ ? __(
+ 'Are you sure you want to remove all custom font size presets?'
+ )
+ : __(
+ 'Are you sure you want to reset all font size presets to their default values?'
+ );
+
+ return (
+ <>
+ { isResetDialogOpen && (
+
+ ) }
+
+
+ { label }
+
+ { origin === 'custom' && (
+
+ ) }
+ { !! handleResetFontSizes && (
+
+ }
+ >
+
+
+ { origin === 'custom'
+ ? __( 'Remove font size presets' )
+ : __( 'Reset font size presets' ) }
+
+
+
+ ) }
+
+
+
+ { !! sizes.length && (
+
+ { sizes.map( ( size ) => (
+
+
+
+ { size.name }
+
+
+
+
+ { size.size }
+
+
+
+
+
+
+ ) ) }
+
+ ) }
+
+ >
+ );
+}
+
+function FontSizes() {
+ const [ themeFontSizes, setThemeFontSizes ] = useGlobalSetting(
+ 'typography.fontSizes.theme'
+ );
+
+ const [ baseThemeFontSizes ] = useGlobalSetting(
+ 'typography.fontSizes.theme',
+ null,
+ 'base'
+ );
+ const [ defaultFontSizes, setDefaultFontSizes ] = useGlobalSetting(
+ 'typography.fontSizes.default'
+ );
+
+ const [ baseDefaultFontSizes ] = useGlobalSetting(
+ 'typography.fontSizes.default',
+ null,
+ 'base'
+ );
+
+ const [ customFontSizes = [], setCustomFontSizes ] = useGlobalSetting(
+ 'typography.fontSizes.custom'
+ );
+
+ const [ defaultFontSizesEnabled ] = useGlobalSetting(
+ 'typography.defaultFontSizes'
+ );
+
+ const handleAddFontSize = () => {
+ const index = getNewIndexFromPresets( customFontSizes, 'custom-' );
+ const newFontSize = {
+ /* translators: %d: font size index */
+ name: sprintf( __( 'New Font Size %d' ), index ),
+ size: '16px',
+ slug: `custom-${ index }`,
+ };
+
+ setCustomFontSizes( [ ...customFontSizes, newFontSize ] );
+ };
+
+ const hasSameSizeValues = ( arr1, arr2 ) =>
+ arr1.map( ( item ) => item.size ).join( '' ) ===
+ arr2.map( ( item ) => item.size ).join( '' );
+
+ return (
+
+
+
+
+
+
+ { !! themeFontSizes?.length && (
+
+ setThemeFontSizes(
+ baseThemeFontSizes
+ )
+ }
+ />
+ ) }
+
+ { defaultFontSizesEnabled &&
+ !! defaultFontSizes?.length && (
+
+ setDefaultFontSizes(
+ baseDefaultFontSizes
+ )
+ }
+ />
+ ) }
+
+ 0
+ ? () => setCustomFontSizes( [] )
+ : null
+ }
+ />
+
+
+
+
+ );
+}
+
+export default FontSizes;
diff --git a/packages/edit-site/src/components/global-styles/font-sizes/rename-font-size-dialog.js b/packages/edit-site/src/components/global-styles/font-sizes/rename-font-size-dialog.js
new file mode 100644
index 00000000000000..cb718959c6c27a
--- /dev/null
+++ b/packages/edit-site/src/components/global-styles/font-sizes/rename-font-size-dialog.js
@@ -0,0 +1,70 @@
+/**
+ * WordPress dependencies
+ */
+import {
+ __experimentalInputControl as InputControl,
+ __experimentalVStack as VStack,
+ __experimentalHStack as HStack,
+ Button,
+ Modal,
+} from '@wordpress/components';
+import { __ } from '@wordpress/i18n';
+import { useState } from '@wordpress/element';
+
+function RenameFontSizeDialog( { fontSize, toggleOpen, handleRename } ) {
+ const [ newName, setNewName ] = useState( fontSize.name );
+
+ const handleConfirm = () => {
+ // If the new name is not empty, call the handleRename function
+ if ( newName.trim() ) {
+ handleRename( newName );
+ }
+ toggleOpen();
+ };
+
+ return (
+
+
+
+ );
+}
+
+export default RenameFontSizeDialog;
diff --git a/packages/edit-site/src/components/global-styles/screen-typography.js b/packages/edit-site/src/components/global-styles/screen-typography.js
index 08472325ebc1f8..6d1b69f0ddb5e5 100644
--- a/packages/edit-site/src/components/global-styles/screen-typography.js
+++ b/packages/edit-site/src/components/global-styles/screen-typography.js
@@ -13,6 +13,7 @@ import TypographyElements from './typography-elements';
import TypographyVariations from './variations/variations-typography';
import FontFamilies from './font-families';
import ScreenHeader from './header';
+import FontSizesCount from './font-sizes/font-sizes-count';
function ScreenTypography() {
const fontLibraryEnabled = useSelect(
@@ -35,6 +36,7 @@ function ScreenTypography() {
fontLibraryEnabled && }
+
>
diff --git a/packages/edit-site/src/components/global-styles/size-control/index.js b/packages/edit-site/src/components/global-styles/size-control/index.js
new file mode 100644
index 00000000000000..a7e7bd6127a5fb
--- /dev/null
+++ b/packages/edit-site/src/components/global-styles/size-control/index.js
@@ -0,0 +1,86 @@
+/**
+ * WordPress dependencies
+ */
+import { __ } from '@wordpress/i18n';
+
+/**
+ * Internal dependencies
+ */
+import {
+ BaseControl,
+ RangeControl,
+ Flex,
+ FlexItem,
+ useBaseControlProps,
+ __experimentalUseCustomUnits as useCustomUnits,
+ __experimentalParseQuantityAndUnitFromRawValue as parseQuantityAndUnitFromRawValue,
+ __experimentalUnitControl as UnitControl,
+ __experimentalSpacer as Spacer,
+} from '@wordpress/components';
+
+const DEFAULT_UNITS = [ 'px', 'em', 'rem', 'vw', 'vh' ];
+
+function SizeControl( props ) {
+ const { baseControlProps } = useBaseControlProps( props );
+ const { value, onChange, fallbackValue, disabled } = props;
+
+ const units = useCustomUnits( {
+ availableUnits: DEFAULT_UNITS,
+ } );
+
+ const [ valueQuantity, valueUnit = 'px' ] =
+ parseQuantityAndUnitFromRawValue( value, units );
+
+ const isValueUnitRelative =
+ !! valueUnit && [ 'em', 'rem', 'vw', 'vh' ].includes( valueUnit );
+
+ // Receives the new value from the UnitControl component as a string containing the value and unit.
+ const handleUnitControlChange = ( newValue ) => {
+ onChange( newValue );
+ };
+
+ // Receives the new value from the RangeControl component as a number.
+ const handleRangeControlChange = ( newValue ) => {
+ onChange?.( newValue + valueUnit );
+ };
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
+
+export default SizeControl;
diff --git a/packages/edit-site/src/components/global-styles/style.scss b/packages/edit-site/src/components/global-styles/style.scss
index 51e83f33c03eee..8fa66b6d1b7947 100644
--- a/packages/edit-site/src/components/global-styles/style.scss
+++ b/packages/edit-site/src/components/global-styles/style.scss
@@ -23,6 +23,17 @@
overflow: hidden;
}
+.edit-site-font-size__item {
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ overflow: hidden;
+ line-break: anywhere;
+}
+
+.edit-site-font-size__item-value {
+ color: $gray-700;
+}
+
.edit-site-global-styles-screen {
margin: $grid-unit-15 $grid-unit-20 $grid-unit-20;
}
diff --git a/packages/edit-site/src/components/global-styles/ui.js b/packages/edit-site/src/components/global-styles/ui.js
index 08cd20e9aac6a8..40d20bc1ec86f8 100644
--- a/packages/edit-site/src/components/global-styles/ui.js
+++ b/packages/edit-site/src/components/global-styles/ui.js
@@ -33,6 +33,8 @@ import {
import ScreenBlock from './screen-block';
import ScreenTypography from './screen-typography';
import ScreenTypographyElement from './screen-typography-element';
+import FontSize from './font-sizes/font-size';
+import FontSizes from './font-sizes/font-sizes';
import ScreenColors from './screen-colors';
import ScreenColorPalette from './screen-color-palette';
import { ScreenShadows, ScreenShadowsEdit } from './screen-shadows';
@@ -313,6 +315,14 @@ function GlobalStylesUI() {
+
+
+
+
+
+
+
+